glytch/fktry/lanes/LANE-RENDER.md
type-two 5ee0fb9e78 [render] round 1 NOTES
Decisions, verification evidence, honest limitations, and 3 contract
requests (BeltItem stable id, optional MachineDef.color, main.ts placing
on any pointer button).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 16:31:10 +10:00

9.6 KiB
Raw Blame History

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 1 (goal: milestone M1 "FLOW")

Replace the stub in src/render/index.ts:

  1. Camera + controls. Orthographic isometric-style view. Pan: middle-drag or WASD. Zoom: wheel (clamped ~8..40 world units). Window resize handled.
  2. Ground. Tile grid over the 64×64 world; subtle Stream-Crust styling (dark #0a0812 base, faint #181425 lines) — see style guide §8. A soft vignette or fog so the world doesn't just end.
  3. Asset registry + hot-swap. registry.ts: maps asset key → THREE.Object3D factory. Default: procedural placeholder (footprint-sized beveled box, body color from data, one emissive accent, tiny idle animation — e.g. quantizer jaw bob). On init, attempt GLTFLoader for public/assets/models/<key>.glb; if present it replaces the placeholder factory. Also poll or re-check on a 10s timer in dev so assets dropped in mid-session appear without reload (note what you implement).
  4. Entities. Sync scene to snap.entities (add/remove/rotate). Belts get a flat directional piece with an animated arrow/chevron texture (shader or UV scroll).
  5. Belt items. InstancedMesh per item type (colored octahedron/box placeholder), positioned from BeltItem.t + belt entity pos/dir, interpolated with alpha for butter-smooth motion at 30 tps sim.
  6. Picking + ghost. pickTile via raycast to the ground plane. setGhost shows the selected machine's placeholder at the hovered tile, tinted cyan; red tint if tiles occupied (you can read occupancy from snap.entities footprints).
  7. Feedback dressing (cheap but load-bearing): jammed machines get a small blinking amber light; brownout dims the whole scene ~30% and flickers lights (listen to snapshot.bandwidth.brownout).

Definition of done: dev server shows the factory; placing a line from the UI (or console dispatch) shows machines + items gliding down belts smoothly; a GLB dropped into public/assets/models/quantizer.glb replaces the placeholder. npm run check clean. NOTES written.


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/<key>.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).