Marble physics research + design (M11)
Research findings
Box3D meshes (docs/collision.md, sample_mesh.cpp, pinned commit):
- Trimesh via b3MeshDef → b3CreateMesh → b3CreateMeshShape (static only).
identifyEdges=truesuppresses internal-edge (ghost) collisions — the modern equivalent of Marble Blast's smooth convex-brush interiors.- Docs recommend
weldVertices=true(tolerance ~1mm). Samples pass a degenerate-triangle capacity; b3CreateMesh can fail → NEVER ignore a null return (our first ribbon dropped whole strips silently = holes). - Per-triangle
materialIndices+ b3ShapeDef materials = native per-surface friction/restitution → the future icy/boost surface hook. - Character mover doc: roundness beats soup — capsules/spheres slide because they're convex; keep the marble a dynamic sphere.
Marble Blast recipe (MBHaxe src/Marble.hx — "99% identical physics"):
- gravity 20 (marble r≈0.2 → ~our world scale): gravity-DOMINANT feel.
- Restitution 0.5 with minBounceVel 0.1 / minBounceSpeed 3 (thresholded — no micro-bounce); shock absorber 0.01, super bounce 0.9 (timed override).
- Rolling: accelerate HARD toward a target velocity (maxRoll 15, angular accel 75 ≈ linear 15) — responsiveness comes from authority, not speed.
- Friction: kinetic 0.7, static 1.1, bounce-kinetic 0.2. Air control 5.
- Powerups are cheap parameter/impulse mutations with timers (helicopter = gravity ×0.25). Gravity pads rotate the gravity vector.
Our design mapping
- Geometry: ONE welded ribbon trimesh per strip (identifyEdges), cups for drop shafts, boxes for islands. Mesh creation failures are LOUD.
- Feel: gravity −20; assist = strong acceleration (≈8) toward target cruise, climb carry sized by rail lookahead, soft brake above max; restitution ~0.35 on the marble.
- Per-tile modifiers (next): rail stations inherit def-family tags → speed/gravity multipliers; per-triangle materials for icy later.
- Powerup pattern (later): timed overrides {gravityScale, restitution, impulse} — Marble Blast's proven cheap trick. Island gravity rotation = rotating the world gravity vector while anchored.
Ribbon debug state (next session)
- Shim mesh path WORKS: 2-triangle repro (scratchpad mesh-repro.mjs) collides with winding [0,2,1] (one-sided; CW in (x,z) viewed from +y = up-facing collision). ph_add_mesh is loud on failure now.
- The FULL ribbon holds the marble in NEITHER winding — the discrepancy is unexplained. NEXT: isolated repro — build ribbonOf() output for a synthetic straight 10-unit path, drop a marble in node, inspect. Suspects: vertex ordering per ring vs winding assumption, weld collapsing rings, triangle indices crossing ring boundaries wrongly, or scale (verts are fine floats).
- USE_RIBBON=false gates it in proxies.ts; slab law is live and proven (65/65). Adopt Marble Blast constants (g=20, accel 8, restitution 0.35 thresholded) TOGETHER WITH the ribbon once it holds — recorded values in the section above.
- Then: per-tile modifiers via rail-station def tags (speed/gravity) and per-triangle materials (icy); Marble Blast powerup pattern; island gravity rotation.
Ribbon session 2 findings (isolated repro chain — major progress)
- FIXED: upV cross-product order (right×t hung gutters/walls BELOW the floor; straights masked it — flat middle sits at up=0 either way).
- FIXED: winding is layout-handed (profile runs along right = up×t = −z for +x travel → original winding was correct; the square repro misled because its layout mirrored).
- FIXED: strips now extrapolate to true boundary ports (stations are mid-samples; the half-step gap at every segment boundary dropped marbles at speed). Shaft-adjacent ends never extend.
- Isolated straight AND arc ribbons now hold a rolling marble perfectly.
- REMAINING: full-stream falls at chute VALLEYS — the pitch crease where a steep chute cubic meets flat acts as a kicker (marble arrives vy≈−11, reflects off the sharp V, launches over the 0.66 wall laterally; slabs' overlapping geometry incidentally rounded this). NEXT: round the valley — insert interpolated rings across part-boundary pitch jumps (bound Δpitch per ring like MAX_TURN bounds Δyaw), and/or raise walls through high-|dv| creases. Repro: phys-deep cua-seed-1 → FELL at s=27.3.
- Ribbon stays gated (default slabs) until the valley fix lands green.