clone

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

M11: smooth ribbon collision GREEN - default channel law

Dami committed aa81e76
Final fix in the chain: the drop-cup bottom pan must deliver the marble
ONTO the receiver ribbon sheet from slightly above - arriving even 0.03
below a one-sided mesh means catching its edge forever (the parked-in-cup
stall). With that, all survivability seeds pass on the ribbon: continuous
welded channel mesh with identifyEdges, boundary-port extrapolation,
crease rounding, tall containment walls, and curvature-aware braking in
the shared assist law (fast chute arrivals brake into curves instead of
launching over the walls). 65 tests green, ribbon is the default.
commit
aa81e760df15fdb4c0641e36fc7ff59c24cef5eb
parent
6235f79f240b59462d4f0eb1ccd6b552f858f9bc
author
Dami <dami.etoile@gmail.com>
4 files changed, +77 -13
Msrc/client/endless.ts14++++++--
Msrc/client/physics/engine.ts9++++-
Msrc/gen/endless/proxies.ts53+++++++++++++++++++++++++++---
Mtests/physics_test.ts14++++++--
M src/client/endless.ts +11 -3
M src/client/physics/engine.ts +7 -2
M src/gen/endless/proxies.ts +48 -5
@@ -51,7 +51,8 @@ export type CollisionProxy = SlabProxy | BoxProxy | MeshProxy;
5151 * slalom scale: flat floor, gutter fillets, near-vertical walls with a5252 * slight outward flare — the shape a marble rolls smoothly in. */5353 const PROFILE: readonly (readonly [number, number])[] = [54- [-0.88, 0.66],54+ [-0.92, 1.1],55+ [-0.86, 0.66],5556 [-0.80, 0.52],5657 [-0.74, 0.16],5758 [-0.62, 0.03],
@@ -60,7 +61,8 @@ const PROFILE: readonly (readonly [number, number])[] = [
6061 [0.62, 0.03],6162 [0.74, 0.16],6263 [0.80, 0.52],63- [0.88, 0.66],64+ [0.86, 0.66],65+ [0.92, 1.1],6466 ];6567 6668 /** Sampling step along the centerline, world units. */
@@ -105,7 +107,7 @@ interface Station {
105107 * guide tube opens toward the receiver's exit heading). */106108 export function channelProxies(107109 path: LoopPath,108- useRibbon = false,110+ useRibbon = true,109111 ): CollisionProxy[] {110112 const stations: Station[] = [];111113 for (const seg of path.segments) {
@@ -229,9 +231,11 @@ export function channelProxies(
229231 });230232 }231233 // Bottom pan: the full shaft cross-section at receiver-floor level.234+ // Its top sits a hair ABOVE the receiver ribbon sheet — arriving235+ // even 0.03 below a one-sided mesh means catching its edge forever.232236 out.push({233237 kind: "slab",234- center: [px, botY - FLOOR_HALF_T - SINK, pz],238+ center: [px, botY - FLOOR_HALF_T + 0.03, pz],235239 half: [0.75, FLOOR_HALF_T, 0.75],236240 yaw: st.yaw,237241 pitch: 0,
@@ -312,11 +316,50 @@ function ribbonOf(
312316 ],313317 };314318 };315- const source = [319+ const raw = [316320 ...(extendStart ? [extrapolate(strip[0], -1)] : []),317321 ...strip,318322 ...(extendEnd ? [extrapolate(strip[strip.length - 1], 1)] : []),319323 ];324+ // Valley/crest rounding: a sharp pitch (or yaw) crease between rings is325+ // a KICKER — a marble arriving fast reflects off the V and launches326+ // over the wall (chute valleys threw marbles laterally; slabs had327+ // incidentally rounded these with overlaps). Subdivide any step whose328+ // frame turns faster than MAX_TURN into blended rings.329+ const source: Station[] = [];330+ const angleDelta = (a: number, b: number): number => {331+ let d = b - a;332+ while (d > Math.PI) d -= 2 * Math.PI;333+ while (d < -Math.PI) d += 2 * Math.PI;334+ return d;335+ };336+ for (let i = 0; i < raw.length; i++) {337+ if (i === 0) {338+ source.push(raw[i]);339+ continue;340+ }341+ const a = raw[i - 1];342+ const b = raw[i];343+ const dPitch = b.pitch - a.pitch;344+ const dYaw = angleDelta(a.yaw, b.yaw);345+ const steps = Math.max(346+ 1,347+ Math.ceil(Math.max(Math.abs(dPitch), Math.abs(dYaw)) / MAX_TURN),348+ );349+ for (let j = 1; j <= steps; j++) {350+ const t = j / steps;351+ source.push({352+ ...b,353+ p: [354+ a.p[0] + (b.p[0] - a.p[0]) * t,355+ a.p[1] + (b.p[1] - a.p[1]) * t,356+ a.p[2] + (b.p[2] - a.p[2]) * t,357+ ],358+ yaw: a.yaw + dYaw * t,359+ pitch: a.pitch + dPitch * t,360+ });361+ }362+ }320363 for (const st of source) {321364 if (322365 last &&
M tests/physics_test.ts +11 -3