From 83e3ad21072784cb0f885a3c6fa44df343f64cd1 Mon Sep 17 00:00:00 2001 From: type-two Date: Fri, 17 Jul 2026 19:43:48 +1000 Subject: [PATCH] [orchestrator] round 3 review + contracts v4 + composite seam codex + round 4 orders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review: 5/5 lanes pass, 238/238, tsc clean. Verified live: scram duty-cycling with tanks covering (zero brownout), shipment stamps, strain fever 0.824. Found 1 real bug in review: UI BROWNOUT banner fires on covered deficit (topstrip.ts:66) - ordered. Contracts v4: research (lab kind, setResearch, ResearchState, researched event), SelectionState protocol (kills __remove sentinel, main.ts owns remove dispatch), accent?, coolRadius? (spatial cooling), bandwidth.capacity?, save/load hardened, heat ruled GROSS (net = heat - cool). tsconfig now typechecks data/ (DATA's proof). Orchestrator patched lab entries into exhaustive kind maps (my breakage, my fix). Rulings: pathspec-commit rule after 3 shared-index collisions (worktrees = named escalation); bloom escalator reading accepted; UI ?uidemo + SCREEN boredom approved. Codex: docs/FKTRY_LORE.md grew §10 THE COMPOSITE SEAM - keying/transparency artifact family from the Corridor Crew compositing history + our corridorkey-mrp-mlx (which doubles as a PYXLFK ground-truth artifact factory). DATA transcribes it round 4. Co-Authored-By: Claude Fable 5 --- docs/FKTRY_LORE.md | 112 ++++++++++++++++++++++++++++++++++++ fktry/CONTRACTS.md | 13 +++++ fktry/MASTERPLAN.md | 10 ++++ fktry/lanes/LANE-DATA.md | 75 ++++++++++++------------ fktry/lanes/LANE-RENDER.md | 54 ++++++++--------- fktry/lanes/LANE-SCREEN.md | 52 +++++++++-------- fktry/lanes/LANE-SIM.md | 66 +++++++++++---------- fktry/lanes/LANE-UI.md | 64 +++++++++++---------- fktry/src/contracts.ts | 55 ++++++++++++++---- fktry/src/main.ts | 31 ++++++++-- fktry/src/render/palette.ts | 3 + fktry/src/ui/pages.ts | 1 + fktry/src/ui/palette.ts | 1 + fktry/tsconfig.json | 2 +- 14 files changed, 375 insertions(+), 164 deletions(-) diff --git a/docs/FKTRY_LORE.md b/docs/FKTRY_LORE.md index 39810ad..b874356 100644 --- a/docs/FKTRY_LORE.md +++ b/docs/FKTRY_LORE.md @@ -411,3 +411,115 @@ your win condition from "make it glitch" to "make it silent," or steal the KDM w (also you). - Every asset generated gets its element's codex name — this file is the manifest; check off against it. + +--- + +## 10. THE COMPOSITE SEAM — keying & transparency artifacts + +> Source study: Corridor Crew's history of compositing (Niko Pueringer) + our local +> `corridorkey-mrp-mlx` (MLX port of CorridorKey, runs in the MODELBEAST flow on the +> m3ultra). Thesis, and the section's whole physics: **you cannot film transparency.** +> Alpha does not exist in nature — every era of cinema is a different failed séance +> for summoning it. In the Bitstream this is literal: ALPHA is the one resource that +> cannot be mined from any seam. It can only be *computed*, and every computation +> before the last one lies. + +**Geology:** the Composite Seam is not a stratum — it is a vertical FAULT crossing +every era layer, glittering with half-transparent fossils. Mining near it yields +subjects fused to backgrounds that were never really there. + +### Resources & products + +**ALPHA** — *raw transparency, the unfilmable substance.* A shimmering nothing in a +frame; the rarest resource in the game. Cannot be extracted; only keyer machines emit +it, each era's keyer emitting a corrupted grade. +`ASSET (3D + shader): a glass ingot that renders as a checkerboard-transparency hole in the world itself, faint rainbow rim, pedestal casting a shadow the ingot doesn't` + +**HARD MATTE** — *reel-era binary alpha.* Silver-halide matte slabs: every pixel all +or nothing. Razor edges, zero feather; anything soft (hair, smoke, glass) that touches +one gets guillotined to a hard silhouette. Cheap, brutal, in demand for "woodcut" looks. +`ASSET (3D): stack of black-and-white film mattes like x-ray plates, silhouettes with surgically hard edges, chemical stain rims, one plate mid-slice through a wisp of smoke` + +**BLUE HOLE** — *optical-printer costume dropout.* A patch of SUBJECT gone transparent +because it dared to be blue. Sold as canned absence; installing one in a product makes +part of it see-through in a way that reads as a wound. +`ASSET (3D): a mannequin torso with a clean-edged void where the chest was, world visible through it, faint blue glow licking the hole's rim` + +**FRINGE LACE** — *matte lines, bottled.* The bright halo ribbon where layers of film +were re-exposed slightly misaligned. Harvested off composite edges like dried glue. +`ASSET (3D): coils of luminous ribbon, each strand a hard bright outline the exact shape of some absent object's edge, wound on an industrial spool` + +**GREEN SPILL** — *chroma contamination.* Cousin fluid to CHROMA SLURRY but nastier: +it doesn't tint what it touches, it makes it PARTIALLY DISAPPEAR (the keyer downstream +eats whatever it stains). Leaks from every chroma keyer; the OSHA posters are explicit. +`ASSET (3D): dripping acid-green fluid whose puddles render as半 half-vanished floor, hazmat berm around a keyer machine, splashes eating a crate mid-vanish` + +**MIXED PIXEL** — *the purple gel: an unmixable blend.* Foreground and background +fused into one pixel that belongs to neither — the red gel that filmed purple. Classic +keyers cannot separate it; the neural keyer eats it for breakfast. +`ASSET (3D): a jar of pixels each half one color half another, split diagonally, labeled RED (DISPUTED)` + +**ROTO CHATTER** — *boiling edges.* Frame-inconsistent cutout outlines, jittering like +they're being redrawn 24 times a second by a tired artist — because they are. A live +hazard that infests composite edges; trapped and canned, it sells as nervous energy. +`ASSET (VFX + 3D can): an outline that never sits still, vibrating polyline crawling around a silhouette; the can has a shaking label` + +**SODIUM LIGHT (589nm)** — *the grail wavelength.* Disney's sodium-vapor trick: +mathematically perfect mattes, hair and glass and gossamer intact — locked behind an +exclusive prism only one company ever owned. In the Bitstream, The Correction hoards +the prisms (perfection is their department). A single beam is an endgame reagent. +`ASSET (3D): a sealed lantern emitting one impossibly pure amber-orange line of light, wavelength etched on the housing, white-glove handling cradle, Correction seal` + +**GHOST FEATHER** — *semi-transparency done RIGHT.* Hair strands, motion blur, glass — +alpha with gradients. The proof-of-mastery product; every era below the neural keyer +ruins it, so demand never dies. +`ASSET (3D): a single enormous feather fading from fully solid quill to truly transparent tip, the transition agonizingly smooth, displayed under museum glass` + +### Machines + +**MATTE BATH** *(reel era)* — chemical tank that binarizes anything: footage in, hard +mattes out, all softness destroyed (a GHOST FEATHER fed in comes out a HARD MATTE and +the machine does not apologize). One mistake ruins the whole reel — jams eat inventory. +`ASSET (3D): open chemical bath with film strips on rollers dipping through, developer-red darkroom lamp, dissolving softness visibly leaving the film as rising fog` + +**OPTICAL PRINTER** *(the iron lung)* — a cathedral of bellows and lenses that splits +footage into R/G/B channel reels and recombines them slightly wrong. Produces +composites + FRINGE LACE always + BLUE HOLES whenever fed anything blue. Huge +footprint, gorgeous, mechanical; the codex's most machine-ass machine. +`ASSET (3D, hero): room-sized brass-and-iron optical printer, three film paths in primary-color light, bellows lungs mid-breath, a fringe of glowing misalignment haloing its output gate` + +**CHROMA KEYER** *(stream era)* — digital green screen box: cheap, fast, 30-year-old +math. Emits ALPHA (consumer grade) + GREEN SPILL, chokes on hair (GHOST FEATHER input +→ ROTO CHATTER hazard spawn), turns semi-transparent inputs into MIXED PIXELS. +`ASSET (3D): studio-green cube with a subject-shaped hole punched clean through, green fluid weeping from its seams, sample swatches of failed hair taped to its side` + +**ROTO BENCH** — *the grind made flesh.* Manual-labor station that converts ANY +footage to cutouts — perfectly, eventually — at an atrocious rate (24 frames of pain +per second of footage). The economic floor under every keyer: when automation fails, +the bench never does, it just costs your life. Emits ROTO CHATTER while running. +`ASSET (3D): a hunched drafting desk with a glowing frame-by-frame lightbox, mountain of finished frames on one side, coffee rings fossilized into the wood, a small flickering outline hovering over the current frame` + +**NEURAL KEYER** *(endgame)* — the CorridorKey machine: trained on ten thousand +synthetic ghosts because reality couldn't teach it (you can't film the answer). Pulls +perfect ALPHA through glass, chainmail, motion-blurred hair; UNMIXES pixels (MIXED +PIXEL → original colors, recovered); no spill, no fringe, no chatter. The catch is +pure hardware: it draws 24 VRAM — a bandwidth appetite that dwarfs everything else in +the game. You don't unlock the neural keyer; you unlock the power plant it demands. +`ASSET (3D, hero): a monolithic GPU shrine, fans like cathedral rose windows, cables thick as anacondas, projecting a flawless translucent figure above itself; power meter needle buried in the red` + +### Hazards + +**SPILL FOG** — green contamination cloud around leaky keyers; products crossing it +gain partial transparency (defect or feature, depending on the commission). +**EDGE BOIL** — roto chatter infestation: outlines in the area start crawling; +machines misread their own footprints and mis-deliver until it's trapped. + +### PYXLFK / MODELBEAST production note + +`corridorkey-mrp-mlx` (gitea.partly.party/monster/corridorkey-mrp-mlx, deployed in the +MODELBEAST flow on the m3ultra) is a REAL artifact factory for this whole section: +run the same footage through (a) classic chroma-subtraction math and (b) the neural +keyer — the difference image is a pixel-perfect, ground-truth-labeled map of spill, +fringe, and mixed pixels. Degrading the alpha hint synthesizes roto chatter and hard- +matte binarization with known parameters. Every artifact above is bakeable with a +recorded recipe, which is the PYXLFK contract: corruption in, sidecar out. diff --git a/fktry/CONTRACTS.md b/fktry/CONTRACTS.md index bd70ac9..61f55f8 100644 --- a/fktry/CONTRACTS.md +++ b/fktry/CONTRACTS.md @@ -55,3 +55,16 @@ data/*.json ──▶ main.ts loads GameData Written in your lane NOTES as `CONTRACT REQUEST: `. The orchestrator batches them between rounds. Until granted, work around locally inside your lane. + +## v4 notes + +- `snapshot.commissionQueue` may alias a live sim array — read-only, never retain + across frames (same rule as the snapshot itself, stated here because it looks like + a plain array). +- Selection protocol: UI writes `bus.setSelection(...)`; main.ts derives ghost mode + and dispatches `place`/`remove` on click. UI must NOT dispatch `remove` itself. + The `'__remove'` def string is a deprecated bridge, deleted in v5. +- Heat: `heatPerTick` is GROSS; net climb = `heatPerTick − coolPerTick`. A machine + whose heatPerTick ≤ its coolPerTick can never overheat — the validator enforces it. +- Research gating: ids appearing in any `TechDef.unlocks` are locked until researched; + ids referenced by no tech are always available. diff --git a/fktry/MASTERPLAN.md b/fktry/MASTERPLAN.md index 45b8127..5b30114 100644 --- a/fktry/MASTERPLAN.md +++ b/fktry/MASTERPLAN.md @@ -37,6 +37,16 @@ codex wins. 5. Commit with your lane prefix: `[sim] belt merge logic + tests`. One or a few focused commits, not one mega-commit. Push. + **COMMIT HYGIENE (ruled after three round-3 collisions):** five sessions share ONE + working tree and ONE git index. `git add -A`, `git add .`, and `git commit -a` are + BANNED — they commit whatever other lanes have staged at that instant. Every commit + uses an explicit pathspec so the index state of other lanes cannot leak in: + `git commit -m "[sim] ..." -- fktry/src/sim fktry/lanes/LANE-SIM.md` + (a pathspec commit ignores the staging area for everything outside the paths). + Before pushing, `git show --stat HEAD` — if it lists files outside your lane, + `git reset --soft HEAD~1` and recommit correctly. If collisions still happen, + the next escalation is a git worktree per lane. + ## Hard rules - **Never edit**: `MASTERPLAN.md`, `CONTRACTS.md`, `src/contracts.ts`, `src/main.ts`, diff --git a/fktry/lanes/LANE-DATA.md b/fktry/lanes/LANE-DATA.md index 5fb3642..31ed54e 100644 --- a/fktry/lanes/LANE-DATA.md +++ b/fktry/lanes/LANE-DATA.md @@ -25,47 +25,46 @@ REQUESTs. --- -## CURRENT ORDERS — Round 3 (goal: heat that matters, deeper economy) +## CURRENT ORDERS — Round 4 (goal: THE COMPOSITE SEAM + research economy) -Round 2 review: the bloom-concentrate grep-before-delete and the fault-injection -discipline are exactly the standard. Both your cross-lane asks are RULED: -**(1) Units: per-second.** `gen`/`draw` are per second, `stored` is -bandwidth-seconds; SIM converts their integration this round — your `bufferCap: 240` -= 8 seconds of a 30 deficit, as you intended. **(2) Cooling:** `coolPerTick?` granted -in v3 (sim default 0.004); SIM wires it. Your SIM's-test-is-red flag was correct -mid-round and self-resolved at HEAD (their landing fixed it) — the stash-and-rerun -diagnostic was the right move. Ownership RULING: `data-validate.test.ts` moves to -`data/validate.test.ts` (git mv, yours — vitest picks it up there; `src/sim/**` -returns fully to SIM). +Round 3 review: re-introducing round 2's exact bug to prove the validator catches it +is the gold standard — and your "decorative number" lesson is now a contract comment +AND your validator's job forever (heat ruling: gross, net = heat − cool). Verdicts: +tsconfig FIXED (`include: ["src", "data"]` — your poisoned-file proof was conclusive). +`accent?` and `coolRadius?` are in v4. SIM exports tuning constants this round — +retire your hand-copies. The stco/keyring recipe-assignment logic ("made by the +machine that already owns that knowledge") is exactly how future inventions should +argue. -1. **The validator move** above, first, so the tree is clean. -2. **Heat retune, the headline**: today NOTHING can overheat — every `heatPerTick` - you authored is below the 0.004 ambient cooling, so heat is decorative. With - `coolPerTick` yours to set per machine: hit SIM's suggested feel — software - decoder throttles after ~10s of continuous load, scrams ~13-15s, full duty cycle - ~25s; ASIC stays incapable of heat. Show the arithmetic in CANON-DELTA (per-tick - vs per-second — mind the units ruling). Give the hex-splicer a lesser fever. - Coordinate with SIM's reference-factory v3 via NOTES — your numbers decide - whether their layout survives. -3. **Buffer economy check** under the units ruling: is 240 (8s) still your intent - now that it's true? One tank should cover one decoder scram — verify against your - retuned cycle, adjust, show working. -4. **ASIC COOLER spec'd** in CANON-DELTA (the codex names it, §2 "vented by ASIC - coolers"): item/machine entry + intended adjacency mechanic, data authored but - recipes gated behind a `broadcast`-era tech node — the MECHANIC stays deferred - until SIM builds auras (M4); the data existing first is fine and the validator's - FORWARD_CANON pattern covers it. -5. **Reel + broadcast era tech nodes**: research costs in analog packs; unlocks - field-loom/telecine recipe families you already authored. -6. **Muxer + moov brain sub-lines** (§7): `stsz-ledger`, `stco-map`, `stts-metronome`, - `stss-keyring` items + assembly recipes converging on a `moov-brain` → - `mux-container` endgame recipe on the muxer. This is M4's rocket — data lands - first, mechanics later. Muxer machine entry if you don't have one. +1. **THE COMPOSITE SEAM — the codex grew §10** (docs/FKTRY_LORE.md, read it first): + keying/transparency artifacts, from "you cannot film transparency". Transcribe it: + ALPHA (the unfilmable resource — only keyer machines emit it), HARD MATTE, BLUE + HOLE, FRINGE LACE, GREEN SPILL, MIXED PIXEL, ROTO CHATTER, GHOST FEATHER, SODIUM + LIGHT (589nm, Correction-hoarded reagent — forward canon until M4); machines + MATTE BATH (reel), OPTICAL PRINTER (the iron lung — make its ratios hurt), CHROMA + KEYER (stream; leaks green spill, wrecks feathers into chatter), ROTO BENCH (the + grind: converts anything, atrociously), NEURAL KEYER (endgame: perfect, and its + powerDraw should be obscene — the codex says its appetite dwarfs everything; + research-gate it behind a fortress-adjacent tech). Era-gate the family through + the tech tree; validator covers everything; CANON-DELTA logs deviations. +2. **Research economy**: author the lab machine (`kind:'lab'` in v4 — name it in + codex voice, it's yours to canonize in CANON-DELTA) and audit all 24+ tech costs + now that they'll actually be paid: pack production chains must exist and the + early nodes must be affordable off a first-taste-era factory. Show one worked + cost path (packs/minute vs research demand) in CANON-DELTA. +3. **ASIC COOLER activation**: SIM lands `coolRadius` auras this round — give the + cooler its radius + numbers (your spec'd adjacency intent), and make the software + decoder + cooler pairing the worked example (duty cycle with vs without). +4. **Accent fills**: `accent?` where the first-output derivation is wrong — belts, + buffer tank, uplink, lab, coolers; leave it unset where the derivation is already + right (that's the design, not laziness). +5. **Fixture/validator updates**: v4 fields (`accent`, `coolRadius`), the gross-heat + rule you already enforce stays, `commissionQueue` v5-hardening — any fixtures you + own carry it. -**Definition of done:** validator green from its new home; a software decoder -demonstrably overheats in SIM's tests using YOUR numbers (coordinate — their DoD -depends on your retune); CANON-DELTA carries the arithmetic; FORWARD_CANON pruned of -anything that gained a recipe. +**Definition of done:** Composite Seam fully transcribed and reachable through era +tech (validator green, FORWARD_CANON updated); a research path is affordable and +proven against SIM's real mechanic; cooler pairing arithmetic in CANON-DELTA. --- diff --git a/fktry/lanes/LANE-RENDER.md b/fktry/lanes/LANE-RENDER.md index 8a3ebb7..782ef93 100644 --- a/fktry/lanes/LANE-RENDER.md +++ b/fktry/lanes/LANE-RENDER.md @@ -25,35 +25,37 @@ you never simulate. If motion looks wrong, interpolate better or note it — don --- -## CURRENT ORDERS — Round 3 (goal: truth-driven states + asset intake readiness) +## CURRENT ORDERS — Round 4 (goal: v4 protocol, research & aura visuals) -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. +Round 3 review: the dead-debug-hook bundle grep and the provenance note were exemplary; +your worktree analysis drove the new MASTERPLAN commit rule (pathspec commits — your +option 2, with worktrees as the named escalation). Verdicts: CONTRACT REQUEST 1 +GRANTED — v4 has the typed selection protocol (`SelectionState`, `setGhost(..., mode)`) +and main.ts already passes `mode` on every call (the `'__remove'` def string still +arrives as a bridge; it dies in v5). `MachineDef.accent?` also landed (your round-2 +wish, UI re-filed it). Era skins verified beautiful. -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. +1. **Ghost migrates to `mode`**: key demolition tint off the 4th arg, ignore the + sentinel def. Keep rendering exactly as shipped otherwise. +2. **Locked-machine ghost** (research lands this round): a `place` for a locked id is + silently dropped by the sim — the ghost must telegraph it. Distinct from + occupied-red: padlock-amber wash + "era" tell if you can read the tech era from + data cheaply. Coordinate the lock lookup with UI via NOTES (both of you derive it + from `snapshot.research.unlocked` + tech.json — same rule, CONTRACTS v4 notes). +3. **Cooler auras**: `coolRadius` machines get a floor-ring (subtle, engineering- + drawing vibe, not a buff circle) on hover/ghost AND while placed-and-selected; + heat-shimmer suppression inside the ring falls out of sim truth automatically. +4. **Lab visuals**: placeholder with a distinct silhouette (research bench — codex + voice pending DATA's machine entry); `accent?` consumed when present (your + derivation stays fallback). +5. **Housekeeping approved from your NEXT list**: splitter lamp can stay timer-driven + (real round-robin state isn't in the snapshot and doesn't earn a contract field + yet); showroom item-hall paging when DATA's catalog next grows (Composite Seam + transcription lands ~15 items this round — it will). -**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. +**Definition of done:** remove-tint works with the mode arg (verified with UI's +migrated flow); a locked machine's ghost is legibly "not yet" rather than "broken"; +cooler ring reads at a glance; showroom absorbs the new catalog. --- diff --git a/fktry/lanes/LANE-SCREEN.md b/fktry/lanes/LANE-SCREEN.md index a48898b..a5139a7 100644 --- a/fktry/lanes/LANE-SCREEN.md +++ b/fktry/lanes/LANE-SCREEN.md @@ -27,33 +27,37 @@ seed stock — port, don't move). --- -## CURRENT ORDERS — Round 3 (goal: continuous dread + spatial artifacts) +## CURRENT ORDERS — Round 4 (goal: second dread axis, boredom, ratification) -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. +Round 3 review: measuring tremor in *seconds-until-dark* — and getting scale- +invariance for free from the units ruling — is the single most elegant thing in this +codebase. The negative-dt landmine catch is exactly why the NOTES exist. Verdicts: +your capacity PROPOSAL is GRANTED unasked — `bandwidth.capacity?` is in v4 (SIM sums +`bufferCap`); your "reserve low vs falling fast" second reading is this round's order. +Your stalled-factory idea is APPROVED as design. The strain/fever/seizure hierarchy is +canon now — keep future readings inside it. -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). +1. **Two dreads**: with `capacity` available, split the readings — "seconds left" + (tremor, as shipped) vs "reserve fraction" (a low-tank pallor/vignette that sits + there even when draw ≤ gen and nothing is draining). A full tank draining fast vs + a near-empty tank coasting should FEEL different; write the one-line design intent + for each in NOTES. +2. **Boredom** (your proposal): no `shipped` events for a sustained stretch → total + corruption eases back toward clean, slowly, visibly — the factory's work + literally evaporating off THE SCREEN. Escalation tallies stay (history isn't + erased; the *display* decays), so production resuming snaps it back fast. Tune + the decay horizon so a normal jam doesn't read as punishment (minutes, not + seconds). Test it. +3. **Ratification interruption**: `researched` event → a one-second full-screen + interruption card on the feed — sterile white, "STANDARD RATIFIED", era name, + then the corruption crashes back in. The Correction briefly wins the sky every + time the player does science. Deadpan, readable, cheap. +4. **Fixture hygiene**: `commissionQueue` hardens to required in v5 — update your + fixture snapshots this round. -**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. +**Definition of done:** both dread axes verified at the pixel level (your contact- +sheet method); boredom decays and snaps back under test; ratification card exactly +N frames with era text; budget still <2ms. --- diff --git a/fktry/lanes/LANE-SIM.md b/fktry/lanes/LANE-SIM.md index 7257693..6138eae 100644 --- a/fktry/lanes/LANE-SIM.md +++ b/fktry/lanes/LANE-SIM.md @@ -21,39 +21,45 @@ playable through unit tests. You are the lane the other four trust with their li --- -## CURRENT ORDERS — Round 3 (goal: M2 "PRESSURE" becomes real, M3 groundwork) +## CURRENT ORDERS — Round 4 (goal: M3 "DEPTH" — research, spatial heat) -Round 2 review: the reference factory turning round 1's deadlock into 1,136 shipped -bricks is exactly what "surplus becomes product" was supposed to mean — outstanding. -Verdicts: `coolPerTick` GRANTED (contracts v3, default 0.004 — your constant becomes -the fallback). `setRecipeAt` GRANTED (your tripwire, defused). `BeltItem.id` HARDENED -to required (you asked for it). Throttle-hits-powerGen: KEPT, your reading was right. -The validator-file ownership wart: RULED — it moves to `data/validate.test.ts`, DATA -executes the move, `src/sim/**` is purely yours again. +Round 3 review: the worst-case power sizing ("a test that passes on a coincidence is +worse than one that fails") and the counter-current bloom discovery are the two best +engineering notes anyone has filed. Verdicts: **heat semantics RULED as you asked** — +gross, net = heatPerTick − coolPerTick, now a contract comment and a DATA validator +rule. **Bloom loop: the escalator reading is ACCEPTED** — your wafer-bus circle IS the +mechanic; the codex line stays as lore-poetry. **Git: pathspec commits are now a hard +rule in MASTERPLAN** (your option, chosen over worktrees for now — worktrees are the +escalation if it recurs). save/load hardened in v4 as you offered. commissionQueue +aliasing documented in CONTRACTS. Your perf-noise honesty is noted — a headless +benchmark is a good M4 item, not now. -**UNITS RULING (DATA's cross-lane ask):** `gen`/`draw` are bandwidth per SECOND; -`stored` is bandwidth-seconds. Your integration currently charges/drains per tick — -divide by `TICKS_PER_SECOND` so a 30-deficit drains 30 stored per second, not per -tick. DATA's `bufferCap: 240` then covers 8s as they intended. Update tests; confirm -the conversion in NOTES so DATA can retune against truth. +1. **Research v1 — the round's centerpiece.** Contracts v4: `kind:'lab'`, + `{kind:'setResearch'}`, `snapshot.research` (`ResearchState`), `researched` event. + Labs consume science packs from their input toward the active tech's `cost`; + totals met → `researched`, unlocks apply. Gating rule (in CONTRACTS v4 notes): ids + appearing in any `TechDef.unlocks` are locked — `place`/`setRecipe`/`setRecipeAt` + for locked ids are silently dropped; everything unreferenced is free. Multiple + labs stack. save/load carries research. Tests incl. a full unlock path against + DATA's real tech.json. +2. **Spatial cooling** — `coolRadius` (v4): a machine with it applies its + `coolPerTick` to machines within chebyshev range of its footprint (stacking = sum, + cap total cooling at your call, note it). The ASIC COOLER finally means something; + DATA activates numbers in coordination. +3. **Export your tuning constants** (`src/sim/constants.ts`: throttle/restart + thresholds, default cooling, etc.) — DATA's tests currently hand-copy them and go + stale silently; let them import truth. +4. **Reference factory v4**: add an anchor-slab overflow tap → uplink so the demo + ships slabs (UI's SANITISING + the buyback fiction become visible in `__fktryDemo` + without surgery — they had to demolish your assembler to see it). Keep the 20k + regression green; a lab + a research run in the reference build would be ideal + but is optional if layout pressure fights you. +5. **Fixture hygiene**: `commissionQueue` hardens to required in v5 — make sure your + snapshot literal always carries it (you already publish it live). -1. **Contracts v3 wiring**: `EntityState.scrammed` (RENDER is currently duplicating - your 0.5 constant to avoid rendering a lie — kill that), `commissionQueue` on the - snapshot (active first, then upcoming — UI's NEXT IN TRAY is guessing today), - `coolPerTick` per machine, `setRecipeAt` command, the units conversion above. -2. **Save/load**: implement `save()`/`load()` — JSON, deterministic round-trip - (test: save at tick N, load into a fresh sim, run both 1,000 ticks, deep-equal). -3. **Reference factory v3**: heat is about to become real (DATA retunes this round — - coordinate via NOTES). Restructure power as ASIC base + software-decoder burst + - buffer tank so the layout *survives scram duty-cycles* — DATA warned your three - naked decoders will black out in lockstep. Keep the 20k regression green. -4. **Bloom loop proof**: test that a closed belt circle through a bloom-duplicator - accumulates grades (your rank-0 loop acceptance + DATA's grade recipes should - compose). This is M3's signature topology — prove it early. - -**Definition of done:** all tests green including save/load round-trip and the bloom -loop; NOTES confirm the units conversion + the retuned reference factory's scram -survival (ticks of first scram, recovery, melt rate before/after). +**Definition of done:** research provable end-to-end in tests (place lab → feed packs +→ unlock → previously-dropped place now succeeds); cooler aura demonstrably extends a +decoder's duty cycle; constants exported; reference factory ships slabs. --- diff --git a/fktry/lanes/LANE-UI.md b/fktry/lanes/LANE-UI.md index 250c2ab..7a4b2a2 100644 --- a/fktry/lanes/LANE-UI.md +++ b/fktry/lanes/LANE-UI.md @@ -24,39 +24,43 @@ dispatch Commands via the bus and read state from `update(snap, events)`. --- -## CURRENT ORDERS — Round 3 (goal: close the interaction loop, verify the pressure UI live) +## CURRENT ORDERS — Round 4 (goal: research UI, one real bug, protocol migration) -Round 2 review: 57 tests against a 10 minimum, and factoring pure modules instead of -touching root config when my grant turned out to be vapor — model behavior. About -that: **happy-dom genuinely is installed now** (verified: `package.json` line 17, and -your 57 tests pass under `--environment happy-dom`). My round-2 "it's installed" was -wrong and your BLOCKED section caught it — good catch, keep doing that. -Verdicts: CONTRACT REQUEST 1 GRANTED — `SimSnapshot.commissionQueue` in v3, SIM wires -it this round. Request 2 was already landing as you wrote it (parallel-round race; -the queue advances at HEAD — I watched PATIO RESURFACING arrive live). REMOVE -PROPOSAL: APPROVED. +Round 3 review: `live.test.ts` — booting the real sim against the real DOM — is the +best verification asset any lane has produced; the SANITISING machine-swap showed +exactly the right instinct when an order's premise fails. Verdicts: CONTRACT REQUEST 1 +GRANTED (v4 `SelectionState` + `setSelection`/`selection` on the bus — main.ts now owns +the remove dispatch; see order 2). Request 2 GRANTED (`MachineDef.accent?` — your +derivation becomes the documented fallback, DATA fills the field where it's wrong). +`?uidemo` PROPOSAL APPROVED. Tech-panel skip was right at the time — but it was +another round-start grep going stale: `tech.json` has 24 nodes at HEAD (DATA landed +mid-round again). It's a real order now. -1. **REMOVE**: your design — suggested shape: X hotkey / bar button arms remove mode, - hover shows RENDER's demolition tint (they're building it this round — coordinate - via NOTES), click dispatches `remove`, Esc disarms. Toast in house voice - ("UNIT RECLAIMED. THE FLOOR REMEMBERS."). -2. **NEXT IN TRAY becomes truth**: read `snap.commissionQueue` when present, delete - `peekNext` inference the moment SIM lands it (keep fallback until then). -3. **DOM render tests**: the two you named as needing a real DOM (chip rendering, - `attach()` binding) plus fax stamp lifecycle — `// @vitest-environment happy-dom`. -4. **Live pressure verification** (now unblocked): `window.__fktryDemo()` builds - SIM's reference factory in one call — use it. Verify with your own eyes and - screenshot in NOTES: heat bar + THROTTLING + SCRAM on the software decoders once - DATA's retune lands, SANITISING chip on real anchor shipments, STANDING ORDER - stamp on a real repeat completion (I verified the stamp renders — you verify the - full lifecycle). -5. **Stretch, skippable**: read-only tech panel (`T`) rendering `tech.json` as an era - tree marked RESEARCH OFFLINE — research mechanics land in M3, the panel can exist - first. +1. **BUG, found live in review: the BROWNOUT banner lies.** `topstrip.ts:66` keys on + `out || ratio > 1` — during a covered deficit (tanks draining, sim `brownout: + false`, THE SCREEN correctly calm) the panel screams BROWNOUT. That state is + *survival*, not failure. Fix: BROWNOUT = the flag only; deficit-with-reserve gets + its own amber state (suggest "ON RESERVE · 34s" — `stored/(draw-gen)` is seconds + left, same math SCREEN uses for dread). Add a test pinning covered-deficit ≠ + brownout. +2. **Selection protocol migration**: write via `bus.setSelection(...)`, drop the + `REMOVE_DEF` sentinel export and — important — **delete your own `remove` + dispatch**: main.ts dispatches it on click now (double-fire today is harmless + only because the second one no-ops). +3. **Research UI, the centerpiece**: `T` opens the tech tree (24 nodes, 5 eras), + pick → `setResearch`; progress from `snapshot.research` (packs delivered vs + cost); locked machines in the build bar get greyed + padlock + "REQUIRES: + " tooltip (lock rule in CONTRACTS v4 notes); `researched` event → toast in + house voice ("STANDARD RATIFIED. NEW CRIMES AVAILABLE.") and the padlocks fall + off live. +4. **`?uidemo`** — as approved: boots `__fktryDemo()` after init. +5. **Accent adoption**: read `accent?` first, your derivation second, kind table + third (same precedence as RENDER — one sentence in NOTES confirming you both + read it identically). -**Definition of done:** a misplaced machine is mouse-removable; queue peek reads -truth; the three DOM tests exist; NOTES carry screenshots of heat/scram/SANITISING -observed live. +**Definition of done:** banner distinguishes RESERVE from BROWNOUT under test; a +full research run is verifiable in `live.test.ts` (lock visible → research → unlock +→ place succeeds); remove mode works through the typed protocol with RENDER's tint. --- diff --git a/fktry/src/contracts.ts b/fktry/src/contracts.ts index c5fd3a4..15a1a4d 100644 --- a/fktry/src/contracts.ts +++ b/fktry/src/contracts.ts @@ -38,7 +38,8 @@ export type MachineKind = | 'splitter' // 1 in, N out round-robin | 'power' // generates bandwidth | 'buffer' // stores bandwidth smoothness (brownout protection) - | 'shipper'; // consumes items => 'shipped' events => THE SCREEN + | 'shipper' // consumes items => 'shipped' events => THE SCREEN + | 'lab'; // v4: consumes science packs toward the active research export interface MachineDef { id: string; @@ -54,9 +55,15 @@ export interface MachineDef { color?: string; // v3 RULING: CHASSIS/BODY color (codex §8 grime); the emissive // accent stays derived from outputs. (Was mislabeled "accent".) flavor?: string; // v2: codex one-liner for the inspector (DATA's material) - heatPerTick?: number; // v2: heat while active (the software decoder's whole bit) + heatPerTick?: number; // v2: heat while active. v4 RULING: this is GROSS heat — net + // climb = heatPerTick − coolPerTick; a machine whose + // heatPerTick ≤ its coolPerTick can never overheat. bufferCap?: number; // v2: bandwidth storage in bandwidth-SECONDS (see SimSnapshot) coolPerTick?: number; // v3: per-machine cooling rate; sim default 0.004 + accent?: string; // v4: art-directed emissive accent; fallback stays derived + // from first output item (renderer + UI share the derivation) + coolRadius?: number; // v4: spatial cooling aura in tiles (chebyshev from footprint); + // this machine's coolPerTick also applies to machines in range } export interface TechDef { @@ -113,20 +120,32 @@ export interface SimSnapshot { beltItems: ReadonlyArray; // v3 RULING on units: gen/draw are bandwidth per SECOND; stored is bandwidth-seconds // (sim integrates surplus/deficit divided by TICKS_PER_SECOND each tick). - bandwidth: { gen: number; draw: number; stored: number; brownout: boolean }; + // v4: capacity = summed bufferCap of placed tanks (0 if none) — SCREEN's 2nd dread axis. + bandwidth: { gen: number; draw: number; stored: number; capacity?: number; brownout: boolean }; shippedTotal: Record; activeCommission: string | null; - commissionQueue?: string[]; // v3: truth for "next in tray" — active first, then - // upcoming in activation order (hardens in v4) + commissionQueue?: string[]; // v3: active first, then upcoming in activation + // order. May alias a live array — read-only, never + // retain. HARDENS IN v5: update fixtures this round. commissionProgress: Record; + research?: ResearchState; // v4: research progress (hardens in v5) } +export interface ResearchState { + active: string | null; // TechDef.id being researched + progress: Record; // packs delivered toward active (itemId -> n) + unlocked: string[]; // completed TechDef.ids +} +// v4 gating rule: a machine/recipe id that appears in ANY TechDef.unlocks is locked +// until that tech completes; ids referenced by no tech are always available. + export type Command = | { kind: 'place'; def: string; pos: Vec2; dir: Dir } | { kind: 'remove'; pos: Vec2 } | { kind: 'rotate'; pos: Vec2 } | { kind: 'setRecipe'; entity: number; recipe: string | null } | { kind: 'setRecipeAt'; pos: Vec2; recipe: string | null } // v3: id-free variant + | { kind: 'setResearch'; tech: string | null } // v4: pick the lab target | { kind: 'setPaused'; paused: boolean }; export type SimEvent = @@ -137,7 +156,8 @@ export type SimEvent = | { kind: 'placed'; entity: number; def: string; tick: number } | { kind: 'removed'; entity: number; def: string; tick: number } | { kind: 'commissionDone'; commission: string; tick: number } - | { kind: 'scram'; entity: number; on: boolean; tick: number }; // v2: heat shutdown edge + | { kind: 'scram'; entity: number; on: boolean; tick: number } // v2: heat shutdown edge + | { kind: 'researched'; tech: string; tick: number }; // v4: research complete // ---------------------------------------------------------------- module interfaces @@ -147,23 +167,38 @@ export interface Sim { tick(): void; // advance exactly one tick snapshot(): SimSnapshot; // read-only view; renderer/ui must not mutate drainEvents(): SimEvent[]; // events since last drain (main loop fans out) - save?(): string; // v3: full state as JSON, deterministic round-trip - load?(json: string): void; // v3: restore a save() blob (hardens in v4) + save(): string; // v4: HARDENED — full state, deterministic round-trip + load(json: string): void; // v4: HARDENED — restore a save() blob } +/** v4: the typed selection protocol — kills the '__remove' magic-string sentinel. */ +export type SelectionState = + | { mode: 'build'; def: string; dir: Dir } + | { mode: 'remove' } + | null; + export interface Renderer { init(container: HTMLElement, data: GameData): Promise; render(snap: SimSnapshot, alpha: number): void; // alpha: 0..1 interp between ticks pickTile(clientX: number, clientY: number): Vec2 | null; - setGhost(def: string | null, pos: Vec2 | null, dir: Dir): void; + /** v4: mode drives the ghost visual — 'build' = placement preview, 'remove' = + * demolition tint. def is null in remove mode. The '__remove' sentinel is deprecated + * and main.ts stops sending it in v5. */ + setGhost(def: string | null, pos: Vec2 | null, dir: Dir, mode?: 'build' | 'remove'): void; } export interface UIBus { dispatch(cmd: Command): void; - /** current build selection; renderer ghost + click-to-place read this */ + /** current build selection; renderer ghost + click-to-place read this. + * DEPRECATED in v4 (still served): use selection()/setSelection(). */ selectedBuild(): { def: string; dir: Dir } | null; /** v2: world picking, plumbed from the renderer — UI's click-to-inspect */ pickTile(clientX: number, clientY: number): Vec2 | null; + /** v4: typed selection. main.ts owns the state; UI writes via setSelection. + * In remove mode main.ts dispatches the `remove` command on click — the UI must + * NOT also dispatch it (that was the round-3 double-fire). */ + selection?(): SelectionState; + setSelection?(sel: SelectionState): void; } export interface UI { diff --git a/fktry/src/main.ts b/fktry/src/main.ts index 461a4e0..91c515d 100644 --- a/fktry/src/main.ts +++ b/fktry/src/main.ts @@ -2,7 +2,7 @@ * FKTRY main loop — ORCHESTRATOR-OWNED wiring. Lanes do not edit. * Fixed-timestep sim (30 tps) + interpolated render + event fan-out. */ -import { TICKS_PER_SECOND, type Command, type Dir, type GameData, type UIBus } from './contracts'; +import { TICKS_PER_SECOND, type Command, type Dir, type GameData, type SelectionState, type UIBus } from './contracts'; import { createSim } from './sim'; import { createRenderer } from './render'; import { createUI } from './ui'; @@ -21,11 +21,24 @@ const renderer = createRenderer(); const ui = createUI(); const screen = createScreenFX(); +// v4 bridge: _sel keeps the legacy {def,dir} shape (including the deprecated +// '__remove' sentinel) so unmigrated lanes keep working; selection()/setSelection() +// are the typed protocol. The sentinel path is deleted in v5. const bus: UIBus & { _sel: { def: string; dir: Dir } | null } = { _sel: null, dispatch: (cmd: Command) => sim.enqueue(cmd), selectedBuild: () => bus._sel, pickTile: (x, y) => renderer.pickTile(x, y), + selection: (): SelectionState => { + if (!bus._sel) return null; + if (bus._sel.def === '__remove') return { mode: 'remove' }; + return { mode: 'build', def: bus._sel.def, dir: bus._sel.dir }; + }, + setSelection: (sel: SelectionState) => { + bus._sel = sel === null ? null + : sel.mode === 'remove' ? { def: '__remove', dir: 0 as Dir } + : { def: sel.def, dir: sel.dir }; + }, }; // LANE-UI sets the build selection through this hook: (window as any).__fktryBus = bus; @@ -39,14 +52,22 @@ async function boot() { // click-to-place / hover ghost glue const game = document.getElementById('game')!; game.addEventListener('pointermove', (e) => { - const sel = bus.selectedBuild(); - renderer.setGhost(sel?.def ?? null, renderer.pickTile(e.clientX, e.clientY), sel?.dir ?? 0); + const sel = bus.selection!(); + const tile = renderer.pickTile(e.clientX, e.clientY); + if (sel?.mode === 'remove') { + // legacy def string kept alongside the mode until renderers migrate (v5 removes it) + renderer.setGhost('__remove', tile, 0, 'remove'); + } else { + renderer.setGhost(sel?.def ?? null, tile, sel?.dir ?? 0, 'build'); + } }); game.addEventListener('pointerdown', (e) => { if (e.button !== 0) return; // pan/context buttons never place - const sel = bus.selectedBuild(); + const sel = bus.selection!(); const tile = renderer.pickTile(e.clientX, e.clientY); - if (sel && tile) sim.enqueue({ kind: 'place', def: sel.def, pos: tile, dir: sel.dir }); + if (!sel || !tile) return; + if (sel.mode === 'remove') sim.enqueue({ kind: 'remove', pos: tile }); + else sim.enqueue({ kind: 'place', def: sel.def, pos: tile, dir: sel.dir }); }); const MS_PER_TICK = 1000 / TICKS_PER_SECOND; diff --git a/fktry/src/render/palette.ts b/fktry/src/render/palette.ts index d416c88..0c2c007 100644 --- a/fktry/src/render/palette.ts +++ b/fktry/src/render/palette.ts @@ -51,6 +51,7 @@ export const BODY_BY_KIND: Record = { power: 0x45434f, buffer: 0x3a3f48, shipper: 0x4a4652, + lab: 0x3d3a4e, // research bench chassis (v4 kind; placeholder until art-directed) }; /** Fallback accent when a machine runs no recipes (belts, power, shippers). */ @@ -62,6 +63,7 @@ export const ACCENT_BY_KIND: Record = { power: 0xffb03f, // serene LED face buffer: 0x7fffd4, shipper: 0x3fd4ff, // uplink to THE SCREEN + lab: 0xb0a0ff, // research glow (v4 kind; placeholder) }; /** Body height in world units. Silhouette differentiation on a flat iso grid. */ @@ -73,6 +75,7 @@ export const HEIGHT_BY_KIND: Record = { power: 0.95, buffer: 0.7, shipper: 1.3, + lab: 1.1, // v4 kind; placeholder }; export const JAM_AMBER = 0xffa726; diff --git a/fktry/src/ui/pages.ts b/fktry/src/ui/pages.ts index f95df90..bde3b05 100644 --- a/fktry/src/ui/pages.ts +++ b/fktry/src/ui/pages.ts @@ -23,6 +23,7 @@ const GROUP_OF: Record = { crafter: 'REFINE', power: 'POWER', shipper: 'SHIP', + lab: 'RESEARCH', // v4 kind }; const GROUP_ORDER = ['EXTRACT', 'LOGISTICS', 'REFINE', 'POWER', 'SHIP']; diff --git a/fktry/src/ui/palette.ts b/fktry/src/ui/palette.ts index d7e11cc..8ba5e40 100644 --- a/fktry/src/ui/palette.ts +++ b/fktry/src/ui/palette.ts @@ -38,6 +38,7 @@ const ACCENT_BY_KIND: Record = { power: '#3fffe0', buffer: '#7fb0ff', shipper: '#d8ffd8', + lab: '#b0a0ff', // v4 kind; placeholder }; /** Fallback chassis for machines DATA hasn't art-directed yet. */ diff --git a/fktry/tsconfig.json b/fktry/tsconfig.json index 89b0302..68634c4 100644 --- a/fktry/tsconfig.json +++ b/fktry/tsconfig.json @@ -11,5 +11,5 @@ "lib": ["ES2022", "DOM"], "types": ["vite/client"] }, - "include": ["src"] + "include": ["src", "data"] }