Review: all 5 lanes pass — 70/70 tests, tsc clean, ownership audit clean,
integration verified live (sim -> events -> UI ticker + SCREEN corruption).
Contracts v2 (granted from lane requests): UIBus.pickTile (wired), BeltItem.id?,
MachineDef.{color,flavor,heatPerTick,bufferCap}?, CommissionDef.repeat?,
SimEvent 'scram'. main.ts: non-left pointerdown no longer places. happy-dom added.
Rulings: canon melt chain IS M1 (masterplan updated); rank-routing stays, splitters
next; anchor-slab sanitizing -> Correction buyback fiction; build-bar paging approved;
per-lane dev ports 8151-8155. Round 2 orders written in all lane docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.3 KiB
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.idthrough the registry — zero hard-coded ids.
CURRENT ORDERS — Round 2 (goal: finish M1 polish, open M2 "PRESSURE")
Round 1 review: excellent — the hot-swap verification and the fog/CAM_DIST honesty
note were exactly right. Verdicts: req 1 GRANTED (BeltItem.id? in contracts v2 —
use when present, keep order-matching as fallback until v3 hardens it). Req 2 GRANTED
(MachineDef.color? — precedence: def.color > your derived-from-outputs >
kind accent; your derivation stays as the fallback, it was the right instinct).
Req 3 DONE (main.ts ignores non-left buttons; drop your camera.ts workaround or keep
it as belt-and-braces, your call).
- Belt topology visuals: corner/junction pieces chosen from neighbor analysis — pure presentation, sim truth unchanged. Splitters (mechanics land this round in SIM) get a visible output-cycling tell.
- Heat: shimmer + dull-red emissive ramp driven by
EntityState.heat; throttled = idle animation visibly slowed; scram = dead-dark + blinking amber until restart. - Buffer tank: visible fill level (shader window or scale trick) proportional to
the tank's share of
snapshot.bandwidth.stored. - AnimationMixer: play clip 0 on rigged GLBs with an idle loop — MODELBEAST assets are imminent; verify with any animated GLB you can find or make.
- Exact cargo interpolation via
BeltItem.id— delete the one-tick approximation. - Multi-mesh belt GLBs: merge-or-group fix (your call, note it) so a fancy belt asset can't half-render.
Definition of done: reference factory (SIM ships it this round — coordinate via
their src/sim/reference.ts export in a dev page or test harness) looks ALIVE: corners
read as corners, heat reads as heat, tanks read as tanks; perf standing rule still met
under ?devscene=stress.
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; probesassets/models/<key>.glbon init + every 10s in dev. Verified live: dropped a GLB intopublic/assets/models/quantizer.glbmid-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
#ff3b30red; empty ground →#3fd4ffcyan. 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:
MachineDefhas nocolorfield, 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 indata/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 touchingCAM_DISTmust keepindex.ts's fog band in step. - Dev scaffold (
devscene.ts) is gated behindimport.meta.env.DEVand?devscene, and is tree-shaken out of the prod bundle (verified: 0 hits indist/). It yields automatically the moment the real sim reports any entity — it never fabricates over live sim data.?devscene=stressis 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
BeltItem.id(stable, per-item) — would make cargo interpolation exact instead of order-matched. Low priority; current error is imperceptible.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.main.tsplaces 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.tsstops non-left buttons from bubbling to#game), but the clean fix isif (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 isMachineDef.asset). Hero assets (mosh reactor, muxer) are the ones worthfal_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).