clone

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

M13: per-branch diamond supports — alt lane gets its own abutment+pier group

Dami committed 967a157
commit
967a1572182338eefbb334f849536f3ddd85e6de
parent
f8b6d94b904d43cdf095b6a6e914592a1f5d18dd
author
Dami <dami.etoile@gmail.com>
2 files changed, +70 -0
Msrc/gen/assemble.ts25+++++++++++++++++
Mtests/track_test.ts45++++++++++++++++++++++++++++++
M src/gen/assemble.ts +25 -0
M tests/track_test.ts +45 -0
@@ -158,6 +158,51 @@ function samplePart(
158158 ) as unknown as readonly [number, number, number];159159 }160160 161+Deno.test("per-branch diamond supports: the alt branch carries its own columns", () => {162+ // M13: an elevated diamond's support columns used to follow only the163+ // MAIN branch centerline; the other branch deck hung unsupported.164+ // Rule: for every elevated diamond window (loop segment with an alt165+ // curve above ground), some support placement stands within a pier166+ // span of the ALT centerline — and is not merely a main-lane pillar167+ // (it sits distinctly closer to the alt lane than to the main one).168+ const seeds = ["dia-loop-63", "dia-loop-125", "dia-loop-237", "dia-loop-402"];169+ let elevatedDiamonds = 0;170+ for (const seed of seeds) {171+ const track = generateTrack(seed, { packs: [] }, { skipDecor: true });172+ const supports = track.placements.filter((p) => p.role === "support");173+ for (const seg of track.loop.segments) {174+ if (!seg.alt) continue;175+ const y0 = samplePart(seg.curve[0], 0)[1];176+ if (y0 <= 0.26) continue; // ground diamonds need no columns177+ elevatedDiamonds++;178+ const altPts: (readonly [number, number, number])[] = [];179+ const mainPts: (readonly [number, number, number])[] = [];180+ for (const part of seg.alt.curve) {181+ for (let t = 0; t <= 8; t++) altPts.push(samplePart(part, t / 8));182+ }183+ for (const part of seg.curve) {184+ for (let t = 0; t <= 8; t++) mainPts.push(samplePart(part, t / 8));185+ }186+ const distTo = (187+ pts: readonly (readonly [number, number, number])[],188+ p: { pos: readonly number[] },189+ ) =>190+ Math.min(191+ ...pts.map((q) => Math.hypot(q[0] - p.pos[0], q[2] - p.pos[2])),192+ );193+ const carried = supports.some((s) => {194+ const da = distTo(altPts, s);195+ return da < 1.3 && da < distTo(mainPts, s) - 0.3;196+ });197+ assert(198+ carried,199+ `${seed}: elevated diamond's alt branch has no dedicated support`,200+ );201+ }202+ }203+ assert(elevatedDiamonds > 0, "no elevated diamonds across the pinned seeds");204+});205+161206 Deno.test("determinism: same seed → deep-equal track", () => {162207 const a = generateTrack("determinism-check");163208 const b = generateTrack("determinism-check");