History moved to where it already lives: git log (detailed per-round commit messages) and agent memory (measured facts, physics laws, pin tables, debugging methods). The file keeps only the backlog: race presentation (HUD, contact cues, respawn envelope), physics/feel (respawn immunity, skill rubber-banding, torque escalation, pack-scale LOD), rendering (glass-through-glass port, gravity-zone camera pitch, occlusion polish), generation (stream goldens, slope-claim residual, destructible decor, endless takeable spurs, bumper tuning), and housekeeping notes.
- commit
- 9ce417398d381fa7867d060685981d3920be504e
- parent
- 6a17c3d4c4ed35e0a4fa9a99b1acf6032706fdc9
- author
- Dami <dami.etoile@gmail.com>
| M | progress.md | 1514 | ++---------------------------- |
@@ -1,1434 +1,80 @@
1-# progress2-3-Original prompt: Greenfield Deno webapp using Kenney Marble Kit assets to4-generate emergent, infinitely-looping marble tracks; URL fragment = seed5-(browser history); seed splits into RNG families across systems; WFC as6-realization layer; other Kenney packs as decor+setting layer. Current task: M57-polish / M6 (M5 endless mode core landed this round).8-9-## State (2026-08-08, round 2)10-11-- Milestone 1 complete: shell, asset pipeline, generation core, client renderer,12- CUA hooks. See git history for details.13-- **Milestone 2 complete**: bends, s-curves, waves, bumps, wide channel, two new14- themes.15-- **M2a complete**: decor is a world field (region+oracle API, ambient band).16-- **M3 complete**: all 12 helixes, corner drops/climbs, grade crossings17- (pierce), tunnel runs, hole spurs.18-- **M4 complete**: split sidings + turn junctions, split–merge diamonds with19- per-lap branch alternation, boost fans, ramp b/d tiers, track handedness, full20- kit coverage (122 present / 40 exempt / 0 missing @ 20k seeds).21-- **Hole semantics complete** (user design, see its section): drop-stacks22- (guaranteed falls, non-connected loop) + bridge pierces with straight-hole23- chance-falls onto the channel below.24-- **M5 endless mode complete (this round — REDESIGNED with the user, see its25- section)**: the void line — an unbounded open path through empty 3D space,26- anchored by generative floating islands, streamed segment-by-segment.27- `?endless=1` + the same `#seed` hash; finite loop mode untouched.28-- 57 deno tests green (51 finite + 6 endless); goldens UNCHANGED — the finite29- pipeline is byte-identical. CURRENT genVersion: ga265f1373eed.30-31-## M2a systems32-33-- **placeDecor is inverted** (decor.ts): caller supplies a `CellRegion` +34- `TrackOracle` (`(x,z) → {dist, dir}`); the function realizes the field for35- exactly that region. `regionAround(cells, margin)` + `cellSetOracle(cells)`36- are the single-track backers; M5 passes chunk regions + a chunk-backed oracle.37- Per-cell RNG children (`cell/${x}/${z}`) unchanged.38-- **ORACLE_HORIZON = 6**: oracles must see exactly this far (bands stop at 5.5).39- `dist` saturates to Infinity beyond it — that's the ambient band. Chunked40- backers need neighbor context of exactly this radius.41-- **Ambient band** beyond 5.5 out to the region edge: reuses the far-band rules42- thinned ×0.3 (extra per-cell chance draw), fans excluded (a lone spinning fan43- in the void reads as debris). Single-track margin 5 → 13.44-- **Forward-compat compromise (M5 list)**: ambient density is FLAT (0.3×), not45- distance-graded — the oracle saturates at horizon 6 so there's no distance to46- grade by. If a graded falloff is ever wanted, widen the oracle contract, don't47- special-case the field.48-- tests/decor_test.ts pins the tiling invariant (quadrant regions == single49- region, exact), ambient existence, and track clearance.50-- Avg ~143 decor pieces/track at margin 13 (was ~margin-5 ring). Draw cost fine51- today; instancing is M5's job.52-- Ground plane: client-side, track bbox + 90/side — already covers the wider53- field; M5 replaces it with streamed ground.54-55-## M2 systems56-57-- **Geometry (measured from GLBs this round)**: bends are pure semicircles with58- ZERO lead-in (diameter on the port face; lanes 1/3/5 apart, r 0.5/1.5/2.5);59- s-curves are two tangent 90° arcs of the curve/curve-large radii (lane60- momentarily perpendicular to travel mid-piece); waves are purely lateral,61- bumps purely vertical, both smootherstep-eased trough→crest→trough over 462- cells (ports at trough); wave ports sit at lane −A of the mesh frame (mesh63- shifts +A to grid-align); funnel taper is exactly linear over 1 cell;64- funnel-long = straight+funnel+straight-wide fused; straight-wide channel is65- 1.7 wide centered on the cell (0.35 bulge into each flank cell).66-- **curve-wide is OFF-GRID** (ports on ±0.85/±1.35/±1.85 bbox faces, lane circle67- non-concentric with the corner, oblique at ports) — cannot mate with the68- 1-unit port lattice; DEFERRED to a later milestone as a set-piece macro.69-- **Topology mutations** (topology.ts): alongside bump —70- - U-extrude(m∈{2,4,6}, k≤5): peninsulas whose two columns MAY touch (adjacent71- parallel channels clear each other; walls at ±0.35). m=2 tops are LL/RR72- hairpins (bend windows); m=4/6 host bend-medium ("FLFFLF") and bend-large73- ("FFLFFFFLFF"); k=3/5 shoulders host s-curve windows ("FLFFRF" /74- "FFLFFFFRFF" dog-legs).75- - staircase: two-cell lane shift via LRLR diagonals (hosts chained short76- s-curve jogs "FRL"/"FLR").77-- **Registry infra**: mirrorDef() (x-flip; L↔R, arc θ→π−θ, meshRot→−meshRot,78- placement.mirrored renders scale(−1,1,1); mirrored def ids keep meshId of the79- real GLB node); overlays now have absolute entry-frame offsets + optional80- rot/mirrored (composite pieces: wiggle-a/b = short-jog + mirrored jog,81- lane-shift-b = two chained b jogs); widthIn/widthOut ports; reservedOnly (wide82- pieces never free-collapse); columnSupports (off-center channels get83- centerline-snapped plain columns, never cradles).84-- **Feature planner** (features.ts, rng family "features"): plans wide runs85- funnel→straight-wide×n→funnel(reversed) on straight runs, checking lateral86- margins at plan time. Runs BEFORE elevation (ring flat at plan time);87- elevation avoids feature cells (`blocked` param). Order matters: elevation88- used to eat all long runs — 54% of skeletons had no eligible wide run.89-- **WFC**: reservations merged from elevation+features (modulo seam-safe),90- reserved defs' footprint extras go into occupancy, width continuity is a hard91- post-collapse assertion (funnel bracketing guarantees it by construction —92- tested).93-- **Supports are structural now** (assemble.ts): per contiguous elevated94- stretch, pillar at each end (abutments) + piers every SUPPORT_SPAN=2.4 units,95- global SUPPORT_CLEARANCE=1.0 dedupe (hairpin arms share pillars).96- Deterministic greedy in ring order, no RNG. (User: "you wouldn't put 3097- pillars to cover a U curve".)98-- **Themes**: serpentine (bends/s-curves/waves hot), boulevard (wide runs,99- wideAffinity 0.9, stately large curves). wideAffinity on StyleField drives the100- feature planner.101-- **Solid-turn rule** (user feedback, via inspect mode): corner-solid /102- curve-solid(-large) fill their full footprint squares beyond the pipe (open103- forms are contour-cut: corner's outer corner is rounded away, curve is an104- annulus). `solidExtends` on those defs; assemble picks the solid ONLY when105- some orthogonal footprint-neighbor is a same-height RING cell (ports excluded;106- footprint extras like funnel flanks are void, not deck, and never count as107- support). Otherwise the pipe-only open form is placed. Turn solids still108- appear in ~48% of tracks (hairpins/plazas).109-- **Inspect mode** (client): tilde toggles; click placements to select110- (drag-suppressed raycast), red box highlights, panel lists111- `#i piece @ (cell) yH rR [role]`; buttons: copy debug info (portable JSON112- {seed, version, url, tiles[]}), clear selection, exit mode. This exists113- because stale browser bundles caused three rounds of confusion.114-- **Computed version** (src/gen/version.ts): genVersion() hashes canonical115- placements of 4 fixed probe seeds (decor included, synthetic catalog) with116- cyrb128 — NO manual bumps; any behavior change changes the string, refactors117- that preserve output don't. Shown in HUD/reports; also embedded in118- tests/golden_tracks.json as "__version" so the committed goldens are119- self-documenting. ~12ms on first call, cached.120-- **Goal gate** (user idea): one `banner-high` per track straddles the channel121- as the lap line — post feet at ±0.425 = exactly the rail-top centers122- (measured; Kenney designed banners to sit on the rails), cloth underside +0.75123- clears the marble by 0.6. Placed deterministically on the first plain straight124- window; `goal: true` on the Placement makes the renderer clone+tint its125- materials dark charcoal (0x30303a) so it reads as the finish marker against126- any palette (cloned materials disposed with the build). Proper checkered127- retexture: candidate for M6 polish.128-- **Plate-tile rule** (user feedback, two rounds): the s-curve-short meshes are129- square slabs with the channel carved diagonally — their triangular platform130- wings look wrong jutting into bare grass. `plateSides` on a def lists the131- lateral wing cells; candidateOk requires the connected deck to lie along AT132- LEAST ONE wing (user-specified threshold — port-edge neighbors don't count,133- they touch every piece by construction) AND at the SAME base height (the WFC134- occupancy set is a cell→baseY map now; a channel a level below a wing doesn't135- visually carry it). Flagged: short-c singles, wiggle-b, lane-shift-b (a-tier136- wings are sub-0.35 slivers, unflagged). Effect: short-c presence 550→364 of137- 600 tracks, always with a wing against neighboring deck. Standard corners stay138- the default in open terrain.139-140-## M3 systems (complete)141-142-- **Helix geometry (measured this round)**: every helix is its flat cousin's arc143- with the floor descending LINEARLY IN ANGLE — quarters = curve / curve-large144- quarter arcs (FLF / FFLFF windows, drop 0.5), halves = bend-medium /145- bend-large semicircles (FLFFLF / FFLFFFFLFF, lanes 3/5 apart, drop 1.0).146- Ports/radii/footprints bit-identical to the flat pieces; helix walls are147- floor+0.25 (not +0.5); mesh grounded on its EXIT side so meshOffset.y = −drop.148- Native right GLBs are exact mirrors (mirrorDef + meshId cleared, s-curve149- pattern).150-- **FULL helixes (all 4) enter and exit at the SAME cell edge** (e.g. small:151- port plane x=0 at z=1.5 of the ±2 bbox, both ports heading −x, bases 2.0 and152- 0.0, 360° at r=1.5/2.5 dropping 2.0). SOLVED as one reserved ensemble window153- (see below) — no general leveled-occupancy model was needed.154-- **Full-helix ensembles**: registry defs helix-left/right (15-cell window,155- moves "RFLFFLFFLFFLFFF") and helix-large-left/right (24 cells,156- "RFFLFFFFLFFFFLFFFFLFFFFF"): approach corner overlay (mirrored corner = R157- turn) at the top + 360° perimeter-walk spiral + ground-level straight overlays158- under the first spiral cells (the underpass). The window's moves REVISIT its159- own first cells (approach cell + underpass); footprint = the whole N×N block.160- meshOffset [0.5, −2, R], meshRot 2 (ports mid-face). deckDelta −2. Topology's161- helix-loop mutation (topology.ts, HELIX_LOOP_SPECS — moves strings pinned to162- the defs by a registry test) replaces a turn cell + `under` following163- straights with the walk, applied LAST, mutually exclusive with pierce, and164- only if a ≥4-cell straight run survives for the mandatory climb. The window165- lands at ring index 0 (normalization is identity because its first cell is a166- turn). Skeleton.helixLoops declares it; elevation reserves the def + a 2.0167- climb (CLIMB_VOCAB["2"], longest eligible run) and skips other hills; features168- (wide runs) are skipped on loop tracks so the climb keeps its run. Elevation169- now NORMALIZES baseY by −min (the integration constant is free on a closed170- loop), so drop-before-climb ring order is fine. ~5% of tracks. [Chirality note171- SUPERSEDED: M4's 50/50 ring reversal + site enumeration balanced left/right.]172- The spiral coil is freestanding (the GLB has no central column and no mid-coil173- support points — Kenney authored it as a self-standing slide; flagged for user174- feedback).175-- **Grade crossings (cross piece + pierce mutation)**: `cross` measured as a176- flat 4-way (straight's port signature on all four faces — same height, NOT two177- levels). Topology "pierce": an m=2 hairpin pushed THROUGH a perpendicular178- straight run (d ≥ 2 rows out, k ≥ d+1), creating two adjacent twice-visited179- cells; Skeleton.crossings lists index pairs (a, b). Realization pre-reserves180- both visits: `cross` renders the mesh, `cross-over` (noMesh flag on PieceDef)181- contributes centerline/occupancy only; its LoopSegment carries placementIndex182- −1. Crossing tracks stay FLAT (M3 compromise: keeping the ring at base 0 is183- the only rule that can't split a pair's heights; a hill-interval constraint184- could lift it). Wide runs treat crossing indices as blocked. Stubs can't185- attach beside a crossing (its neighbors are ring cells). ~4-5% of tracks186- (pierce chance 0.55, geometry-limited).187-- **Corner drops** (elevation.ts): forward helix defs are descents (deckDelta<0,188- never free-collapse); TURN_DESCENTS/TURN_CLIMBS map window patterns → helix189- ids (a climb through pattern P is the REVERSED variant of the190- opposite-chirality helix). findTurnWindows() scans the ring move string,191- checks footprint extras against ring cells at plan time. planElevation takes192- the registry now (footprint checks). [SUPERSEDED by the M4 restructure:193- affinity is 0.5, ONE pre-claimed corner set-piece per track, and the194- ring-order/underground-throw invariant is GONE — baseY is min-normalized, see195- "Corner-drop planner restructured" under M4.]196-- Helix emergence: 38% of tracks (quarters dominate; halves/large rare197- set-pieces tied to peninsula windows). 0 failures / 600 strict seeds.198-- **Tunnel runs**: tunnel-run-2/3 defs (composite straight+arch per cell,199- weights 2.5/1.5). Placements carry MESH ids ("straight" + "tunnel" overlays) —200- count arches, not def ids, when measuring emergence.201-- **Hole spurs** [SUPERSEDED: M4 replaced wall-butt spurs with real split202- junctions — see "Split sidings"]. Kept measurement: a straight's block side is203- a flat 1×0.5 face at the cell edge, so branch ports sit flush against it.204-- **Decor banners moved near → mid band**: posts at ±0.425 + jitter ±0.3 can205- reach across into an adjacent track cell from the near band (caught by206- validate at ~1/1000 seeds).207-- M3 final state: 51 tests green, 1500-seed strict fuzz (decor on) clean.208- genVersion after M3: g9d8e4c534f18. Goldens regenerated.209-- M3 emergence (1000 seeds): quarter/half helixes 33%, full helixes 6%,210- crossings 6%, tunnel runs (2+ arches) 50%, stubs 42%.211-212-## M4 systems (complete)213-214-- **Split sidings** (stubs.ts rewritten, same "stubs" rng family): the M3215- wall-butt spurs are REPLACED by real 3-way junctions — a realized plain216- `straight` placement at ground level is mutated into `split` (same cell, same217- rotation: the split's main channel IS the straight; measured ports z−/z+/x−,218- so the native branch goes LEFT of flow; right branches set `mirrored`). Branch219- = optional straight-hole + end cap (end-hole-rounded/square or220- end-rounded/square), 1–2 cells, flush. Skips tunnel-arched cells and the goal221- straight. ALSO turn junctions: split-left is a drop-in for the curve MESH222- (+branch port on the outer flank at mesh (1.0, 0.5), branch cell (1.5, 0.5));223- split-large-left ditto for curve-large (branch cell (2, 1)); solid hosts swap224- to split-solid-*. Left/right ids alternate 50/50 — the right GLB renders225- mirrored to the same shape (s-curve convention). 36% of tracks.226-- Split geometry (measured): split-left/right = 2×2 Y (curve's r1.5 arc +227- opposite-side branch exit); split-large = 3×3 curve-large analog; split-double228- = 3×2 center entry → two +z exits at x±1; split-double-sides = 3×2 center229- entry → exits BOTH x-sides (NO through port — unusable on the ring, exempt).230-- **Split–merge diamonds** (`diamond-1/2/3` composite reserved defs + feature231- planner): split-double + N branch straights per side + a 180°-rotated232- split-double merge, one composite window (moves F^(4+N), footprint 3 columns).233- PieceDef.altCenterline carries the second branch; LoopSegment.alt flows to the234- client; buildCenterline fits TWO CatmullRom variants and the marble swaps at235- lap wrap (verified: laps cross at z±1 alternately). Branch jogs are236- z-linear-third cubics (lane-shift law). Planner: exhaustive (N, offset) search237- per shuffled run, both flanks must be free; ~2% of tracks (rare set-piece;238- boulevard hosts more).239-- **Fans as track elements** (fans.ts, rng family "fans", 30% of tracks): 1–2240- fans flush beside ground straights, blades facing the channel (all six fan241- GLBs), spin + `Placement.boost` (6 u/s²); the client marble accelerates while242- within 1.2 units (POSITION-based, so diamond branches both work). Verified:243- speed 2.85→4.53 passing a fan. Fans removed from the decor table.244-- **Ramp b/d pair tiers** authored (ramp-start/end-b: 0.25 each — mid-base 0.25245- is fine, only delta-window starts sit there; ramp-start/end-d: 0.75 each = the246- chute cap, DESCENT_VOCAB["1.5"] only).247-- **Track handedness**: the seed rectangle ring is REVERSED 50/50 — without it248- every loop is turn-left-dominant and left-handed helix ensembles never fit249- (their R anchors only arose in cluttered bump detours). This symmetrized all250- chirality coverage.251-- **Corner-drop planner restructured** (elevation.ts): the corner set-piece252- window is picked BEFORE hills grab straight runs (large helix windows straddle253- several runs and were starved — helix-large-half never appeared). One254- length²-weighted corner descent per track (affinity 0.5) + straight or spiral255- climb; hills use the remaining runs; ring order is free (baseY normalized to256- min 0 after integration — the old climb-before-descent invariant is obsolete).257-- **Full-helix site enumeration** (topology.ts): all valid (anchor, spec) sites258- enumerated, pick weighted ×3 large, ×3 minority chirality.259-- **Coverage dashboard** (tools/coverage.ts,260- `deno run --allow-read261- tools/coverage.ts [N]`): 20k seeds → **122 present,262- 40 exempt, 0 missing**. Exemption categories (curated in the tool): curve-wide263- family (off-grid), marble variants, tier-a verticals (0.125 off-grid),264- slant-c/d (steeper than caps), top-double cradles (0.4 saddle pitch matches265- nothing), split-double-sides (no through port), straight-wide-hole (hole on266- the marble's path), solids of always-elevated pieces (solids are ground-only267- by user rule). bend-solid-large / s-curve-*-large flicker at ~1–3/10k (present268- at 20k).269-- **Funnel drops: deliberately SKIPPED** — the kit has no bowl/cone piece;270- "funnel" is the width taper (covered by wide runs).271-- M4 final state: 51 tests green, 1500-seed strict fuzz (decor on) clean,272- goldens regenerated. genVersion after M4: gc6f70b6760f9.273-274-## M5 systems (complete) — endless mode is a VOID LINE with ISLAND ANCHORS275-276-USER REDESIGN (this round, supersedes the old "chunk streaming" sketch below in277-the historical handoff): endless generation is NOT a tile system and has NO278-ground plane. The old plan (disjoint chunk footprints on a ground plane) was279-rejected explicitly: "infinite generation should not be independent footprints…280-it is not a tile system." Design conversation settled on:281-282-- **The world is a void** (space, starfield, no ground). Removing the ground283- decoupled elevation entirely — the line dives forever (net-down drift, the284- honest physics for a marble) and weaves over/under/around its own past.285-- **Islands are anchors, not scenery**: "An island is a thing a track has to286- anchor to. When the island is no longer required as an anchor it can be287- culled." The path planner owes the line a landing every `anchorEvery` cells;288- the island builder erects a cliff-block voxel mass UNDER the landed run.289-- **Island families are the new settings layer**: biome table (forest / meadow /290- rocky / barren today; lava/snow/city are future rows pointing at future291- packs). Decor demoted to a per-island field on the island top (same M2a292- region+oracle API, run cells = "the track", lifted to topY).293-- **Cross + graze only**: the line may bridge over, pierce under, and run flush294- beside earlier track, but never merges into an old channel.295-- Destructible decor: dormant (nothing can collide with decor in the void);296- revisit when something can.297-298-### Architecture (src/gen/endless/)299-300-- **occupancy.ts**: cell (x,z) → occupied y-bands `{y0, y1, ref}`. A channel301- claims [baseY, baseY+0.99] (walls + marble headroom — stacked channels at the302- kit's Δ1.0 just clear). Refs (`seg/${i}`, `isle/${k}`) release wholesale on303- cull. `OccupancyTx` = write overlay for transactional planning (stackable —304- probes run on a second layer).305-- **path.ts**: clause-based planner. Void clauses: straight runs, turns, FRL/FLR306- jogs (s-curve food), slope windows straight from elevation.ts's307- CLIMB/DESCENT_VOCAB (exported now), corner drops from TURN_DESCENTS (helix308- quarters/halves, bare-corner drop-stacks). Island clause: flat run 5–11 cells309- (occasional bends), mass envelope + CANOPY claims immediately. Elevation310- mirrors elevation.ts exactly: a window's whole delta steps at its start index.311- 18% of slopes are climbs; the rest descend — that's the drift.312-- **Pocket guard**: every clause commit requires a 3-deep flat continuation from313- its exit (claim-aware, incl. the pending clause's own cells). `probe()` also314- checks self-intersection within a single clause (a turny island run can loop315- onto itself before anything commits).316-- **world.ts**: `EndlessWorld.get(i)` generates strictly in order. Per segment,317- ONE TRANSACTION wraps: plan → realize (open WFC) → claim WFC footprint extras318- → deep exit checks. Attempt labels: plain, retry/1..5, then PROBE_LABELS.319- **Successor-probe lookahead**: an attempt only commits if segment i+1 actually320- PLANS under ≥1 of 4 fixed probe labels on a stacked tx — no fixed-depth321- corridor check can rule out every cul-de-sac, but "the successor plans" can;322- the successor replays those exact labels as its last-resort attempts. Result:323- 0 failures / 400 seeds × 80 segments (~1.5ms/segment). Earlier iterations for324- the record: naive → snake-traps itself; +guard → post-planning claims (WFC325- extras, canopy) broke guard promises; +tx retries → trapped ENTRY poses326- unfixable; +single-probe gate → WORSE (a no-retry gate rejects good attempts,327- failures compound); +probe SET → clean.328-- **islands.ts**: biome table (weights, cliff material rock/stone, cap tile,329- setting row, decor density). Mass = cliff_block/blockHalf/blockQuarter columns330- (1×1×1 / 0.5 / 0.25 — the nature kit cliff family is EXACTLY our grid,331- bottom-center origins), run columns always, ring columns thin+shallow with332- Chebyshev distance (stepped inverted-ziggurat underside). Depth stops at older333- claims → islands wrap around old track (a line under an island = recessed334- floor). Caps (ground_grass) + placeDecor filtered to mass tops. Vegetation335- only on cells whose CANOPY airspace claim ([topY, topY+1.8]) succeeded — no336- tree ever threads a future channel; approach/departure corridors are337- canopy-exempt (a natural shoreline clearing).338-- **Culling is generation-driven** (KEEP_SEGMENTS=10): generating segment i339- releases segment i−10 and its islands. NEVER client/marble-driven — that would340- make the stream depend on frame timing. The client render window341- (BEHIND=2..AHEAD=3) sits strictly inside, so everything visible is co-live in342- the occupancy and nothing on screen can overlap. The line MAY reoccupy343- long-culled space (legal: that geometry is despawned far behind the render344- window; the void heals).345-- **wfc.ts/assemble.ts open mode** (opt-in `RealizeOptions`/ `AssembleOptions`,346- closed path byte-identical — genVersion unchanged): no window wrap, no seam347- width check, `extraFree` world-occupancy veto for footprint extras; assemble348- skips supports/goal banner/solids (ground concepts). Style: same "style"349- family per seed — one theme both modes.350-351-### Client (src/client/endless.ts)352-353-- `?endless=1` (+ `#seed`), HUD `#mode-toggle` navigates between modes.354-- Session per seed: segments built into per-index groups (track + island355- placements together — islands cull with their birth segment), open CatmullRom356- per segment (`buildOpenCurve`), global arc length chained via per-index357- `sMeta`. Marble = same physics constants as loop mode, global s, no laps.358- Camera: `retargetPath(sampler, proxy)` follow mode (loop mode's closed-curve359- follow untouched); follow is the default in endless.360-- Void dressing: bg/fog 0x070b16, camera-pinned starfield Points361- (sizeAttenuation off, fog off). No ground. `instantiatePlacements` was362- extracted from track.ts and shared.363-- CUA state: `mode: "endless"`, marble {s, speed, pos, segment}, stream {built,364- generated, islandsSeen, occupancyCells, theme}.365-366-### Verified (CUA harness)367-368-- Spawn on home island, grass-capped cliff mass with trees, line dives into the369- void, islands stream past, HUD stats update; loop mode regression clean370- (`#loop-check`: 346 pieces, 0 missing).371-- 6-sim-minute browser soak (scratch `soak.mjs`: one `advanceTime(60000)` call372- per minute — the per-frame harness loop is too slow for soaks): 914u, 31373- segments, 31 islands, render window slid [3,8]→[29,34], occupancy bounded374- 625–847 cells, depth −61, ZERO console errors. Tunnel arches emerge in void375- flight (free WFC), the marble dives through them.376-- Deep generator fuzz: 0 failures / 400 seeds × 80 segments, ~1.5ms/segment.377-378-## Hole semantics (user design, post-M4)379-380-USER RULE: hole pieces represent the POTENTIAL to fall through — guaranteed for381-end-hole, "same as a split" for straight-hole — and a fall must land ON TRACK382-BELOW (a WFC-level requirement, satisfied by construction: holes are only383-realized inside compositions that carry their own lower track). Plain `end`384-pieces are supported so the channel network may be NON-CONNECTED — the lap385-closes through the fall.386-387-- **Drop-stacks** (`drop-stack-{rounded,square}-{f,l,r}` defs): an end-hole388- stacked FLUSH on a receiver `end` piece (0.5 grid, nothing floats). One cell,389- deckDelta −0.5; the CENTERLINE ITSELF dives vertically through the hole into390- the cup and exits the receiver's port (overlay rot 2/1/3 for F/L/R) — no391- client mechanic needed, the gravity model makes the marble plunge (verified:392- speed spike, clean landing). Placed by DESCENT_VOCAB["0.5"] (SlopePart.asIs —393- the FORWARD def descends, don't reverse it) and by TURN_DESCENTS "L"/"R" (bare394- corners; no TURN_CLIMBS form — guarded, you can't fall upward). ~24% of395- tracks.396-- **Bridge pierces**: pierce with d ≥ 3 upgrades to a bridge (chance 0.7): the397- hairpin climbs +1.0 on its columns (elevation reserves climb at indices [1,398- d−1], descent at [2k−d+2, 2k] — the pierce splice lands at ring index 0, and399- the NEAR ANCHOR MUST BE A GENUINE TURN or seam normalization rotates the400- indices; approach-along-normal anchors are rejected — this was a real401- 1%-of-seeds loop-gap bug), crosses OVER the run, descends. Upper deck =402- `straight-hole` (60% per crossing, rng family "falls") or plain straight;403- lower visit reserved as plain straight (keeps waves/tunnels from growing under404- the deck). Track.falls carries {pos, landPos} per hole (same (x,z), Δy 1.0).405-- **Client chance-fall** (marble.ts): falls mapped to arc positions per curve406- variant (dense closest-point sampling; y separates the two lines);407- deterministic per (lap, hole) hash takes the fall ~50% of passes — an eased408- 0.35s vertical drop, then s jumps to the landing and the marble continues on409- the lower channel (verified in telemetry and screenshots).410-- Guards: no support pillars and no goal banner at twice-visited cells (the411- ground belongs to the lower channel); stubs/fans skip stacked cells.412-- Emergence (1500 seeds): bridges 1.5%, flat crossings 0.9%, drop-stacks 24%.413- Coverage: straight-wide-hole stays exempt (no wide line ever has track below —414- a wide bridge is a future macro); end-rounded/end-square now ALSO appear as415- drop-stack receivers.416-- NEXT (not built): takeable spur branches (split siding → end-hole = guaranteed417- drain onto a lower line where alignment exists) — needs branch-taking beyond418- diamonds; noted for M5/M6.419-- Final state: 51 tests green, 1200-seed strict fuzz (decor on) clean, goldens420- regenerated. genVersion: ga265f1373eed.421-422-## Emergence snapshot (current, post-hole-semantics)423-424-Registry: 119 variants. Per-track presence: helixes ~50% (quarters dominate;425-halves/larges rare set-pieces), drop-stacks 24%, split sidings 36%, boost fans426-30%, tunnel runs 50%, wide runs ~14%, diamonds ~2%, bridges 1.5%, flat crossings427-~1%, full helixes ~5%. bend 95%, s-curve-short 92% (M2-era, still428-representative). Kit coverage: 122 present / 40 exempt / 0 missing @ 20k429-(tools/coverage.ts).430-431-## Notes / gotchas432-433-- Reversed-def dedup in buildRegistry must compare widthIn/widthOut too, or the434- reversed (wide→narrow) funnel silently vanishes.435-- Window cells need NOT all touch the channel — bend-large/s-curve-large use436- rectangular dog-leg windows whose corner cells are only wall-clipped; the ring437- path is an abstraction, the marble follows the centerline.438-- Adjacent-parallel channels (hairpin columns) are physically fine: walls reach439- ±0.35, gap 0.3. The topology clearance rule is relaxed ONLY for a mutation's440- own column pair.441-- Goldens (tests/golden_tracks.json) regenerated this round — still442- pre-release/breaking-allowed. M6 freezes them.443-- Elevated diamonds (a diamond on a plateau between climb and descent) get444- support columns only along the LEFT branch centerline (assemble follows the445- window's main centerline); the right branch and slab edges are unsupported —446- rare compound case, fix with per-branch column candidates (M6 polish).447-- Twice-visited cells store ONE baseY in the WFC occupancy map (last write wins)448- — plate-wing/solid-turn equality checks near crossings/bridges are449- cosmetic-only approximations.450-- Reservation planners must not overlap: features exclude crossing indices,451- helix-loop tracks skip wide runs entirely, and turn windows are pre-claimed452- before hills. Adding a new planner? Thread its cells through the same453- blocking, or WFC will double-cover a ring index and tear the loop.454-- MEASUREMENT SCRIPTS live in tools/ now:455- `deno run --allow-read456- tools/measure_ports.ts <piece-ids…>` (per-bbox-face457- port signatures: floor marks at lat ±0.1 y=+0.25, walls ±0.32, slab edges) and458- `tools/measure_floor_path.ts <piece> [cx cz]` (upward-facing floor ribbon with459- polar coords — how the helix spirals were decoded). Measure before authoring;460- this project never guesses geometry.461-- CUA harness recipe (rebuilt this round): `npm install playwright` in a scratch462- dir, copy the develop-web-game skill's client script next to it,463- `deno task build`, serve on a port, then464- `node client.mjs --url http://localhost:PORT/#seed --click-selector465- "#cam-mode" --actions-json '{"steps":[{"buttons":[],"frames":40}]}'466- --iterations N`467- — screenshots + render_game_to_text land in output/web-game/. Follow-cam468- sweeps + state JSON (marble pos/speed per iteration) are how falls, boosts,469- and lap alternation were verified.470-471-## ROADMAP — the physics era (planned with the user, 2026-08-08)472-473-USER DIRECTION (verbatim intent): expand the forms of variation — textures,474-spawn/despawn animation (currently we pop in/out) — and introduce the physics475-aspects: fall to another path, plinko-style filters, dynamic speed, speed476-boosts, track gaps (jumps, falls, cliffs, stairs), island verticality (generally477-flat today; should flow around itself in x/y/z with varied start→end elevation).478-True physics introduces the chance to fall OFF the track → a respawn system is479-required, with generated CHECKPOINTS so the respawn point always moves forward.480-To make falling less likely, change the marble:track proportion —481-marble/islands/decor keep their world scale, the TRACK becomes a slalom (bigger482-channel relative to the marble). Physics target: the new 3D version of Box2D483-("recently came out" per user — post my knowledge cutoff; VERIFY484-name/status/license/WASM first, see M9). Everything sequential; each milestone485-lands only once the previous system is validated. VALIDATION STYLE (user rule):486-avoid time-based fuzzing — use scenarios against rules (WFC-style rule487-enumeration, and survivability checks for specific combos found to be488-problematic).489-490-### DECISION LOG (user, 2026-08-08 late round)491-492-- NO PLAYER CONTROL, ever: the marble is an autonomous performer; the assist law493- is the choreographer. The product is the EMERGENT GENERATIVE SPECTACLE494- (screensaver-grade). All physics/powerup work optimizes for watchability, not495- input.496-- Loop mode = visual inspection bench + BRANCHING testbed. Branches are two497- traversals of ONE generation pass (never into ungenerated space); in endless498- they become RESPAWN VARIETY — after a fall the line may resume down the499- alternate branch.500-- Powerups are PLANNER VOCABULARY, not pickups: placed by rule where the track501- supports them (propeller-cap BECAUSE a big jump with wide landing follows;502- shock absorber before a hard drop; gravity pads on islands). This fuses M11's503- speed envelope + momentum vocabulary + Marble Blast powerup patterns into one504- system: MOVEMENT SET-PIECES = approach + modifier + flight + catch, composed505- generatively, validated by scenario.506-507-### Milestone sequence (each gates the next)508-509-**M6 — Living stream: spawn/despawn animation + texture variation.** [GOAL 1510-DONE: spawn/despawn waves — src/client/streamAnim.ts. Per-placement511-drop-in-and-settle (easeOutBack, stagger 0.045s/unit along travel dir +512-deterministic position jitter), sink-out on disposal (segment lingers in513-`exiting` until its wave finishes); initial session fill appears whole; session514-teardown immediate. Validated rule-based via CUA stream state515-(spawningSegments/exitingSegments lifecycle: saw spawn on build, saw exit, all516-settled; sceneNodes bounded 2.9k–4.2k over 4 sim-min; zero console errors) +517-screenshots. GOAL 2 DONE: per-instance color variation + instancing —518-src/client/islandRender.ts. USER RULE: variation must be per-part, never a519-blanket tint. Islands render as InstancedMesh per (piece, submesh) with520-setColorAt per instance: geological strata (courses darken/cool with depth),521-position-hash hue/value jitter, moss accents on the top course (forest/meadow),522-rust mottling (barren), per-island foliage hue lean (most green, some autumn,523-some teal — foliage submeshes detected by green dominance, trunks value-jitter524-only), cap tiles with field-parity alternation. streamAnim animates instances525-individually (base matrices in userData.animInstances). Scene nodes 3.4k → ~300,526-whole frame ~35 draw calls. FOUND + FIXED en route: Kenney GLTFs omit527-metallicFactor → glTF default metallic=1.0 → GLTFLoader builds full-metal528-materials that render BLACK off-sun (no envmap); assets.ts now normalizes529-metalness=0 on load — this also visibly fixed loop-mode nature decor. GOALS 3–5530-DONE, M6 COMPLETE: (3) generative ATMOSPHERE family (src/client/atmosphere.ts) —531-mood table (midnight/ember/violet/abyss/ snowdrift/goldhour) × param jitter ×532-weather draw (none/snow/dust/embers), one combo per 5-segment ZONE via533-`atmosphere/zone/${z}` labels; zone crossings crossfade sky/fog/sun534-color+intensity+direction/hemisphere/ starfield over 4s; weather = camera-local535-wrapping particle volumes with fade swaps. Supersedes "per-biome fog tints". (4)536-checkered goal gate — the banner is one colormap primitive (no cloth submesh),537-so a generated- CanvasTexture checker slab wraps the cloth band (posts stay538-charcoal). (5) "abstract" biome row (gen, weight 0.6) — dev-map islands: orange539-masses with grey courses, no caps/vegetation, renderer swaps the color scheme.540-KNOWN COSMETIC: follow cam can clip through island masses it trails past541-(pre-existing; polish).] Placements animate IN as the stream builds542-(transform-based rise/scale settle, staggered per placement by distance from543-segment entry; island blocks bottom-up, decor after) and animate OUT before544-disposal (segment lingers one window while exiting). Visual-only wall-clock545-easing — generation untouched, determinism unaffected. Textures: biome-tinted546-cliff materials (shared material clone PER BIOME, not per block), per-theme547-accents, the checkered goal-gate texture (old backlog), and evaluate extra548-Kenney texture packs (e.g. Prototype Textures as an "abstract" biome row).549-Validation: headless build/dispose cycle asserting renderer.info counts return550-to baseline (no leaks), animation state-machine unit tests, screenshot review.551-552-**M7 — Scenario validation harness (the pre-physics gate).** Demote time-based553-soaks to smoke tests. Build: (a) transition-matrix rule tests — enumerate554-realized window transitions from the registry and assert geometry/band555-invariants per PAIR, no time dimension; (b) a named scenario runner556-(tests/scenarios/): each scenario = fixed seed+segment or a constructed557-fragment + assertions, so a problematic combo found in play becomes a permanent558-named regression; (c) survivability scaffolding that M10+ physics scenarios plug559-into (deterministic sim of a fragment at min/typical/max entry speed → must560-complete). WFC rules help here — the piece vocabulary IS the enumeration space.561-562-**M8 — Slalom proportion (world rescale).** [DONE. TRACK_SCALE=2563-(src/gen/endless/scale.ts): the generator plans in TRACK CELLS; world.ts maps564-track placements ×2 (pos + mesh scale via Placement.scale) and scales the565-centerline; islands/decor stay 1× — buildIsland now emits on the WORLD grid (one566-track cell = 2×2 world cells via worldCellsOf; mass columns, caps, and the decor567-field all per world cell — finer variation for free). Island.topY is now WORLD568-units; plan/crossings stay track units. CANOPY_HEIGHT 1.8→1.0 track units (trees569-are 1.8 WORLD). Endless-only — loop mode untouched, goldens intact. Follow cam570-rides higher/farther (trail 7, height 3.1 via CameraOptions) or the ×2 pieces571-swallow it. Channel ≈ 3.5 marble diameters — the slalom. 63 tests green (island572-anchorage assertions now world-aware).] Track pieces scale up (proposal: ×2) in573-world space; marble, islands, decor stay 1×. One track cell = 2×2 island cells574-(mass builder maps columns), decor bands rescale, elevation quantum doubles in575-world units. PROPOSAL: endless-only at first — loop mode keeps 1× so finite576-seeds/goldens survive; revisit at M13 freeze. Slalom channel ≈ 3.5–4× marble577-diameter = falling off is rarer under physics, and the marble reads as a marble,578-not a boulder. Validation: geometry rule tests at the new scale (band579-clearances, island column mapping, port continuity), screenshot pass. OPEN580-DECISIONS: exact scale factor (2 vs 2.5), whether speeds/gravity constants581-rescale.582-583-**M9 — Physics engine spike.** [DONE — see tools/physics-spike/README.md. BOX3D584-CONFIRMED: Erin Catto released Box3D 2026-06-30 (MIT, C17, the 3D successor to585-Box2D; verified via web search — it's real and it's exactly what the user586-meant). Pinned commit 3fc20f5b453ba9e14cdf54ecafa87a2a4bcdf53c. Gates: builds to587-WASM via `emcmake cmake` with ZERO patches (emscripten via brew); BIT-IDENTICAL588-determinism across runs (advertises cross-platform determinism + record/replay);589-~228k steps/s ≈ 3800× realtime at 60 Hz ×4 substeps; contact precision exact to590-the geometric wall plane (slalom maxAbsX = 0.500 = wall face − radius); C API in591-Box2D-v3 style592-(b3DefaultWorldDef/b3CreateBody/b3CreateHullShape/b3CreateSphereShape/593-b3CreateMeshShape/b3World_Step/b3Body_GetPosition); double-precision positions594-by default — fits the endless line's unbounded coords; spike .wasm 369 KB.595-b3MakeOffsetBoxHull is the workhorse for channel proxies.]596-597-**M11 COMPLETE (this round).** All five handoff items landed, each gated598-green before the next:599-600-1. JUMP set-piece (below).601-2. PROPELLER (2-cell gap flown at 0.35 gravity — anti-gravity force while602- the nearest station is a flight station; ~40% of planned gaps) + SHOCK603- zones (marble restitution 0.02 over the two cells after every ≥1.5604- chute; shim gained ph_marble_restitution + wasm rebuilt;605- PhysicsEngine.setRestitution caches). Catch redesign en route: funnel606- wall slabs failed TWICE (tall walls clipped shallow propeller flights607- from above; short walls made wedge pockets) — the catch is now the608- RIBBON MOUTH itself: landing strips flare ×1.35 laterally blending to609- ×1 over 2 world units (ribbonOf flareStart), plus the wide pan. WFC610- rule: wave windows are excluded from jump trajectory cells611- (approach/landing assume a straight channel — a wave landing612- edge-bounced the marble).613-3. SPRINT runs (6–8 flat cells, boost zone stops 2 short — brake runway) —614- the only zones with ≥4 free flat cells, so they are where BUMP windows615- re-admit: tiers a/b only (rise 0.5/1.0 world; crest curvature vs the616- boost target stays under the launch threshold — c/d threw the marble617- over the walls). Plus the LATERAL CENTERING spring-damper in boost618- zones (±14 max accel): a marble still oscillating from an earlier bend619- launched diagonally (−1.9 u/s lateral) and missed every catch; the620- choreographer now straightens it before the lip.621-4. BRANCH RESPAWNS: island diamonds — tryIsland reserves diamond-N on ≥5622- straight run cells (50% chance), claims both flank columns, crossing623- carries branchCells (island builder: full mass, no caps/decor; canopy624- auto-skips via the claim). world.ts scales the alt centerline (was625- passed through unscaled — latent bug). Collision: the diamond window is626- a SLAB COMPOSITE (wide deck + outer flare/straight walls + center627- wedge, mirroring the real split-double) — two overlapping one-sided628- branch ribbons crossed at the split and destabilized the solver629- ("Box3D: unstable"). Client: railForSegment(seg, useAlt) rides the630- other branch; respawn at a diamond segment ALTERNATES the branch631- (fall → reveal). ~60% of scanned seeds have a diamond within 5 segs.632-5. PAD VISUALS: banners stand on the channel rails (goal-gate633- measurement), so they work in the void — banner-high at jump lips,634- banner-double-high at propeller lips, plain banner at standalone boost635- zone entries. Rule-derived, no RNG; scenario asserts every lip has its636- banner.637-638-Assist-law fixes found by the gates: grav tile mods apply only on639-DESCENDING stations (extra gravity on ramp climbs wedged marbles at climb640-bases); hands-off threshold 0.6 → 0.7 (climb cubics peak at ty 0.63 and641-the carry died mid-slope; chute dives start at 0.77).642-643-STATE: 70 tests green. Deep fuzz 120 seeds × 40 segs: 0 failures,644-~2.2 ms/seg. phys-deep: all 7 survivability seeds ride the full 8-segment645-build to the horizon with ZERO falls/wedges. Browser (CUA): 6-sim-minute646-soaks on 2 seeds = 1500u, ~25 segments, 0 deaths, 0 console errors,647-bounded nodes/occupancy (baseline before this round: ~507u/3min WITH648-escalating deaths). Loop mode regression clean; genVersion ga265f1373eed649-unchanged (finite pipeline byte-identical); goldens untouched.650-651-**M11 jump detail (first item of the round):** JUMP = the first real652-channel gap. Planner clause (path.ts tryJump, weight 0.8): 3 flat approach653-cells under a boost zone → 1 gap cell reserved as slant-b REVERSED (the only654-1-cell 0.5-descent that isn't a drop-stack) → 2 flat landing cells at655-lip−0.5. Trajectory priced at plan time: launch ≈ 6.1 world u/s (boost656-target 3.2×1.9), 1.0-world drop takes 0.316 s ≈ the 2.0-world gap. world.ts657-DROPS the gap window's placements (windows are ring-sorted; placement spans658-recovered via LoopSegment.placementIndex) — absence of the piece IS the gap;659-windowMods carries "jump". proxies.ts: gap path-segments emit no ribbon660-(strips extend to the gap edges, unlike shafts) + a catch pan reaching ~1.4661-world back under the flight path (top a hair above the landing sheet, the662-shaft-pan trick) + funnel walls at ±0.98 — undershoot-tolerant down to663-~1 u/s lip speed by construction. NEW SHARED RAIL LAW664-(src/client/physics/rail.ts): railForSegment/concatRails/stationForce — ONE665-control law for client, tests/physics_test.ts, and phys-deep (paths now666-relative). assistForce gained speedScale (boost scales TARGET + authority —667-the old force-scale hack couldn't pass 3.2 on flats, which jumps need);668-grav tile mods apply ONLY on descending stations (extra gravity on ramp669-CLIMBS wedged marbles); hands-off threshold 0.6→0.7 (climb cubics peak at670-ty 0.63 and the carry died mid-slope — chute dives start at 0.77, still671-hands-off). Scenarios: jump trajectory contract (scenario_test.ts,672-rule-based over 15 seeds×5 segs) + cold-entry jump survivability673-(physics_test.ts, spawn at 2.2 before the approach). Gates: 67 tests green,674-deep fuzz 150 seeds × 40 segs = 0 failures, 0.49 jumps/seg, ~2 ms/seg.675-Goldens/genVersion untouched (finite pipeline byte-identical).676-677-**M11 IN PROGRESS — ribbon GREEN + Marble Blast feel live.** The smooth channel678-trimesh is the default collision law (repro chain: upV cross order,679-layout-handed winding, boundary-port extrapolation, crease rounding, tall680-containment walls, cup pan above the one-sided sheet) with curvature-aware681-braking in the shared assist. Feel: gravity −20, authority accel 6→target 3.2,682-max 9, restitution 0.35. Live: ~507u/3min, deaths escalate past residual rough683-spots. DONE ALSO: per-tile modifier channel (EndlessSegment.pieceFamilies →684-rail-station {speed, grav} mods → assist law; helix 1.35× speed, slant/ramp685-1.2×/1.15× gravity — the future carrier for icy/boost/bouncers/powerup686-pads/island gravity rotation); survivability deepened to 7 seeds ×687-goal-at-segment-4. SET-PIECE IMPLEMENTATION PLAN (next): (a) ZONE channel:688-SegmentPlan gains mods[{start,len,kind}] (planner- placed, label-derived rng)689-flowing beside pieceFamilies into rail stations — kinds: "boost" (assist target690-×1.8 + pad placement so the spectacle reads), "shock" (restitution override zone691-before hard drops — needs shim setter), "propeller" (gravityScale 0.35 zone692-launched at jump lips). (b) First set-piece: BOOST-CHUTE — planner tags the693-straight run INTO any 1.5-descent as boost (visible pad placements), scenario694-asserts crest/exit speeds. (c) JUMP set-piece: planner opens a real 1-2 cell gap695-on long straights with catch funnel sized by v²/2g trajectory math at plan time;696-ribbon strips already break naturally (absence = gap); cup walls repurposed as697-catch. (d) Branch respawns: enable diamond reservations in endless realize698-(features planner on island runs), alt rail per branch, respawn picks the branch699-not yet ridden. (e) Bumps: re-admit as jump lips only inside boost zones (crest700-speed priced at plan time). REMAINING M11: movement set-pieces (planner-placed701-powerup modifiers: propeller-jump, shock-drop, per-tile speed/gravity tags via702-rail stations), branch respawns, bumps re-admittance, more survivability seeds.703-704-**M10 — DONE (physics marble + checkpoints + respawn).** The endless marble is a705-Box3D body by default (?phys=0 → kinematic fallback).706-707-- src/gen/endless/proxies.ts: ONE generic collision law — curvature-aware slab708- sampling of centerlines (floors pitch with slope, sunk 0.035 vs seam ridges;709- walls are VERTICAL columns to floor+0.55 — pitched walls scissor wedge710- pockets, taller walls spear stacked decks); drop-stacks = one cup per vertical711- run (mouth at the cell edge, tight 3-wall tube open toward the receiver, full712- bottom pan); VERTICAL_LIMIT 0.93 (steep chutes keep floors). Bumps excluded713- from endless WFC until M11 prices momentum.714-- engine.ts: shared assist law (cruise 3.2, climb carry −g·ty+2.2 sized by RAIL715- LOOKAHEAD max-slope-ahead, brake >7.5, hands-off on |ty|>0.6); Box3D mass is716- SI (33.5 kg marble); sleep disabled.717-- endless.ts: per-segment static bodies + dense rails stream with the window;718- nearest-station progress; checkpoints = segment entries; falls below the719- co-live window + a 6s stall net respawn; repeated deaths at one checkpoint720- ESCALATE to the next segment — forward progress guaranteed (user rule). Deaths721- in HUD/CUA.722-- tests/physics_test.ts: real wasm under Deno; 5 seeds × 5 segments all723- complete; trajectory hash identical across runs.724-- KNOWN (M11): cua-seed-1 seg 4–5 ramp base cycles even with climb carry (marble725- slips diagonally; arrives too slow) — escalation nets it; the speed envelope726- must ensure momentum arrival at steep ramps. Debug loop: scratchpad727- phys-debug/phys-deep/phys-contact patterns.728-729-**Original M10 plan (for reference):**730-731-1. Vendor box3d at the pinned commit (extern/box3d or fetch script) + a build732- task emitting static/dist/box3d.js+wasm from a hand-written C shim (keep the733- JS surface tiny: world create/step, batched static hull upload, one dynamic734- sphere, transform readback into a Float64Array).735-2. Collision proxies GENERICALLY from existing def data — no per-piece736- authoring: sample each placement's world centerline (the client already has737- it per segment) and emit short hull runs: floor slab (half-width738- 0.35·TRACK_SCALE narrow / 0.85 wide) + two wall slabs (to +0.5·scale),739- oriented by tangent. Curved pieces = more samples. Island blocks = box hulls740- on the world grid. Tunnel roofs: skip v1.741-3. Physics streaming mirrors the render window: static bodies per segment742- built/destroyed with build/dispose (marble only needs [cur−1, cur+2]).743-4. Marble = dynamic sphere r 0.2; keep-rolling assist force along the local744- tangent when |v| < target (replaces the old kinematic drag model); fixed 60745- Hz × 4 substeps stepping with accumulator.746-5. Progress s = nearest-point projection on the built window's curves (client747- samplePoint machinery inverts fine via coarse scan + refine).748-6. Checkpoints: island run starts + every K≈10 void cells → {s, pos, dir} list749- per segment; respawn when marble y < (lowest co-live band world y − 8) OR750- horizontal distance to line > 12: reset to last checkpoint with its tangent751- velocity at BASE_SPEED. Camera snaps.752-7. Survivability scenarios (M7 harness extension): node script under tools/753- drives the SAME wasm module headless over generated segments; deno test754- shells it (--allow-run) and asserts completion/respawn rates. Physics755- determinism test: same seed → same trajectory hash.756-757-**M10 — Physics marble + checkpoints + respawn (one milestone — a physics marble758-without respawn is broken the moment it falls).** Marble = dynamic body;759-slopes/gravity real; centerline demoted to camera rail + progress metric (s via760-nearest-point projection). Generator stamps checkpoints (every island + every K761-cells of void flight; a flag/banner placement carrying {pos, dir, s}); falling762-below the lowest co-live band − margin (or stuck timeout) respawns at the last763-checkpoint with its entry speed. Checkpoints always move forward with the764-stream. Validation: per-piece survivability scenarios (every def traversed at765-speed-envelope extremes without leaving the channel), named combo scenarios766-(chute→bend, helix exit, jog at speed, drop-stack landing), replay determinism.767-768-**M11 — Physics vocabulary: gaps, jumps, plinko, real falls, speed systems.**769-Jump specs (launch ramp + gap + catch funnel — trajectory checked analytically770-at PLAN time against the speed envelope, then scenario- tested); cliff drops771-onto lower channels and fall-to-other-path zones (chance-fall semantics made772-real — leaving the track is INTENDED and recaptured by construction); stairs773-(cliff_steps on islands); plinko filter pieces (pin fields over a catch basin —774-outcome stochastic, but recapture guaranteed by walls, so survivability is 100%775-by construction and only the PATH varies); boost pads/fans as impulses; a776-per-cell worst-case SPEED ENVELOPE tracked by the planner so vocabulary is gated777-(no hairpin after a long chute unless a brake/spreader sits between). Every new778-spec ships with named scenarios at envelope extremes.779-780-**M12 — Island verticality. [DONE this round.]** Island runs ≥7 cells may781-embed ONE slope window (0.5 or 1.0, climb or descent, shared vocab filtered782-to ≤0.5/cell parts, no drop-stacks; ≥2 flat shoreline cells each side).783-IslandCrossing gained `profile` (terrace top per run cell): flat cells784-anchor at their OWN base, each slope PART's cells share that part's low785-base (the ramp grounds on the low terrace and bridges — finite-mode rule).786-The envelope + canopy claims and the island builder both use the same787-nearest-run-cell terrace rule (Chebyshev, ties to the LOWER top — a rule788-mismatch would orphan claims, so it's mirrored verbatim). Island descents789-≥1.0 get boost zones like any chute; diamonds require flat, unreserved790-windows with same-level neighbors. TWO REAL BUGS found by the gates:791-(1) the envelope loop skipped ALL same-segment bands — the island's own792-APPROACH CORRIDOR (planned earlier in the same segment) was invisible and793-terraced mass plowed through it, jamming the marble (det-seed s=159); own794-bands are now transparent ONLY on run/branch cells. (2) Occupancy.claimsOf795-double-counted cells claimed twice under one ref (mass + canopy) — every796-island block had been placed TWICE since M5 (invisible: superimposed).797-Tests generalized (per-cell terrace anchorage, ≤1.0 terrace steps, mass798-never above the highest terrace) + new named scenario "island verticality"799-(emergence pinned). Gates: 71 tests green, 120×40 fuzz clean, 7-seed deep800-drives to the horizon, 6-sim-min browser soak (1 death, recovered, 0801-console errors); terraces read on screen. Gravity rotation / bouncers /802-icy: deferred to M13 backlog (icy needs a visible surface treatment first803-— zones must READ, per the set-piece rule).804-805-**M13 — Release polish. SEED FREEZE IS OFF (user, 2026-08-08: "lets not806-seed freeze. its a limitation for no reason yet") — goldens stay807-regenerable-on-change indefinitely.** [BACKLOG FULLY SHIPPED — banking808-lean + icy zones landed earlier; gravity rotation, bouncers, ambient809-audio, curve-wide scenery macro, tee-spur, takeable spur branches, wide810-bridges, hill-interval constraint, per-branch diamond supports, and811-distance-graded ambient decor all landed in the 2026-08-09 autonomous812-round — see "M13 DEFERRED SET — ALL SHIPPED" below.]813-814-**DONE this round (post-M12, user-directed):**815-816-- **M13 POLISH — camera + icy zones**: (1) FOLLOW-CAM ISLAND OCCLUSION —817- createCameras gained `occluders` (endless passes the built window's818- island InstancedMeshes); the desired follow position is pulled in819- front of the first marble→camera hit, so the cam slides over820- shoulders instead of clipping through mass (the known cosmetic).821- (2) BANKING LEAN — the follow cam rolls into the upcoming curve822- (three-sample signed turn around the marble, speed-scaled, smoothed,823- ±0.32 rad; rotateZ(+φ) banks right — both modes). (3) ICY ZONES —824- new planner clause tryIcy (flat straight 4–7 cells, PlanMod "icy",825- weight 0.7); rail law: RailMod.icy drops assist authority to 0.3826- (grip IS the assist — the marble carries momentum and slides); READS827- via Placement.zone="icy" → ice-pale pipe recolor [178,224,240]828- regardless of chapter color. Physics scenario icy-run rides829- jump-scan-2's patch. Scenario pins re-shuffled again (icy changed830- every draw): jump→wide-scan-0, helix→repin-16, diamond→dia-2.831- 90 tests green. REMAINING (deferred, needs design): gravity rotation832- (wasm gravity is global-Y — needs a shim gravityDir override +833- rebuild), bouncers (physical bumper prop + survivability pricing),834- ambient audio, and the track-variety follow-ons (curve-wide scenery835- macro, tee-spur, takeable spurs, wide bridges, hill-interval,836- per-branch diamond supports, distance-graded ambient decor).837-838-- **ISLAND SHAPING + VARIED TEXTURING** (promoted backlog): islands.ts839- mass builder restructured into two passes (pass 1: per-cell-keyed840- existence + depth into a Col map; pass 2: emission with neighbor841- knowledge). SHOULDER TRIM: non-run edge tops with 1–2 open orthogonal842- sides (open = no column, or top ≥ a course lower) swap their top block843- for cliff_blockSlope (single side) or cliff_blockDiagonal (corner),844- chance 0.7 — MEASURED in gallery: slope descends local +x, diagonal845- cuts the +x/+z corner, cave arch and waterfall face −z (slopeRot/846- diagRot/faceRot map accordingly; first guess was wrong, screenshots847- settled it). CAVE MOUTHS: a column that bottoms out on an older848- channel band (crossing.clipped) emits cliff_blockCave facing along849- the pierced line (neighbor track cell carrying the same ref band) —850- rare, as piercings are. WATERFALLS: 0.4/island, one edge column851- (depth ≥2) gets cliff_waterfallTop + cliff_waterfall faces running852- down toward the void. LANDMARKS: 0.35 (margin ≥2) / 0.15 chance, one853- statue/tall-rock on a far flat top with owned airspace. TEXTURE:854- 0.4/island the deep courses (≥2 below top) swap to the OTHER cliff855- material (stone base under rock and vice versa). Shaped tops carry no856- caps/decor. Emergence: 1912 slopes / 923 diagonals / 59 waterfall857- pieces / 65 landmarks / 1 cave over 10 seeds × 8 segs. 89 tests858- green; browser: shoulders slope outward, rim reads as terrain.859-860-- **GROUND VOCABULARY ON ISLANDS** (promoted backlog: "an island top IS861- local ground"). Tunnels needed nothing (already free-collapse in862- endless — the banner rule's arched cells). WIDE RUNS: tryIsland plans863- the loop-mode feature shape (funnel → straight-wide×1..3 → funnel864- reversed) on flat straight island stretches; BOTH flank columns865- claimed and recorded (crossing.wideFlanks → track patch: full mass,866- no caps/decor); collision proxies gained per-window lateral scaling867- (Station.wScale, wideScalesOf from realized families — funnels lerp868- 1→2.43 across their window). FURNITURE: tryIsland claims 1–3 ledge869- cells beside flat straight run cells (crossing.sideCells); after870- realize, endless/furniture.ts furnishes ledges whose host realized as871- a plain straight — stub sidings (host straight→split in place, the872- loop stubs.ts move; ledge gets a no-hole end cap) or fans (blades873- face the channel, spin + kinematic boost). SOLIDS: AssembleOptions874- gained localGroundY (window grounded on its terrace → solids return875- in open mode via style.solidAt(0)) and deckAt (the planner's own876- mass-claim record, crossing.deckCells — solid neighbor support may877- lean on the island deck; NO third mirror of the terrace rule);878- solidSupported's ring-wrap ports clamp in open mode. Emergence over879- 12 seeds × 10 segs: 22 wide windows, 31 stub junctions, 27 fans, 84880- solids. New scenario "island ground vocabulary" pins the rules881- (wide = grounded + bracketed + flanked, ledges furnished per host882- kind, fans only on ledges, solids near island ground) + physics883- scenario rides wide-scan-0's wide run. 89 tests green; browser: wide884- deck reads on screen, 0 deaths.885-886-- **HUD DISTANCE MILESTONES + PHOTO KEY** (promoted backlog): milestone887- toast (src/client/hud.ts — pure milestoneFor: landmarks 100/250/500/888- 750/1000 then every 500; MilestoneToast self-owned DOM, fades, CUA889- `milestone`); endless Session shows "✦ N TRAVELLED · K ISLANDS ✦" on890- crossing. PHOTO: user rule "don't freeze — P exports an image LIVE":891- src/client/photo.ts installPhotoKey re-renders and downloads the892- canvas as marbles-<seed>-<distance>u.png (HUD is DOM so captures are893- clean; the sim never pauses). Wired in BOTH modes; CUA `photos`.894- 3 rule tests (tests/hud_test.ts); browser-verified (milestone fired,895- photo count incremented).896-897-- **FULL-HELIX SPIRAL DIVES IN THE VOID** (promoted backlog): new898- planner clause tryHelixDive (path.ts, weight 0.5, one per segment) —899- the loop-mode full-helix ensemble (HELIX_LOOP_SPECS, approach corner +900- 360° spiral dropping 2.0 + underpass back through its own first cells)901- dropped into the endless line. The duplicate-cell-tolerant path: the902- generic step machinery can't express revisited cells (per-visit bands903- at one cell self-collide), so the clause claims each footprint cell904- ONCE over the whole block depth [−2, clearance] (nothing threads the905- spiral block), and plan.cells simply carries the revisited coordinates906- twice at their two bases (cell 0 = entry base, interiors = exit base:907- step-at-start, exactly what realize expects of a reserved window).908- Channel-collision rule tests gained a within-window exemption for909- full-helix reservations (endless fuzz + assertStreamInvariants).910- Scenario "helix dive" pins the window contract corpus-wide (moves ==911- def moves, bases, revisits, one helix window, mesh placement) +912- physics scenario rides helix-scan-0's dive cold. FOUND EN ROUTE913- (fuzz-0 reshuffle): a diamond-merge wedge — marble parked on the merge914- wedge-slab edge seam 0.68 off-center; fixed in the shared rail law915- with RailMod.center on family "split" (the rail follows the branch916- lane, so the boost-zone centering spring is correct along the whole917- window). Jump-gap scenario re-pinned (det-seed → prop-scan-1; no seed918- freeze, pins follow the stream). 84 tests green.919-920-- **MARBLE RING** (user: a revolving set of peer marbles so the run is921- constantly alive ahead and behind): src/client/peersCore.ts (pure922- slot-lifecycle core, headless-tested) + peers.ts (render layer), wired923- into the endless Session. Six SLOTS at signed arc offsets from the924- player; a slot spawns when open (session start / after its peer925- despawned + cooldown) and the peer despawns when it drifts out of the926- live band ([−18, +38] rel or outside the built window). SPAWN TIME is927- part of the seed material by design (rngFamily label928- `peer/<slot>/<n>/<quarter-second stamp>`) — presentation-only929- nondeterminism; generation stays a pure function of the seed. Each draw930- fixes identity (mesh variant incl. rare butterfly, HSL glass tint or931- stock, scale 0.8–1.3, seeded lateral offset + wobble so passes read932- side-by-side) and behaviour (base/min/max speed, gravity response,933- drag riffing on the kinematic law) — the character SPREAD is what934- revolves the ring: fast peers pull away, slow ones fall off the back,935- slots refill. Peers are kinematic centerline riders (wasm stays936- single-marble); rolling spin + easeOutBack scale-in / ease-out.937- CUA exposes peers + peerRing rel/speed. 7 rule tests938- (tests/peers_test.ts); browser soak 4 sim-min: 6 live with turnover,939- 0 deaths, nodes bounded, only the favicon 404 in console.940-941-- **PIPE COLOR IS PER ISLAND** (user refinement of the round below): in942- endless, every Placement carries a `chapter` (the most recent island's943- world-unique id at its cells — world.ts stamps it via the window→944- placement span walk; pads too). The client draws one color per chapter945- from rngFamily(seed, `pipe-color/${islandId}`) and swaps that946- placement's shared colormap materials for cached clones over the947- derived texture (assets.applyPipeColor; one texture+material set per948- (variation, color), LRU 16, evicted together). The journey reads as949- color chapters that turn over at each island anchor; two chapters are950- often visible at once across the void. Loop mode keeps its per-seed951- draw. Scenario: every track placement chaptered, chapters952- non-decreasing, ≥2 chapters emerge early.953-- **MARBLE SHRUNK 50%** (user): MARBLE_RADIUS 0.2→0.1, kit mesh scaled by954- MARBLE_SCALE 0.5 in both modes (channel ≈ 7 marble diameters now — the955- full slalom). All physics gates re-validated green with the small ball;956- 6-sim-min soak 0 deaths, 0 errors.957-- **PIPE COLOR VARIATION** (user: "the pipe color should be able to958- change"): the track's plastic body color is now a per-seed axis.959- Measured the pipe-body swatch texel per colormap variation (straight/960- curve/slant UVs → a=(207,83,79), b=(255,192,68), c=(134,139,161));961- applyPalette gained an optional pipe color that repaints exactly those962- texels on a canvas copy (swatch-level recolor, NOT a blanket tint —963- interiors/rails/decor untouched; small LRU of derived CanvasTextures).964- Color drawn client-side from rngFamily(seed, "pipe-color")965- (src/client/pipeColor.ts): 22% stock, else HSL h∈[0,1) s 0.35–0.75966- l 0.45–0.67. ZERO generator impact (goldens/genVersion untouched). Both967- modes wired (endless Session + loop buildTrack). Verified: det-seed968- crimson, abs-1 mint, jump-scan-7 varied; 0 console errors.969-- **BANNER RULE** (user: "flags cannot go on curved pieces or in the same970- spot as a tunnel piece"): zone banners only place on windows whose971- family is "straight" (excludes waves/wiggles — F-move but the channel972- swings away from the posts — and tunnel arches). EndlessSegment gained973- windowRanges so tests map cells→windows; the jump-lip banner assertion974- is now conditional (banner ⟺ lip realized plain straight); new corpus975- rule test pins every banner to a straight window.976-- **REGRESSION island-block-in-channel** (user screenshot, seed977- hctqaix2hl seg 25: mass blocks stood in the channel and stopped the978- marble): this was the M12 envelope corridor-blindness bug — already979- fixed in the M12 commit (same-segment claims transparent only on980- run/branch cells); the user's browser ran a pre-fix bundle. Verified 0981- block-vs-channel intersections through seg 30 on hctqaix2hl + 6 more982- seeds; pinned as a permanent placement-level named scenario.983-984-### Absorbed M5-polish backlog (fold into milestones above, grab985-986-opportunistically): full-helix spiral dives in the void (duplicate-cell-tolerant987-clause), tunnel runs/stubs/fans/wide runs/solids on islands (an island top IS988-local ground), island shaping (slope/diagonal shoulder trim, cave mouths at989-old-track piercings, waterfalls, landmarks), InstancedMesh for island blocks +990-decor (do during M6 — animation work touches the same code), HUD distance991-milestones, photo mode, per-biome fog/lighting tints, destructible decor revival992-(unblocks at M10 — physics gives decor something to collide with; user's993-pop-and-fling answer from the design round still stands).994-995-## M13 DEFERRED SET — ALL SHIPPED (2026-08-08/09 autonomous round)996-997-Every item of the deferred M13 backlog landed, each gated green before998-the next (105 tests; genVersion g535114c0fc9e after the graded-decor +999-tee-spur + scenery golden regens):1000-1001-- **GRAVITY ROTATION** (shim `ph_gravity` → b3World_SetGravity + wasm1002- rebuild): planner clause tryGravity (flat straight 6–8 cells, mod stops1003- 2 short — brake runway; weight 0.5). RailMod.tilt (0.3 rad) →1004- `stationGravity(rail, idx)` tilts WORLD gravity forward along the1005- station tangent, |g| preserved at 20 — the marble surges down a1006- geometrically flat run; the assist stays silent between cruise and the1007- 9 u/s envelope cap, so gravity itself is the spectacle. Zone READS via1008- violet pipe recolor [186,148,255] + entry banner. One law in client +1009- tests + phys-deep (all three call engine.setGravity(stationGravity)).1010- Scenario "gravity-run" (grav-scan-7) + corpus rule test (flat straight1011- zone cells + runway + tagged placements). RE-PINS from this clause's1012- reshuffle: propeller→grav-scan-0, diamond→grav-scan-5,1013- helix→grav-scan-23 (icy jump-scan-2 and jump wide-scan-0 survived).1014-- **BOUNCERS** (shim `ph_add_bumper`: b3CreateCylinder hull, restitution1015- 0.85 — contact restitution combines via MAX, so pins bounce regardless1016- of shock zones; hull is interned by the world, destroyed locally):1017- mushroom_red pins (nature kit, ×2 furniture scale) on WIDE island deck1018- cells only — plinko pins, recapture guaranteed by the wide walls1019- (survivability by construction). Furniture pass (label-derived rng — NO1020- planner reshuffle): chance 0.6/wide cell, lateral offset ±0.25 track,1021- never inside a planner zone. Placement.bumper → proxies.bumperProxies1022- (BUMPER_RADIUS 0.16 / HEIGHT 0.42 world). Scenario "bouncer-pins"1023- (wide-scan-10 seg 0 = 3 pins) + corpus rule test.1024-- **AMBIENT AUDIO** (src/client/audio.ts — fully procedural WebAudio, no1025- assets): PAD (mood-tinted drone chord per atmosphere mood, 4s1026- crossfade at zone turns), ROLL (bandpass noise following marble speed),1027- CUES (boost/launch/land/fall/milestone/checkpoint sweeps, rule-derived1028- edge detectors off the rail mods). Gesture-gated (autoplay policy):1029- muted until the M key / #audio-toggle HUD button. The mapping layer1030- (padForMood/rollParams/cueFor) is PURE and rule-tested headlessly1031- (tests/audio_test.ts); the WebAudio layer is a thin realization. CUA1032- `audio`.1033-- **DISTANCE-GRADED AMBIENT DECOR**: oracle contract widened as promised1034- (ORACLE_GRADE_HORIZON = 13; oracles return true distances to 13,1035- Infinity beyond; band rules still stop at 5.5). ambientDensity(dist)1036- grades 0.3 → 0.05 linearly across the ambient band. Rule test pins the1037- falloff (near-annulus rate > 1.8× deep-annulus). Goldens regenerated.1038-- **HILL-INTERVAL CONSTRAINT** (lifts the M3 "crossings stay flat"1039- compromise): baseY[b] − baseY[a] = Σ deltas in ring interval (a, b], so1040- a hill is safe iff its climb and descent land on the SAME SIDE of every1041- crossing pair's interval. Runs touching crossing cells are excluded1042- (the cross piece is one flat 4-way); any crossing-free run lies wholly1043- on one side. Works for flat crossings (Δ0) AND bridges (Δ1.0 —1044- differences are preserved, not just equality). Corner set-pieces stay1045- off crossing tracks. 13 of 18 crossing tracks now have hills (1500-seed1046- strict fuzz, 0 failures). Rule test: pinned xh-* seeds, all centerline1047- passes at each cross cell agree in height.1048-- **PER-BRANCH DIAMOND SUPPORTS**: elevated diamonds emit a second1049- candidate line snapped onto the ALT centerline — in its OWN greedy1050- group (stretch + 0.5): measured against main-lane pillars the parallel1051- lane never accumulates a full span of gap and every candidate was1052- clearance/span-skipped (that was the actual bug). Rule test: dia-loop-*1053- pinned seeds, alt branch carries a dedicated column.1054-- **TEE-SPUR**: split-double-sides (no through port — was exempt) enters1055- as a spur TERMINAL: a split-siding branch stems into the T (3×21056- footprint + 2 cap cells, chance 0.3 when free), arms dead-end in plain1057- caps. Measured: entry z− face center, exits x± faces at lat 0.5;1058- rotation rT = (1−w)%4, mesh at s1 + rot(rT, 0, 0.5). ~2.7% of tracks.1059- Exemption lifted (solid form stays exempt — the T stands in open1060- apron). Goldens regenerated (stub rng draw added).1061-- **TAKEABLE SPUR BRANCHES** (branch-taking beyond diamonds):1062- src/gen/takeable.ts — an ELEVATED straight (Y≥1) mutates to a split1063- whose branch is an end-hole DIVING BOARD cantilevered directly above a1064- lower ring cell at exactly Δ1.0 (the bridge law). Composes two existing1065- systems: the split window gains a LoopSegment.alt TEARDROP (out over1066- the hole and back — the return leg is never ridden) and the drop is a1067- Track.falls entry flagged `guaranteed`. Client (marble.ts): falls now1068- carry per-variant `active` (hole must lie within 0.5 of the variant —1069- a spur hole on the main line would fire a phantom drop at the nearest1070- unrelated point) and `guaranteed` skips the lap hash. ~2% of tracks.1071- New rng family "takeable" — no reshuffle of existing streams.1072-- **WIDE BRIDGES**: the takeable spur upgrades (preference 0.85 when the1073- footprint fits): funnel → straight-wide-hole (over the lower line) →1074- reversed funnel → cap, with falsework at the mouth (plain column) and1075- the abutment (cradle stack) — emitSupportStack exported from assemble.1076- Flank rule: the wide bulge's lateral cells must be open air OR the1077- lower line a full course below. straight-wide-hole exemption LIFTED1078- (hole over track below — honest at last). ~0.13% of tracks (4/3000) —1079- a true rarity. Pins: wb-62/357/2036/2165; take-168 re-pinned to wb-811080- (it upgraded to wide).1081-- **CURVE-WIDE SCENERY MACRO** (src/gen/scenery.ts): the off-grid family1082- mates with ITSELF — four quarter-turn wide arcs close into a1083- freestanding roundabout ring (measured: ports centered on x−/z− faces;1084- rotations [0,3,2,1] at ring-center offsets (±h,±h) mate all four).1085- Variants small/medium/large (h 0.85/1.35/1.85), solid plaza forms for1086- medium/large (50%). Placed on open ground near the line (chance 0.3),1087- cells join trackCells so the decor field bands around it — a clearing1088- with a landmark. ALL FIVE curve-wide exemptions lifted. Goldens1089- regenerated.1090-- **PYRAMID-WEDGE FIX** (user report mid-round: "pyramid blocks should1091- respect the surface they are on"): island shoulder trims — openAt now1092- requires a FULL course of drop (0.99, was 0.26 — a half-lower neighbor1093- buried the wedge's low edge); slopes descend toward the LOWER of two1094- open sides; diagonal cuts require the corner cell BETWEEN the two open1095- sides open too; waterfalls keep ONE face per column (no per-course1096- zigzag). Corpus rule test "island wedges" (rebuilds the mass surface1097- from placements and checks every wedge's open sides). Emergence still1098- healthy (2124 slopes / 661 diagonals / 56 falls over 10×8).1099-1100-VALIDATION (this round): 105 tests green; endless deep fuzz 120×40 = 01101-failures (~940 gravity zones); loop strict fuzz 1500 = 0 failures;1102-browser soak (CUA) 6 sim-min on grav-scan-7: 1306u, 22 segments, 1 death1103-(recovered), nodes bounded 332–437, 0 console errors; gravity zone reads1104-violet on screen; scenery ring/wide bridge/tee verified on screen (loop)1105-+ by rule tests. Coverage: exemptions now 33 (was 40) — curve-wide ×5,1106-split-double-sides, straight-wide-hole all lifted.1107-1108-## ROADMAP — M14/M15: the autopilot era (planned with the user, 2026-08-09)1109-1110-USER DIRECTION: stop treating the centerline as an automated forward path;1111-treat it as a WISHDIR — the marble propels itself, jumps, and changes speed1112-to maximize safety and progress ("closer to a real physics situation").1113-Also: marbles interacting DIRECTLY (real multi-marble contact). Research1114-round done (prior art: MBHaxe Marble.hx authority servo + traction budget +1115-top-up jumps; Quake accelerate projection rule; pure pursuit + curvature1116-speed planning with backward brake sweep; Reynolds corridor dead zone;1117-Marbles-on-Stream/Jelle's variance-as-product). Plan approved as "do this1118-all".1119-1120-### DECISION LOG (agent defaults, 2026-08-09 — user may override)1121-1122-- Force-at-COM authority (not torque servo): Box3D contact friction 0.71123- already converts COM force → rolling spin; torque needs new shim surface1124- for unclear feel gain. Revisit only if feel demands spin-on-bounce.1125-- The pack is a SPECTACLE RACE: N marbles (default 6) running continuously,1126- leader-cam, no finish line in endless; finish-order variance per segment1127- is the entertainment. Personality via per-marble controller params1128- (label-derived rng), NEVER thrust — capability rubber-banding is what1129- spectators detect.1130-- Autopilot lands behind a flag (?autopilot=0 fallback = old assist law)1131- and becomes default once all gates are green.1132-1133-### Milestones1134-1135-**M14a — Autopilot core.** One law replaces assistForce's special cases:1136-per-station curvature at rail build; online windowed brake sweep1137-(v_safe(i) = min over lookahead of sqrt(v_corner² + 2·a_brake·Δs), v_corner1138-= max(cruise·speedScale, sqrt(A_LAT/κ))); pure-pursuit lookahead point at1139-L = clamp(k·|v|, Lmin, Lmax) → wishdir = normalize(look − pos); accelerate1140-with the Quake projection rule (addspeed = wishspeed − dot(v, wishdir); ≤01141-→ NO force — momentum is sacred, replaces hands-off threshold AND climb1142-carry: deficit-proportional authority clamped by a traction budget climbs1143-ramps by slowing naturally); separate decisive brake clamp above v_safe;1144-weak air accel (≈5) toward the lookahead when airborne (replaces the1145-ballistic hands-off; propeller gravScale force stays). Icy = grip1146-multiplier on the traction budget (same knob, now principled). Gates: 71147-survivability seeds, all named scenarios, deep fuzz, determinism hash,1148-re-pin scenario table.1149-1150-**GENERATION BAND BUGS — FOUND BY M14a FUZZ, FIXED (user: "include bug1151-fix for fuzz findings"):** (a) apfuzz-81 s=41.9: multi-cell slope parts1152-put the whole delta on cell 0's stepAfter, so j>0 cells claimed1153-[poseY−delta, poseY+0.99] a full delta BELOW their real geometry — a1154-slant-long-d's unclaimed upper half walled a channel legally crossing1155-above (one-frame velocity reversal at ratio ≈ restitution = static face1156-head-on; constant-force zero-velocity equilibrium = contact normal1157-through the center). FIX: every part cell claims the part's FULL envelope1158-(commitSlope + island slope steps — j>0 bands flip sign). (b) apfuzz-581159-s=330.3: the realized climb cubic SPILLS past the slope window (rail1160-still ty 0.45 inside the following "straight" window), so the upper-1161-terrace mass poked 0.6 world above the still-climbing floor. FIX: the1162-terrace low-base sharing extends one cell past a climb / one before a1163-descent lip (path.ts partLow spillover), and the slope picker moves one1164-cell inward on that side to keep the ≥2 flat shoreline contract.1165-Diagnosis tool: scratch tri-scan-all.ts (point→triangle distance over1166-every co-live proxy — the "invisible wall" finder; recreate from this1167-note). Gates: 112 tests green, 90-seed × 8-segment physics fuzz 0 falls1168-0 wedges. Streams reshuffled → pins moved: diamond→wide-scan-1,1169-wide→wide-scan-7, gravity→grav-scan-2 (others held).1170-1171-**M14b — Jumping as a decision.** Shim ph_marble_jump (top-up: velocity1172-along normal SET to jumpImpulse, never added — deterministic height);1173-autopilot commits when a gap/rise in the lookahead needs it AND the1174-ballistic arc from current v lands inside the corridor. Planner jump1175-set-pieces remain; the marble now executes them and can recover bad1176-approaches. Gates: jump contract at envelope extremes, cold-entry.1177-1178-**M14c — Naturalizer.** Reynolds corridor dead zone (no lateral correction1179-inside ~60% of half-width; ramped Stanley-style correction near the edge;1180-boost/split/gravity zones keep dead zone 0 = today's centering); per-marble1181-personality {bravery, lookahead gain, jump eagerness, OU-noise lateral1182-offset target}, label-derived rng, seed-stable. Gates: determinism, soak1183-review by eye.1184-1185-**M15 — Marbles interacting directly.** Shim: marble ARRAY (N dynamic1186-spheres, batched state readback); peers promoted from kinematic ghosts1187-(peersCore.ts) to real bodies, each running its own autopilot + personality;1188-marble-marble material friction ~0, restitution ~0.35 (Box3D combines via1189-MAX — bumpers stay bouncy); separation steering when a neighbor occupies1190-the lookahead cone within ~2 radii, WAIVED when heavier (shoving is1191-content); respawn behind the pack tail with a collision-immunity window;1192-leader-cam = camera follows max-s marble. Gates: N-marble determinism (same1193-seed → same order), pack-stall scenario on flats, N=6 fuzz.1194-1195-## HANDOFF — 2026-08-09 (round 2): M14 AUTOPILOT ERA + M15 RACE COMPLETE1196-1197-STATE: M14a/b/c + M15 + the fuzz-found generation band fixes are all1198-live (see the M14/M15 ROADMAP section above for the laws). 113 tests1199-green; genVersion g535114c0fc9e unchanged (finite pipeline untouched).1200-Key surfaces: src/client/physics/autopilot.ts (the whole control law +1201-wantJump + pilots), rail.ts (zone env + stationGravityDelta), shim.c1202-(16-marble array; REBUILD after edits: ./tools/build_physics.sh),1203-endless.ts Racer machinery. Drivers share one law: client,1204-tests/physics_test.ts, tests/multimarble_test.ts, phys-deep1205-(LAW=assist = old law A/B). ?autopilot=0 client fallback; ?peers=N real1206-racers (default 5+hero, 0 in assist/kinematic mode → decorative ring).1207-Scenario pins after the band fixes: diamond→wide-scan-1,1208-wide→wide-scan-7, gravity→grav-scan-2 (others held; full table in the1209-M13 handoff below). Browser soak recipe: PORT=8907 deno task serve +1210-scratch playwright soak.mjs (advanceTime(60000) per sim-minute; state1211-JSON now carries `race` telemetry).1212-1213-USER FEEDBACK APPLIED: leader-cam is OPT-IN (SPACE toggles hero-cam ↔1214-leader-cam, toast confirms) — the default follows the hero, who carries1215-the trail; the trail rides the CAMERA SUBJECT and resets on handover,1216-with 0.75u hysteresis on lead changes (neck-and-neck racers alternate1217-the top spot every few frames; without hysteresis the shot flickered1218-and the trail reset endlessly).1219-1220-BOOST ECONOMY (competition round, user: "limited boost that recharges1221-over time... pushing forward aggressively on a fuel system"): each pilot1222-carries a fuel tank (burns ~3s, refills ~12s x regen trait); ignition is1223-a DECISION — attack (rival ahead within 8·aggro), breakaway (chaser1224-within 4·aggro), or overflow bleed — gated on envelope HEADROOM1225-(wishspeed = min(target·1.55, vSafe): a boosting marble still brakes for1226-every corner, safety unchanged by construction). BOOST_MIN_FUEL 0.51227-commits burns to ~1.4s attacks (a low floor sputtered); per-pilot regen1228-0.75–1.25 desynchronizes tanks (identical rates surged the pack in1229-unison). aggro + regen are APPENDED pilotFor draws — older traits stay1230-byte-identical. Boosting marbles glow (own cloned materials, emissive);1231-audio boost cue for the camera subject; fuel/boosting in race telemetry.1232-Measured: ~20% burn duty, lead changes 5→785 on one seed, mid-race1233-orders genuinely shuffle. NOTE race-order measurement pitfall: racers1234-reaching the built rail's END all ratchet to the same s — sample order1235-BEFORE the horizon (multimarble test stops at endS−6).1236-1237-MAIN MENU (user rules: settings belong on a MAIN MENU; it is a TITLE1238-SCREEN page, "game feel, not a web ui", NOT an overlay —1239-src/client/menu.ts): opaque void page with drifting starfield + bobbing1240-marble orb; game-menu rows navigated by ↑↓ (cursor), ◂▸ (change),1241-ENTER (start); mouse works too. Rows: MODE endless/loop, RACERS1242-hero+N, SEED (free-text input, any string, blank = mint random); folded1243-ADVANCED carries the legacy toggles (phys/autopilot/debug/gallery).1244-Boost speed increase + fuel efficiency are NOT user knobs (user1245-correction) — they are PILOT PERSONALITY traits (pilotFor appended1246-draws boostMult 1.35–1.75, efficiency 0.8–1.25, tangential to1247-aggro/regen). ROUTING CONTRACT: the menu owns BARE urls and ?menu=11248-only — every seed link (hash/params) boots straight to the game1249-(shared URLs + CUA harnesses unaffected); when the menu is active the1250-game never boots (no scene, HUD hidden — it is a separate page); START1251-always writes an explicit endless= param; choices persist in1252-localStorage; the HUD "menu" button returns via ?menu=1 keeping seed +1253-settings.1254-1255-PACK SCALE (user: racer count is a NUMERIC input 1–9999): shim1256-MAX_MARBLES 10000 + ph_states batched readback (one wasm call/step);1257-per-racer persistent pos/vel/quat scratch (no per-step allocation);1258-progress-SORTED neighbor windows for gaps + separation (O(N log N));1259-DECISION DECIMATION — control decisions recompute round-robin every K1260-steps (K = 1/2/4/8 at ≤64/≤256/≤1024/beyond) while the cached force1261-applies every step; peers ≤32 keep kit meshes + materials, beyond that1262-ONE InstancedMesh sphere with per-instance color (camera/trail use a1263-proxy Object3D for instanced subjects); grid-wrapped avalanche spawn1264-(stations × 3 lanes × stacked tiers); tail respawns fan across lanes.1265-Measured: N=200 17× realtime, N=2000 1.7×, N=9999 runs correctly at1266-~0.27× (slow-mo spectacle; Box3D step is the wall). raceStats caps its1267-list at top-12 (plus n/leader). document.title = "marbles - #seed"1268-(readable history entries, set in seed.ts emit).1269-1270-MARBLE PHENOTYPE (user: "tint the outside and effects as two additional1271-multiplicative phenotype expressions"): racerIdentity (peersCore.ts) →1272-RacerLook gains SHELL (per-channel cosine-palette color multiplier,1273-strength ≤0.35) and EFFECT ({gloss|pearl|ember|ghost|none} × intensity1274-0.4–1) as appended draws — layered MULTIPLICATIVELY over the base1275-mesh+tint. Kit-mesh path: shell multiplies color; gloss cuts roughness/1276-raises metalness, pearl lifts toward white at low roughness, ember sets1277-emissive (boost-glow flameout RESTORES the ember baseline, not black),1278-ghost goes transparent. Instanced path folds phenotype into instance1279-color (brighten/lift/darken approximations).1280-1281-GLASS ROUND (user: shell too aggressive → research three.js glass;1282-also glass/wire TUNNEL variants; research report summarized in the1283-marbleGlass.ts header): the kit marble is AUTHORED as opaque core +1284-"glass"-named shell submesh — the phenotype had been color-multiplying1285-BOTH (the "aggressive" look). Now: identity tint + effects live on the1286-CORE only; the shell material becomes real MeshPhysicalMaterial1287-transmission glass tinted via Beer–Lambert attenuationColor (the1288-phenotype shell color; ghost = long attenuationDistance, pearl =1289-iridescence). ONE PMREM RoomEnvironment texture feeds material.envMap1290-on glass/gloss materials ONLY (scene.environment would relight the1291-void); renderer.transmissionResolutionScale = 0.5. Instanced packs:1292-opaque core (0.82r) + additive-fresnel shell InstancedMesh (order-1293-independent — no sorting artifacts at any N; three injects1294-instanceMatrix/instanceColor for ShaderMaterial on InstancedMesh —1295-declaring them again is a shader redefinition error). TUNNELS:1296-position-hashed render variants — 20% alpha-glass (NON-transmission:1297-a transmissive arch would erase transmissive marbles behind it — only1298-opaque objects land in the transmission buffer), 14% wireframe cage.1299-1300-AUTOPILOT FIXES (user reports): (a) crest ground-seeking — pursuit now1301-steers YAW ONLY (wishdir = horizontal direction to the lookahead,1302-pitch supplied by the local ground-plane projection; drop-shaft1303-fallback to tangent heading) — a 3D seek aimed into the floor on1304-ramp-up-then-down and jittered; unit test pins it. (b) respawn1305-distribution — a culled segment respawned every straggler onto one1306-station; respawnPeer now distributes across an N-scaled fill range1307-(10 + 0.35·N units, 70% trailing / 30% ahead of the camera subject,1308-clamped to built rails) with golden-ratio idx slots + lane/height1309-stagger. Liveliness is distribution, not a clump (user rule).1310-1311-REMAINING / NEXT IDEAS: marble-marble collision-immunity window on1312-respawn (shim filter bits — deferred from M15); rubber-banding via pilot1313-skill params (never thrust — Melder); race HUD (leaderboard strip, name1314-labels); contact audio cues; torque-servo authority if feel ever1315-demands spin-on-bounce; endless stream goldens; destructible decor;1316-takeable spurs in endless. Known cosmetic: laggard racers can pop when1317-respawning at the pack tail (no scale-in envelope yet).1318-1319-## OLD HANDOFF — 2026-08-09 state: M1–M13 deferred set COMPLETE1320-1321-CURRENT: all M13 backlog items above are live. Remaining/possible next:1322-M13 release freeze if the user wants it (seed freeze explicitly OFF per1323-user), endless stream goldens (pin segment hashes), destructible decor1324-(physics now exists to collide with), takeable spurs in ENDLESS mode as1325-respawn variety (loop testbed proved the machinery), bumper density1326-tuning after user feedback, camera pitch during gravity zones (deeper1327-spectacle), audio: bumper-hit cue via contact events if the shim ever1328-exposes them. Scenario seed pins move whenever a planner clause changes1329-the weighted draw — re-scan with a scratch script (current pins:1330-propeller→grav-scan-0, jump→wide-scan-0, helix→grav-scan-23,1331-diamond→grav-scan-5, icy→jump-scan-2, gravity→grav-scan-7,1332-bouncers→wide-scan-10, wide→wide-scan-0; loop pins: crossings-with-hills1333-xh-*, diamond-supports dia-loop-*, tee tee-*, takeable take-*/wb-81,1334-wide-bridge wb-*, ring ring-*).1335-1336-## OLD HANDOFF (historical) — next agent starts MID-M11 (jump set-piece next)1337-1338-STATE (2026-08-08 end): M1–M10 done. M11 mid-flight, all green (65 tests):1339-smooth ribbon collision is the default channel law; Marble Blast feel live1340-(gravity −20, authority accel, curvature braking, restitution 0.35); per-tile1341-modifier channel live (pieceFamilies + planner zone mods → rail stations →1342-assist law); FIRST set-piece live: boost-chute (descents ≥1.0 get a boost zone).1343-Physics gates: tests/physics_test.ts (7 seeds, survivability + trajectory1344-determinism). Rebuild wasm after any shim.c change: ./tools/build_physics.sh.1345-Client: deno task dev, /?endless=1 (?phys=0 = kinematic fallback). Read1346-docs/physics.md (research + all collision lessons) before touching proxies.1347-1348-NEXT WORK, IN ORDER (detailed plans in the ROADMAP section + docs/physics.md):1349-1350-1. JUMP set-piece: planner opens a real 1–2 track-cell channel gap on long1351- straight runs (new clause: gap after a boost zone; claim the flight band;1352- ribbon strips already break naturally — absence of stations IS the gap); size1353- the catch with trajectory math at plan time (launch v from the boost target ≈1354- 6·speedScale, flight t = gap·2/vh, drop = ½·20·t²; catch = widened profile1355- rings or a cup-style pan + funnel walls at the landing); add a named1356- survivability scenario at min/max entry speeds. SPECTACLE: pad placements at1357- the lip so it reads.1358-2. Propeller + shock zone kinds: PlanMod.kind union grows; propeller =1359- gravityScale ~0.35 during flight (needs a per-zone gravity override in the1360- client eachStep — same pattern as boost's extra pull); shock = marble1361- restitution override near hard landings (needs a shim setter1362- ph_marble_restitution(float)).1363-3. Bump lips: re-admit family "bump" in endless WFC ONLY inside boost zones (wfc1364- exclude callback already exists in world.ts) — at boost speeds the bump crest1365- is a launch, caught like the jump.1366-4. Branch respawns: enable diamond reservations in endless (planFeatures1367- currently skipped — run it on island runs' straight windows), alt centerline1368- → second rail per branch, respawn picks the branch not yet ridden. Loop mode1369- is the visual testbed for branch correctness.1370-5. Pad visuals for all zones (placements at zone cells so set-pieces read on1371- screen), then M12 (see ROADMAP: island verticality + gravity1372- rotation/bouncers/icy over the modifier channel), then M13 freeze (endless1373- stream goldens: pin segment hashes for fixed seeds).1374-1375-DEBUG LOOP (vendored in tools/physics-debug/, run with deno --allow-read; paths1376-inside are absolute — fix to relative when first used):1377-1378-- phys-deep.ts <seed>: drives the real wasm marble over 8 segments with the1379- SHARED assist law; prints FELL/WEDGED with rail context. Keep its assist loop1380- in sync with tests/physics_test.ts (one law, one truth).1381-- phys-contact.ts <seed> x y z: which slab/box proxies touch a stuck marble1382- (meshes skipped — extend if needed).1383-- ribbon-repro.ts / ribbon-bisect.ts: isolated mesh-collision repros (synthetic1384- straight/arc; square-vs-ribbon winding bisect). Method: never guess — repro in1385- isolation, instrument, fix the law, rerun gates. Every real bug becomes a1386- named scenario in tests/scenario_test.ts.1387-1388-Before anything: `deno task test` (57 green), `genVersion()` (ga265f1373eed),1389-read "M5 systems" + the ROADMAP. Work the milestones IN ORDER; do not start a1390-milestone until the previous one's validation gate is green and the user has1391-seen it. The CUA harness recipe is under Notes/gotchas; prefer `soak.mjs`-style1392-single-evaluate advanceTime calls for long sims (the per-frame harness loop1393-costs ~60ms/frame of Playwright overhead).1394-1395-### Standing design principles (endless edition)1396-1397-- RNG is label/position-derived, NEVER sequential-draw-dependent. Endless1398- families: `segment/${i}` (+ children `clause/${cellOffset}`, `wfc`,1399- retry/probe labels), `island/${k}`, per-cell decor children. A retry label is1400- deterministic BECAUSE the dead end it recovers from is deterministic.1401-- The endless stream must remain a PURE FUNCTION OF THE SEED. Culling is1402- generation-driven (KEEP_SEGMENTS); never let client/frame timing decide when1403- occupancy is released. The client render window must stay strictly inside the1404- keep window.1405-- All planning writes go through OccupancyTx transactions; nothing touches the1406- base map until a segment fully validates (plan + realize + extras + successor1407- probes). If you add a new post-planning claim source, it MUST happen inside1408- the transaction, before the exit checks — two bugs this round (WFC extras,1409- island canopy) came from claims landing after a guard's promise.1410-- Composition honesty carries into the void: bands never overlap among co-live1411- segments, islands never intrude into channel bands, vegetation only where the1412- island owns the airspace, no supports/solids without ground.1413-- The finite loop pipeline is shared machinery: open-mode behavior is opt-in1414- flags with the closed path byte-identical. Any change that moves1415- `genVersion()` regenerates goldens (documented-breaking until M6 freeze).1416-1417-### Working with the user (read this)1418-1419-- Debug reports come from INSPECT MODE (tilde key → click tiles → copy JSON1420- {seed, version, tiles[]}). Ask for that; do not interpret screenshots by eye —1421- it went wrong three times before the tool existed.1422-- FIRST check the report's `version` against a fresh `genVersion()` — a mismatch1423- means their browser has a stale bundle (hard refresh), not a bug.1424-- The version is COMPUTED from generator output (src/gen/version.ts); never add1425- manual version bumps. Goldens embed it as "__version".1426-- Goldens (tests/golden_tracks.json) may be regenerated freely until the M6 seed1427- freeze — every behavior change does it: rm the file, run tests/track_test.ts1428- with --allow-write, commit the diff.1429-- The user cares hard about tile composition honesty: nothing juts, nothing1430- floats, plates/solids only where connected. When adding pieces, check what the1431- mesh's base actually covers (measure, don't assume) and wire the same1432- contextual rules.1433-- Marble feel (banking lean), checkered goal-gate texture, ambient audio: M6,1434- don't drift into them.1+# Work ahead2+3+This file is exclusively the backlog. Completed work lives in `git log`4+(each round carries a detailed commit message) and in the agent memory5+(measured kit facts, physics laws, debugging methods, pin tables).6+Current state in one line: M1–M15 + the autopilot/boost/glass rounds are7+live — endless mode is an N-marble physics race (1–9999 racers, fuel8+boost economy, glass marbles, hero-anchored streaming), loop mode is the9+finite inspection bench. 116 tests green.10+11+## Race presentation12+13+- **Race HUD**: leaderboard strip (top ranks + hero rank), name labels14+ over nearby marbles (racer identities exist — `racerIdentity`), maybe15+ a fuel gauge for the camera subject. The CUA `race` telemetry already16+ carries everything the HUD needs.17+- **Contact audio cues**: marble-marble hits for the camera subject.18+ Needs the shim to expose contact events (Box3D has them; the shim19+ currently exposes none).20+- **Respawn spawn-in envelope**: respawned racers pop into place — give21+ them the streamAnim-style scale-in (the decorative peer ring had one;22+ real racers don't).23+24+## Physics / feel25+26+- **Collision-immunity window on respawn** (deferred from M15): a27+ respawned marble materializing inside the pack should ghost through28+ neighbors for ~2s. Needs shape filter bits in the shim29+ (b3ShapeDef filter) + a timer per racer.30+- **Rubber-banding through pilot skill** (never thrust — Melder/Game AI31+ Pro rule): trailing racers get braver corner margins + earlier jump32+ commits; leaders get conservative ones. All knobs exist on `Pilot`;33+ the missing piece is a race-position → parameter modulation law.34+- **Torque-servo authority**: only if feel ever demands spin-on-bounce /35+ ice burnouts. Force-at-COM + Box3D contact friction is the standing36+ decision; revisit needs new shim surface (apply torque).37+- **Pack scale realtime**: N=9999 runs at ~0.27× realtime (the 10k-body38+ Box3D step is the wall). If max-scale realtime matters: physics LOD39+ (far racers kinematic riders on the rail, promoted to bodies near the40+ camera) or substep reduction at large N.41+42+## Rendering43+44+- **Glass-through-glass**: transmission shells cannot refract other45+ transparent shells (only opaques land in the transmission buffer).46+ Escalation if close-ups demand it: port a MeshTransmissionMaterial-47+ style shared-buffer material (drei-vanilla reference).48+- **Camera pitch during gravity zones**: the void's gravity leans — the49+ camera could lean with it (deeper spectacle).50+- **Follow-cam island clipping polish**: occluder raycast exists; edge51+ cases remain when the cam trails past island masses.52+53+## Generation54+55+- **Endless stream goldens**: pin per-segment hashes for fixed seeds so56+ planner refactors can't silently reshuffle streams (seed freeze itself57+ stays OFF per user).58+- **Slope cubic-spillover residual**: island terraces got the spillover59+ low-base fix; PLAIN slope windows' neighbor cells still claim flat60+ bands while the realized cubic dips slightly below its base near part61+ boundaries — no observed failure (fuzz clean), but the claim is not62+ airtight. Tighten commitSlope's neighbor bands if a fuzz ever finds63+ it.64+- **Destructible decor**: physics now exists to collide with; dormant65+ until something can hit decor (island-top pieces are off the channel).66+- **Takeable spurs in endless**: the loop testbed proved the machinery67+ (LoopSegment.alt + guaranteed falls); wire them into endless as68+ respawn variety.69+- **Bumper density tuning**: after watching feedback (pins currently70+ only on wide island decks).71+72+## Housekeeping73+74+- Scenario pins move whenever a planner clause changes the weighted75+ draw — re-scan with a scratch script (current pins recorded in76+ memory + tests).77+- `deno task check` has two pre-existing failures unrelated to the game78+ (src/server/main.ts Timeout typing, tools/physics-debug/phys-contact.ts79+ proxy-union access) — `deno task test` is the real gate. Fix when80+ touching those files.