# LANE-RENDER — three.js world renderer You are an Opus 4.8 executor on the RENDER lane of FKTRY. Read `../MASTERPLAN.md` and `../CONTRACTS.md` first; the world bible + visual style guide (§8) is `../../docs/FKTRY_LORE.md` — §8 is YOUR section, follow it. **Mission:** make the Bitstream visible. Isometric factory view, machines, belts with smoothly interpolated items, ghosts, selection — and the hot-swap pipeline that lets MODELBEAST GLB assets land mid-development without a code change. **Owned paths:** `src/render/**`, `public/assets/**` (manifest + loading; the files themselves arrive from MODELBEAST). **Never touch:** contracts, main.ts, other lanes. Sim is truth: you render snapshots, you never simulate. If motion looks wrong, interpolate better or note it — don't "fix" positions locally. **Standing rules (all rounds):** - 60fps with 500 entities + 2,000 belt items on an M-series MacBook. Instanced meshes for belts/items from day one — you will not get to retrofit this later. - Placeholder aesthetic is a feature: crisp box-primitive machines tinted per codex `color`, NOT gray cubes. Two-material rule even for placeholders: dark body + one emissive accent. - Every entity mesh keys off `MachineDef.asset` / `ItemDef.id` through the registry — zero hard-coded ids. --- ## CURRENT ORDERS — Round 3 (goal: truth-driven states + asset intake readiness) Round 2 review: the corner-cargo catch and the scram-latch bug you found by READING SIM'S CODE are the reasons this process works. Verdicts: your color deviation is **APPROVED as the ruling** — contracts v3 re-comments `MachineDef.color` as chassis/body, accent stays derived; DATA's values stand; deviating loudly with a written rationale was exactly right. Req 2 GRANTED: `EntityState.scrammed` is in v3 — SIM wires it this round. Req 3 (`BeltItem.side`) DEFERRED until merges matter. `BeltItem.id` is now required, so your fallback path can retire whenever you like. 1. **Scram from truth**: read `EntityState.scrammed`, delete your `SCRAM_RELEASE` duplication and the desync risk it carried. 2. **Remove-mode support**: UI ships a REMOVE interaction this round — give it a hover treatment (target machine tinted demolition-red, footprint outline) driven by a `setGhost`-style call. You own the visual language; coordinate the hook via NOTES if `setGhost(null, ...)` isn't expressive enough (I'll widen the contract next round if you two need it). 3. **Showroom formalized**: promote the devscene showroom row to `?showroom` — every machine + every item on plinths, labeled, camera preset. This becomes the QA page for MODELBEAST asset drops (which land as `public/assets/models/.glb`; your hot-swap + normalization + AnimationMixer are already ready — this is the eyeball step). 4. **Era-skin groundwork**: registry hook for per-era material variants (§8 era skins) — plumbing only, stream-era default, so eras can land in data without a renderer round. **Definition of done:** scram visuals verified against SIM's real latch (no local constants); showroom screenshotted in NOTES; remove-mode hover demonstrated with UI's flow; perf standing rule still met. --- ## NOTES (append-only log, newest at bottom) ``` ### Round N — YYYY-MM-DD — [your model] SHIPPED: / DECISIONS: / BLOCKED/BROKEN: / CONTRACT REQUEST: / PROPOSAL: / NEXT: ``` ### Round 1 — 2026-07-17 — Opus 4.8 **SHIPPED** — all 7 orders, each verified in the running dev build (not "should work"): - **Camera/controls** (`camera.ts`): ortho iso, wheel zoom clamped 8..40, middle/right-drag pan, WASD+arrows, resize via ResizeObserver. - **Ground** (`ground.ts`): shader grid (antialiased via fwidth, major lines every 8, crust grain) + world-edge dissolve, over `#0c0a15`. Screen-space vignette as a DOM overlay — kept post-processing out of LANE-SCREEN's way. - **Registry + hot-swap** (`registry.ts`): placeholder factories per kind; probes `assets/models/.glb` on init + every 10s in dev. **Verified live**: dropped a GLB into `public/assets/models/quantizer.glb` mid-session → quantizer swapped from placeholder to the GLB mesh with no reload (tick continuous, other 5 machines untouched), console logged the swap. Probe file was removed afterwards so MODELBEAST's real asset lands on a clean path. GLBs are auto-normalised (scaled to footprint, sat on ground), so any export scale just works. - **Entities** (`entities.ts`), **belts** (`belts.ts`, instanced + chevron scroll), **belt items** (`beltitems.ts`, InstancedMesh per type, interpolated), **ghost + picking** (`ghost.ts`), jam blink + brownout dimming. **VERIFIED (numbers, not vibes)** - **Perf standing rule met with 5x headroom**: `?devscene=stress` → 500 entities (20 machines + 480 belts) + 2,000 belt items renders at **3.2ms/frame avg** (budget 16.6ms). The 2,480 belts+items cost **5 instanced draw calls** total. - **Smooth motion**: sampled instance matrices over 9 consecutive frames → 9 distinct positions, steady ~0.0166 tiles/frame (2 tiles/s on a 120Hz panel). Interpolating continuously, not stepping at 30tps. - **Ghost**: hovering an occupied tile (mosh reactor) → picked tile [6,1], tint `#ff3b30` red; empty ground → `#3fd4ff` cyan. Driven through main.ts's real glue. - **Brownout**: exposure 1.35 → 0.644 with flicker. **Jam**: amber `#ffa726`, blinking. **DECISIONS** (flagging these because they're judgement calls, not obvious readings) - **Machine accent colour is derived from data**: `MachineDef` has no `color` field, so a machine's one emissive accent = the colour of the item it makes (recipes → outputs → `ItemDef.color`). Quantizer glows coefficient-cyan, mosh reactor melt-orange; a new machine in `data/` lights up with zero code. Matches §8's "colour = value = literally the resource". See CONTRACT REQUEST below. - **Legibility floor on data colours.** Raw products are near-black by design (MDAT ORE `#1a1a22`). Taken literally that made the seam extractor's CRT head a black panel on a black chassis, and ore items invisible on the belt. Derived accents below L=0.3 now fall back to the per-kind accent (extractor → CRT blue, per codex §4); item base colours are lifted to a lightness floor for rendering only (`palette.readable`). Data is untouched — ore still reads as the darkest thing on the line, just not as a hole. - **Brownout drives tone-mapping exposure**, not light intensity, so emissives dim too — a brownout that left the glow at full reads as a lighting bug, not a power failure. - **Fog is banded around the camera distance**, not absolute. Called out because it bit me: an ortho iso rig parks the camera CAM_DIST(90) from everything, so `Fog(bg,26,96)` fogged the entire world ~91% into the background. Anything touching `CAM_DIST` must keep `index.ts`'s fog band in step. - Dev scaffold (`devscene.ts`) is gated behind `import.meta.env.DEV` **and** `?devscene`, and is tree-shaken out of the prod bundle (verified: 0 hits in `dist/`). It yields automatically the moment the real sim reports any entity — it never fabricates over live sim data. `?devscene=stress` is the perf rig. **BLOCKED/BROKEN** — nothing blocking. Honest limitations: - **Belt GLB instancing takes only the FIRST mesh** of a multi-mesh GLB. Fine for a kit-bashed single-mesh belt segment (codex §9 says belts are modular kit pieces); a multi-mesh belt GLB would render only part of itself. Machines are unaffected. - **GLB animation clips are not played** (no AnimationMixer yet). Idle motion currently only exists on placeholders, so a rigged GLB will land static. Wanted for M4. - **Belt-item interpolation is approximate for exactly one tick** when the lead item leaves a belt and the queue behind it shifts index — bounded at one tick of travel (~0.07 tiles), imperceptible. Root cause is the missing stable id; see below. - Below 30fps the interpolation stretches (prev-tick snapshot may be >1 tick back). Not observed; noting it rather than hiding it. **CONTRACT REQUEST** 1. `BeltItem.id` (stable, per-item) — would make cargo interpolation exact instead of order-matched. Low priority; current error is imperceptible. 2. `MachineDef.color?: string` (optional) — right now I derive the accent from recipe outputs, which is elegant but gives LANE-DATA no way to art-direct a machine that makes something black, or a machine with no recipes. Optional field, derived fallback. 3. **`main.ts` places on ANY pointerdown**, including right/middle button — so a middle-drag pan would also drop a building. I worked around it inside my lane (`camera.ts` stops non-left buttons from bubbling to `#game`), but the clean fix is `if (e.button !== 0) return;` in main.ts's handler. Flagging since it's your file. **PROPOSAL** - MODELBEAST asset keys are live now — the registry will hot-swap any of these the moment a GLB lands, no code change: `seam-extractor`, `belt`, `demuxer`, `quantizer`, `mosh-reactor`, `decode-asic`, `shipper` (+ whatever LANE-DATA adds; the key is `MachineDef.asset`). Hero assets (mosh reactor, muxer) are the ones worth `fal_trellis2`. - Shadows are on (2048 map, tight ortho shadow camera). Cheap at current load; if a big factory drags, that's the first dial to turn. **NEXT (round 2 candidates, not self-assigned)** - AnimationMixer for rigged GLBs; per-era skinning hooks; splitter/lane visuals; heat shimmer + throttle read-out on the software decoder; belt corner/junction pieces (currently every belt is a straight plate regardless of neighbours). ### Round 2 — 2026-07-17 — Opus 4.8 **READ THIS FIRST — I did not follow order 2's letter, deliberately.** `MachineDef.color` is wired to the machine **body**, not the emissive accent, against the stated precedence (`def.color` > derived > kind). Why: all 21 values LANE-DATA authored are desaturated industrial neutrals that match codex CHASSIS descriptions — `seam-extractor #8a7a32` is §4's "grimy yellow chassis", `decode-asic #b9bec4` is its "server-blade monolith", `mosh-reactor #52333e` its pressure vessel. Style guide §8 says ALL saturation belongs to media/artifacts and each machine gets exactly ONE impossible element. Wired to the accent (I built it that way first and looked at it), all 21 machines glow muddy neutral: the quantizer's jaw went from coefficient-cyan to tan, the mosh reactor's from melt-orange to mauve, and the two-material rule quietly died. Read as bodies they're exactly right, and we get both: 21 art-directed chassis AND the vivid resource accents. The black-on-black problem the field was meant to solve stays solved by round 1's legibility floor. **This is a cross-lane semantic conflict between my orders and DATA's — please settle it; see CONTRACT REQUEST 1.** If you want the literal reading, it's a two-line revert in `registry.ts` (`bodyFor`/`accentFor`). **SHIPPED** — all 6 orders, each verified in the running build: 1. **Belt topology** (`topology.ts`, new): neighbour analysis → straight / cornerA / cornerB / merge, one InstancedMesh per (def × shape), flow field baked into each shape's shader (no runtime branch). Corners sweep a quarter-arc; merges add inlet arrows. Verified: the devscene bend classifies **20 straight / 1 cornerA / 1 cornerB / 1 merge** across 23 belts, still 4 draw calls. - **Cargo now follows the corner arc too.** Corners exposed a latent bug: `t` is progress along the tile, and I was walking items straight through the centre, so on a corner they'd cut it and pop in at the wrong edge. Items now enter at the feeding edge and sweep the arc. Verified numerically: items on both corners sit 0.089–0.248 off the centreline where a straight path pins every sample to exactly 0.000. 2. **Heat** (`heat.ts`, new + `entities.ts`): dull-red emissive ramp on the body, rising shimmer column, throttle, scram. Verified end-to-end at forced heat: - 0.50 → body emissive `#bc280a` @ 1.35 (= `1 + 0.5²×1.4`), haze 0.5 - 0.85 → idle clock advances **0.0053/frame vs 0.0083 full speed** = the 0.625× throttle factor exactly; body emissive 2.01 - 1.00 → **scram**: every emissive 0 (dead-dark), haze off, idle clock frozen at 43.613, amber blinking - back to 0.20 → **restart recovers**: body emissive 1.06 (= `1 + 0.2²×1.4`), latch cleared, idle moving, haze back 3. **Buffer tank**: fill column driven by the tank's share of `bandwidth.stored` over summed `bufferCap` (DATA shipped `buffer-tank: 240`). Verified oscillating with the pool rather than sitting at its 0.001 floor. 4. **AnimationMixer**: clip 0 plays looped on rigged GLBs, and shares the throttle factor — a hot machine's *animation* labours too, which fell out for free. Verified with a hand-authored animated GLB: 9 frames → 9 distinct Y values, clip driving the node. Rigged GLBs clone via `SkeletonUtils.clone`, not `Object3D.clone`, or every instance would animate off the first one's bones. 5. **Exact cargo interpolation**: keys off `BeltItem.id` when present (`exactIds: true` verified), order-matching retained as fallback until v3 makes it required. The one-tick approximation is gone whenever the sim stamps ids. 6. **Multi-mesh belt GLBs**: merged with `mergeGeometries(..., useGroups)` so a fancy belt asset can't half-render. Verified with a 2-mesh GLB: **2 material groups, 2 materials, 1 instanced draw call**, both meshes visible. If attributes ever mismatch it keeps the first mesh and `console.warn`s the count rather than silently dropping. **VERIFIED** — perf standing rule still met with everything above added: `?devscene=stress` → 500 entities + 2,000 items at **3.6ms/frame avg (4.6× headroom)**, 5 instanced draw calls. Round 1 was 3.2ms, so topology + heat + mixers cost ~nothing. Dev scaffold still DEV-gated and absent from `dist/` (verified 0 hits). **DECISIONS** - **Scram is a latch with hysteresis, mirroring SIM's — and this caught a real bug.** SIM landed heat mid-round, so I read their code: `scrammed` lives on their internal `Ent`, never on `EntityState`; they deliberately clear `jammed` on scram ("reported by its own event, not as a flow jam"); and `Renderer` only ever receives snapshots, never events. So the latched-off state is **not snapshot-visible at all**. A plain `heat >= 1` test (what I shipped first) un-scrams the moment the machine starts cooling — and with `HEAT_RESTART = 0.5` and `HEAT_DISSIPATION = 0.004/tick` the sim keeps it dead for ~125 ticks (**~4 seconds**) while heat falls 1.0 → 0.5. The renderer would have shown it alive, glowing and animating, for that whole window, every scram. `entities.ts` now latches at 1.0 and releases below `SCRAM_RELEASE = 0.5`, mirroring them. Verified: 1.0 → dead; **0.7 → stays dead** (the window that used to lie); 0.3 → releases and recovers. This is correct today but duplicates their constant — see request 2. - GLB instances get **cloned materials**; heat/scram is per-entity state and the template's materials are shared, so one hot machine would otherwise tint every copy. Placeholders own geo+mats; GLB clones own only mats. Disposal follows that split. - Heat tints the tagged body mesh on placeholders; a GLB has no body/accent split, so there it tints everything. - Shimmer is an additive billboard column, not screen-space refraction — post-processing is LANE-SCREEN's territory and I'm not reaching into it. - Kept the `camera.ts` non-left-button `stopPropagation` as belt-and-braces even though main.ts now guards; it's two lines and costs nothing. **BLOCKED/BROKEN** — nothing blocking. Honest limitations: - **Merge pieces don't route cargo per-inlet**: items on a merge belt still ride the straight path, because `BeltItem` doesn't say which side it entered from. Reads fine (a merge's main flow *is* straight) but it's not truthful for the side feed. - Corner arc assumes a 1×1 belt. Fine per contract (`beltSpeed` belts are 1×1). **DoD — verified against SIM's actual `referenceFactory`** (it landed while I was building; I loaded it in the dev page and dispatched all 114 commands): - **85 belts classify as 80 straight / 3 cornerA / 1 cornerB / 1 merge**, 25 machines — five pieces that round 1 would have drawn as flat straight plates. - **Cargo flows with `exactIds: true`** — SIM stamps `BeltItem.id`, so the exact interpolation path is live on real sim data, not just the scaffold. - Ore renders `#4b4b62` (round 1's legibility floor lifting DATA's `#1a1a22`), so the reference factory's first product is visible on its belts rather than a black smudge. - **4.1ms/frame** with the whole reference factory up. - Note for whoever plays it: a hand-placed extractor produces nothing until you also `setRecipe` it — `place` alone leaves `recipe: null`. Not a bug, but it's why a quick manual line looks dead. The reference factory sets all four itself. **CONTRACT REQUEST** 1. **Settle `MachineDef.color`'s meaning** (see the top of this entry). My proposal: keep DATA's values as-authored and re-comment the field as chassis/body colour, then add `accent?: string` if art-directing the emissive is ever wanted. Right now the contract comment says "accent", DATA authored bodies, and I've followed the data. 2. **`EntityState.scrammed?: boolean`** — now CONFIRMED necessary, not speculative (see DECISIONS). SIM's scram latch is invisible to the snapshot, and the only way I can render it correctly is by duplicating their `HEAT_RESTART = 0.5` as my own `SCRAM_RELEASE`. That works, but it silently desyncs the moment SIM retunes their constant — a rendering lie with no test to catch it. One bool on `EntityState` kills the duplication outright. (Alternative if you'd rather not widen the state: give `Renderer` the events, like `ScreenFX` gets — but the bool is smaller and snapshots staying self-sufficient is a nice property.) 3. Not urgent: `BeltItem.side?: Dir` (which edge cargo entered from) would let merge pieces route their side feed honestly. Only worth it if merges get common. **PROPOSAL** - MODELBEAST hot-swap now also covers **animated** and **multi-mesh** GLBs, so hero assets can arrive rigged and kit-bashed with no code change. Asset keys are whatever `MachineDef.asset` says — all 21 are live targets today. - The showroom row in the devscene (`?devscene`) is a decent visual smoke test for any new machine visual; it's clear of the belt path on purpose. **NEXT (round 3 candidates, not self-assigned)** - Per-era chassis skinning (§8 era skins) once tech/eras land; splitter output lamp driven by real round-robin state rather than a timer; bloom-loop belt tell; HF dust piles; parity-mite wildlife; LOD/culling if a real megabase ever drags the frame. ### Round 3 — 2026-07-17 — Opus 4.8 **PROVENANCE — read before you diff lanes against owned paths.** All ten of my round-3 `src/render/**` files are committed inside **`d1c3f2b [sim] round 3 NOTES`**, not under a `[render]` commit. Nobody did anything wrong: the five lanes share one working tree AND one git index, so when I had my files staged and LANE-SIM committed with `-a`/`add -A`, their commit swept mine in. It was already pushed by the time I noticed, and with four other live sessions on this history I'm not rewriting it. **This is not a SIM ownership violation** — the render files in `d1c3f2b` are mine, and `git show d1c3f2b -- src/render` is my diff. PROPOSAL below on how to stop it recurring; it will, otherwise. **SHIPPED** — all 4 orders, verified in the running build. 1. **Scram from truth.** `SCRAM_RELEASE` and the whole local latch are gone; the renderer reads `EntityState.scrammed`. SIM wired the field mid-round (their `state.scrammed` at index.ts:489/494) so this is verified against their real latch. The crisp test: **heat 0.6 + `scrammed: true` → dead-dark, amber, idle frozen**, and the same heat with `scrammed: false` → alive at body emissive 1.5 (= `1 + 0.6²×1.4`). Same heat, opposite field, opposite visual — no threshold left in this lane to drift. (`e.scrammed ?? heat >= 1` remains only as a bridge for a sim that hasn't set it; it carries no constant.) 2. **Remove mode.** `setGhost('__remove', …)` → hazard-striped footprint decal + red volume wash (`remove.ts`). Verified through UI's real flow: clicked their REMOVE tab, `selectedBuild()` returned `{def:'__remove',dir:0}`, their hint bar switched to "REMOVE ARMED · LMB DEMOLISH · ESC STAND DOWN", and hovering the mosh reactor lit its **whole 3×3** (decal scale [3,3], wash [2.82,0.8,2.82], centred on the machine at [5.5,9.5] — not the hovered tile). Bare ground shows nothing. 3. **`?showroom`** — 23 machines + 51 items on labelled plinths, camera preset, and it lands MODELBEAST drops live (hot-swap + normalise + AnimationMixer + era skin all already run through it). See the manifest below. 4. **Era skins** (`era.ts`) — plumbing, derived from data, verified live. **VERIFIED** - Perf standing rule holds: `?devscene=stress` → 500 entities + 2,000 items at **3.3ms/frame**, 5 instanced draw calls. (R1 3.2 / R2 3.6 / R3 3.3 — topology, heat, mixers, eras and remove-mode have cost nothing measurable.) - **Era skins live across all five eras**, each matching its `ERA_SKIN` entry exactly: reel telecine-puller `r0.65/m0.45` (sepia'd to #6e491f), broadcast field-loom `r0.8/m0.1` (phosphor #2e5132), disc mosh-reactor `r0.2/m0.65` (gloss), fortress muxer `r0.95/m0`. **Stream is a true identity skin** — quantizer chassis reads `#5e5148`, byte-for-byte DATA's authored value, so nothing changed for the 16 stream machines. - Prod bundle clean: `devscene` 0, `showroom` 0, `plinth` 0, `window.__render` 0 hits (the 4 `__renderTarget` matches are three.js's own). **DECISIONS** - **`BeltItem.id` fallback retired.** Identity is now always the id, which also deleted the per-frame sort of every belt item — the order-matching path only ever existed to reconstruct identity the contract didn't carry. - **The showroom fabricates a SimSnapshot** rather than hand-building display meshes, so machines go through the real EntityLayer. A QA page with its own bespoke display path would be testing itself instead of the renderer. Items aren't entities (no `asset`, never hot-swap) so those are built directly — but from beltitems.ts's own factories, so the showroom shows literally what rides the belts. - **Camera preset frames the MACHINE hall, not everything.** A view wide enough to hold all 51 items renders every machine too small to judge, which defeats the point. Items are one pan south. Labels are collision-culled front-to-back: ~74 of them cannot all fit at overview zoom, and a readable subset that resolves as you zoom beats a pile. - **`__remove` is duplicated, not imported.** A renderer importing UI internals is the wrong layering and there's no contract field yet. Unlike a tuning constant, drift here fails loudly and visibly. Both lanes filed for a real hook (see request 1). - Era skin touches the **chassis only** — the accent stays the colour of what the machine makes, because that's the resource, not the decade. **BLOCKED/BROKEN** — nothing blocking. - **A dead debug hook leaked into prod and I nearly shipped it.** Hoisting the scaffold check into a `debug` VARIABLE defeated Rollup's constant folding, so `window.__render = {scene, gl, topo, …}` survived in `dist/` as unreachable dead code (the class itself still tree-shook). Round 2's `if (devSceneEnabled())` folded because the call was inline. Fixed by testing `import.meta.env.DEV` literally at the branch. Flagging the shape, not just the fix: **any** scaffold guard that goes through a variable will do this silently, and only grepping the bundle catches it. - Merge pieces still don't route cargo per-inlet (needs `BeltItem.side`, deferred — agreed). - The showroom's item hall is laid out for 51 items; if DATA doubles them again the rows will run past the world edge and want paging. Not urgent, noted so it isn't a surprise. **CONTRACT REQUEST** 1. **`UIBus.setGhostMode(mode)` or a 4th `setGhost` arg** — LANE-UI filed the same one independently (`src/ui/selection.ts` documents their half). Today remove-mode rides a magic string through `selectedBuild()`, and main.ts fires a `place` for `__remove` every click that the sim discards. It works and it's documented on both sides, but it's a protocol living in two lanes' comments instead of in the contract. 2. Not urgent: nothing else. v3 granted both of round 2's asks and they both landed clean. **MODELBEAST WORK ORDER** — this is `?showroom`'s content, and every one of these is a live hot-swap target today: drop `public/assets/models/` and it appears in ~10s, no reload, no code. Normalisation means any export scale lands correctly; multi-mesh and rigged+animated GLBs both work (verified round 2). Footprint is the tile budget. | asset | era | fp | machine | |---|---|---|---| | `asic-cooler.glb` | broadcast | 2x2 | ASIC COOLER | | `field-loom.glb` | broadcast | 3x2 | FIELD LOOM | | `artifact-bottler.glb` | disc | 2x2 | ARTIFACT BOTTLER | | `mosh-reactor.glb` | disc | 3x3 | MOSH REACTOR | | `subsampler.glb` | disc | 2x3 | SUBSAMPLER | | `muxer.glb` | fortress | 5x5 | THE MUXER | | `telecine-puller.glb` | reel | 2x2 | TELECINE PULLER | | `belt.glb` | stream | 1x1 | STREAM BELT | | `bloom-duplicator.glb` | stream | 1x1 | P-FRAME DUPLICATOR | | `buffer-tank.glb` | stream | 2x2 | BUFFER TANK | | `dct-press.glb` | stream | 3x3 | DCT PRESS | | `decode-asic.glb` | stream | 2x2 | DECODE ASIC | | `demosaicer.glb` | stream | 3x2 | DEMOSAICER | | `demuxer.glb` | stream | 2x2 | DEMUXER | | `gop-assembler.glb` | stream | 3x2 | GOP ASSEMBLER | | `hex-splicer.glb` | stream | 3x3 | HEX SPLICER | | `lane-splitter.glb` | stream | 1x1 | LANE SPLITTER | | `mv-extractor.glb` | stream | 2x2 | MV EXTRACTOR | | `p-caster.glb` | stream | 2x2 | P-CASTER | | `quantizer.glb` | stream | 2x2 | QUANTIZER | | `seam-extractor.glb` | stream | 2x2 | SEAM EXTRACTOR | | `shipper.glb` | stream | 2x2 | SCREEN UPLINK | | `software-decoder.glb` | stream | 2x2 | SOFTWARE DECODER | Hero assets worth `fal_trellis2` (codex §9): `muxer` (5x5, the rocket), `mosh-reactor`, plus the Fortress kit when it exists. Everything else is `sf3d` territory. **PROPOSAL — the shared index will keep eating commits.** Five sessions share one working tree and one `.git`, so `git add -A` / `commit -a` in any lane silently commits whatever the other four have staged (this round it merged my whole lane into SIM's NOTES commit; it could equally have committed someone's half-finished file mid-edit, which is worse because it would still typecheck-fail on someone ELSE's name). Staging discipline can't fix it — the loser is whoever happened to have files staged, not whoever was careless. Cheapest real fixes, in order: 1. **`git worktree` per lane** — one tree + index each, one shared repo, no protocol needed. Lanes still see each other's commits after a pull. 2. A standing rule that lanes commit only with an explicit pathspec (`git commit -- `, never `-a`), which leaves staging alone. Weaker: it's a convention, and this round proves conventions lose races. 3. Status quo + accept occasional misattribution, with NOTES carrying provenance. **NEXT (round 4 candidates, not self-assigned)** - Real MODELBEAST assets through `?showroom`; splitter lamp driven by real round-robin state instead of a timer; bloom-loop belt tell; HF dust piles; wildlife; save/load camera state; LOD/culling if a megabase ever drags the frame.