From 08761e8eeb500a219977ea5bdeee287a63b598a8 Mon Sep 17 00:00:00 2001 From: type-two Date: Fri, 17 Jul 2026 18:05:53 +1000 Subject: [PATCH] [orchestrator] round 2 review + contracts v3 + round 3 orders MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- fktry/MASTERPLAN.md | 4 +- fktry/lanes/LANE-DATA.md | 80 +++++++++++++------------- fktry/lanes/LANE-RENDER.md | 50 +++++++++-------- fktry/lanes/LANE-SCREEN.md | 51 ++++++++--------- fktry/lanes/LANE-SIM.md | 60 ++++++++++---------- fktry/lanes/LANE-UI.md | 53 ++++++++++-------- fktry/package-lock.json | 112 +++++++++++++++++++++++++++++++++++++ fktry/package.json | 1 + fktry/src/contracts.ts | 21 +++++-- fktry/src/main.ts | 12 +++- 10 files changed, 293 insertions(+), 151 deletions(-) diff --git a/fktry/MASTERPLAN.md b/fktry/MASTERPLAN.md index dbd7ae6..45b8127 100644 --- a/fktry/MASTERPLAN.md +++ b/fktry/MASTERPLAN.md @@ -59,7 +59,9 @@ codex wins. ## Milestones (orchestrator's map — lanes don't self-assign ahead) -- **M1 — FLOW** (rounds 1-2): the full canon melt chain — ore → demuxer → +- **M1 — FLOW** ✅ COMPLETE (round 2; verified live round-2 review: reference factory + ships melt, commission queue advances, SCREEN corrupts item-specifically): the full + canon melt chain — ore → demuxer → {quantizer, subsampler, p-caster, dct-press} → gop-assembler → mosh reactor → shipper. RULING (round 1): LANE-DATA's codex-faithful chain replaces the original 6-machine sketch; the ~50-entity build IS the fantasy. A deadlock-free reference diff --git a/fktry/lanes/LANE-DATA.md b/fktry/lanes/LANE-DATA.md index cceb87b..0272b61 100644 --- a/fktry/lanes/LANE-DATA.md +++ b/fktry/lanes/LANE-DATA.md @@ -9,9 +9,8 @@ codex wins; when the codex is silent, invent IN ITS VOICE and note what you inve validator that keeps five parallel lanes from drowning in dangling ids. Content should light up in-game with zero code changes from other lanes. -**Owned paths:** `data/**`, `src/sim/data-validate.test.ts` — EXCEPTION: this one test -file in sim's directory is yours; it validates data, not sim. Nothing else in -`src/sim/`. +**Owned paths:** `data/**` (the validator now lives at `data/validate.test.ts`; +the old `src/sim/data-validate.test.ts` exception is closed — round 3 moves it). **Never touch:** contracts, main.ts, other lanes' code. Schema changes are CONTRACT REQUESTs. @@ -26,46 +25,47 @@ REQUESTs. --- -## CURRENT ORDERS — Round 2 (goal: contracts-v2 content + M2 "PRESSURE" economy) +## CURRENT ORDERS — Round 3 (goal: heat that matters, deeper economy) -Round 1 review: the validator's fault-injection and the FORWARD_CANON allowlist were -exactly right. Verdicts on your report: **M1 IS your canon chain** — MASTERPLAN -updated, no shortcut recipe, the depth is the game. Your four inventions -(`press-anchor-slab`, `color-plate` + `v-hold-roll`, `artifact-bottler`, -`lane-splitter`) are **CANONIZED**. Dials-as-recipes STAYS (right call; a real dial is -a v3+ contract discussion). CONTRACT REQUEST GRANTED: `MachineDef.heatPerTick?` exists -now — and your validator should now also accept `color?`, `flavor?`, `bufferCap?`, -`CommissionDef.repeat?`. +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). -1. **`data/CANON-DELTA.md`** (new file, yours): ledger of everything invented beyond - the codex — round 1's four + whatever follows. The lore doc absorbs it in one - orchestrator pass later; until then this is the single place canon-drift lives. -2. **Art direction pass**: `color` on all 21 machines (renderer precedence: - your color > derived-from-outputs) and `flavor` on all 21 — codex one-liner each, - house voice. This kills UI's hardcoded lookup and RENDER's black-on-black problem. -3. **Heat economy**: `heatPerTick` on software-decoder, and scale its `powerGen` so - hot-but-mighty vs ASIC cool-and-steady is a real choice; a pinch of `heat` on - hex-splicer recipes. SIM implements throttle/scram this round — your numbers decide - whether anyone ever dares run two software decoders side by side. -4. **`bufferCap` on buffer-tank** — one tank should ride out roughly 10 seconds of - typical M1-chain deficit. Show your arithmetic in CANON-DELTA. -5. **Commission queue content** (SIM lands queue + `repeat` this round): reorder for - a difficulty ramp; add 2-3 `repeat:true` standing orders, including **THE - CORRECTION BUYBACK** — pays generous bandwidth for anchor-slabs, fax written in The - Correction's voice: polite, clean, quietly horrifying. This is the sellout loop - (ship your anchors for power now, watch your SCREEN go clean — SCREEN lane already - sanitizes on anchor shipments). -6. **Container line**: `moov-shard` / `offset-ribbon` / `sync-key` items + recipes - assembling `container-pack` (unblocks `stream-hex-splicing`); prune the - FORWARD_CANON allowlist as they gain recipes. -7. **Bloom line**: `bloom-grade-1..5` items + bloom-duplicator recipes - (grade n + delta-wafers → grade n+1); grade 5 is commission bait. Validator covers - all new content. +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. -**Definition of done:** validator green over the expanded catalog; every machine has -color + flavor; the queue tells a story from first-taste to the first buyback; SIM's -reference-factory test still passes against your rebalanced numbers (coordinate via -NOTES if a ratio change breaks their layout). +**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. --- diff --git a/fktry/lanes/LANE-RENDER.md b/fktry/lanes/LANE-RENDER.md index ec50d9d..8c07c29 100644 --- a/fktry/lanes/LANE-RENDER.md +++ b/fktry/lanes/LANE-RENDER.md @@ -25,33 +25,35 @@ you never simulate. If motion looks wrong, interpolate better or note it — don --- -## CURRENT ORDERS — Round 2 (goal: finish M1 polish, open M2 "PRESSURE") +## CURRENT ORDERS — Round 3 (goal: truth-driven states + asset intake readiness) -Round 1 review: excellent — the hot-swap verification and the fog/CAM_DIST honesty -note were exactly right. Verdicts: req 1 GRANTED (`BeltItem.id?` in contracts v2 — -use when present, keep order-matching as fallback until v3 hardens it). Req 2 GRANTED -(`MachineDef.color?` — precedence: `def.color` > your derived-from-outputs > -kind accent; your derivation stays as the fallback, it was the right instinct). -Req 3 DONE (main.ts ignores non-left buttons; drop your camera.ts workaround or keep -it as belt-and-braces, your call). +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. -1. **Belt topology visuals**: corner/junction pieces chosen from neighbor analysis — - pure presentation, sim truth unchanged. Splitters (mechanics land this round in - SIM) get a visible output-cycling tell. -2. **Heat**: shimmer + dull-red emissive ramp driven by `EntityState.heat`; throttled - = idle animation visibly slowed; scram = dead-dark + blinking amber until restart. -3. **Buffer tank**: visible fill level (shader window or scale trick) proportional to - the tank's share of `snapshot.bandwidth.stored`. -4. **AnimationMixer**: play clip 0 on rigged GLBs with an idle loop — MODELBEAST - assets are imminent; verify with any animated GLB you can find or make. -5. **Exact cargo interpolation** via `BeltItem.id` — delete the one-tick approximation. -6. **Multi-mesh belt GLBs**: merge-or-group fix (your call, note it) so a fancy belt - asset can't half-render. +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. -**Definition of done:** reference factory (SIM ships it this round — coordinate via -their `src/sim/reference.ts` export in a dev page or test harness) looks ALIVE: corners -read as corners, heat reads as heat, tanks read as tanks; perf standing rule still met -under `?devscene=stress`. +**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. --- diff --git a/fktry/lanes/LANE-SCREEN.md b/fktry/lanes/LANE-SCREEN.md index 59db0bf..da08453 100644 --- a/fktry/lanes/LANE-SCREEN.md +++ b/fktry/lanes/LANE-SCREEN.md @@ -27,36 +27,33 @@ seed stock — port, don't move). --- -## CURRENT ORDERS — Round 2 (goal: artifact fidelity, M2 "PRESSURE" hooks) +## CURRENT ORDERS — Round 3 (goal: continuous dread + spatial artifacts) -Round 1 review: the pixel-reading verification set the bar for the fleet. Verdicts: -your anchor-slab PROPOSAL — option (b) ADOPTED as M3 fiction (The Correction buyback; -DATA writes the standing order this round); LANE-UI surfaces a SANITISING chip -meanwhile; keep potency −2.0 as shipped. Debug hook: KEEP, gated behind -`import.meta.env.DEV`. The 2-red you flagged was mid-round churn — HEAD is 70/70. -The launch.json affair: resolved, per-lane ports are in MASTERPLAN now; you're on 8154. +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. **Artifact passes the 8 params can't express** (your list, adopted wholesale): - - legible 8×8 **macroblock quantization** pass — bricks/coeff overdrive must READ - as a blocky grid, not generic noise; - - edge-detected **ringing halos** (Gibbs oscillation hugging contrast edges); - - **moiré interference lattice**. - Wire signatures into corruptionMap: `macroblock-bricks`, `ringing-halos`, - `moire-crystal`. Keep the per-item legibility bar from round 1: shipping 40 bricks - should look UNMISTAKABLY different from 40 slurry. -2. **Shipment lands**: a ~0.4s stamp/silhouette flash in the item's chip color - composited on the feed per `shipped` event (batch bursts: one stamp per event, not - per unit — a 40-unit shipment is one big stamp, not 40 flickers). -3. **Scram wipe**: `scram` events (new in contracts v2) get a one-frame white - "TRACKING LOST" bar wipe. Cheap, from the event stream you already have. -4. **OPTIONAL — signal strain**: buffer tanks land this round; if you want THE SCREEN - to tremble as `bandwidth.stored` drains BEFORE brownout, file the snapshot-access - CONTRACT REQUEST you deferred (`frame(timeMs, snap?)`) and build it next round — - or skip it. Your call; the seam is noted and I'll grant it in v3 if asked. +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:** three new artifact classes verified the way you did round 1 -(drive, read pixels, screenshot); stamps and scram wipe visible on the dev build; -frame budget still <2ms; tests updated for new signatures. +**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. --- diff --git a/fktry/lanes/LANE-SIM.md b/fktry/lanes/LANE-SIM.md index ea09f18..634b941 100644 --- a/fktry/lanes/LANE-SIM.md +++ b/fktry/lanes/LANE-SIM.md @@ -21,39 +21,39 @@ playable through unit tests. You are the lane the other four trust with their li --- -## CURRENT ORDERS — Round 2 (goal: finish M1, open M2 "PRESSURE") +## CURRENT ORDERS — Round 3 (goal: M2 "PRESSURE" becomes real, M3 groundwork) -Round 1 review: outstanding. Verdicts on your report — **rank-routing STAYS** (it made -M1 flow; your proposal 2, auto-fallback, is REJECTED for exactly the reason you gave: -it deletes the byproduct tension). Proposal 3 (starved) APPROVED. Contracts v2 landed -for you: `BeltItem.id?`, `CommissionDef.repeat?`, `MachineDef.heatPerTick?/bufferCap?`, -`SimEvent 'scram'`. Extractor seam tiles: deferred to M3 worldgen, stop worrying. +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. -1. **Reference factory.** `src/sim/reference.ts` exporting - `referenceFactory(data: GameData): Command[]` — your deadlock-free M1 layout - (second quantizer on crime, second assembler on i-only). Test: ships melt within - 2,000 ticks AND is still shipping at 20,000 (the deadlock regression net). This - becomes the official demo; I wire it to a dev hook in main.ts next round. -2. **Splitters.** `kind:'splitter'` becomes real: 1 input, round-robin across outward- - facing adjacent belts; a blocked output is skipped, not waited on; your rank cache - traces THROUGH splitters (rank = best reachable downstream). This is the sanctioned - surplus-voiding tool — the rank rule elsewhere is unchanged. -3. **Buffer tanks.** `kind:'buffer'` + `bufferCap`: charge when gen>draw, discharge to - cover deficit, brownout only when tanks run dry; `snapshot.bandwidth.stored` = pool. -4. **Heat v1.** Accumulate `heatPerTick` while active + `RecipeDef.heat` per craft; - ambient dissipation; heat>0.7 → per-machine throttle (curve is your call, note it); - heat=1 → scram: machine stops, emits `{kind:'scram',on:true}`, restarts below 0.5 - with `on:false`. `EntityState.heat` is already in the contract — make it real. -5. **Commission queue.** Data-order activation; `commissionDone` advances; `repeat:true` - re-enters at the back of the queue. Progress counts only while active (keep). -6. **`jammed:'starved'`** — recipe idle with empty inputs, edge-triggered like - 'output full'. -7. **`BeltItem.id`** — stable per-item ids in every snapshot (optional field now; - hardens to required in v3, so just always set it). +**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. -**Definition of done:** all of the above tested (determinism suite still green); -reference factory test proves melt at scale with zero deadlock; heat demonstrably -throttles and scrams the software decoder in a test. +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). --- diff --git a/fktry/lanes/LANE-UI.md b/fktry/lanes/LANE-UI.md index 0e287c3..7de5861 100644 --- a/fktry/lanes/LANE-UI.md +++ b/fktry/lanes/LANE-UI.md @@ -24,32 +24,39 @@ dispatch Commands via the bus and read state from `update(snap, events)`. --- -## CURRENT ORDERS — Round 2 (goal: finish M1 interaction, open M2 "PRESSURE") +## CURRENT ORDERS — Round 3 (goal: close the interaction loop, verify the pressure UI live) -Round 1 review: strong, and the honesty section was the best in the fleet. Verdicts: -CONTRACT REQUEST 1 GRANTED — `UIBus.pickTile` is live, wired to the renderer in -main.ts. Req 2 GRANTED (`MachineDef.color?`). Req 3 GRANTED as `MachineDef.flavor?` — -LANE-DATA fills it this round; read data first, keep voice.ts as fallback. Req 4 -GRANTED — `happy-dom` is installed; use `// @vitest-environment happy-dom` per test -file (no root config change needed). Build-bar paging PROPOSAL: APPROVED. +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. -1. **Click-to-inspect** via `bus.pickTile`. Delete the TAB-cycle fallback and - `?uifixture` — the sim is real now; drive a brownout by building. -2. **Build bar paging** (your design): kind tabs, `[`/`]` to page, 1-9 within the - active page — every machine hotkey-reachable at any catalog size. -3. **Inspector upgrades**: recipe picker (`setRecipe` finally gets its UI), heat bar - with THROTTLING/SCRAM states, and starved-vs-backed-up wording (sim distinguishes - `'starved'` from `'output full'` this round). -4. **Commission queue UI**: current fax + a "NEXT IN TRAY" peek; `repeat:true` - commissions get a STANDING ORDER stamp. When anchor-slabs ship, show a SANITISING - chip (hardcoding the id is fine this round — the fiction becomes The Correction's - buyback in M3). -5. **Tests, minimum 10**: chips, roster/footprint hit-testing, selection, hotkey map — - under happy-dom. +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. -**Definition of done:** the reference factory (SIM ships `src/sim/reference.ts` this -round) is inspectable end to end by mouse alone; every machine reachable by hotkey; -tests green; fixture deleted. +**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. --- diff --git a/fktry/package-lock.json b/fktry/package-lock.json index e4b9db7..1c9a2ce 100644 --- a/fktry/package-lock.json +++ b/fktry/package-lock.json @@ -12,6 +12,7 @@ }, "devDependencies": { "@types/three": "^0.170.0", + "happy-dom": "^20.10.6", "typescript": "^5.6.0", "vite": "^6.0.0", "vitest": "^2.1.0" @@ -869,6 +870,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "26.1.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz", + "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.3.0" + } + }, "node_modules/@types/stats.js": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", @@ -898,6 +909,23 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitest/expect": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", @@ -1001,6 +1029,19 @@ "node": ">=12" } }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -1066,6 +1107,19 @@ "node": ">=6" } }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-module-lexer": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", @@ -1175,6 +1229,25 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/happy-dom": { + "version": "20.10.6", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.10.6.tgz", + "integrity": "sha512-6QD0ilzDDt93tX44y8tbmZdAcdTRYDhUP+Asgi6pC8Pp5IA3cvaZGyoVN/EGtlq9ziT65iPuBBn3ASLr6hCgVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/loupe": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", @@ -1448,6 +1521,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", @@ -2619,6 +2699,16 @@ } } }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -2635,6 +2725,28 @@ "engines": { "node": ">=8" } + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } } } diff --git a/fktry/package.json b/fktry/package.json index a835866..cb84e12 100644 --- a/fktry/package.json +++ b/fktry/package.json @@ -14,6 +14,7 @@ }, "devDependencies": { "@types/three": "^0.170.0", + "happy-dom": "^20.10.6", "typescript": "^5.6.0", "vite": "^6.0.0", "vitest": "^2.1.0" diff --git a/fktry/src/contracts.ts b/fktry/src/contracts.ts index eab9586..c5fd3a4 100644 --- a/fktry/src/contracts.ts +++ b/fktry/src/contracts.ts @@ -51,10 +51,12 @@ export interface MachineDef { powerGen?: number; beltSpeed?: number; // tiles/sec, kind==='belt' asset: string; // codex asset key for renderer mesh registry, e.g. "quantizer" - color?: string; // v2: art-direction accent; renderer falls back to derived + 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) - bufferCap?: number; // v2: bandwidth storage, kind==='buffer' + bufferCap?: number; // v2: bandwidth storage in bandwidth-SECONDS (see SimSnapshot) + coolPerTick?: number; // v3: per-machine cooling rate; sim default 0.004 } export interface TechDef { @@ -93,14 +95,15 @@ export interface EntityState { outputBuf: Record; jammed: string | null; // human-readable reason or null heat: number; // 0..1, throttles >0.7, scrams at 1 + scrammed?: boolean; // v3: heat-scram latch (heat alone can't show the + // hysteresis window; renderer/UI read this, not 0.5) } export interface BeltItem { item: string; entity: number; // belt entity id t: number; // 0..1 progress along the belt tile - id?: number; // v2: stable per-item id for exact render interpolation - // (optional this round; hardens to required in v3) + id: number; // v3: stable per-item id (required — sim always stamps) } export interface SimSnapshot { @@ -108,9 +111,13 @@ export interface SimSnapshot { paused: boolean; entities: ReadonlyArray; 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 }; shippedTotal: Record; activeCommission: string | null; + commissionQueue?: string[]; // v3: truth for "next in tray" — active first, then + // upcoming in activation order (hardens in v4) commissionProgress: Record; } @@ -119,6 +126,7 @@ export type Command = | { 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: 'setPaused'; paused: boolean }; export type SimEvent = @@ -139,6 +147,8 @@ 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) } export interface Renderer { @@ -164,5 +174,6 @@ export interface UI { export interface ScreenFX { init(canvas: HTMLCanvasElement, data: GameData): void; onEvents(events: SimEvent[]): void; // shipped items feed the corruption - frame(timeMs: number): void; + frame(timeMs: number, snap?: SimSnapshot): void; // v3: read-only peek for continuous + // strain (stored, heat); never retain } diff --git a/fktry/src/main.ts b/fktry/src/main.ts index 391eae9..461a4e0 100644 --- a/fktry/src/main.ts +++ b/fktry/src/main.ts @@ -59,9 +59,19 @@ async function boot() { renderer.render(snap, acc / MS_PER_TICK); ui.update(snap, events); screen.onEvents(events); - screen.frame(now); + screen.frame(now, snap); requestAnimationFrame(loop); } requestAnimationFrame(loop); + + // dev-only: paste-free demo — builds SIM's reference factory + if (import.meta.env.DEV) { + (window as any).__fktryDemo = async () => { + const { referenceFactory } = await import('./sim/reference'); + const cmds = referenceFactory(data); + cmds.forEach((c) => sim.enqueue(c)); + return `${cmds.length} commands dispatched`; + }; + } } boot();