- commit
- 967a1572182338eefbb334f849536f3ddd85e6de
- parent
- f8b6d94b904d43cdf095b6a6e914592a1f5d18dd
- author
- Dami <dami.etoile@gmail.com>
| M | src/ | 25 | +++++++++++++++++ |
| M | tests/ | 45 | ++++++++++++++++++++++++++++++ |
@@ -253,6 +253,31 @@ export function assemble(
253253 });254254 }255255 }256+ // Per-branch diamond supports (M13): an elevated diamond's window257+ // cells follow the MAIN branch, so its columns covered only that258+ // lane — the OTHER branch deck hung unsupported. Snap a second259+ // candidate line onto the alt centerline; the span/clearance greedy260+ // thins both lanes together.261+ // The alt lane runs PARALLEL to the main lane, so it must be its262+ // own greedy group (stretch + 0.5): measured against main-lane263+ // pillars it never accumulates a full span of gap and every264+ // candidate is skipped. Clearance still dedupes the split/merge265+ // mouths where the lanes converge.266+ if (alt) {267+ for (let j = 0; j < cells.length; j++) {268+ const c = skeleton.cells[(win.start + j) % skeleton.cells.length];269+ if (crossingCells.has(key(c.x, c.z))) continue;270+ const p = nearestOnCurve(alt.curve, c.x, c.z);271+ supportCandidates.push({272+ x: p[0],273+ z: p[2],274+ height: baseY,275+ dir: c.dirIn,276+ cradle: false,277+ stretch: stretch + 0.5,278+ });279+ }280+ }256281 } else {257282 // Ground-level or climbing window: closes any elevated stretch.258283 stretch++;@@ -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");