glytch/fktry/lanes/LANE-SCREEN.md
type-two 08761e8eeb [orchestrator] round 2 review + contracts v3 + round 3 orders
Review: 5/5 lanes pass. 160/160 tests, tsc clean, M1 COMPLETE — verified live:
reference factory ships melt, commission queue advances with standing orders,
SCREEN's new artifact passes respond item-specifically (block 0.46 from bricks).

Contracts v3 (granted): EntityState.scrammed, SimSnapshot.commissionQueue,
MachineDef.coolPerTick, setRecipeAt command, Sim.save/load, ScreenFX.frame(t, snap),
BeltItem.id hardened required, MachineDef.color re-ruled as chassis/body.
main.ts: passes snapshot to screen.frame; dev __fktryDemo() builds reference factory.
happy-dom ACTUALLY installed this time (round-2 grant never landed; UI caught it).

Rulings: bandwidth units are per-second (SIM converts); heat retune ordered (nothing
can currently overheat - heatPerTick < ambient cooling); validator test moves to
data/; RENDER's color deviation approved as the ruling; scram wipe 3 frames; REMOVE
button approved. Round 3 orders in all lane docs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 18:05:53 +10:00

225 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 3 (goal: continuous dread + spatial artifacts)
Round 2 review: the ringing self-rejection ("sterile, but sharper it flunked your
bar and I nearly shipped it") is the exact quality instinct these orders exist to
buy. Verdicts: CONTRACT REQUEST GRANTED `frame(timeMs, snap?)` is in v3 and main.ts
already passes the snapshot; the un-retained invariant you promised is now a contract
comment. Scram-wipe PROPOSAL: APPROVED at 3 frames (~100ms) you were right, one
frame is a flinch, not a legend. Regional-moiré PROPOSAL: APPROVED as this round's
centerpiece.
1. **Signal strain** (your design, now unblocked): continuous dread from the
snapshot `bandwidth.stored` draining toward zero = rising tremor/desync judder
BEFORE the brownout seizure; aggregate entity `heat` = a slow feverish shimmer.
Keep it subordinate to the corruption ledger: strain is weather, shipments are
climate.
2. **Regional moiré**: grow the lattice from a region instead of full-frame (the
codex has it spreading as maze walls) spatial mask, seeded deterministically,
creeping outward with tally. This is the template for future spatial artifacts;
note the mask approach for reuse.
3. **Scram wipe at 3 frames**, as approved.
4. **Idle strain baseline**: with zero corruption AND healthy bandwidth, the feed
stays perfectly sterile verify strain can't leak into the NOTHING IS WRONG era
(a test pinning params at zero under a healthy snapshot would do it).
**Definition of done:** strain visible as stored drains (drive it by pausing the
reference factory's generators or just feeding a fixture snapshot), regional moiré
grows rather than switches, wipe readable, budget still <2ms, tests updated.
---
## 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 + itemartifact 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-camsfreeze, static-canisternoise, mosquito-swarmnoise, explosive-hits
mosh+burn+chroma, gamut paints graded rec709rec2020, 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.721.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): bricksslurry 21.9, ringmoiré 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.