clone

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

main
marbles / tests / braid_test.ts
braid_test.ts 25.5K
1/**2 * Braided paths (M17) — generation invariants. The physics half (both3 * lanes drive clean through fork and merge) lives in physics_test.ts.4 *5 * The contract, per committed braid: L straight flat main cells with the6 * FORK host at cell 0 and the MERGE host at cell L−1, both pinned 1-cell7 * straight windows whose placements mutated straight → split (fork8 * branching toward the alt side; merge the same split z-flipped in its9 * local frame — rotation + 2, mirror inverted — so its mouth faces the10 * arriving lane); a REAL adjacent-cell alt walk beside the main lane11 * (offsets 1..breadth, one lane shift per turn pair); the fork window12 * carrying the full alt chain flagged `braid`; the merge window flagged13 * `braidMerge`; an `altPath` for the alt lane's own channel proxies; and14 * junction composites with OPEN branch mouths where the main ribbon15 * stops. Breadth is capped by the per-seed knob drawn from16 * rngFamily(seed, "braid-breadth").17 */18import { assert, assertEquals } from "@std/assert";19import { EndlessWorld } from "../src/gen/endless/world.ts";20import { TRACK_SCALE } from "../src/gen/endless/scale.ts";21import { channelProxies, wideScalesOf } from "../src/gen/endless/proxies.ts";22import { gapSegmentsOf, railForSegment } from "../src/client/physics/rail.ts";23import { braidLaneAlt } from "../src/client/physics/autopilot.ts";24import { OPEN_BRAID_CAP } from "../src/gen/endless/path.ts";25import { goldenCatalog, segmentHash } from "../tools/pin_endless.ts";26import { rngFamily } from "../src/gen/rng.ts";27import { DIR_STEP, opposite, segPoint, turnRight } from "../src/gen/geom.ts";28import type { DecorCatalog } from "../src/gen/decor.ts";2930// The catalog the client streams with (all decor packs — M18a).31const catalog: DecorCatalog = goldenCatalog();3233/** Scan corpus. Pinned exemplars: grav-scan-17 braids in segments 0 AND34 * 1 (physics_test drives both lanes of the same seed); grav-scan-3835 * (bias 3) shows a breadth-3 braid in segment 1 — re-scan with a fresh36 * label family if a reshuffle moves them. */37const SEEDS = [38 "alpha",39 "dami",40 "cua-seed-1",41 "marbles",42 "grav-scan-17",43 "grav-scan-38",44 ...Array.from({ length: 30 }, (_, i) => `braid-scan-${i}`),45];46const SEGS = 8;4748/** Per-seed breadth knob, exactly as EndlessWorld draws it. */49const biasOf = (seed: string): number =>50 1 + rngFamily(seed, "braid-breadth").int(0, 3);5152Deno.test("braid: pinned seeds braid early, breadth knob honored", () => {53 const w17 = new EndlessWorld("grav-scan-17", catalog);54 assert(55 w17.get(0).plan.braids.length > 0 && w17.get(1).plan.braids.length > 0,56 "grav-scan-17 lost its segment-0/1 braids — re-scan (physics_test " +57 "pins the same seed)",58 );59 // The knob's top end is reachable: a bias-3 seed shows a breadth-360 // braid (alt lane wandering three cells out).61 assertEquals(biasOf("grav-scan-38"), 3, "grav-scan-38 bias drifted");62 const w38 = new EndlessWorld("grav-scan-38", catalog);63 let maxOff = 0;64 for (let i = 0; i < 3; i++) {65 for (const b of w38.get(i).plan.braids) {66 const fork = w38.get(i).plan.cells[b.start];67 const [wx, wz] = DIR_STEP[b.side];68 for (const c of b.altCells) {69 maxOff = Math.max(maxOff, (c.x - fork.x) * wx + (c.z - fork.z) * wz);70 }71 }72 }73 assertEquals(maxOff, 3, "grav-scan-38 lost its breadth-3 braid — re-scan");74});7576Deno.test("braid: every braid obeys the fork/merge/lane contract", () => {77 let braids = 0;78 for (const seed of SEEDS) {79 const bias = biasOf(seed);80 const w = new EndlessWorld(seed, catalog);81 for (let i = 0; i < SEGS; i++) {82 const seg = w.get(i);83 assert(84 seg.plan.braids.length <= 2,85 `${seed} seg ${i}: ${seg.plan.braids.length} braids (max two — M23)`,86 );87 for (const braid of seg.plan.braids) {88 braids++;89 // V3 open/cont braids have their own contract test below — the90 // closed fork/merge law here doesn't apply to them.91 if (braid.open || braid.cont) continue;92 const { cells } = seg.plan;93 const fork = cells[braid.start];94 const merge = cells[braid.start + braid.len - 1];95 const [dx, dz] = DIR_STEP[fork.dirIn];96 const [wx, wz] = DIR_STEP[braid.side];9798 // MAIN lane: L straight flat cells; braid length in range.99 assert(100 braid.len >= 8 && braid.len <= 14,101 `${seed} seg ${i}: braid length ${braid.len}`,102 );103 for (let k = 0; k < braid.len; k++) {104 const c = cells[braid.start + k];105 assert(106 c.move === "F" && c.baseY === fork.baseY &&107 c.dirIn === fork.dirIn,108 `${seed} seg ${i}: main braid cell ${k} not straight/flat`,109 );110 }111112 // Fork + merge: 1-cell straight windows carrying the braid zone.113 for (114 const [tag, at] of [115 ["fork", braid.start],116 ["merge", braid.start + braid.len - 1],117 ] as const118 ) {119 const wi = seg.windowRanges.findIndex((r) =>120 r.start === at && r.len === 1121 );122 assert(wi >= 0, `${seed} seg ${i}: ${tag} window not 1 cell`);123 assertEquals(124 seg.pieceFamilies[wi],125 "straight",126 `${seed} seg ${i}: ${tag} window family`,127 );128 assertEquals(129 seg.windowMods[wi],130 "braid",131 `${seed} seg ${i}: ${tag} window zone`,132 );133 }134 const wi = seg.windowRanges.findIndex((r) =>135 r.start === braid.start && r.len === 1136 );137 const wj = seg.windowRanges.findIndex((r) =>138 r.start === braid.start + braid.len - 1 && r.len === 1139 );140141 // Split placements: fork branches toward the alt side; the merge142 // is the same split z-flipped (rotation + 2, mirror inverted) so143 // its branch port faces the arriving lane.144 const splitAt = (x: number, z: number, y: number) =>145 seg.placements.find((p) =>146 p.pieceId === "split" &&147 Math.round(p.pos[0] / TRACK_SCALE) === x &&148 Math.round(p.pos[2] / TRACK_SCALE) === z &&149 Math.abs(p.pos[1] / TRACK_SCALE - y) < 1e-6150 );151 const fp = splitAt(fork.x, fork.z, fork.baseY);152 const mp = splitAt(merge.x, merge.z, merge.baseY);153 assert(fp, `${seed} seg ${i}: fork host did not mutate to split`);154 assert(mp, `${seed} seg ${i}: merge host did not mutate to split`);155 assertEquals(156 Boolean(fp.mirrored),157 braid.side === turnRight(fork.dirIn),158 `${seed} seg ${i}: fork split mirroring wrong for branch side`,159 );160 assertEquals(161 mp.rotation,162 (fp.rotation + 2) % 4,163 `${seed} seg ${i}: merge split not rotated 180° from the fork`,164 );165 assertEquals(166 Boolean(mp.mirrored),167 braid.side !== turnRight(merge.dirIn),168 `${seed} seg ${i}: merge split mirroring wrong for branch side`,169 );170171 // ALT walk: enters beside the fork heading sideways, leaves172 // beside the merge turning back in; adjacent-cell continuity;173 // offsets in [1, min(3, bias)]; one lane shift per step at most;174 // disjoint from the main lane.175 const alt = braid.altCells;176 assert(alt.length >= braid.len, `${seed} seg ${i}: alt walk short`);177 assertEquals(178 [alt[0].x, alt[0].z, alt[0].dirIn],179 [fork.x + wx, fork.z + wz, braid.side],180 `${seed} seg ${i}: alt walk entry not the fork branch cell`,181 );182 const last = alt[alt.length - 1];183 assertEquals(184 [last.x, last.z, last.dirOut],185 [merge.x + wx, merge.z + wz, opposite(braid.side)],186 `${seed} seg ${i}: alt walk exit not into the merge mouth`,187 );188 const mainSet = new Set(189 Array.from(190 { length: braid.len },191 (_, k) => `${cells[braid.start + k].x},${cells[braid.start + k].z}`,192 ),193 );194 let prevOff: number | null = null;195 for (const [k, c] of alt.entries()) {196 assert(197 c.baseY === fork.baseY,198 `${seed} seg ${i}: alt cell ${k} not flat at the braid level`,199 );200 if (k > 0) {201 const p = alt[k - 1];202 const [ox, oz] = DIR_STEP[p.dirOut];203 assertEquals(204 [p.x + ox, p.z + oz],205 [c.x, c.z],206 `${seed} seg ${i}: alt walk breaks at cell ${k}`,207 );208 assertEquals(209 c.dirIn,210 p.dirOut,211 `${seed} seg ${i}: alt walk heading breaks at cell ${k}`,212 );213 }214 const off = (c.x - fork.x) * wx + (c.z - fork.z) * wz;215 assert(216 off >= 1 && off <= Math.min(3, bias),217 `${seed} seg ${i}: alt offset ${off} outside [1, ${218 Math.min(3, bias)219 }] (bias ${bias})`,220 );221 if (prevOff !== null) {222 assert(223 Math.abs(off - prevOff) <= 1,224 `${seed} seg ${i}: alt lane jumps ${prevOff} → ${off}`,225 );226 }227 prevOff = off;228 assert(229 !mainSet.has(`${c.x},${c.z}`),230 `${seed} seg ${i}: alt cell ${k} collides with the main lane`,231 );232 }233234 // Rail plumbing: the fork window carries the braid-flagged alt235 // chain (fork entry port → merge exit port, C0-continuous); the236 // merge window carries the braidMerge marker; altPath spans the237 // branch ports.238 const lsF = seg.path.segments[wi];239 const lsM = seg.path.segments[wj];240 assert(241 lsF.alt !== undefined && lsF.alt.braid === true,242 `${seed} seg ${i}: fork window lost its braid alt chain`,243 );244 assert(245 lsM.braidMerge === true,246 `${seed} seg ${i}: merge window lost its braidMerge marker`,247 );248 const floorW = (fork.baseY + 0.25) * TRACK_SCALE;249 const a0 = segPoint(lsF.alt.curve[0], 0);250 assertEquals(251 a0.map((v) => Math.round(v * 4)),252 [253 (fork.x - dx / 2) * TRACK_SCALE,254 floorW,255 (fork.z - dz / 2) * TRACK_SCALE,256 ].map((v) => Math.round(v * 4)),257 `${seed} seg ${i}: alt chain does not start at the fork entry port`,258 );259 const aEnd = segPoint(lsF.alt.curve[lsF.alt.curve.length - 1], 1);260 assertEquals(261 aEnd.map((v) => Math.round(v * 4)),262 [263 (merge.x + dx / 2) * TRACK_SCALE,264 floorW,265 (merge.z + dz / 2) * TRACK_SCALE,266 ].map((v) => Math.round(v * 4)),267 `${seed} seg ${i}: alt chain does not end at the merge exit port`,268 );269 for (let s = 1; s < lsF.alt.curve.length; s++) {270 const pa = segPoint(lsF.alt.curve[s - 1], 1);271 const pb = segPoint(lsF.alt.curve[s], 0);272 assert(273 Math.hypot(pa[0] - pb[0], pa[1] - pb[1], pa[2] - pb[2]) < 1e-6,274 `${seed} seg ${i}: alt chain gap at part ${s}`,275 );276 }277 assert(seg.altPath !== undefined, `${seed} seg ${i}: altPath missing`);278 // M23: altPath concatenates EVERY lane's loop (two braids per279 // segment) — assert containment of THIS braid's ports among the280 // altPath endpoints, not global span.281 const forkPort = [282 (fork.x + wx / 2) * TRACK_SCALE,283 floorW,284 (fork.z + wz / 2) * TRACK_SCALE,285 ];286 const mergePort = [287 (merge.x + wx / 2) * TRACK_SCALE,288 floorW,289 (merge.z + wz / 2) * TRACK_SCALE,290 ];291 const endpoints: number[][] = [];292 for (const s of seg.altPath.segments) {293 endpoints.push([...segPoint(s.curve[0], 0)]);294 endpoints.push([...segPoint(s.curve[s.curve.length - 1], 1)]);295 }296 const hasPoint = (p: number[]): boolean =>297 endpoints.some((e) => e.every((v, k) => Math.abs(v - p[k]) < 1e-6));298 assert(299 hasPoint(forkPort) && hasPoint(mergePort),300 `${seed} seg ${i}: altPath misses this braid's branch ports`,301 );302303 // The alt rail rides the whole alt chain and SKIPS the main304 // windows underneath the braid: no alt-rail station may sit on305 // an interior main-lane cell center (the interior lane belongs306 // to main riders only).307 const altRail = railForSegment(seg, true);308 for (let k = 1; k < braid.len - 1; k++) {309 const cx = cells[braid.start + k].x * TRACK_SCALE;310 const cz = cells[braid.start + k].z * TRACK_SCALE;311 assert(312 !altRail.pts.some((p) =>313 Math.hypot(p[0] - cx, p[2] - cz) < 0.4 &&314 Math.abs(p[1] - floorW) < 0.4315 ),316 `${seed} seg ${i}: alt rail rides the interior main lane @${k}`,317 );318 }319320 // PROXIES: junction decks under both hosts (top a hair above the321 // channel floor), mouth deck reaching toward the branch port,322 // and the ribbon stopped over the junctions.323 const proxies = channelProxies(324 seg.path,325 true,326 gapSegmentsOf(seg),327 wideScalesOf(seg),328 );329 const coveredBySlab = (x: number, y: number, z: number): boolean =>330 proxies.some((p) => {331 if (p.kind !== "slab" || Math.abs(p.pitch) > 0.05) return false;332 const top = p.center[1] + p.half[1];333 if (Math.abs(top - y) > 0.1) return false;334 const px = x - p.center[0];335 const pz = z - p.center[2];336 const lx = px * Math.cos(p.yaw) - pz * Math.sin(p.yaw);337 const lz = px * Math.sin(p.yaw) + pz * Math.cos(p.yaw);338 return Math.abs(lx) <= p.half[0] && Math.abs(lz) <= p.half[2];339 });340 for (const [tag, host] of [["fork", fork], ["merge", merge]] as const) {341 assert(342 coveredBySlab(343 host.x * TRACK_SCALE,344 floorW + 0.02,345 host.z * TRACK_SCALE,346 ),347 `${seed} seg ${i}: no junction deck under the ${tag}`,348 );349 assert(350 coveredBySlab(351 (host.x + wx * 0.45) * TRACK_SCALE,352 floorW + 0.02,353 (host.z + wz * 0.45) * TRACK_SCALE,354 ),355 `${seed} seg ${i}: no mouth deck toward the ${tag} branch port`,356 );357 const meshOverJunction = proxies.some((p) => {358 if (p.kind !== "mesh") return false;359 for (let v = 0; v < p.vertices.length; v += 3) {360 if (361 Math.abs(p.vertices[v + 1] - floorW) < 0.1 &&362 Math.hypot(363 p.vertices[v] - host.x * TRACK_SCALE,364 p.vertices[v + 2] - host.z * TRACK_SCALE,365 ) < 0.6366 ) return true;367 }368 return false;369 });370 assert(371 !meshOverJunction,372 `${seed} seg ${i}: ribbon floor crosses the ${tag} junction`,373 );374 }375 }376 }377 }378 assert(braids >= 8, `too few braids across the corpus (${braids})`);379});380381Deno.test("braid: deterministic per seed", () => {382 const a = new EndlessWorld("grav-scan-17", catalog);383 const b = new EndlessWorld("grav-scan-17", catalog);384 for (let i = 0; i < 3; i++) {385 assertEquals(segmentHash(a.get(i)), segmentHash(b.get(i)), `segment ${i}`);386 }387 assertEquals(a.get(0).plan.braids, b.get(0).plan.braids, "braids diverged");388 assertEquals(a.get(0).altPath, b.get(0).altPath, "altPath diverged");389});390391Deno.test("braid lane choice: per-racer streams, deterministic, ~half split", () => {392 // The choice law (USER RULE): each racer draws from ITS OWN label-393 // derived stream — rngFamily(seed, `lane/${idx}/${segIndex}`) — like394 // pilotFor personalities. Deterministic, and independent by395 // construction: one racer's choice is a pure function of (seed, idx,396 // segIndex), so nothing any other racer does can perturb it.397 const seed = "lane-law-seed";398 // Determinism: repeated evaluation is bit-identical.399 for (let idx = 0; idx < 20; idx++) {400 assertEquals(braidLaneAlt(seed, idx, 7), braidLaneAlt(seed, idx, 7));401 }402 // Independence: evaluating the whole pack in any order (or not at403 // all) never changes racer 13's draw.404 const lone = braidLaneAlt(seed, 13, 7);405 const inOrder = Array.from(406 { length: 100 },407 (_, idx) => braidLaneAlt(seed, idx, 7),408 );409 const reversed = Array.from(410 { length: 100 },411 (_, k) => braidLaneAlt(seed, 99 - k, 7),412 ).reverse();413 assertEquals(inOrder, reversed, "draw order perturbed the choices");414 assertEquals(inOrder[13], lone, "pack evaluation perturbed racer 13");415 // A 100-pack splits roughly in half at every braid, across seeds.416 for (const s of [seed, "alpha", "dami"]) {417 for (const segIndex of [2, 7, 31]) {418 let alt = 0;419 for (let idx = 0; idx < 100; idx++) {420 if (braidLaneAlt(s, idx, segIndex)) alt++;421 }422 assert(423 alt >= 30 && alt <= 70,424 `pack split ${alt}/100 at ${s}/${segIndex} — not roughly half`,425 );426 }427 }428 // Different braids re-draw: the same pack's pattern varies by segment.429 const at2 = Array.from(430 { length: 100 },431 (_, idx) => braidLaneAlt(seed, idx, 2),432 );433 const at7 = Array.from(434 { length: 100 },435 (_, idx) => braidLaneAlt(seed, idx, 7),436 );437 assert(438 at2.some((v, k) => v !== at7[k]),439 "lane choices identical across braids — segIndex not in the label",440 );441});442443// ---------------------------------------------------------------------------444// M19b — lane identity: tags only, junction-lip exclusion, compensation.445// ---------------------------------------------------------------------------446447Deno.test("braid lanes: tags obey the lip law, wanderers get their boost", () => {448 let tagged = 0;449 let wanderers = 0;450 for (const fam of ["grav", "jump", "wide"]) {451 for (let n = 0; n < 20; n++) {452 const seed = `${fam}-scan-${n}`;453 let w: EndlessWorld;454 try {455 w = new EndlessWorld(seed, catalog);456 } catch {457 continue;458 }459 for (let i = 0; i < 5; i++) {460 let seg;461 try {462 seg = w.get(i);463 } catch {464 break;465 }466 for (const b of seg.plan.braids) {467 // Compensation law: a wanderer alt lane always carries boost.468 if (b.wanderer) {469 wanderers++;470 assertEquals(471 b.laneMods?.alt,472 "boost",473 `${seed} seg ${i}: wanderer without boost compensation`,474 );475 }476 const lm = b.laneMods;477 if (!lm) continue;478 tagged++;479 // MAIN lane: tagged windows only ≥2 cells from fork AND merge480 // (windowMods carries the tag; fork/merge windows carry481 // "braid" and their neighbors stay untagged). V3: margins only482 // at ends that ARE junctions — boundaries are not mouths.483 const lo = b.cont ? b.start : b.start + 2;484 const hi = b.open ? b.start + b.len - 1 : b.start + b.len - 3;485 for (const [wi, wr] of seg.windowRanges.entries()) {486 const m = seg.windowMods[wi];487 if (m !== "icy" && m !== "boost") continue;488 const overlapsBraid = wr.start < b.start + b.len &&489 wr.start + wr.len > b.start;490 if (!overlapsBraid) continue; // sprint/icy zones elsewhere491 // Genuine planner ZONES (plan.mods — e.g. a jump's boost492 // approach brushing the merge) may overlap the span; the lip493 // law binds LANE TAGS only (windows plan.mods never touched).494 const fromPlanMod = seg.plan.mods.some((pm) =>495 pm.kind === m && wr.start < pm.start + pm.len &&496 pm.start < wr.start + wr.len497 );498 if (fromPlanMod) continue;499 assert(500 wr.start >= lo && wr.start + wr.len - 1 <= hi,501 `${seed} seg ${i}: main tag ${m} inside the junction lip ` +502 `(window ${wr.start}+${wr.len}, braid ${b.start}+${b.len})`,503 );504 }505 // ALT lane: the zones array aligns with the chain curve and506 // keeps both chain ends untagged (2 junction parts each side).507 const fork = seg.path.segments.find((ls) => ls.alt?.braid);508 if (lm.alt) {509 assert(fork?.alt?.zones, `${seed} seg ${i}: alt tag lost`);510 const zones = fork!.alt!.zones!;511 assertEquals(zones.length, fork!.alt!.curve.length);512 // Junction parts stay untagged at whichever ends exist (V3:513 // open chains end at a boundary, cont chains start at one).514 if (!b.cont) {515 assertEquals(zones[0], null);516 assertEquals(zones[1], null);517 }518 if (!b.open) {519 assertEquals(zones[zones.length - 1], null);520 assertEquals(zones[zones.length - 2], null);521 }522 assert(523 zones.some((z) => z === lm.alt),524 `${seed} seg ${i}: no interior part carries the alt tag`,525 );526 }527 }528 }529 }530 }531 assert(tagged > 0, "no lane-tagged braids across the corpus");532 assert(wanderers > 0, "no wanderer braids across the corpus");533});534535Deno.test("braid lanes: rail stations resolve the tag OVER the fork's centering", () => {536 // grav-scan-17 seg 1 pins a TAGGED OPEN braid (M21 re-scan) —537 // boost alt lane vs icy main, crossing the segment boundary.538 const w = new EndlessWorld("grav-scan-17", catalog);539 const seg = w.get(1);540 const braid = seg.plan.braids[0];541 assert(braid && braid.laneMods, "grav-scan-17@1 lost its tagged braid");542 const altRail = railForSegment(seg, true);543 const altTag = braid.laneMods!.alt;544 if (altTag) {545 // The OVERLAY signature: tag + the fork's centering on one station.546 // (A genuine icy/boost zone elsewhere in the segment carries the tag547 // WITHOUT center — the alt rail passes those too, so the signature548 // is what separates lane stations from zone stations.)549 let overlay = 0;550 for (const m of altRail.mods) {551 if (!m || !m.center) continue;552 if (altTag === "icy" ? m.icy === true : (m.speed ?? 1) > 1.5) overlay++;553 }554 assert(555 overlay > 4,556 `only ${overlay} centered ${altTag} stations on the tagged alt lane`,557 );558 }559 // Main lane of the same braid: interior stations carry the main tag.560 if (braid.laneMods!.main) {561 const mainRail = railForSegment(seg, false);562 const wantIcy = braid.laneMods!.main === "icy";563 assert(564 mainRail.mods.some((m) =>565 m && (wantIcy ? m.icy === true : (m.speed ?? 1) > 1.5)566 ),567 "main lane never resolves its tag",568 );569 }570});571572// ---------------------------------------------------------------------------573// V3 (user directive) — forks that resolve in LATER generation.574// ---------------------------------------------------------------------------575576Deno.test("open forks: chains resolve within the cap, port-continuous", () => {577 let opens = 0;578 let chains = 0;579 let middles = 0;580 for (const fam of ["grav", "jump", "wide", "helix"]) {581 for (let n = 0; n < 30; n++) {582 const seed = `${fam}-scan-${n}`;583 let w: EndlessWorld;584 try {585 w = new EndlessWorld(seed, catalog);586 } catch {587 continue;588 }589 for (let i = 0; i < 7; i++) {590 let seg;591 try {592 seg = w.get(i);593 } catch {594 break;595 }596 const fork = seg.plan.braids.find((b) => b.open && !b.cont);597 if (!fork) continue;598 opens++;599 // Walk the chain: stay-open middles (cont+open) ride whole600 // segments; the chain must END with a merging continuation601 // within OPEN_BRAID_CAP total cells, every hop port-exact.602 let total = fork.len;603 let prevChain = seg.path.segments.find((ls) => ls.alt?.braid)!;604 assert(prevChain?.alt?.openBraid, `${seed}@${i}: open chain unflagged`);605 let j = i;606 let done = false;607 while (!done) {608 j++;609 assert(j - i <= 3, `${seed}@${i}: chain overruns 3 segments`);610 const nextSeg = w.get(j);611 const cont = nextSeg.plan.braids.find((b) => b.cont);612 assert(cont, `${seed}@${i}: chain broken at seg ${j}`);613 assertEquals(nextSeg.braidContOf, j - 1, "contOf must chain");614 assertEquals(cont!.side, fork.side, "side must carry");615 total += cont!.len;616 const chainLs = nextSeg.path.segments.find((ls) => ls.alt?.braid)!;617 assert(chainLs?.alt?.contBraid, `${seed}@${j}: cont unflagged`);618 // Port continuity across the boundary.619 const prevEnd = segPoint(620 prevChain.alt!.curve[prevChain.alt!.curve.length - 1],621 1,622 );623 const nextStart = segPoint(chainLs.alt!.curve[0], 0);624 assert(625 Math.hypot(626 prevEnd[0] - nextStart[0],627 prevEnd[1] - nextStart[1],628 prevEnd[2] - nextStart[2],629 ) < 1e-6,630 `${seed}: alt lane tears at the ${j - 1}/${j} boundary`,631 );632 prevChain = chainLs;633 if (cont!.open) {634 middles++;635 assert(chainLs.alt!.openBraid, "stay-open middle unflagged");636 } else {637 assert(638 nextSeg.path.segments.some((ls) => ls.braidMerge),639 `${seed}@${j}: chain end lost its merge junction`,640 );641 done = true;642 }643 }644 chains++;645 assert(646 total <= OPEN_BRAID_CAP,647 `${seed}@${i}: chain span ${total} over the cap`,648 );649 }650 }651 }652 assert(opens > 3, `only ${opens} open forks across the corpus`);653 assertEquals(opens, chains, "every open fork must resolve exactly once");654 assert(middles > 0, "no stay-open middle segments across the corpus");655});