# 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 test` green 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 1 (goal: milestone M1 "FLOW") Replace the stub in `src/sim/index.ts` with a real implementation: 1. **World + placement.** Tile grid (start 64×64, world-centered). `place` command: validate footprint fits and tiles are free, spawn `EntityState`, emit `placed`. `remove` (emit `removed`, refund nothing yet), `rotate`, `setPaused`. Invalid commands are silently dropped (UI will preview validity later). 2. **Belts.** `kind:'belt'` entities carry up to 2 items each as `BeltItem` (t: 0..1), moving at `beltSpeed` tiles/sec. Items transfer to the belt/machine the belt points into (dir). Items on a belt keep minimum spacing 0.45. Belt→machine: push into `inputBuf` if the machine's current recipe wants that item and buffer < 2× recipe need; otherwise the item waits (belt backs up — this is core Factorio feel, get it right). Machine→belt: outputs pop onto an adjacent belt facing away, one item per available slot. 3. **Crafting.** Machines with recipes: when `inputBuf` covers `inputs`, consume, run `ticks` (progress 0..1), emit `crafted`, deposit `outputs` into `outputBuf`. If `outputBuf` holds ≥ 4× outputs, machine stalls with `jammed:"output full"` (emit `jammed` once per stall, not per tick). Extractors need no inputs. Machines with multiple recipes use `setRecipe`; default to `recipes[0]`. 4. **Bandwidth (power) v1.** Sum powerGen (power kind) vs powerDraw of all entities + active recipe `bandwidth` (note: negative recipe bandwidth GENERATES — quantizer compression pays power, per lore). If draw > gen: global speed multiplier = gen/draw for all progress and belt movement (brownout), emit `brownout` on/off edges, set `snapshot.bandwidth` accordingly. No storage/buffer tanks yet. 5. **Shipping.** `kind:'shipper'` consumes ANY item arriving in its input, increments `shippedTotal`, emits `shipped`. Track the single active commission (first in data): fill `commissionProgress`, emit `commissionDone` once satisfied. 6. **Tests** (minimum): placement collision; belt line moves an item end to end in the expected tick count; belt back-pressure (full line stops cleanly, no item loss/dupe); demux recipe consumes 2 ore → 1 luma + 1 slurry; brownout halves speed at draw=2×gen; determinism (two sims, same seed+commands, 500 ticks, deep-equal snapshots); shipping increments and fires commissionDone. **Definition of done:** with the dev server running, the orchestrator can (via UI or console `__fktryBus.dispatch(...)`) place extractor→belts→demuxer→quantizer→ mosh-reactor→shipper and watch `shippedTotal.melt` climb. All tests green. NOTES written (decisions, gotchas, what you'd tackle in round 2). --- ## 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 ```