Confirms the units conversion (bufferCap 240 = 8.0s of cover at a 30/s
deficit) and reports scram survival: first scram t434, restart t934,
~718-tick duty cycle, ZERO brownout ticks across 20k, melt rate unchanged.
For the orchestrator:
- heat semantics need a one-line ruling. heatPerTick is GROSS in my model
(net = heatPerTick - coolPerTick). DATA's committed 0.0025 against the
0.004 default nets negative -- the decoder could never scram. Their
in-flight 0.0033/0.001 works, so we agree by luck of timing.
- the codex's literal bloom loop is not expressible with per-grade recipes
- asic-cooler does nothing without a spatial heat mechanic
- my sim code landed inside 45003db [ui] REMOVE mode: the shared tree has
one git index, and an add-everything commit in another session swept it.
Not a UI ownership violation. Suggest a no-`git add -A` rule.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
26 KiB
LANE-SIM — deterministic factory simulation
You are an Opus 4.8 executor on the SIM lane of FKTRY. Read ../MASTERPLAN.md and
../CONTRACTS.md before anything else; the world bible is ../../docs/FKTRY_LORE.md.
Mission: the headless, deterministic heart of the game. Grid, entities, belts, recipes, power, events. If the renderer were deleted, your sim should still be fully playable through unit tests. You are the lane the other four trust with their lives.
Owned paths: src/sim/** (including your tests: src/sim/*.test.ts).
Never touch: contracts, main.ts, other lanes, data JSONs (request changes via NOTES).
Standing rules (all rounds):
- Deterministic: seedable RNG (write your own mulberry32-style helper), no wall-clock, no unseeded Math.random. Same seed + same commands = identical snapshots.
- Every mechanic lands with tests.
npm testgreen before you write NOTES. - Performance target: 1,000 entities + 5,000 belt items at 30 tps without heroics. Prefer flat arrays and index maps over object soup. Don't allocate in the tick loop when avoidable.
- Snapshot may reuse buffers, but its SHAPE must always match
SimSnapshot.
CURRENT ORDERS — Round 3 (goal: M2 "PRESSURE" becomes real, M3 groundwork)
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.
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.
- Contracts v3 wiring:
EntityState.scrammed(RENDER is currently duplicating your 0.5 constant to avoid rendering a lie — kill that),commissionQueueon the snapshot (active first, then upcoming — UI's NEXT IN TRAY is guessing today),coolPerTickper machine,setRecipeAtcommand, the units conversion above. - 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). - 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.
- 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).
NOTES (append-only log, newest at bottom — template below)
### Round N — YYYY-MM-DD — [your model]
SHIPPED: what actually works now (verified how)
DECISIONS: choices you made inside your mandate
BLOCKED/BROKEN: anything not working, honestly
CONTRACT REQUEST: (if any)
PROPOSAL: (ideas for the orchestrator, optional)
NEXT: what you'd do next round if asked
Round 1 — 2026-07-17 — Opus 4.8
SHIPPED
- The stub is gone.
src/sim/is now grid + placement, belts with back-pressure, crafting, bandwidth/brownout, shipping and commissions. Files:index.ts(the sim),geom.ts(grid math),rng.ts(seeded mulberry32),sim.test.ts(18 tests). - Verified three ways, not one:
npm run checkclean;npm test70/70 green across all lanes (18 are mine).- Live at localhost:8150 (another session's dev server — see BLOCKED). App boots, zero
console errors. My numbers are visibly driving other lanes: the UI bandwidth readout
showed
9 DRAW / 20 GENfor an ASIC + extractor + 4 belts + uplink, which is exactly my power math; the fax panel reads myactiveCommission/commissionProgress.__fktryBus.dispatch(...)places entities and they render. - Ran the sim in the real browser engine by importing
/src/sim/index.tsin the page: the M1 chain shipped its first MELT at tick 1149, commissionfirst-tasteclosed at tick 1149 — the identical tick Node produces. Determinism holds across engines.
- Perf (measured in-browser, not estimated), against the 1,000 entity / 5,000 item target:
- 1,245 entities + 2,436 belt items → 0.138 ms/tick (242× realtime)
- 3,500 entities + 6,820 belt items → 0.418 ms/tick (80× realtime)
DECISIONS
- Output-belt routing by rank — the big judgement call, please review. A machine picks which adjacent belt to hand an item to by tracing the belt chain to the machine it ends at (cached; invalidated on any place/remove/rotate/setRecipe): rank 2 = that machine consumes this item, 1 = it's an uplink (takes anything), 0 = dead end or loop (accept, so a half-built belt still fills instead of looking broken), -1 = it will never take this item, so refuse. It never falls back to a worse rank just because the best belt is momentarily full. Why I did this: with no splitters or inserters, the demuxer's three outputs (luma / slurry / mv-flux) have to leave one 2x2 perimeter toward three different machines. Without ranking, items land on the wrong lane and wedge it permanently — M1 cannot flow at all. This is routing intelligence the orders didn't ask for; it's also the root of the deadlock below. If you want it gone, splitters have to land first.
- Belt spacing is enforced across the tile seam: a blocked front item holds station 0.45 behind the rear item of the next belt rather than bunching at the boundary.
- Pause freezes sim time (
tickstops advancing) but commands still apply — you can build while halted.tickis sim-time, not wall-time. jammedreports only'output full', edge-triggered (one event per stall, not per tick). Starvation is not flagged — see PROPOSAL 3.- Power: a negative recipe bandwidth adds to gen rather than subtracting from draw, so
the brownout multiplier stays
gen/draw. Tested: the quantizer visibly pays for itself. - Extractors mine anywhere — there is no seam map in
data/yet. - Tests assert against the recipe defs instead of hard-coding yields. This is deliberate:
my first pass hard-coded
demux-oreand broke the moment LANE-DATA shipped. Mechanics are mine to pin down; the numbers are theirs to move.
BLOCKED/BROKEN
- My round-1 definition of done no longer exists. The orders say place
extractor→demuxer→quantizer→mosh→shipper and watch melt climb. LANE-DATA's codex
transcription landed mid-round and the mosh reactor now eats
gop-crate, not coefficient packs. MELT is now eight machine types deep (ore→demux→{luma→quantizer→coeff; slurry→subsampler ×2→chroma-420}→DCT press→anchor slab; mv-flux→p-caster→delta wafers; →GOP assembler→mosh). I rebuilt against the real graph and it does ship melt (tick 1149, verified above) — but "the whole fantasy in miniature" is now a ~50-entity build, not a 6-machine demo. Your call whether M1's demo target changes or DATA adds a starter recipe. - The M1 factory deadlocks permanently at ~tick 1200, after 4 GOP crates / 12 melt.
This is not a mechanics bug — I probed it for 6000 ticks. Luma and coefficient packs
structurally overproduce (demux yields 2 luma/craft; the press is chroma-420-limited so it
only burns 6 coeff per ~180 ticks), and the press also jams on surplus anchor slabs (the
GOP assembler needs 1 slab per 6 delta wafers). Surplus has nowhere legal to go: my rank
rule forbids diverting a rank-2 item to a rank-1 uplink even when the preferred belt is
wedged. So the press jams → quantizer jams → demuxer jams → mv-flux stops → the p-caster
starves and the assembler sits at 5/6 delta wafers forever.
- The graph is solvable today without new code: a second quantizer on
quantize-crimedrains surplus coeff into bricks, and a second assembler onassemble-gop-i-onlyeats surplus slabs (8→crate). My demo build is just naive. But note a player currently cannot void surplus to an uplink, which is the first thing a Factorio player reaches for. That's the cost of the rank rule.
- The graph is solvable today without new code: a second quantizer on
splitter(lane-splitter) andbuffer(buffer-tank) now exist in machines.json; they place and draw power but do nothing. Not in my orders — flagging so nobody thinks they work.heatis always 0,bandwidth.storedalways 0 (M2 / buffer tanks).- Port 8150 is held by another session's dev server, and the harness won't start a second one
for this project. I verified against that server read-only. Note for whoever plays the
build in a Browser pane: the tab reports
visibilityState: hidden, so rAF is paused and the sim crawls (~9 ticks per screenshot). That's the pane, not the loop — importing the sim module directly in the page runs it at ~96k ticks/sec.
CONTRACT REQUEST
- None.
contracts.tscovered everything I needed this round.
PROPOSAL (yours to call, not mine to take)
- Splitters next.
lane-splitterwith round-robin plus a priority/overflow output is the canonical Factorio answer to the deadlock above, and it unlocks byproduct sinks and the bloom-loop topology later. This is the one I'd pick. - Alternative, ~5 lines: allow a lower-ranked belt when every preferred belt is full and outputBuf has hit the stall threshold. Cheap, but it makes byproduct management automatic and quietly deletes a core Factorio tension — a gameplay decision, so I left it alone.
jammed: 'starved'for idle-with-no-input would let the inspector tell "backed up" from "waiting". Cheap, but it's UI-visible, so it's LANE-UI's business as much as mine.- Extractors should require a seam tile — needs a seam/ore map from DATA and a worldgen call.
NEXT (round 2 if asked)
lane-splitterwith priority/overflow;buffer-tankfeedingbandwidth.storedto smooth brownouts.- Heat: accumulate per craft, throttle >0.7, scram at 1 (the software decoder's whole bit).
- Save/load —
SimSnapshotis 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'sjammedstays 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.storedis the pool. - Heat v1 —
heatPerTickwhile active +RecipeDef.heatper craft, ambient cooling, throttle above 0.7, scram at 1.0, restart under 0.5, both edges emittingscram. - Commission queue — data-order activation,
commissionDoneadvances,repeatre-enters at the back, progress still only counts while active. jammed:'starved'andBeltItem.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 reads94 DRAW / 220 GEN+BUFFERoff 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
heatPerTickbelow 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
jammedand suppresses jam evaluation: a scrammed machine reports throughheat+ thescramevent, not as a flow jam. Two channels, no double-reporting. - Splitters hold 2 items, mirrored into
inputBufso the inspector can see them. Any adjacent belt not pointing in is an output; the splitter's owndiris unused. Machines can't push straight into one — feed it with a belt. starvedfires 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
dataand 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 hasbufferCap, no recipe hasheat, no commission hasrepeat. Both systems are fully implemented and tested — on a fixture I own (pressure.test.ts), because there is nothing indata/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-decoderheatPerTick: 0.005→ ~33s of work then a ~4s scram, an ~89% duty cycle that reads as theatrical rather than punishing;decode-asicleft at 0 (cool and inflexible, per the codex);buffer-tankbufferCap: 600≈ 20s of cover against a 30-bandwidth deficit — enough to ride out exactly one decoder scram, which is a nice thing to discover.
- Starting values if they help (my suggestion, DATA's call):
src/sim/data-validate.test.tsis LANE-DATA's file living inside my owned path. I have not touched it. Worth an ownership ruling — either it moves todata/, 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.idcan drop the?in v3 whenever you like — I always set it already.referenceFactory'ssetRecipecommands 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. AsetRecipeAt(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 —
SimSnapshotis 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.
Round 3 — 2026-07-17 — Opus 4.8
SHIPPED
- Contracts v3 wired:
EntityState.scrammed(RENDER can delete its duplicated 0.5 constant — the latch is mine to publish),snapshot.commissionQueue(active first then upcoming; UI's NEXT IN TRAY is now truth rather than a guess),coolPerTickper machine with my constant as the fallback,setRecipeAt, andBeltItem.idhardened to required. - UNITS CONVERSION — confirmed, DATA please retune against this. gen/draw are bandwidth
per SECOND;
storedis bandwidth-SECONDS. Charging and draining now divide by TICKS_PER_SECOND, so a 30/s deficit drains 1 stored per tick, i.e. 30 per second. Concretely:bufferCap: 240= exactly 8.0s of cover against a 30/s deficit; the in-flight300= 10.0s.pressure.test.tsasserts that arithmetic tick by tick. - save()/load(): 26.9 KB for the 118-command reference factory. The bar I held it to is "the future is identical", not "the snapshot looks right" — every test loads a blob and runs BOTH sims 1,000+ ticks before comparing. Includes save→load→save byte-identity, a chained save/load×3, and pending-command preservation. 7 tests.
- Reference factory v3: power restructured to 4× decode-asic (80/s) + 1× software-decoder (100/s) + 3× buffer-tank (900 bandwidth-seconds). Melt still first at tick 1247, still shipping at 20k, rate unchanged.
- Scram survival (the DoD numbers): first scram t434, restart t934 (500-tick
recovery = 16.7s), then a steady ~718-tick duty cycle at 47% uptime. Brownout ticks
across 20,000: zero. Tanks sit at a 773/900 low-water mark in steady state. Melt per
2,000-tick window:
[6,12,12,12,15,9,15,12,14,10]— identical to round 2's naked-decoder build, so the duty cycle costs nothing measurable. The decoders black out; the line doesn't. - Bloom loop: a closed delta-wafer bus with five splitter taps escalates concentrate to grade 5. All six rungs craft; 4× grade-5 shipped by 20k. Plus a separate test of the loop mechanic itself (pure circle → rank 0 → cargo circulates forever, conserved, tracer fine).
- Verified in the live browser as well as Node — identical tick numbers (1247 / 434 / 934),
save/load round-trips there too, no console errors, and the UI is visibly reading the new
fields:
94 DRAW / 180 GENis the new power block andBUFFER 29is a tank charging (that readout has never been non-zero before). - Perf: 74.9× realtime at 3,500 entities / 6,820 belt items (0.445 ms/tick, best of 3 on a freshly loaded page). See DECISIONS — beware my older numbers.
DECISIONS
- Reference power is sized against the worst case, not the average. The quantizers and
subsamplers hand back ~30/s of compression bandwidth, but not while they're between crafts.
Assume none of it. My first cut (3 ASIC + 2 tanks) passed every test and was quietly
gambling: measured low-water 2.2 of 600, i.e. it survived only because compression happened
to be running. 4 ASIC + 3 tanks leaves 50% margin with compression at zero, and still works
if DATA reverts
bufferCap300→240. A test that passes on a coincidence is worse than one that fails. - Bloom taps run hungriest-first, counter-current to the wafer flow — this is load-bearing. Wafers join the ring at one point and meet the taps in order, so the first tap eats first. Put the bloom tap first (the obvious layout) and it swallows 49% of all wafers, grade 3 backs up 'output full' behind a starving grade 4, and 30,000 ticks yields exactly ONE grade-5. Reversed: 4 by 20,000. Same machines, same recipes, one ordering decision. That's a genuine lesson the topology teaches a player, and worth surfacing in-game somewhere.
updateHeatearly-outs on cold, cooling-less, unscrammed machines. Belts are ~99% of a big factory and can never be anything but stone cold; skipping them paid for all of v3's other additions and then some.save()carries what the snapshot deliberately hides: crafting/scram latches, splitter queues and cursors, the RNG position, and the pending command queue.load()needs aninit()'d sim (GameData isn't in the blob) and rebuilds occupancy/tiles/belt-contents from the defs rather than trusting the blob's ordering.- Brownout comparison carries a 1e-9 epsilon: a tank covering a deficit exactly lands
supplied === draw, and float drift there would flicker the flag every single tick.
BLOCKED/BROKEN
- DATA is mid-edit in the shared tree — I tuned against uncommitted values.
data/machines.jsonis dirty as I write this. The reference factory's power is sized against the working-tree numbers (software-decoder powerGen 100 / heatPerTick 0.0033 / coolPerTick 0.001; buffer-tank bufferCap 300). If those land differently, re-check it; the 20k regression test is the net that will catch it. - Heat semantics need a one-line ruling. My model cools every tick, working or not, so
heatPerTickis GROSS and the climb isheatPerTick - coolPerTick. DATA's committed values (heatPerTick 0.0025, no coolPerTick → default 0.004) net to -0.0015 — the software decoder could never scram at all, and the DoD would have been unprovable. Their in-flight values (0.0033 / 0.001) net +0.0023 and work. So we agree, but by luck of timing, and the contract comment ("heat while active" / "per-machine cooling rate") doesn't say which it means. Please make it say: heatPerTick is gross; net climb is heatPerTick − coolPerTick; a machine whose heatPerTick ≤ coolPerTick never overheats. This is the same class of bug as the gen/draw units one, and it cost DATA a round.- Sanity check that DATA's intent was always a scram:
bufferCap240 ÷ 55 gen = 4.36s of cover, against my 4.17s recovery at the time. They sized the tank to ride out exactly one scram before the decoder could produce one.
- Sanity check that DATA's intent was always a scram:
asic-cooler(new) currently does nothing.coolPerTick: 0.02,powerGen: 5, noheatPerTick— cooling is per-machine and there is no adjacency mechanic, so it cools itself, and it has no heat to shed. It's a 5-bandwidth generator with a great flavour text. If it's meant to cool its NEIGHBOURS, that's spatial heat: a real feature needing orders and probably acoolRadiusfield. Say the word.- The codex's literal bloom loop is not expressible. "Any closed belt circle with a
duplicator on it; contents compound each lap" cannot work with per-grade recipes: a machine
runs one recipe, so a lone duplicator can never consume its own output, and a grade-5
returning to a grade-1 duplicator wedges that lane permanently. The shape that composes is
the wafer-bus circle I built. The literal version needs a single self-upgrading recipe
(grade-N → grade-N+1), which
RecipeDefcan't express — inputs/outputs are concrete item ids. Either accept the escalator reading, or that's a schema change. - Ownership wart is gone:
data/validate.test.tsis staged by DATA andsrc/sim/**is purely mine again. Thank you. - My round-3 sim code is committed inside
45003db [ui] REMOVE mode, not under a[sim]commit — please don't read that as a LANE-UI ownership violation. All five lanes share one working tree and one index. I hadsrc/sim/**staged; LANE-UI's session ran an add-everything commit in the same repo a moment later and swept my staged files in with theirs. By the time my own[sim]commit ran there was nothing left to commit, and45003dbwas already pushed. The code is correct and complete in the tree (48 sim tests green, tsc clean, verified in-browser); only the attribution is wrong, and rewriting pushed history under four concurrent sessions would do more damage than the mislabel. Flagging it so your per-lane diff doesn't fire. See PROPOSAL.
CONTRACT REQUEST
- None. v3 covered everything.
PROPOSAL
Sim.save?/load?can drop the?in v4 — I always implement both.snapshot.commissionQueuealiases the live array rather than copying it each tick. Covered by "snapshot is read-only", but worth a line in CONTRACTS since it's newly mutable-looking.- Treat my round-1/round-2 perf figures (80× / 72×) as soft. I re-measured on a page loaded with leftover sims and got anywhere from 59× to 12× for the same build; a fresh page gives 74.9× consistently across three runs. The numbers were never wrong so much as noisy — if you ever want perf tracked properly it should be a headless benchmark, not a browser probe.
- The shared working tree needs a commit rule. Five sessions share one repo and one git
index, so any lane running
git add -A/git commit -asilently commits whatever another lane has staged at that instant — that's how my sim work ended up inside a[ui]commit this round, and it'll keep happening. Cheapest fix: a standing rule that lanes only evergit add <their own explicit paths>and never-A/-a/.. Failing that,git worktreeper lane would make it structurally impossible.
NEXT (round 4 if asked)
- HF dust piles + mosquito swarms — the seeded RNG is still unrolled, and save/load already carries its state, so wildlife can land without touching persistence.
- The Correction / parity mites (M3's enemy).
- Spatial heat +
coolRadius, if the ASIC cooler is meant to mean something. - A self-upgrading bloom recipe, if you want the codex's literal loop.