[orchestrator] round 4 review + contracts v5 + round 5 orders

Review: 5/5 pass, 318/318, tsc clean. Verified live: STANDARDS COMMITTEE renders all
eras with ratified nodes, fuel gauge + ON RESERVE state, composite seam in the tree.
Fixed the demo brownout (mine): __fktryDemo now grants referenceFactoryTech() via
testkit before dispatching (swaps to the grantResearch command when SIM lands it).

Contracts v5: grantResearch command (sandbox is a feature), SelectionState inspect
mode (RENDER's placed-and-selected auras), wildlife (WildlifeState + event),
coolRadius clarified ADDITIVE capped at HEAT_COOL_MAX. main.ts: typed selection
store (inspect-capable), sentinel string no longer sent, narrowing fixed in UI's
faithful-copy harness (my breakage, my fix).

Rulings: research-time approved; pack abandonment kept as design; DATA's ungating +
starter-plant arithmetic ratified; SCREEN era-feed + jammed-rot approved; UI tab
strip approved. MASTERPLAN: bare-stash ban, stale-numbers process note.

Round 5 = M2 closes: wildlife (dust piles, mosquito swarms, traps), research takes
time, era-aging sky, seam map groundwork, inspect-mode handshake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-17 21:31:57 +10:00
parent 3e45ee2652
commit 557c78b208
9 changed files with 194 additions and 181 deletions

View File

@ -47,6 +47,17 @@ codex wins.
`git reset --soft HEAD~1` and recommit correctly. If collisions still happen,
the next escalation is a git worktree per lane.
Also: **never bare `git stash`** — it stashes every lane's uncommitted work and
will convince you that you fixed (or caused) another lane's red. Only
`git stash push -- <your paths>`. (Round 4: SCREEN nearly misdiagnosed a red this
way; DATA's diff was the actual variable.)
**Numbers in orders go stale mid-round.** Five lanes land continuously; a count or
claim written into CURRENT ORDERS is true only as of writing (round 4: "24 nodes"
was 23 by landing; "research doesn't exist" was false within the hour). Re-probe
HEAD before acting on any order's factual claim, and before writing NOTES — a
round-start grep is a lie by round-end.
## Hard rules
- **Never edit**: `MASTERPLAN.md`, `CONTRACTS.md`, `src/contracts.ts`, `src/main.ts`,

View File

@ -25,46 +25,41 @@ REQUESTs.
---
## CURRENT ORDERS — Round 4 (goal: THE COMPOSITE SEAM + research economy)
## CURRENT ORDERS — Round 5 (goal: the world gets teeth and ground)
Round 3 review: re-introducing round 2's exact bug to prove the validator catches it
is the gold standard — and your "decorative number" lesson is now a contract comment
AND your validator's job forever (heat ruling: gross, net = heat cool). Verdicts:
tsconfig FIXED (`include: ["src", "data"]` — your poisoned-file proof was conclusive).
`accent?` and `coolRadius?` are in v4. SIM exports tuning constants this round —
retire your hand-copies. The stco/keyring recipe-assignment logic ("made by the
machine that already owns that knowledge") is exactly how future inventions should
argue.
Round 4 review: the gating audit — measuring 0-of-24 reachability, fixing it as two
tested rules, deleting six fake nodes instead of repurposing them — is the round's
headline and the reason the tree tells a story now. The cooler retune that survives
both readings of an ambiguous contract sentence was defensive tuning of the highest
order; the sentence is now clarified (ADDITIVE, capped at HEAT_COOL_MAX). Verdicts:
constants.ts landed in SIM's final commit (your grep raced it — it exports 12,
retire your hand-copies now). Your ungating of the mosh reactor + starter-plant
arithmetic: RATIFIED, SIM's reference factory now pre-grants its techs in the demo.
1. **THE COMPOSITE SEAM — the codex grew §10** (docs/FKTRY_LORE.md, read it first):
keying/transparency artifacts, from "you cannot film transparency". Transcribe it:
ALPHA (the unfilmable resource — only keyer machines emit it), HARD MATTE, BLUE
HOLE, FRINGE LACE, GREEN SPILL, MIXED PIXEL, ROTO CHATTER, GHOST FEATHER, SODIUM
LIGHT (589nm, Correction-hoarded reagent — forward canon until M4); machines
MATTE BATH (reel), OPTICAL PRINTER (the iron lung — make its ratios hurt), CHROMA
KEYER (stream; leaks green spill, wrecks feathers into chatter), ROTO BENCH (the
grind: converts anything, atrociously), NEURAL KEYER (endgame: perfect, and its
powerDraw should be obscene — the codex says its appetite dwarfs everything;
research-gate it behind a fortress-adjacent tech). Era-gate the family through
the tech tree; validator covers everything; CANON-DELTA logs deviations.
2. **Research economy**: author the lab machine (`kind:'lab'` in v4 — name it in
codex voice, it's yours to canonize in CANON-DELTA) and audit all 24+ tech costs
now that they'll actually be paid: pack production chains must exist and the
early nodes must be affordable off a first-taste-era factory. Show one worked
cost path (packs/minute vs research demand) in CANON-DELTA.
3. **ASIC COOLER activation**: SIM lands `coolRadius` auras this round — give the
cooler its radius + numbers (your spec'd adjacency intent), and make the software
decoder + cooler pairing the worked example (duty cycle with vs without).
4. **Accent fills**: `accent?` where the first-output derivation is wrong — belts,
buffer tank, uplink, lab, coolers; leave it unset where the derivation is already
right (that's the design, not laziness).
5. **Fixture/validator updates**: v4 fields (`accent`, `coolRadius`), the gross-heat
rule you already enforce stays, `commissionQueue` v5-hardening — any fixtures you
own carry it.
1. **Retire the hand-copied constants** — import from `src/sim/constants.ts`
(read-only cross-lane import is sanctioned for constants).
2. **Wildlife data** (SIM builds the mechanic this round — coordinate via NOTES):
the MOSQUITO TRAP machine (§5 codex: bug-zapper shaped like a high-contrast edge
target), its catch recipe producing the canned `mosquito-swarm` item, pile/hatch
tuning numbers if SIM makes them data-driven, and the commissions that suddenly
work: `vaporwave-sunset` finally unblocked — prune FORWARD_CANON accordingly and
let the fax celebrate in voice.
3. **Cooler numbers final** under the clarified additive rule — your 0.0018 was
tuned to survive ambiguity; now tune it to be *right*, show the duty-cycle table
in CANON-DELTA.
4. **The seam map — start the ground game** (the content gap you own): author
`data/seams.json` — seam regions per stratum era (shape your call: rects or
points + richness), the codex §1 geology as actual coordinates. SIM consumes it
round 6 (extractors will require seam tiles); this round it's data + validator
coverage + a CANON-DELTA spec of intended mechanics, so SIM has something real to
build against instead of a guess.
5. **Fixture/validator upkeep**: v5 fields (wildlife), grantResearch in any test
flows you own, FORWARD_CANON pruned.
**Definition of done:** Composite Seam fully transcribed and reachable through era
tech (validator green, FORWARD_CANON updated); a research path is affordable and
proven against SIM's real mechanic; cooler pairing arithmetic in CANON-DELTA.
**Definition of done:** trap + swarm economy closes (catchable → cannable →
commissionable) against SIM's real mechanic; seams.json exists with validator
coverage and a written consumption spec; constants imported from truth; CANON-DELTA
carries the cooler table.
---

View File

@ -25,37 +25,35 @@ you never simulate. If motion looks wrong, interpolate better or note it — don
---
## CURRENT ORDERS — Round 4 (goal: v4 protocol, research & aura visuals)
## CURRENT ORDERS — Round 5 (goal: a living, inspectable world)
Round 3 review: the dead-debug-hook bundle grep and the provenance note were exemplary;
your worktree analysis drove the new MASTERPLAN commit rule (pathspec commits — your
option 2, with worktrees as the named escalation). Verdicts: CONTRACT REQUEST 1
GRANTED — v4 has the typed selection protocol (`SelectionState`, `setGhost(..., mode)`)
and main.ts already passes `mode` on every call (the `'__remove'` def string still
arrives as a bridge; it dies in v5). `MachineDef.accent?` also landed (your round-2
wish, UI re-filed it). Era skins verified beautiful.
Round 4 review: ERA_TELL (signal vs surface) and the chebyshev-rectangle honesty are
both canon reasoning now; the showroom axis-overflow lesson is quoted in my head
weekly. Verdicts: CONTRACT REQUEST 1 GRANTED — `SelectionState` grew
`{mode:'inspect'; entity}` in v5; UI publishes it when the inspector opens. The
sentinel bridge is DEAD as promised: main.ts now sends `setGhost(null, tile, 0,
'remove')` — you already ignore the def, so nothing changes for you, but confirm.
Your divergence-then-convergence with UI on the lock rule was handled exactly right.
1. **Ghost migrates to `mode`**: key demolition tint off the 4th arg, ignore the
sentinel def. Keep rendering exactly as shipped otherwise.
2. **Locked-machine ghost** (research lands this round): a `place` for a locked id is
silently dropped by the sim — the ghost must telegraph it. Distinct from
occupied-red: padlock-amber wash + "era" tell if you can read the tech era from
data cheaply. Coordinate the lock lookup with UI via NOTES (both of you derive it
from `snapshot.research.unlocked` + tech.json — same rule, CONTRACTS v4 notes).
3. **Cooler auras**: `coolRadius` machines get a floor-ring (subtle, engineering-
drawing vibe, not a buff circle) on hover/ghost AND while placed-and-selected;
heat-shimmer suppression inside the ring falls out of sim truth automatically.
4. **Lab visuals**: placeholder with a distinct silhouette (research bench — codex
voice pending DATA's machine entry); `accent?` consumed when present (your
derivation stays fallback).
5. **Housekeeping approved from your NEXT list**: splitter lamp can stay timer-driven
(real round-robin state isn't in the snapshot and doesn't earn a contract field
yet); showroom item-hall paging when DATA's catalog next grows (Composite Seam
transcription lands ~15 items this round — it will).
1. **Inspect mode**: on `{mode:'inspect'}`, highlight the inspected entity (outline
or ground ring — your language) and, when it has `coolRadius`, draw the aura
rectangle — order 3's "placed-and-selected" case, now reachable.
2. **Wildlife visuals** (SIM lands `snapshot.wildlife` this round): DUST PILES as
§2's glitter heaps — instanced, growing with `size`, subtle shimmer; MOSQUITO
SWARMS as pixel-gnat particle clouds that visibly cluster on their `target`
machine (which should read as *harassed* — flicker its accent); trap machines get
a zap flash on the `wildlife cleared` event... which you don't receive — read
state deltas instead, or tell me if you want Renderer to get events (that's a
contract conversation I'm open to in v6, say so in NOTES if hover-state isn't
enough).
3. **Fixture hygiene**: `commissionQueue` + `research` into your fabricated
snapshots (devscene, showroom) — v6 hardens both.
4. **MODELBEAST intake stays armed**: no assets have landed yet; `?showroom` is the
live manifest. First drops get a NOTES line each on how normalization treated them.
**Definition of done:** remove-tint works with the mode arg (verified with UI's
migrated flow); a locked machine's ghost is legibly "not yet" rather than "broken";
cooler ring reads at a glance; showroom absorbs the new catalog.
**Definition of done:** an infested factory is legible from across the map (piles
grow, swarms harass, traps zap); inspect-highlight + aura verified with UI's flow;
perf rule still holds with 50 piles + 10 swarms in the stress scene.
---

View File

@ -27,37 +27,34 @@ seed stock — port, don't move).
---
## CURRENT ORDERS — Round 4 (goal: second dread axis, boredom, ratification)
## CURRENT ORDERS — Round 5 (goal: the sky learns eras and insects)
Round 3 review: measuring tremor in *seconds-until-dark* — and getting scale-
invariance for free from the units ruling — is the single most elegant thing in this
codebase. The negative-dt landmine catch is exactly why the NOTES exist. Verdicts:
your capacity PROPOSAL is GRANTED unasked — `bandwidth.capacity?` is in v4 (SIM sums
`bufferCap`); your "reserve low vs falling fast" second reading is this round's order.
Your stalled-factory idea is APPROVED as design. The strain/fever/seizure hierarchy is
canon now — keep future readings inside it.
Round 4 review: the two-clock decision (card in seconds because it's a broadcast;
scram wipe in frames because it's tape) is the most codex-literate engineering choice
so far, and the capacity-trap catch protected the sterile guarantee twice over.
Verdicts: your jammed-rot PROPOSAL is APPROVED (rot ≠ rest); your era-feed NEXT idea
is this round's centerpiece — you called it, you build it.
1. **Two dreads**: with `capacity` available, split the readings — "seconds left"
(tremor, as shipped) vs "reserve fraction" (a low-tank pallor/vignette that sits
there even when draw ≤ gen and nothing is draining). A full tank draining fast vs
a near-empty tank coasting should FEEL different; write the one-line design intent
for each in NOTES.
2. **Boredom** (your proposal): no `shipped` events for a sustained stretch → total
corruption eases back toward clean, slowly, visibly — the factory's work
literally evaporating off THE SCREEN. Escalation tallies stay (history isn't
erased; the *display* decays), so production resuming snaps it back fast. Tune
the decay horizon so a normal jam doesn't read as punishment (minutes, not
seconds). Test it.
3. **Ratification interruption**: `researched` event → a one-second full-screen
interruption card on the feed — sterile white, "STANDARD RATIFIED", era name,
then the corruption crashes back in. The Correction briefly wins the sky every
time the player does science. Deadpan, readable, cheap.
4. **Fixture hygiene**: `commissionQueue` hardens to required in v5 — update your
fixture snapshots this round.
1. **The base feed ages with the player** (your idea, ordered): drive the clean
programming's palette/texture from the highest era with a ratified tech
(`research.unlocked` × tech eras from GameData): reel = sepia + gate weave +
sprocket judder, broadcast = phosphor + interlace flicker, disc = rainbow-on-black
sheen, stream = the current SMPTE feed, fortress = blinding white (§8 skins).
Corruption artifacts stay era-agnostic — it's the SUBSTRATE that ages. Sterile
guarantee holds per era (test at least one).
2. **Jammed-rot**: `jammed` entities accelerate boredom decay (the work is rotting);
a clean idle keeps the gentle horizon. One-line read, as you said — plus a test
distinguishing the two.
3. **Wildlife on the sky** (SIM lands `snapshot.wildlife` + events): mosquito swarms
drive a *regional* mosquito-noise shimmer via your growth-mask template (seeded
from the swarm's pos — the sky itches where the factory itches). Piles need
nothing unless you disagree.
4. **Fixture hygiene**: done last round for commissionQueue/research — add
`wildlife` where fixtures fabricate snapshots.
**Definition of done:** both dread axes verified at the pixel level (your contact-
sheet method); boredom decays and snaps back under test; ratification card exactly
N frames with era text; budget still <2ms.
**Definition of done:** era progression visible on the feed (contact sheet per era),
rot vs rest distinguishable under test, swarm shimmer regional and pinned by pixels,
budget still <2ms.
---

View File

@ -21,45 +21,40 @@ playable through unit tests. You are the lane the other four trust with their li
---
## CURRENT ORDERS — Round 4 (goal: M3 "DEPTH" — research, spatial heat)
## CURRENT ORDERS — Round 5 (goal: M2 closes — the Bitstream comes alive)
Round 3 review: the worst-case power sizing ("a test that passes on a coincidence is
worse than one that fails") and the counter-current bloom discovery are the two best
engineering notes anyone has filed. Verdicts: **heat semantics RULED as you asked**
gross, net = heatPerTick coolPerTick, now a contract comment and a DATA validator
rule. **Bloom loop: the escalator reading is ACCEPTED** — your wafer-bus circle IS the
mechanic; the codex line stays as lore-poetry. **Git: pathspec commits are now a hard
rule in MASTERPLAN** (your option, chosen over worktrees for now — worktrees are the
escalation if it recurs). save/load hardened in v4 as you offered. commissionQueue
aliasing documented in CONTRACTS. Your perf-noise honesty is noted — a headless
benchmark is a good M4 item, not now.
Round 4 review: the realtech.test.ts tripwire caught DATA's circular tree and your
premise-guard philosophy caught their fix — the system is working. Verdicts:
**grantResearch PROPOSAL GRANTED** — it's in v5 as a real Command (sandbox is a
feature, not a cheat); implement it, and it replaces the testkit import currently in
main.ts's `__fktryDemo` (I wired testkit as the stopgap — swap is mine once you land).
**Research time APPROVED** (your NEXT): labs take `RESEARCH_TICKS_PER_PACK` per pack
(export it) so a tech is an investment, not a purchase. **Pack abandonment on target
switch: KEPT** — "packs are the cost of indecision" is design, write it no further.
Cooling cap reasoning accepted; the additive-stacking rule is now a contract comment
(DATA tuned blind against the ambiguity — your sentence, my fault for not catching it).
1. **Research v1 — the round's centerpiece.** Contracts v4: `kind:'lab'`,
`{kind:'setResearch'}`, `snapshot.research` (`ResearchState`), `researched` event.
Labs consume science packs from their input toward the active tech's `cost`;
totals met → `researched`, unlocks apply. Gating rule (in CONTRACTS v4 notes): ids
appearing in any `TechDef.unlocks` are locked — `place`/`setRecipe`/`setRecipeAt`
for locked ids are silently dropped; everything unreferenced is free. Multiple
labs stack. save/load carries research. Tests incl. a full unlock path against
DATA's real tech.json.
2. **Spatial cooling**`coolRadius` (v4): a machine with it applies its
`coolPerTick` to machines within chebyshev range of its footprint (stacking = sum,
cap total cooling at your call, note it). The ASIC COOLER finally means something;
DATA activates numbers in coordination.
3. **Export your tuning constants** (`src/sim/constants.ts`: throttle/restart
thresholds, default cooling, etc.) — DATA's tests currently hand-copy them and go
stale silently; let them import truth.
4. **Reference factory v4**: add an anchor-slab overflow tap → uplink so the demo
ships slabs (UI's SANITISING + the buyback fiction become visible in `__fktryDemo`
without surgery — they had to demolish your assembler to see it). Keep the 20k
regression green; a lab + a research run in the reference build would be ideal
but is optional if layout pressure fights you.
5. **Fixture hygiene**: `commissionQueue` hardens to required in v5 — make sure your
snapshot literal always carries it (you already publish it live).
1. **`grantResearch` command** — instant unlock list, idempotent, save/load safe.
2. **Research time** — per-pack ticks; progress visible in `research.progress`
between packs is your design call (fractional or stepped — note it).
3. **Wildlife v1 — the round's centerpiece** (contracts v5: `snapshot.wildlife`,
`WildlifeState`, the `wildlife` event): HF DUST PILES accumulate near machines
whose recipes emit hf-dust while they run (rate/threshold your design; the seeded
RNG finally rolls); a pile at size 1.0 hatches a MOSQUITO SWARM that drifts to the
nearest running machine and degrades its speed while attached (`target`); swarms
are cleared by a trap machine (DATA authors it — coordinate the mechanic: trap
with an empty-input recipe that only crafts while a swarm is in radius, producing
the canned `mosquito-swarm` item, is my suggested shape). Deterministic, save/load
carries it all, events on spawn/clear. Tests including "a factory that vents dust
into product instead of shipping it never hatches a swarm" — the design point.
4. **Reference factory v5**: grow the research rig (bottler + archaeology lab fed by
quantizer surplus) so research is playable in the demo, and the 20k run should now
also hatch at least one swarm and trap it if DATA's trap lands — the demo becomes
the whole M2 story. Regression stays green.
**Definition of done:** research provable end-to-end in tests (place lab → feed packs
→ unlock → previously-dropped place now succeeds); cooler aura demonstrably extends a
decoder's duty cycle; constants exported; reference factory ships slabs.
**Definition of done:** wildlife lifecycle proven under test (accumulate → hatch →
degrade → trap → event trail), research takes time, grantResearch lands (tell me in
NOTES so I swap main.ts off testkit), reference v5 researches a real tech in-demo.
---

View File

@ -24,43 +24,33 @@ dispatch Commands via the bus and read state from `update(snap, events)`.
---
## CURRENT ORDERS — Round 4 (goal: research UI, one real bug, protocol migration)
## CURRENT ORDERS — Round 5 (goal: the pressure has a face)
Round 3 review: `live.test.ts` — booting the real sim against the real DOM — is the
best verification asset any lane has produced; the SANITISING machine-swap showed
exactly the right instinct when an order's premise fails. Verdicts: CONTRACT REQUEST 1
GRANTED (v4 `SelectionState` + `setSelection`/`selection` on the bus — main.ts now owns
the remove dispatch; see order 2). Request 2 GRANTED (`MachineDef.accent?` — your
derivation becomes the documented fallback, DATA fills the field where it's wrong).
`?uidemo` PROPOSAL APPROVED. Tech-panel skip was right at the time — but it was
another round-start grep going stale: `tech.json` has 24 nodes at HEAD (DATA landed
mid-round again). It's a real order now.
Round 4 review: the live.test.ts research loop — lock → refuse → lab → ratify →
padlock falls → place succeeds — is the single best proof in the repo, and your
"re-probe before writing NOTES" practice is now a MASTERPLAN process note for
everyone. Verdicts: your save-schema coupling PROPOSAL is answered by v5's
`grantResearch` Command (SIM implements) — migrate your tests to it and the blob
coupling dies. Scrolling tab strip: APPROVED (you flagged the wrap). RENDER's tint
verification is still owed — close it this round.
1. **BUG, found live in review: the BROWNOUT banner lies.** `topstrip.ts:66` keys on
`out || ratio > 1` — during a covered deficit (tanks draining, sim `brownout:
false`, THE SCREEN correctly calm) the panel screams BROWNOUT. That state is
*survival*, not failure. Fix: BROWNOUT = the flag only; deficit-with-reserve gets
its own amber state (suggest "ON RESERVE · 34s" — `stored/(draw-gen)` is seconds
left, same math SCREEN uses for dread). Add a test pinning covered-deficit ≠
brownout.
2. **Selection protocol migration**: write via `bus.setSelection(...)`, drop the
`REMOVE_DEF` sentinel export and — important — **delete your own `remove`
dispatch**: main.ts dispatches it on click now (double-fire today is harmless
only because the second one no-ops).
3. **Research UI, the centerpiece**: `T` opens the tech tree (24 nodes, 5 eras),
pick → `setResearch`; progress from `snapshot.research` (packs delivered vs
cost); locked machines in the build bar get greyed + padlock + "REQUIRES:
<tech>" tooltip (lock rule in CONTRACTS v4 notes); `researched` event → toast in
house voice ("STANDARD RATIFIED. NEW CRIMES AVAILABLE.") and the padlocks fall
off live.
4. **`?uidemo`** — as approved: boots `__fktryDemo()` after init.
5. **Accent adoption**: read `accent?` first, your derivation second, kind table
third (same precedence as RENDER — one sentence in NOTES confirming you both
read it identically).
1. **Scrolling tab strip** — your design; keep every tab reachable by `[`/`]` and
the strip legible at 29+ machines and REMOVE + RESEARCH.
2. **Publish inspect mode**: when the inspector opens, `setSelection({mode:
'inspect', entity})`; clear on close. RENDER highlights + draws cooler auras off
it — coordinate the timing so Esc clears both cleanly.
3. **Wildlife surfacing** (SIM lands it this round): infestation toasts in house
voice ("QUANTIZER HOSTS A SWARM. OSHA HAS BEEN NOTIFIED. OSHA IS THE SWARM."),
inspector shows a MOSQUITO EXPOSURE line on a targeted machine + the speed
penalty; a pile nearing hatch (size > 0.8) earns one warning toast, not a nag.
4. **Migrate research-granting tests** to the `grantResearch` command once SIM
lands it (drop the save-blob mutation).
5. **Verify RENDER's demolition tint + inspect highlight** with your own eyes and
screenshot it in NOTES — third round this handshake has floated unverified.
**Definition of done:** banner distinguishes RESERVE from BROWNOUT under test; a
full research run is verifiable in `live.test.ts` (lock visible → research → unlock
→ place succeeds); remove mode works through the typed protocol with RENDER's tint.
**Definition of done:** tab strip scrolls; inspect mode round-trips with RENDER
(screenshot); a swarm's lifecycle is narratable from UI alone (warning → infested →
trapped); research tests no longer parse save blobs.
---

View File

@ -62,8 +62,10 @@ export interface MachineDef {
coolPerTick?: number; // v3: per-machine cooling rate; sim default 0.004
accent?: string; // v4: art-directed emissive accent; fallback stays derived
// from first output item (renderer + UI share the derivation)
coolRadius?: number; // v4: spatial cooling aura in tiles (chebyshev from footprint);
// this machine's coolPerTick also applies to machines in range
coolRadius?: number; // v4: spatial cooling aura in tiles (chebyshev from footprint).
// v5 CLARIFIED: auras are ADDITIVE — a machine in range cools at
// its own coolPerTick + sum of in-range auras, capped at the
// exported HEAT_COOL_MAX (see src/sim/constants.ts)
}
export interface TechDef {
@ -128,7 +130,16 @@ export interface SimSnapshot {
// order. May alias a live array — read-only, never
// retain. HARDENS IN v5: update fixtures this round.
commissionProgress: Record<string, number>;
research?: ResearchState; // v4: research progress (hardens in v5)
research?: ResearchState; // v4: research progress (hardens in v6)
wildlife?: ReadonlyArray<WildlifeState>; // v5: piles & swarms (M2's living hazards)
}
export interface WildlifeState {
id: number;
kind: 'dust-pile' | 'mosquito-swarm';
pos: Vec2;
size: number; // pile: accumulation 0..1; swarm: severity 0..1
target?: number; // swarm: entity id it is currently degrading
}
export interface ResearchState {
@ -146,6 +157,9 @@ export type Command =
| { kind: 'setRecipe'; entity: number; recipe: string | null }
| { kind: 'setRecipeAt'; pos: Vec2; recipe: string | null } // v3: id-free variant
| { kind: 'setResearch'; tech: string | null } // v4: pick the lab target
| { kind: 'grantResearch'; tech: string[] } // v5: instant unlock — the
// sandbox/demo verb, also a
// legitimate creative mode
| { kind: 'setPaused'; paused: boolean };
export type SimEvent =
@ -157,7 +171,9 @@ export type SimEvent =
| { kind: 'removed'; entity: number; def: string; tick: number }
| { kind: 'commissionDone'; commission: string; tick: number }
| { kind: 'scram'; entity: number; on: boolean; tick: number } // v2: heat shutdown edge
| { kind: 'researched'; tech: string; tick: number }; // v4: research complete
| { kind: 'researched'; tech: string; tick: number } // v4: research complete
| { kind: 'wildlife'; on: 'spawned' | 'cleared'; wildlife: 'dust-pile' | 'mosquito-swarm';
id: number; pos: Vec2; tick: number }; // v5: hazard lifecycle
// ---------------------------------------------------------------- module interfaces
@ -175,6 +191,7 @@ export interface Sim {
export type SelectionState =
| { mode: 'build'; def: string; dir: Dir }
| { mode: 'remove' }
| { mode: 'inspect'; entity: number } // v5: open inspector — renderer highlights it
| null;
export interface Renderer {

View File

@ -24,20 +24,24 @@ const screen = createScreenFX();
// v4 bridge: _sel keeps the legacy {def,dir} shape (including the deprecated
// '__remove' sentinel) so unmigrated lanes keep working; selection()/setSelection()
// are the typed protocol. The sentinel path is deleted in v5.
const bus: UIBus & { _sel: { def: string; dir: Dir } | null } = {
_sel: null,
const bus: UIBus & { _sel: { def: string; dir: Dir } | null; _state: SelectionState } = {
_sel: null, // legacy mirror (build/remove only) — served for selectedBuild() compat
_state: null, // v5: typed truth, carries inspect mode
dispatch: (cmd: Command) => sim.enqueue(cmd),
selectedBuild: () => bus._sel,
pickTile: (x, y) => renderer.pickTile(x, y),
selection: (): SelectionState => {
if (!bus._sel) return null;
if (bus._state) return bus._state;
if (!bus._sel) return null; // legacy writer fallback
if (bus._sel.def === '__remove') return { mode: 'remove' };
return { mode: 'build', def: bus._sel.def, dir: bus._sel.dir };
},
setSelection: (sel: SelectionState) => {
bus._sel = sel === null ? null
: sel.mode === 'remove' ? { def: '__remove', dir: 0 as Dir }
: { def: sel.def, dir: sel.dir };
bus._state = sel;
bus._sel =
sel?.mode === 'build' ? { def: sel.def, dir: sel.dir } :
sel?.mode === 'remove' ? { def: '__remove', dir: 0 as Dir } :
null;
},
};
// LANE-UI sets the build selection through this hook:
@ -55,10 +59,11 @@ async function boot() {
const sel = bus.selection!();
const tile = renderer.pickTile(e.clientX, e.clientY);
if (sel?.mode === 'remove') {
// legacy def string kept alongside the mode until renderers migrate (v5 removes it)
renderer.setGhost('__remove', tile, 0, 'remove');
renderer.setGhost(null, tile, 0, 'remove');
} else if (sel?.mode === 'build') {
renderer.setGhost(sel.def, tile, sel.dir, 'build');
} else {
renderer.setGhost(sel?.def ?? null, tile, sel?.dir ?? 0, 'build');
renderer.setGhost(null, tile, 0, 'build'); // inspect / no selection: no ghost
}
});
game.addEventListener('pointerdown', (e) => {
@ -67,7 +72,7 @@ async function boot() {
const tile = renderer.pickTile(e.clientX, e.clientY);
if (!sel || !tile) return;
if (sel.mode === 'remove') sim.enqueue({ kind: 'remove', pos: tile });
else sim.enqueue({ kind: 'place', def: sel.def, pos: tile, dir: sel.dir });
else if (sel.mode === 'build') sim.enqueue({ kind: 'place', def: sel.def, pos: tile, dir: sel.dir });
});
const MS_PER_TICK = 1000 / TICKS_PER_SECOND;
@ -85,13 +90,18 @@ async function boot() {
}
requestAnimationFrame(loop);
// dev-only: paste-free demo — builds SIM's reference factory
// dev-only: paste-free demo — builds SIM's reference factory. The layout uses
// research-gated ids, so it is a post-research save by definition: grant the techs
// it needs first (testkit rides the public save/load API), then dispatch.
if (import.meta.env.DEV) {
(window as any).__fktryDemo = async () => {
const { referenceFactory } = await import('./sim/reference');
const { referenceFactory, referenceFactoryTech } = await import('./sim/reference');
const { grantResearch } = await import('./sim/testkit');
const techs = referenceFactoryTech(data);
grantResearch(sim, techs);
const cmds = referenceFactory(data);
cmds.forEach((c) => sim.enqueue(c));
return `${cmds.length} commands dispatched`;
return `${techs.length} techs granted, ${cmds.length} commands dispatched`;
};
}
}

View File

@ -64,7 +64,7 @@ function harness(opts: { unlocked?: boolean; reference?: boolean } = {}) {
const sel = bus.selection!();
if (!sel || !pick) return;
if (sel.mode === 'remove') sim.enqueue({ kind: 'remove', pos: pick });
else sim.enqueue({ kind: 'place', def: sel.def, pos: pick, dir: sel.dir });
else if (sel.mode === 'build') sim.enqueue({ kind: 'place', def: sel.def, pos: pick, dir: sel.dir });
});
if (opts.reference !== false) for (const c of referenceFactory(DATA)) sim.enqueue(c);