clone

an endless marble-race generator in the browser; the url is the seed, so every track can be shared

main
marbles / docs / collision-mesh-audit.md
collision-mesh-audit.md 12.3K rendered
1# Collision-to-mesh audit — piece bench23Audit date: 2026-08-09 4Source: `?endless=1&peers=0&gallery=1` 5Inventory: 119/119 registry variants reviewed with collision wireframes enabled67The per-piece results are in [`collision-mesh-audit.csv`](./collision-mesh-audit.csv).8This was a static geometry audit: every ordinal was selected explicitly through9the CUA-accessible numeric selector, the selected ordinal/name was validated,10and the collision-on render was compared with the visible mesh. The yellow ring11is only the selection marker and was ignored. No simulation outcome is inferred12from a visual pass alone.1314## Result summary1516| Severity | Pieces | Meaning |17|---|---:|---|18| blocker | 1 | Render mesh is missing/invalid, so comparison cannot be made |19| critical | 18 | Collision changes topology or leaves a major part of the mesh unsupported |20| high | 12 | Local mismatch is likely to change reachable motion or contacts |21| medium | 88 | Floor is broadly usable, but guard or edge fit is visibly inaccurate |2223The dominant issue classes are:2425- **Over-conservative ribbon wall guard — 115 pieces.** The floor is often26 close, but the collision profile rises far above the visible rim. The current27 profile reaches `up=1.1` world units (`CHANNEL_PROFILE` in28 `src/gen/endless/proxies.ts`) while the rendered channel rim is much lower.29 This consumes tunnel/stack clearance and masks legitimate edge exits.30- **Openings and drop shafts — 13 pieces.** `straight-hole` is completely31 capped by the ordinary ribbon. All twelve drop-stack variants use the same32 square five-slab tube/pan regardless of rounded vs square aperture, forward33 vs turning exit, or reversed direction.34- **Variable width — 5 pieces.** `straight-wide`, both funnels, and both35 reversed funnels are rendered at up to ±1.7 but the bench shows a standard36 ribbon. There is also a diagnostic parity defect: production calls37 `channelProxies(..., wideScalesOf(data))` in `src/client/endless.ts`, while38 `src/client/pieceBench.ts` omits the fourth argument. Extracting a shared39 segment-to-proxies helper is safer than fixing the bench call independently.40- **Turns and curved profiles — 62 pieces.** Long curves, helixes, waves, and41 bumps broadly follow the visible floor. Error concentrates at inner/outer42 boundaries, tight corners, double bends, and short S-curve inflections. The43 current angular threshold bounds center-frame rotation, not the geometric44 error of the offset channel boundaries.45- **Junction composites — 4 pieces.** `cross` covers only the active straight46 route. `diamond-1..3` use thirteen slabs; the boxes visibly overshoot the47 sculpted split/merge deck and center wedge.48- **Invalid asset/bench state — 1 piece.** `cross-over` reports null mesh bounds49 and focus remains on the prior `cross`, so its row is intentionally marked50 `not-evaluable`.5152## Recommended technique map5354The CSV uses these short technique IDs.5556### T1 — Sweep the actual, variable cross-section5758Keep the useful one-piece ribbon model, but make width and rim height functions59of arc length and piece metadata. A path extrusion with explicit subdivision is60established practice; Three.js exposes the same model through61[`ExtrudeGeometry.extrudePath`](https://threejs.org/docs/pages/ExtrudeGeometry.html).62For this project, the important change is to pass `wScale` in every environment63and use a mesh-derived rim profile instead of one global `CHANNEL_PROFILE`.6465### T2 — Author apertures and junctions as topology, not drop points6667Holes need boundary loops and open faces. Give `straight-hole`, drop-stack68mouths, crosses, and split/merge pieces dedicated collision data in the GLB (or69an adjacent compact collider asset). A procedural alternative is an outer70contour plus explicit hole contours; Three.js `Shape` models this directly with71[`Shape.holes`](https://threejs.org/docs/pages/Shape.html). Do not infer a hole72only from a near-vertical centerline tangent.7374### T3 — Use an error-bounded adaptive turn tessellation7576Replace the global `MAX_TURN` rule with an error target in world units. For an77offset boundary radius `r`, choose angular steps so the chord sagitta78`r * (1 - cos(delta / 2))` stays below the allowed collision-to-mesh error.79Evaluate both inner and outer offsets and add mandatory rings at ports,80inflections, curvature extrema, and width changes. This directly controls the81over/undercut the player sees, rather than only controlling tangent rotation.8283### T4 — Cook a simplified static triangle mesh from the render source8485These tracks are static environmental geometry, the strongest case for a86cleaned/welded concave collider. PhysX triangle-mesh cooking validates, welds,87builds adjacency, and constructs an acceleration structure88([PhysX Geometry guide](https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/Geometry.html)).89Unity likewise recommends non-convex mesh colliders where high-fidelity static90collision is essential and documents mesh cleaning/welding91([Unity mesh cooking](https://docs.unity3d.com/jp/current/Manual/physics-optimization-cpu-mesh-cooking-options.html)).92Godot describes concave trimeshes as the most accurate static-level option,93with the usual warning about fast small bodies94([ConcavePolygonShape3D](https://docs.godotengine.org/en/stable/classes/class_concavepolygonshape3d.html)).9596For Marbles, generate a low-poly collision surface during asset import, retain97the exact aperture and rim boundaries, weld it, and keep Box3D's internal-edge98identification enabled. The render mesh itself need not be the runtime collider.99100### T5 — Use compound convex pieces where a concave surface is unreliable101102Drop tubes and split/junction walls can be a small, authored compound of convex103parts. When automatic decomposition is useful, CoACD is specifically designed104to preserve collision conditions with fewer convex components105([CoACD project and SIGGRAPH 2022 paper](https://github.com/SarahWeiii/CoACD)).106This is preferable to axis-aligned boxes that ignore a curved mouth or turning107shaft, but authored parts will be more predictable for this small kit.108109### T6 — Separate geometric fidelity from solver forgiveness110111Do not encode contact tolerance by making walls much taller or wider than the112art. Keep collider geometry close to the mesh and apply small, measured solver113margins. PhysX explicitly separates `contactOffset` and `restOffset` for this114purpose115([Advanced Collision Detection](https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/AdvancedCollisionDetection.html)).116For Box3D, establish the equivalent narrow margin through sphere radius/contact117settings, then test at the maximum marble speed.118119Add CI measurements per piece: signed floor-height error, lateral boundary120error, aperture false-positive area, and minimum roof/stack clearance. Report121max and percentile errors and fail on a piece-specific budget. This turns the122gallery from a visual-only regression tool into a quantitative one.123124### T7 — Enforce asset and bench/runtime parity125126- Assert every selected render group has finite, non-empty bounds; this catches127 `cross-over` before a camera operation or audit.128- Build collision through one shared function used by production, the bench,129 tests, and simulation. The current wide-scale argument mismatch proves that130 separately wired diagnostic paths can produce false conclusions.131- Add stable piece number, piece id, family, mesh bounds, proxy composition, and132 production options to the accessible bench state (now present in the updated133 gallery UI).134135## Suggested order of work1361371. Fix `cross-over` asset/bounds and share the production proxy builder with the138 bench so the diagnostic is trustworthy.1392. Replace `straight-hole` and drop-stack generic logic with authored aperture140 and shaft colliders.1413. Make width a mandatory part of the swept profile and add parity tests for all142 five wide/funnel variants.1434. Reduce the wall profile to the visible rim plus a small solver margin.1445. Convert turn sampling from angle-bounded to boundary-error-bounded, starting145 with `corner`, `bend`, and the four short S-curve variants.1466. Replace cross/diamond slabs with authored junction collision surfaces or a147 small compound-convex set.148149## Remediation — 2026-08-09 (same day, ready for re-audit)150151All six work items landed; new kit truths were measured headlessly from152`assets/marble-kit.glb` (scratch tooling patterned on `tools/measure_ports.ts`):153wall inner face 0.287 grid, rim floor+0.25 grid, floor bore ROUND r 0.25 grid154in `straight-hole` and BOTH end-hole flavors (flavors differ only in cup155cosmetics), wide inner wall 0.575 grid → widening is an additive wall shift,156not a section scale.1571581. **Parity (T7)** — `segmentProxies` in `src/gen/endless/proxies.ts` is now159 the ONE channel builder used by production, the bench, and the sims;160 `gapSegmentsOf` moved beside it (rail.ts re-exports). The bench asserts161 finite mesh bounds, frames empty plots safely, and renders `cross-over`162 (a deliberate noMesh phantom, not a broken asset) with its ghosted host163 mesh so the row is evaluable.1642. **Apertures (T2/T5)** — `PieceDef.holes` → `LoopSegment.holes` opens a165 real bore in the ribbon with lip decks, side walls, and an under-deck166 guide ring (`emitAperture`). Drop-stacks get an authored measured shape:167 annulus deck floor, octagonal cup walls (r 1.0 world) with the entry168 arch open, octagonal shaft bore, receiver cup opened toward the true169 exit yaw, and the pan law kept.1703. **Width (T1)** — width flows as a measured additive wall shift171 (0.576 world) through `wideScalesOf`; the bench passes def-authored172 `widthIn/widthOut` so isolated funnels taper. Parity tests:173 `tests/collision_width_test.ts`.1744. **Rim (T6)** — `CHANNEL_PROFILE` now tops at 0.62 over the floor175 (measured rim 0.5 + 0.12 solver cap) with the wall face at 0.62 lateral176 (measured 0.574 + clearance); junction composite walls follow the same177 law.1785. **Turns (T3)** — arc stations are chosen by outer-boundary chord-sagitta179 budget (`BOUNDARY_ERR` 0.008 world) instead of a fixed angle; ring-level180 crease rounding (kicker prevention) is unchanged and documented as181 deliberate forgiveness geometry.1826. **Junctions (T2)** — the cross cell emits one authored 4-way composite183 (deck + corner wall Ls, all four port arches open) from the mesh visit;184 the phantom stays silent — previously the two same-height route ribbons185 walled each other off. Diamond/spur/braid composites tightened to the186 measured split-double wall faces (branch ± 0.574 + clearance).187188**Quantitative CI (the audit's T6 closing ask)** —189`tests/collision_metrics_test.ts` measures every registry variant: signed190ring-floor error vs centerline (budget 0.05, worst measured 0.041 at the191slant-d crest rounding), ring lateral extents, rim cap band, bore openness192probes for all 13 hole/drop bores, and cross junction coverage probes. It193found and fixed one real defect during bring-up: approach ribbons194overhanging drop bores (now lip-trimmed in the proxy law).195196**Re-audit gap closure (2026-08-09, after the fresh CUA pass in197`collision-mesh-reaudit.md`)** — the CI now also measures against the198RENDER MESH itself, not just centerline + constants:199200- *Render-to-collision directed distance*: every GLB channel-surface201 vertex (placement-transformed, corridor-filtered to the driveable band;202 port-face bands excluded — those are shared window boundaries covered by203 the neighbor's ring) must lie within 0.2 world of the collision surface204 (point-to-slab + point-to-ribbon-triangle). 40k+ vertices across all205 variants; worst 0.19. Bringing this up hardened the colliders further:206 straight-hole gained its bore side-sliver decks (collar verts were 0.24207 adrift) and the cross corner Ls became solid corner posts matching the208 kit's blocks (0.29 adrift at the post corners).209- *Drop compound probes* (all 12): entry arch open, far/lateral cup walls210 present, shaft bore guide present, receiver exit mouth open toward the211 true exit heading with its far wall present, pan present.212- *Diamond compound probes* (all 3): branch decks present, outer and wedge213 walls at the measured lines, branch lanes and entry throat clear.214- *Coverage accounting*: bench-build failures fail the suite; every215 variant must be evaluated by ≥1 metric family (rings 113, render-dist216 101, probes 17, paired-by-design 1 — `cross-over`, whose collision is217 the co-located cross composite).218219Suite: 174 tests green.