From 557c78b20853d95d1dd4d918634074fa07fe395f Mon Sep 17 00:00:00 2001 From: type-two Date: Fri, 17 Jul 2026 21:31:57 +1000 Subject: [PATCH] [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 --- fktry/MASTERPLAN.md | 11 ++++++ fktry/lanes/LANE-DATA.md | 69 ++++++++++++++++++-------------------- fktry/lanes/LANE-RENDER.md | 54 ++++++++++++++--------------- fktry/lanes/LANE-SCREEN.md | 53 ++++++++++++++--------------- fktry/lanes/LANE-SIM.md | 67 +++++++++++++++++------------------- fktry/lanes/LANE-UI.md | 58 +++++++++++++------------------- fktry/src/contracts.ts | 25 +++++++++++--- fktry/src/main.ts | 36 +++++++++++++------- fktry/src/ui/live.test.ts | 2 +- 9 files changed, 194 insertions(+), 181 deletions(-) diff --git a/fktry/MASTERPLAN.md b/fktry/MASTERPLAN.md index 5b30114..62be0bc 100644 --- a/fktry/MASTERPLAN.md +++ b/fktry/MASTERPLAN.md @@ -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 -- `. (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`, diff --git a/fktry/lanes/LANE-DATA.md b/fktry/lanes/LANE-DATA.md index b8cf640..2a22155 100644 --- a/fktry/lanes/LANE-DATA.md +++ b/fktry/lanes/LANE-DATA.md @@ -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. --- diff --git a/fktry/lanes/LANE-RENDER.md b/fktry/lanes/LANE-RENDER.md index 18e4b62..f89ba7b 100644 --- a/fktry/lanes/LANE-RENDER.md +++ b/fktry/lanes/LANE-RENDER.md @@ -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. --- diff --git a/fktry/lanes/LANE-SCREEN.md b/fktry/lanes/LANE-SCREEN.md index 00dbad3..0ca37d5 100644 --- a/fktry/lanes/LANE-SCREEN.md +++ b/fktry/lanes/LANE-SCREEN.md @@ -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. --- diff --git a/fktry/lanes/LANE-SIM.md b/fktry/lanes/LANE-SIM.md index 82a9671..62995d1 100644 --- a/fktry/lanes/LANE-SIM.md +++ b/fktry/lanes/LANE-SIM.md @@ -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. --- diff --git a/fktry/lanes/LANE-UI.md b/fktry/lanes/LANE-UI.md index a4c32d5..3fc4158 100644 --- a/fktry/lanes/LANE-UI.md +++ b/fktry/lanes/LANE-UI.md @@ -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: - " 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. --- diff --git a/fktry/src/contracts.ts b/fktry/src/contracts.ts index 15a1a4d..7a9410e 100644 --- a/fktry/src/contracts.ts +++ b/fktry/src/contracts.ts @@ -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; - research?: ResearchState; // v4: research progress (hardens in v5) + research?: ResearchState; // v4: research progress (hardens in v6) + wildlife?: ReadonlyArray; // 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 { diff --git a/fktry/src/main.ts b/fktry/src/main.ts index 91c515d..7de0156 100644 --- a/fktry/src/main.ts +++ b/fktry/src/main.ts @@ -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`; }; } } diff --git a/fktry/src/ui/live.test.ts b/fktry/src/ui/live.test.ts index 4aa383e..05fb955 100644 --- a/fktry/src/ui/live.test.ts +++ b/fktry/src/ui/live.test.ts @@ -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);