diff --git a/fktry/lanes/LANE-SIM.md b/fktry/lanes/LANE-SIM.md index a76afb3..ea09f18 100644 --- a/fktry/lanes/LANE-SIM.md +++ b/fktry/lanes/LANE-SIM.md @@ -165,3 +165,95 @@ NEXT (round 2 if asked) - Heat: accumulate per craft, throttle >0.7, scram at 1 (the software decoder's whole bit). - Save/load — `SimSnapshot` is already the right shape to serialize. - A worked, deadlock-free reference factory you can paste into the console to demo M1. + +### Round 2 — 2026-07-17 — Opus 4.8 + +SHIPPED +- **Reference factory** (`src/sim/reference.ts`, `referenceFactory(data)`): 114 commands, + 110 entities. Ships its first MELT at **tick 1247** and is still shipping at 20,000 — + 6–15 melt per 2,000-tick window, flat, no decay. Round 1's build wedged at ~1200; this + one never does, and the demuxer's `jammed` stays null the whole way. Surplus becomes + product instead of a deadlock: 1,136 macroblock bricks + 1,997 hf dust shipped, 219 + slurry voided over 20k ticks. +- **Splitters** — round-robin, blocked outputs skipped, rank traced *through* them (a lane's + rank is now the best outcome reachable across every terminal it can reach). +- **Buffer tanks** — charge on surplus, discharge to cover a deficit, brownout only once dry; + `bandwidth.stored` is the pool. +- **Heat v1** — `heatPerTick` while active + `RecipeDef.heat` per craft, ambient cooling, + throttle above 0.7, scram at 1.0, restart under 0.5, both edges emitting `scram`. +- **Commission queue** — data-order activation, `commissionDone` advances, `repeat` re-enters + at the back, progress still only counts while active. +- **`jammed:'starved'`** and **`BeltItem.id`** (always set; survives belt transfers *and* + splitters, so the renderer never sees an item teleport). +- Verified: 37 sim tests green (18 + 15 M2 mechanics + 4 reference), tsc clean in `src/sim`. + In the live app I dispatched the whole reference factory through `__fktryBus` — it builds, + renders, and runs, and my round-2 work is already visibly driving other lanes: the UI reads + `94 DRAW / 220 GEN` + `BUFFER` off bandwidth v2, the fax shows the **queue** ("NEXT IN + TRAY: DUST ALLOWANCE"), and LANE-UI's OSHA voice is narrating my new flag ("DCT PRESS: + INTAKE STARVED. NOTHING IS ARRIVING."). +- Determinism holds across engines: browser and Node both give tick 1247 and identical + per-window melt counts. +- Perf: 3,500 entities + 6,820 belt items → 0.465 ms/tick (**72× realtime**). Round 1 was + 0.418 (80×); the ~11% is heat touching every entity every tick. Still well inside target. + +DECISIONS +- **Heat curve (mine to pick, per orders).** Throttle is linear from 1.0 at heat 0.7 down to + 0.5 at heat 1.0 — so the lore's "tick rate visibly halves" lands exactly at the scram + point. Ambient cooling is a flat 0.004/tick applied *always*, working or not (1.0 → cold in + 250 ticks ≈ 8s), which means any machine with `heatPerTick` below 0.004 can never overheat. + Scram at 1.0, restart under 0.5, so a hot generator duty-cycles rather than dying. +- **Throttle applies to `powerGen`, not just craft speed.** A hot decoder's output sags before + it drops off the bus entirely — which is precisely the hole buffer tanks exist to fill. That + synergy is my reading of "per-machine throttle"; say the word if generators should be exempt. +- Scrammed machines keep their idle `powerDraw` (stopped, not unplugged) and freeze mid-craft + progress, resuming on restart rather than binning the work. +- Scram clears `jammed` and suppresses jam evaluation: a scrammed machine reports through + `heat` + the `scram` event, not as a flow jam. Two channels, no double-reporting. +- Splitters hold 2 items, mirrored into `inputBuf` so the inspector can see them. Any adjacent + belt not pointing in is an output; the splitter's own `dir` is unused. Machines can't push + straight into one — feed it with a belt. +- **`starved` fires whenever a recipe sits idle without enough input — including a machine that + simply outruns its feed.** LANE-UI: expect it to flicker on a fast machine with a slow + supply. That's accurate rather than a bug, but you may want to debounce the voice line. +- The reference factory validates every machine/recipe id against `data` and throws a named + error, so DATA drift fails loudly instead of resurfacing as a mystery deadlock. + +BLOCKED/BROKEN +- **DATA carries none of the v2 fields, so heat and buffer tanks are inert in the real game.** + No machine has `heatPerTick`, none has `bufferCap`, no recipe has `heat`, no commission has + `repeat`. Both systems are fully implemented and tested — on a fixture I own + (`pressure.test.ts`), because there is nothing in `data/` to test them against. The DoD's + "heat throttles and scrams the software decoder" is proven mechanically, but the actual + SOFTWARE DECODER cannot overheat until DATA gives it a number. This is the one thing + standing between M2 heat and being real. + - Starting values if they help (my suggestion, DATA's call): `software-decoder` + `heatPerTick: 0.005` → ~33s of work then a ~4s scram, an ~89% duty cycle that reads as + theatrical rather than punishing; `decode-asic` left at 0 (cool and inflexible, per the + codex); `buffer-tank` `bufferCap: 600` ≈ 20s of cover against a 30-bandwidth deficit — + enough to ride out exactly one decoder scram, which is a nice thing to discover. +- `src/sim/data-validate.test.ts` is LANE-DATA's file living inside my owned path. I have not + touched it. Worth an ownership ruling — either it moves to `data/`, or my lane doc should + say I share the folder. + +CONTRACT REQUEST +- `MachineDef.coolPerTick?: number` (default 0.004). Right now cooling is a global constant, so + DATA can tune how fast a machine heats but not how fast it recovers — every machine cools 1.0 + → 0.5 in exactly 125 ticks. The codex explicitly has ASIC coolers venting heat, which wants a + per-machine number. Cheap to add, and I'd wire it the round it lands. + +PROPOSAL +- `BeltItem.id` can drop the `?` in v3 whenever you like — I always set it already. +- `referenceFactory`'s `setRecipe` commands depend on entity ids being handed out in placement + order (1..N). That's true and deterministic, but it's an implicit coupling to my id + allocation. A `setRecipeAt(pos)` command variant would remove it. Not urgent, just a + tripwire worth knowing about before someone edits the layout. +- Splitters have no filter or priority, and the reference factory proves you don't need either + — skip-blocked self-balances. Worth revisiting only when bloom loops want a filtered tap. + +NEXT (round 3 if asked) +- Save/load — `SimSnapshot` is already the right shape. +- Bloom loops: my trace already treats a closed belt circle as "no terminal" → rank 0 → accept + and pile, which is exactly what a loop wants. The P-frame duplicator should drop straight in. +- HF dust piles + mosquito swarms (the rest of M2 PRESSURE) — the seeded RNG is sitting there + unused and waiting for its first roll. +- Heat, for real, the moment DATA has numbers.