[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>
This commit is contained in:
type-two 2026-07-17 18:05:53 +10:00
parent dc1591d0e6
commit 08761e8eeb
10 changed files with 293 additions and 151 deletions

View File

@ -59,7 +59,9 @@ codex wins.
## Milestones (orchestrator's map — lanes don't self-assign ahead) ## 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 → {quantizer, subsampler, p-caster, dct-press} → gop-assembler → mosh reactor →
shipper. RULING (round 1): LANE-DATA's codex-faithful chain replaces the original 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 6-machine sketch; the ~50-entity build IS the fantasy. A deadlock-free reference

View File

@ -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 validator that keeps five parallel lanes from drowning in dangling ids. Content should
light up in-game with zero code changes from other lanes. light up in-game with zero code changes from other lanes.
**Owned paths:** `data/**`, `src/sim/data-validate.test.ts` — EXCEPTION: this one test **Owned paths:** `data/**` (the validator now lives at `data/validate.test.ts`;
file in sim's directory is yours; it validates data, not sim. Nothing else in the old `src/sim/data-validate.test.ts` exception is closed — round 3 moves it).
`src/sim/`.
**Never touch:** contracts, main.ts, other lanes' code. Schema changes are CONTRACT **Never touch:** contracts, main.ts, other lanes' code. Schema changes are CONTRACT
REQUESTs. 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 Round 2 review: the bloom-concentrate grep-before-delete and the fault-injection
exactly right. Verdicts on your report: **M1 IS your canon chain** — MASTERPLAN discipline are exactly the standard. Both your cross-lane asks are RULED:
updated, no shortcut recipe, the depth is the game. Your four inventions **(1) Units: per-second.** `gen`/`draw` are per second, `stored` is
(`press-anchor-slab`, `color-plate` + `v-hold-roll`, `artifact-bottler`, bandwidth-seconds; SIM converts their integration this round — your `bufferCap: 240`
`lane-splitter`) are **CANONIZED**. Dials-as-recipes STAYS (right call; a real dial is = 8 seconds of a 30 deficit, as you intended. **(2) Cooling:** `coolPerTick?` granted
a v3+ contract discussion). CONTRACT REQUEST GRANTED: `MachineDef.heatPerTick?` exists in v3 (sim default 0.004); SIM wires it. Your SIM's-test-is-red flag was correct
now — and your validator should now also accept `color?`, `flavor?`, `bufferCap?`, mid-round and self-resolved at HEAD (their landing fixed it) — the stash-and-rerun
`CommissionDef.repeat?`. 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 1. **The validator move** above, first, so the tree is clean.
the codex — round 1's four + whatever follows. The lore doc absorbs it in one 2. **Heat retune, the headline**: today NOTHING can overheat — every `heatPerTick`
orchestrator pass later; until then this is the single place canon-drift lives. you authored is below the 0.004 ambient cooling, so heat is decorative. With
2. **Art direction pass**: `color` on all 21 machines (renderer precedence: `coolPerTick` yours to set per machine: hit SIM's suggested feel — software
your color > derived-from-outputs) and `flavor` on all 21 — codex one-liner each, decoder throttles after ~10s of continuous load, scrams ~13-15s, full duty cycle
house voice. This kills UI's hardcoded lookup and RENDER's black-on-black problem. ~25s; ASIC stays incapable of heat. Show the arithmetic in CANON-DELTA (per-tick
3. **Heat economy**: `heatPerTick` on software-decoder, and scale its `powerGen` so vs per-second — mind the units ruling). Give the hex-splicer a lesser fever.
hot-but-mighty vs ASIC cool-and-steady is a real choice; a pinch of `heat` on Coordinate with SIM's reference-factory v3 via NOTES — your numbers decide
hex-splicer recipes. SIM implements throttle/scram this round — your numbers decide whether their layout survives.
whether anyone ever dares run two software decoders side by side. 3. **Buffer economy check** under the units ruling: is 240 (8s) still your intent
4. **`bufferCap` on buffer-tank** — one tank should ride out roughly 10 seconds of now that it's true? One tank should cover one decoder scram — verify against your
typical M1-chain deficit. Show your arithmetic in CANON-DELTA. retuned cycle, adjust, show working.
5. **Commission queue content** (SIM lands queue + `repeat` this round): reorder for 4. **ASIC COOLER spec'd** in CANON-DELTA (the codex names it, §2 "vented by ASIC
a difficulty ramp; add 2-3 `repeat:true` standing orders, including **THE coolers"): item/machine entry + intended adjacency mechanic, data authored but
CORRECTION BUYBACK** — pays generous bandwidth for anchor-slabs, fax written in The recipes gated behind a `broadcast`-era tech node — the MECHANIC stays deferred
Correction's voice: polite, clean, quietly horrifying. This is the sellout loop until SIM builds auras (M4); the data existing first is fine and the validator's
(ship your anchors for power now, watch your SCREEN go clean — SCREEN lane already FORWARD_CANON pattern covers it.
sanitizes on anchor shipments). 5. **Reel + broadcast era tech nodes**: research costs in analog packs; unlocks
6. **Container line**: `moov-shard` / `offset-ribbon` / `sync-key` items + recipes field-loom/telecine recipe families you already authored.
assembling `container-pack` (unblocks `stream-hex-splicing`); prune the 6. **Muxer + moov brain sub-lines** (§7): `stsz-ledger`, `stco-map`, `stts-metronome`,
FORWARD_CANON allowlist as they gain recipes. `stss-keyring` items + assembly recipes converging on a `moov-brain`
7. **Bloom line**: `bloom-grade-1..5` items + bloom-duplicator recipes `mux-container` endgame recipe on the muxer. This is M4's rocket — data lands
(grade n + delta-wafers → grade n+1); grade 5 is commission bait. Validator covers first, mechanics later. Muxer machine entry if you don't have one.
all new content.
**Definition of done:** validator green over the expanded catalog; every machine has **Definition of done:** validator green from its new home; a software decoder
color + flavor; the queue tells a story from first-taste to the first buyback; SIM's demonstrably overheats in SIM's tests using YOUR numbers (coordinate — their DoD
reference-factory test still passes against your rebalanced numbers (coordinate via depends on your retune); CANON-DELTA carries the arithmetic; FORWARD_CANON pruned of
NOTES if a ratio change breaks their layout). anything that gained a recipe.
--- ---

View File

@ -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 Round 2 review: the corner-cargo catch and the scram-latch bug you found by READING
note were exactly right. Verdicts: req 1 GRANTED (`BeltItem.id?` in contracts v2 — SIM'S CODE are the reasons this process works. Verdicts: your color deviation is
use when present, keep order-matching as fallback until v3 hardens it). Req 2 GRANTED **APPROVED as the ruling** — contracts v3 re-comments `MachineDef.color` as
(`MachineDef.color?` — precedence: `def.color` > your derived-from-outputs > chassis/body, accent stays derived; DATA's values stand; deviating loudly with a
kind accent; your derivation stays as the fallback, it was the right instinct). written rationale was exactly right. Req 2 GRANTED: `EntityState.scrammed` is in v3 —
Req 3 DONE (main.ts ignores non-left buttons; drop your camera.ts workaround or keep SIM wires it this round. Req 3 (`BeltItem.side`) DEFERRED until merges matter.
it as belt-and-braces, your call). `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 — 1. **Scram from truth**: read `EntityState.scrammed`, delete your `SCRAM_RELEASE`
pure presentation, sim truth unchanged. Splitters (mechanics land this round in duplication and the desync risk it carried.
SIM) get a visible output-cycling tell. 2. **Remove-mode support**: UI ships a REMOVE interaction this round — give it a
2. **Heat**: shimmer + dull-red emissive ramp driven by `EntityState.heat`; throttled hover treatment (target machine tinted demolition-red, footprint outline) driven
= idle animation visibly slowed; scram = dead-dark + blinking amber until restart. by a `setGhost`-style call. You own the visual language; coordinate the hook via
3. **Buffer tank**: visible fill level (shader window or scale trick) proportional to NOTES if `setGhost(null, ...)` isn't expressive enough (I'll widen the contract
the tank's share of `snapshot.bandwidth.stored`. next round if you two need it).
4. **AnimationMixer**: play clip 0 on rigged GLBs with an idle loop — MODELBEAST 3. **Showroom formalized**: promote the devscene showroom row to `?showroom` — every
assets are imminent; verify with any animated GLB you can find or make. machine + every item on plinths, labeled, camera preset. This becomes the QA page
5. **Exact cargo interpolation** via `BeltItem.id` — delete the one-tick approximation. for MODELBEAST asset drops (which land as `public/assets/models/<asset>.glb`;
6. **Multi-mesh belt GLBs**: merge-or-group fix (your call, note it) so a fancy belt your hot-swap + normalization + AnimationMixer are already ready — this is the
asset can't half-render. 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 **Definition of done:** scram visuals verified against SIM's real latch (no local
their `src/sim/reference.ts` export in a dev page or test harness) looks ALIVE: corners constants); showroom screenshotted in NOTES; remove-mode hover demonstrated with
read as corners, heat reads as heat, tanks read as tanks; perf standing rule still met UI's flow; perf standing rule still met.
under `?devscene=stress`.
--- ---

View File

@ -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: Round 2 review: the ringing self-rejection ("sterile, but sharper — it flunked your
your anchor-slab PROPOSAL — option (b) ADOPTED as M3 fiction (The Correction buyback; bar and I nearly shipped it") is the exact quality instinct these orders exist to
DATA writes the standing order this round); LANE-UI surfaces a SANITISING chip buy. Verdicts: CONTRACT REQUEST GRANTED — `frame(timeMs, snap?)` is in v3 and main.ts
meanwhile; keep potency 2.0 as shipped. Debug hook: KEEP, gated behind already passes the snapshot; the un-retained invariant you promised is now a contract
`import.meta.env.DEV`. The 2-red you flagged was mid-round churn — HEAD is 70/70. comment. Scram-wipe PROPOSAL: APPROVED at 3 frames (~100ms) — you were right, one
The launch.json affair: resolved, per-lane ports are in MASTERPLAN now; you're on 8154. 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): 1. **Signal strain** (your design, now unblocked): continuous dread from the
- legible 8×8 **macroblock quantization** pass — bricks/coeff overdrive must READ snapshot — `bandwidth.stored` draining toward zero = rising tremor/desync judder
as a blocky grid, not generic noise; BEFORE the brownout seizure; aggregate entity `heat` = a slow feverish shimmer.
- edge-detected **ringing halos** (Gibbs oscillation hugging contrast edges); Keep it subordinate to the corruption ledger: strain is weather, shipments are
- **moiré interference lattice**. climate.
Wire signatures into corruptionMap: `macroblock-bricks`, `ringing-halos`, 2. **Regional moiré**: grow the lattice from a region instead of full-frame (the
`moire-crystal`. Keep the per-item legibility bar from round 1: shipping 40 bricks codex has it spreading as maze walls) — spatial mask, seeded deterministically,
should look UNMISTAKABLY different from 40 slurry. creeping outward with tally. This is the template for future spatial artifacts;
2. **Shipment lands**: a ~0.4s stamp/silhouette flash in the item's chip color note the mask approach for reuse.
composited on the feed per `shipped` event (batch bursts: one stamp per event, not 3. **Scram wipe at 3 frames**, as approved.
per unit — a 40-unit shipment is one big stamp, not 40 flickers). 4. **Idle strain baseline**: with zero corruption AND healthy bandwidth, the feed
3. **Scram wipe**: `scram` events (new in contracts v2) get a one-frame white stays perfectly sterile — verify strain can't leak into the NOTHING IS WRONG era
"TRACKING LOST" bar wipe. Cheap, from the event stream you already have. (a test pinning params at zero under a healthy snapshot would do it).
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.
**Definition of done:** three new artifact classes verified the way you did round 1 **Definition of done:** strain visible as stored drains (drive it by pausing the
(drive, read pixels, screenshot); stamps and scram wipe visible on the dev build; reference factory's generators or just feeding a fixture snapshot), regional moiré
frame budget still <2ms; tests updated for new signatures. grows rather than switches, wipe readable, budget still <2ms, tests updated.
--- ---

View File

@ -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 Round 2 review: the reference factory turning round 1's deadlock into 1,136 shipped
M1 flow; your proposal 2, auto-fallback, is REJECTED for exactly the reason you gave: bricks is exactly what "surplus becomes product" was supposed to mean — outstanding.
it deletes the byproduct tension). Proposal 3 (starved) APPROVED. Contracts v2 landed Verdicts: `coolPerTick` GRANTED (contracts v3, default 0.004 — your constant becomes
for you: `BeltItem.id?`, `CommissionDef.repeat?`, `MachineDef.heatPerTick?/bufferCap?`, the fallback). `setRecipeAt` GRANTED (your tripwire, defused). `BeltItem.id` HARDENED
`SimEvent 'scram'`. Extractor seam tiles: deferred to M3 worldgen, stop worrying. 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 **UNITS RULING (DATA's cross-lane ask):** `gen`/`draw` are bandwidth per SECOND;
`referenceFactory(data: GameData): Command[]` — your deadlock-free M1 layout `stored` is bandwidth-seconds. Your integration currently charges/drains per tick —
(second quantizer on crime, second assembler on i-only). Test: ships melt within divide by `TICKS_PER_SECOND` so a 30-deficit drains 30 stored per second, not per
2,000 ticks AND is still shipping at 20,000 (the deadlock regression net). This tick. DATA's `bufferCap: 240` then covers 8s as they intended. Update tests; confirm
becomes the official demo; I wire it to a dev hook in main.ts next round. the conversion in NOTES so DATA can retune against truth.
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).
**Definition of done:** all of the above tested (determinism suite still green); 1. **Contracts v3 wiring**: `EntityState.scrammed` (RENDER is currently duplicating
reference factory test proves melt at scale with zero deadlock; heat demonstrably your 0.5 constant to avoid rendering a lie — kill that), `commissionQueue` on the
throttles and scrams the software decoder in a test. 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).
--- ---

View File

@ -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: Round 2 review: 57 tests against a 10 minimum, and factoring pure modules instead of
CONTRACT REQUEST 1 GRANTED — `UIBus.pickTile` is live, wired to the renderer in touching root config when my grant turned out to be vapor — model behavior. About
main.ts. Req 2 GRANTED (`MachineDef.color?`). Req 3 GRANTED as `MachineDef.flavor?` that: **happy-dom genuinely is installed now** (verified: `package.json` line 17, and
LANE-DATA fills it this round; read data first, keep voice.ts as fallback. Req 4 your 57 tests pass under `--environment happy-dom`). My round-2 "it's installed" was
GRANTED — `happy-dom` is installed; use `// @vitest-environment happy-dom` per test wrong and your BLOCKED section caught it — good catch, keep doing that.
file (no root config change needed). Build-bar paging PROPOSAL: APPROVED. 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 1. **REMOVE**: your design — suggested shape: X hotkey / bar button arms remove mode,
`?uifixture` — the sim is real now; drive a brownout by building. hover shows RENDER's demolition tint (they're building it this round — coordinate
2. **Build bar paging** (your design): kind tabs, `[`/`]` to page, 1-9 within the via NOTES), click dispatches `remove`, Esc disarms. Toast in house voice
active page — every machine hotkey-reachable at any catalog size. ("UNIT RECLAIMED. THE FLOOR REMEMBERS.").
3. **Inspector upgrades**: recipe picker (`setRecipe` finally gets its UI), heat bar 2. **NEXT IN TRAY becomes truth**: read `snap.commissionQueue` when present, delete
with THROTTLING/SCRAM states, and starved-vs-backed-up wording (sim distinguishes `peekNext` inference the moment SIM lands it (keep fallback until then).
`'starved'` from `'output full'` this round). 3. **DOM render tests**: the two you named as needing a real DOM (chip rendering,
4. **Commission queue UI**: current fax + a "NEXT IN TRAY" peek; `repeat:true` `attach()` binding) plus fax stamp lifecycle — `// @vitest-environment happy-dom`.
commissions get a STANDING ORDER stamp. When anchor-slabs ship, show a SANITISING 4. **Live pressure verification** (now unblocked): `window.__fktryDemo()` builds
chip (hardcoding the id is fine this round — the fiction becomes The Correction's SIM's reference factory in one call — use it. Verify with your own eyes and
buyback in M3). screenshot in NOTES: heat bar + THROTTLING + SCRAM on the software decoders once
5. **Tests, minimum 10**: chips, roster/footprint hit-testing, selection, hotkey map — DATA's retune lands, SANITISING chip on real anchor shipments, STANDING ORDER
under happy-dom. 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 **Definition of done:** a misplaced machine is mouse-removable; queue peek reads
round) is inspectable end to end by mouse alone; every machine reachable by hotkey; truth; the three DOM tests exist; NOTES carry screenshots of heat/scram/SANITISING
tests green; fixture deleted. observed live.
--- ---

112
fktry/package-lock.json generated
View File

@ -12,6 +12,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/three": "^0.170.0", "@types/three": "^0.170.0",
"happy-dom": "^20.10.6",
"typescript": "^5.6.0", "typescript": "^5.6.0",
"vite": "^6.0.0", "vite": "^6.0.0",
"vitest": "^2.1.0" "vitest": "^2.1.0"
@ -869,6 +870,16 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/@types/stats.js": {
"version": "0.17.4", "version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz",
@ -898,6 +909,23 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/@vitest/expect": {
"version": "2.1.9", "version": "2.1.9",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
@ -1001,6 +1029,19 @@
"node": ">=12" "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": { "node_modules/cac": {
"version": "6.7.14", "version": "6.7.14",
"resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@ -1066,6 +1107,19 @@
"node": ">=6" "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": { "node_modules/es-module-lexer": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "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": "^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": { "node_modules/loupe": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
@ -1448,6 +1521,13 @@
"node": ">=14.17" "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": { "node_modules/vite": {
"version": "6.4.3", "version": "6.4.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", "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": { "node_modules/why-is-node-running": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
@ -2635,6 +2725,28 @@
"engines": { "engines": {
"node": ">=8" "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
}
}
} }
} }
} }

View File

@ -14,6 +14,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/three": "^0.170.0", "@types/three": "^0.170.0",
"happy-dom": "^20.10.6",
"typescript": "^5.6.0", "typescript": "^5.6.0",
"vite": "^6.0.0", "vite": "^6.0.0",
"vitest": "^2.1.0" "vitest": "^2.1.0"

View File

@ -51,10 +51,12 @@ export interface MachineDef {
powerGen?: number; powerGen?: number;
beltSpeed?: number; // tiles/sec, kind==='belt' beltSpeed?: number; // tiles/sec, kind==='belt'
asset: string; // codex asset key for renderer mesh registry, e.g. "quantizer" 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) 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 (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 { export interface TechDef {
@ -93,14 +95,15 @@ export interface EntityState {
outputBuf: Record<string, number>; outputBuf: Record<string, number>;
jammed: string | null; // human-readable reason or null jammed: string | null; // human-readable reason or null
heat: number; // 0..1, throttles >0.7, scrams at 1 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 { export interface BeltItem {
item: string; item: string;
entity: number; // belt entity id entity: number; // belt entity id
t: number; // 0..1 progress along the belt tile t: number; // 0..1 progress along the belt tile
id?: number; // v2: stable per-item id for exact render interpolation id: number; // v3: stable per-item id (required — sim always stamps)
// (optional this round; hardens to required in v3)
} }
export interface SimSnapshot { export interface SimSnapshot {
@ -108,9 +111,13 @@ export interface SimSnapshot {
paused: boolean; paused: boolean;
entities: ReadonlyArray<EntityState>; entities: ReadonlyArray<EntityState>;
beltItems: ReadonlyArray<BeltItem>; beltItems: ReadonlyArray<BeltItem>;
// 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 }; bandwidth: { gen: number; draw: number; stored: number; brownout: boolean };
shippedTotal: Record<string, number>; shippedTotal: Record<string, number>;
activeCommission: string | null; activeCommission: string | null;
commissionQueue?: string[]; // v3: truth for "next in tray" — active first, then
// upcoming in activation order (hardens in v4)
commissionProgress: Record<string, number>; commissionProgress: Record<string, number>;
} }
@ -119,6 +126,7 @@ export type Command =
| { kind: 'remove'; pos: Vec2 } | { kind: 'remove'; pos: Vec2 }
| { kind: 'rotate'; pos: Vec2 } | { kind: 'rotate'; pos: Vec2 }
| { kind: 'setRecipe'; entity: number; recipe: string | null } | { kind: 'setRecipe'; entity: number; recipe: string | null }
| { kind: 'setRecipeAt'; pos: Vec2; recipe: string | null } // v3: id-free variant
| { kind: 'setPaused'; paused: boolean }; | { kind: 'setPaused'; paused: boolean };
export type SimEvent = export type SimEvent =
@ -139,6 +147,8 @@ export interface Sim {
tick(): void; // advance exactly one tick tick(): void; // advance exactly one tick
snapshot(): SimSnapshot; // read-only view; renderer/ui must not mutate snapshot(): SimSnapshot; // read-only view; renderer/ui must not mutate
drainEvents(): SimEvent[]; // events since last drain (main loop fans out) 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 { export interface Renderer {
@ -164,5 +174,6 @@ export interface UI {
export interface ScreenFX { export interface ScreenFX {
init(canvas: HTMLCanvasElement, data: GameData): void; init(canvas: HTMLCanvasElement, data: GameData): void;
onEvents(events: SimEvent[]): void; // shipped items feed the corruption 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
} }

View File

@ -59,9 +59,19 @@ async function boot() {
renderer.render(snap, acc / MS_PER_TICK); renderer.render(snap, acc / MS_PER_TICK);
ui.update(snap, events); ui.update(snap, events);
screen.onEvents(events); screen.onEvents(events);
screen.frame(now); screen.frame(now, snap);
requestAnimationFrame(loop); requestAnimationFrame(loop);
} }
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(); boot();