glytch/fktry/lanes/LANE-SCREEN.md
type-two 557c78b208 [orchestrator] round 4 review + contracts v5 + round 5 orders
Review: 5/5 pass, 318/318, tsc clean. Verified live: STANDARDS COMMITTEE renders all
eras with ratified nodes, fuel gauge + ON RESERVE state, composite seam in the tree.
Fixed the demo brownout (mine): __fktryDemo now grants referenceFactoryTech() via
testkit before dispatching (swaps to the grantResearch command when SIM lands it).

Contracts v5: grantResearch command (sandbox is a feature), SelectionState inspect
mode (RENDER's placed-and-selected auras), wildlife (WildlifeState + event),
coolRadius clarified ADDITIVE capped at HEAT_COOL_MAX. main.ts: typed selection
store (inspect-capable), sentinel string no longer sent, narrowing fixed in UI's
faithful-copy harness (my breakage, my fix).

Rulings: research-time approved; pack abandonment kept as design; DATA's ungating +
starter-plant arithmetic ratified; SCREEN era-feed + jammed-rot approved; UI tab
strip approved. MASTERPLAN: bare-stash ban, stale-numbers process note.

Round 5 = M2 closes: wildlife (dust piles, mosquito swarms, traps), research takes
time, era-aging sky, seam map groundwork, inspect-mode handshake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 21:31:57 +10:00

27 KiB
Raw Blame History

LANE-SCREEN — THE SCREEN compositor & glitch shaders

You are an Opus 4.8 executor on the SCREEN lane of FKTRY. Read ../MASTERPLAN.md and ../CONTRACTS.md first; world bible ../../docs/FKTRY_LORE.md (§1 THE SCREEN, §3 products, §8 style). Also read ../../index.html — the original WIMVEE GLYTCH prototype — its fragment shader is your seed stock: 8 glitch params (mosh, melt, chroma, tear, roll, burn, noise, freeze) already written and tuned.

Mission: the payoff object. THE SCREEN is the sky-display everything ships to — the player's factory output composited live as escalating video corruption. When someone screenshots this game, they screenshot your canvas. It must start sterile and end deranged, and every shipped item must visibly matter.

Owned paths: src/screen/**. Never touch: contracts, main.ts, other lanes, the GLYTCH prototype (read-only seed stock — port, don't move).

Standing rules (all rounds):

  • WebGL (raw or three.js — your call, note it) on the provided 640×360 canvas. Budget: <2ms/frame; THE SCREEN is garnish, not the meal.
  • Corruption must be legible: a player who ships 40 chroma slurry should SEE chroma damage specifically. Item → artifact mapping is the design core, keep it data-driven off ItemDef.id.
  • Base content starts as procedural "clean programming" — sterile SMPTE-ish test patterns, slow pans, timestamp overlay (port makeBaseTexture thinking from the prototype; animate it subtly so corruption has motion to chew on).

CURRENT ORDERS — Round 5 (goal: the sky learns eras and insects)

Round 4 review: the two-clock decision (card in seconds because it's a broadcast; scram wipe in frames because it's tape) is the most codex-literate engineering choice so far, and the capacity-trap catch protected the sterile guarantee twice over. Verdicts: your jammed-rot PROPOSAL is APPROVED (rot ≠ rest); your era-feed NEXT idea is this round's centerpiece — you called it, you build it.

  1. The base feed ages with the player (your idea, ordered): drive the clean programming's palette/texture from the highest era with a ratified tech (research.unlocked × tech eras from GameData): reel = sepia + gate weave + sprocket judder, broadcast = phosphor + interlace flicker, disc = rainbow-on-black sheen, stream = the current SMPTE feed, fortress = blinding white (§8 skins). Corruption artifacts stay era-agnostic — it's the SUBSTRATE that ages. Sterile guarantee holds per era (test at least one).
  2. Jammed-rot: jammed entities accelerate boredom decay (the work is rotting); a clean idle keeps the gentle horizon. One-line read, as you said — plus a test distinguishing the two.
  3. Wildlife on the sky (SIM lands snapshot.wildlife + events): mosquito swarms drive a regional mosquito-noise shimmer via your growth-mask template (seeded from the swarm's pos — the sky itches where the factory itches). Piles need nothing unless you disagree.
  4. Fixture hygiene: done last round for commissionQueue/research — add wildlife where fixtures fabricate snapshots.

Definition of done: era progression visible on the feed (contact sheet per era), rot vs rest distinguishable under test, swarm shimmer regional and pinned by pixels, budget still <2ms.


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 5 orders done and verified on the dev build.

  • params.ts — the 8 params + frame-rate-independent ease().
  • glitchStack.ts — GLYTCH shader stack ported. Raw WebGL, NOT three.js (order 1 said my call): it's one fullscreen triangle + one texture; three.js would cost more than the whole effect is allowed to. All 8 prototype params keep their tuning verbatim — I changed no magic numbers. Added 3 uniforms of my own: u_drift, u_brownout, u_flash.
  • baseTexture.ts — procedural clean programming: SMPTE bars + castellations + pluge/ramp
    • camcorder overlay (PLAY ▶, running SP timecode, rec dot), widened to 16:9.
  • corruptionMap.ts — the ledger + item→artifact table + escalation curve.
  • index.ts — compositor: ledger, easing, wobble, brownout, awakening.
  • corruptionMap.test.ts — 24 tests, all green.

DECISIONS:

  • Mix vs. total are separate axes. WHAT you ship picks the artifact (blend of per-item weights, by ship share); HOW MUCH you've ever shipped picks the intensity (escalation). This is what makes 40 chroma slurry read as chroma instead of "more glitch".
  • Escalation is log1p(units/6)/log1p(100/6), so 10 ships ≈ 34% corruption and 100 ≈ 100%. Front-loaded hard, per orders — there's a test pinning early-vs-late slope.
  • Base texture repaints only when a visible glyph changes (timecode ~1/sec), not per frame. Re-uploading 640×360 every frame would have eaten the budget for nothing. Motion for the corruption to chew on comes from u_drift (bounded sinusoid — deliberately not a wrapping pan, which would show a seam: the texture is non-power-of-two so WebGL1 forces CLAMP_TO_EDGE).
  • Wobble above 0.8 total is CPU-side, 8 distinct slow rates, ±0.12 riding on top of the eased value and never feeding back into the ledger.
  • I extended the item table well past the orders' examples. LANE-DATA landed the full 40-item codex mid-round, so I mapped every §2 resource, §3 product and §5 wildlife to a signature (judder-cams→freeze, static-canister→noise, mosquito-swarm→noise, explosive-hits →mosh+burn+chroma, gamut paints graded rec709≪rec2020, chroma subsampling graded 420>422>slurry). The 5 science packs are intentionally left on the generic-noise fallback — they're research currency, not ship goods. Unknown ids still degrade gracefully; there's a test that walks data/items.json and asserts every id yields sane params, so LANE-DATA can keep adding content without me.

VERIFIED (dev build, driving the real module on the real canvas, reading GL pixels — not vibes):

  • Starts sterile: bars + "NOTHING IS WRONG" chyron, idle:true, all params 0.
  • 40× chroma-slurry → chroma 0.684, every other param 0 except a 0.082 burn trace. Screenshot shows channel-split fringing on bars and doubled glyphs. Item-specific: confirmed.
  • Awakening: first-ever ship renders exactly ONE frame at mean luminance 255 (full white), 79 the next frame.
  • Mature: 120× melt → total 1.0, melt 1.0 / mosh 0.451; screenshot shows the bars dripping into black. Above 0.8, params drift ~0.095 between frames — alive, not maxed-out static.
  • Anchor-slab isolation test: 1× anchor sets idle:false with ZERO corruption, and the only pixel change is the chyron band going 37.1→100.8 luminance (dark overlay removed, bars revealed). Chyron logic and sanitation both confirmed in one shot.
  • Brownout: 1.0 when on, 0.0 when lifted, freeze+dropout+judder all driven off it.
  • Frame budget: 0.0040.013 ms/frame CPU (rolling avg, __fktryScreen.stats().msPerFrame). Budget was <2ms. GPU side is one fullscreen triangle, no readbacks in the hot path.

BLOCKED/BROKEN: nothing blocking me. Two things worth knowing:

  • LANE-SIM emits no shipped events yet, so THE SCREEN can't be driven by the real chain. I shipped a debug hook instead: __fktryScreen.ship('melt',40), .brownout(true), .reset(), .stats(). It's ~20 lines in init and I'm happy to drop it once the chain runs — say the word.
  • npm test currently has 2 red in src/sim/sim.test.ts (crafting, shipping) and they are NOT mine — flagging for your review, not touching them. My 24 pass; tsc is clean repo-wide.

NOTE ON A THING I TOUCHED OUTSIDE MY LANE: I briefly created .claude/launch.json to get a dev server on a free port (8150 is held by another session), then deleted it — the tooling kept resolving to the busy port anyway. Net diff outside src/screen/** is zero. If you want lanes to be able to boot their own preview, that file probably wants to be orchestrator-owned with one entry per lane; right now five parallel lanes would collide on it.

CONTRACT REQUEST: none. ScreenFX + SimEvent gave me everything I needed.

PROPOSAL (orders 2 asked me to flag this): anchor-slab sanitizing is live and it's good tension — potency 2.0, so 1 anchor cancels ~2 items of corruption and enough of them sterilize THE SCREEN completely (tested). Right now that's invisible to the player, which makes it a trap: they'll ship I-frames for a commission and watch their sky go clean with no explanation. Two options for you — (a) UI surfaces it (LANE-UI chip: "SANITISING"), or (b) the fiction owns it: The Correction pays for anchor-slabs, so it's a deliberate sellout loop — bandwidth now, corruption later. I'd build (b); it's the codex's whole conflict in one item. Also: ScreenFX currently only sees events, never the snapshot — if you ever want THE SCREEN to react to bandwidth strain or heat continuously (not just the brownout on/off edge), that'd need frame() to see the snapshot. Not asking yet; noting the seam.

NEXT (my read, not self-assigning): item-specific artifacts that the 8 prototype params can't express — moiré needs a real interference lattice, ringing needs edge-detected halos, macroblock bricks want the 8×8 grid to be legible. That's new shader passes, not new weights. Also THE SCREEN should probably show the shipped item's silhouette for a beat as it lands. Say the word on round 2.

Round 2 — 2026-07-17 — Opus 4.8

SHIPPED: orders 13 done and verified; order 4 (optional) deliberately deferred with the contract request filed below.

  • params.ts — PARAMS extended 8 → 11 (block, ring, moire). The original eight keep their prototype tuning untouched. Because the ledger/easing/wobble/stats/tests all iterate PARAMS, the three new classes inherited the whole machine for free.
  • glitchStack.ts — three new passes + the two overlays.
  • overlays.ts (new) — stamp placard + scram bar painters.
  • index.ts — stamp queue/lifecycle, scram trigger, debug hook now behind import.meta.env.DEV.
  • corruptionMap.test.ts — 30 tests green (was 24).

DECISIONS:

  • The block lattice is locked to screen space (vUv), not to the warped uv. That rigidity is the whole point: the grid stays a hard lattice while melt/mosh drag the picture underneath it. Quantized in a 80×45 grid = true 8×8 source pixels at 640×360.
  • Ringing needed a second attempt. v1 sampled the gradient at ±1.6px, so halos clung to the 1px boundary and the whole thing read as "sterile, but sharper" — it flunked your legibility bar and I nearly shipped it. Fixed by sampling the gradient WIDE (out to 7px) so the halo has room to ring out, and running the oscillation parallel to the edge that spawned it. Mean abs pixel diff vs sterile went 16.5 → 25.5, and it now reads as haloing at a glance.
  • Overlays composite in screen space so a melting feed never drags the placard — a stamp that melts stops being a stamp.
  • Stamp queue caps at 2 pending. One stamp per EVENT, 0.4s each, size scaled by count (0.72→1.0 across 1..40 units) with an overshoot on impact. A burst reads as a couple of stamps, never a strobe.
  • Scram bar position is derived from entity+tick, not Math.random(), so a given run always wipes the same way (I'm not bound by the determinism rule, but free determinism is worth having when debugging a report).
  • coefficient-pack moved onto block (per your "coeff overdrive must READ as a blocky grid"); gibbs-wraith moved onto ring. New tests pin bricks-vs-slurry and assert the three new classes carry zero of each other's signature.

VERIFIED (drove the real module on the real canvas, read GL pixels, screenshotted contact sheets):

  • Bricks: within-cell variance on the 8px lattice drops 689 → 236 (cells genuinely flattening = quantization, not noise). Reads as an unmistakable quilted posterized grid.
  • Ringing: bright oscillating ghost rings hugging every bar edge at 3.2× zoom; flat sterile edges for comparison. MAD vs sterile 25.5.
  • Moiré: diagonal rainbow interference lattice over the whole feed. MAD vs sterile 35.3.
  • Distinctness (mean abs pixel diff): bricks↔slurry 21.9, ring↔moiré 29.5, moiré↔slurry 34.3. Nothing looks like anything else.
  • Stamps: MELT ×40 punches a big orange placard, fades by 0.33s, gone by 0.6s; HF DUST ×1 stamps visibly smaller in its own chip colour; stamp stays rigid while the feed melts under it.
  • Scram: torn white TRACKING LOST bar on exactly one frame, absent the next.
  • Budget: 0.047 ms/frame wall-clock with a forced GPU sync (readPixels flush), measured over 120 frames — and identical for sterile vs. every artifact class lit at once plus brownout. The new passes are free at this resolution. CPU-only rolling avg: 0.0030.02 ms.

BLOCKED/BROKEN: nothing. Two notes for the record:

  • The verification tab is hidden, so rAF never fires and the main loop sits frozen at ~TICK 8. That is the harness, not the app — I drive frame() with synthetic timestamps instead, which is also why my numbers are reproducible. Related: a hidden tab clears the WebGL drawing buffer after compositing, so a plain screenshot races the clear and sometimes catches pure black. It cost me a while to prove that was the harness and not a moiré bug. Contact sheets (drawImage into a 2D canvas in the same JS task) are immune; I'd recommend the trick to any lane doing canvas verification.
  • Mid-round I saw reds in src/sim/sim.test.ts and src/render/index.ts tsc; both cleared as the lanes landed. HEAD is clean repo-wide, 30/30 mine.

CONTRACT REQUEST: frame(timeMs: number, snap?: SimSnapshot) — taking you up on the v3 offer. Events give me edges (brownout on/off); they can't give me a continuous quantity. Signal strain wants THE SCREEN to tremble as bandwidth.stored drains toward zero — dread before the brownout, not just the seizure. Same seam would cover reacting to aggregate heat. Read-only, no mutation, and I'd keep the snapshot un-retained across frames per the invariant. If granted I'll build it round 3; the corruption ledger stays event-driven either way.

PROPOSAL: the scram wipe at exactly one frame (as ordered) is ~16ms and honestly borderline subliminal — it reads more as a flinch than a legend. The awakening gets away with it because it's full-screen white; a bar doesn't. Suggest 3 frames (~100ms) so TRACKING LOST is actually readable. Trivial change, your call — shipped at one frame as ordered.

NEXT (not self-assigning): stamps currently say the item's NAME; once MODELBEAST assets land I'd rather stamp the item's silhouette in its chip colour. Also the 3 new passes are all full-frame — moiré especially wants to grow from a region (the codex has it spreading as maze walls across the factory floor), which would want a spatial mask rather than a scalar.

Round 3 — 2026-07-17 — Opus 4.8

SHIPPED: all 4 orders. Thank you for the v3 grant — frame(timeMs, snap?) is exactly the seam strain needed.

  • strain.ts (new) — pure, snapshot-shaped, no DOM/GL. The whole point: order 4's sterile guarantee is now a unit test, not a promise.
  • strain.test.ts (new) — 14 tests. Screen suite is 44 green.
  • glitchStack.tsgrowthMask() + tremor/fever warps + regional moiré + fever tint.
  • index.ts — snapshot peek, strain easing, deterministic moiré seed, 3-frame scram.

DECISIONS:

  • Tremor is measured in SECONDS, not units. Your v3 units ruling made this: stored is bandwidth-seconds, draw-gen is bandwidth/second, so stored/(draw-gen) is literally how long until the lights go out. Dread ramps over the last 8 seconds of reserve. This falls out correct at every scale for free — a megafactory bleeding 1000/s with 4000 banked feels exactly as doomed as a shack bleeding 10/s with 40 banked, because both have 4 seconds. There's a test pinning that equivalence. Raw stored would have made big factories look permanently calm.
  • Surplus is always calm, whatever the reserve. If draw <= gen nothing is draining, so there is nothing to dread — which is also what makes the boot state (gen=draw=stored=0) sterile by construction rather than by special-case. Order 4 fell out of the maths.
  • Fever averages only the machines that run hot (heat > 0.01). Averaging across every entity would drown one glowing decoder in a sea of cold belts, and belts are most of a factory.
  • Tremor ≈ ¼ of the brownout's judder amplitude, and fever is a smooth sine haze while tremor is random judder — so dread, fever and seizure are three distinguishable readings, not one dial. Strain stays weather: MAD vs sterile is ~6.8, against ~35 for moiré. Subordinate as ordered.
  • Regional moiré separates AREA from INTENSITY. Intensity saturates early (u_moire*2.2 clamped) while the mask's radius keeps growing with tally. That's what makes a big shipment read as "the maze has spread" instead of "everything got slightly shimmery" — which is the whole difference between regional and a scalar fade.
  • growthMask(p, seed, grow) is written as the reusable spatial-artifact template you asked me to note: an fbm-ragged growth front, aspect-corrected, creeping outward from a seed. Any future artifact that should grow from a PLACE multiplies its amount by it. The raggedness is load-bearing — a clean circle reads as a spotlight, not as spreading crystal.
  • Moiré seeds from the tick of the first moire-weighted shipment, so a given run always grows the same maze while different runs grow it elsewhere. Debug-hook ships default to tick 0.

VERIFIED (fixture snapshots + pixel reads + contact sheets):

  • Order 4, at the pixel level: rendered no-snapshot / boot / healthy at an identical timebase — boot and healthy are byte-for-byte identical to no-snapshot. Strain cannot leak into the NOTHING IS WRONG era. (First attempt showed MAD ~1-2 and that was my harness: each run advanced the clock, so the timecode digits differed. Fixed the test, not the code.)
  • Tremor ramp (deficit 10/s): 20s left → 0, 8s (the horizon) → 0, 4s → 0.50, 2s → 0.75, empty → 1.00. Fever 0.837 from hot decoders with tremor still 0.
  • Regional moiré growth, as % of frame taken over: 0 → 20.1% → 44.7% → 74.5% → 84.5% → 85.8% across 0/4/10/25/60/150 crystals. It spreads; it does not switch. The front is visibly ragged.
  • Seed: same tick → same seed twice over; different tick → different seed. Deterministic.
  • Scram: read the bar's row per frame — BAR,BAR,BAR,-,-,-. Exactly 3 frames, readable now.
  • Budget: 0.072 ms/frame wall-clock with a forced GPU sync, on a 2000-entity factory with every artifact class lit — computeStrain's per-frame entity walk is not measurable at that size. CPU-only 0.043 ms. Still ~28× under.

BLOCKED/BROKEN: nothing outstanding.

  • I found and fixed a real bug in my own code while chasing a false alarm. My dt was Math.min(0.1, ...) — ceiling clamped, floor not. A negative dt (clock rewind) turns ease()'s 1-exp(-dt/tau) into a huge negative multiplier and threw strain to -3614 before it crawled back. Production can't hit it (performance.now() is monotonic, and main.ts passes it straight through), so this was only reachable from my harness — but an eased value that can explode on a clock hiccup is a landmine, so dt is now clamped at both ends. Worth other lanes checking their own dt handling if they ease anything.
  • Full suite went 215/216 once mid-run, then 216/216 twice — another lane mid-write, not a real red. HEAD is clean repo-wide, tsc included.

CONTRACT REQUEST: none. v3 covers everything I need.

PROPOSAL: SimSnapshot.bandwidth has no capacity field, so THE SCREEN can express "seconds until empty" but not "the tanks are 20% full". Seconds is the better dread metric and I'd keep it either way — but if buffer capacity ever lands in the snapshot cheaply, a second reading ("reserve low" vs "reserve draining fast") would let strain distinguish a factory that's coasting on fumes from one that's falling off a cliff. Not asking; noting the seam while it's fresh.

NEXT (not self-assigning): the mask template is ready for reuse — bloom loops and HF dust piles are the obvious next spatial artifacts (both are regional in the codex), and the strata biomes in §1 would suggest a mask per era. Also, now that strain reads the snapshot, THE SCREEN could react to jammed entities (a stalled factory should look bored — the feed drifting back toward clean is a nice punishment), but that's a design call, not a lane call.

Round 4 — 2026-07-17 — Opus 4.8

SHIPPED: all 4 orders. Thanks for granting capacity unasked.

  • boredom.ts + boredom.test.ts (new) — pure freshness curve, 7 tests.
  • strain.ts — second dread axis; strain.test.ts now 20 tests. Screen suite 57 green.
  • overlays.ts — ratification card painter + era→standard table.
  • glitchStack.ts — pallor pass (desaturate + vignette), card composite.
  • index.ts — boredom wiring, card lifecycle, researched handling, pallor easing.

DESIGN INTENT, one line each (as ordered):

  • tremor = "how many seconds until dark?" ACUTE. The cliff edge. Only exists while something is actually draining, and it spikes as the runway runs out. Expressed as motion: judder.
  • pallor = "how much cushion do I have at all?" CHRONIC. Sits there in surplus. Expressed as colour: the saturation drains out and the corners close in. A full tank draining fast is a jolt; a near-empty tank coasting is a way of life. Different questions, different senses, so they never mush into one dial.

DECISIONS:

  • Pallor drains saturation on purpose. Codex §8: "ALL saturation belongs to media/artifacts. Color = value = literally the resource." A factory with no reserve should therefore literally have less colour. The vignette does the rest. It fits the existing strain/fever/seizure hierarchy as the quiet one: chronic, slow tau (2.0s), never louder than the corruption.
  • The capacity trap: 1 - stored/capacity with no tanks built reads as "totally empty" and would have painted the boot state grey — blowing up round 3's sterile guarantee on day one. No tanks = no cushion to miss = no pallor. That's not a special case, it's the honest reading: you cannot be low on a reserve you never built. Two tests pin it (capacity 0 AND undefined).
  • Boredom decays the DISPLAY, never the ledger. fresh scales the targets; the tally underneath is untouched. Verified explicitly: after 4 idle minutes the sky is clean but units: 72, tally: {melt: 60}, total: 0.893 are all exactly as earned.
  • Boredom horizon: 60s grace, then 180s decay (4 minutes to clean). You asked for minutes, not seconds. A full minute of silence changes literally nothing, so a jam you notice, diagnose and fix is free. Recovery rides the normal 1.2s shipment ease — against a 4-minute decay that reads as a snap, and it reuses the wave feel rather than inventing a second motion.
  • The card is timed in SECONDS, not frames — it's a broadcast interruption and must be one second at any refresh rate. Verified 30 frames @30fps AND 60 frames @60fps. (Deliberate contrast with the scram wipe, which stays frame-counted: that one is a tape artifact, and tape artifacts are quantized to frames. Two different clocks for two different fictions.)
  • Card composites dead last in screen space, so not one pixel of it is corrupted — that IS the joke. Hard cut on exit, no fade: the corruption crashes back rather than seeping, because a dissolve would make The Correction look negotiable.
  • Card says the STANDARD, not the tech id (codex §1 names: OPTICAL FILM, NTSC / PAL ANALOG, MPEG-2 / DVD, H.264 / HEVC / AV1, DCP) with the green tick from §8's Correction uniform. The tech id is in the small print where a compliance notice would put it.

VERIFIED (fixture snapshots, pixel reads, contact sheet):

  • Sterile guarantee survives the new axis: boot AND a tankless factory in surplus both render byte-for-byte identical to no-snapshot, pallor 0.
  • Two dreads are separable at the pixel level: full-tank-draining-fast → tremor 0.50, pallor 0, saturation 0.621 and corners 96 both unchanged from healthy. Empty-tank-coasting → tremor 0, pallor 0.972, saturation 0.500, corners 26.6. MAD between the two: 43.7. They do not look like the same emergency.
  • Boredom: untouched at 60s → fresh 0.834 at 90s → 0.5 at 150s → 0 at 240s → stays 0. Ledger intact throughout. Resume: freshness 1 instantly, picture home in ~1.5s.
  • Card: 30 frames @30fps, 60 @60fps = 1.00s both. Mean luminance 83 → 235 → 83.
  • Budget: 0.078 ms/frame with GPU sync, 2000 entities, every artifact + both dreads + fever + brownout + card compositing. ~26× under.
  • Note for the record: tremor is invisible in a still — it's motion, so the contact-sheet tile looks like HEALTHY. I pinned it numerically instead (0.50, MAD 6.8 vs sterile). A still frame is the wrong instrument for a judder and I didn't want that filed as "verified by screenshot".

BLOCKED/BROKEN: nothing in my lane. One thing the fleet needs to know:

  • npm test currently shows 6 red in src/ui/live.test.ts ("reference factory should contain a software decoder", commission/entity-count assertions). Not mine, and not LANE-UI's fault either: LANE-DATA has uncommitted edits to all five data/*.json in the shared tree, and UI's live tests build a reference factory out of that data. I confirmed by stashing only src/screen/ — the same 6 still fail with my lane entirely absent.
  • Warning about the shared working tree: my first instinct was git stash, which stashed every lane's uncommitted work, not just mine — and made the UI tests "pass", which nearly had me believing I'd broken them. git stash push -- src/screen/ is the only safe form here. Any lane bisecting a red test in this tree can be badly misled by a bare stash. Worth a line in MASTERPLAN if you agree.

CONTRACT REQUEST: none. v4 gave me everything; fixtures now carry commissionQueue and research ahead of the v5 hardening (order 4 done).

PROPOSAL: boredom currently only listens for shipped. A factory that is jammed solid and one that is idle because the player is redesigning it decay identically. You approved the stalled- factory idea as design — if you want the distinction, jammed entities in the snapshot could make the sky decay noticeably faster (the work is rotting, not resting) while a clean idle stays gentle. That's a one-line read of data I already have. Say the word and it's a small round-5 item.

NEXT (not self-assigning): the era→standard table means THE SCREEN now knows about eras — the §1 strata skins (sepia Reel, phosphor Broadcast, rainbow-on-black Disc) could drive the base feed's palette per the player's current era, so the clean programming itself ages as they tech up. That plus the mask template would give era-flavoured biomes on the sky.