From 8dd46165808451fecf6afc2bd6162d0800a6a1ae Mon Sep 17 00:00:00 2001 From: type-two Date: Sat, 18 Jul 2026 12:56:09 +1000 Subject: [PATCH] [orchestrator] MEGA round 5: the paint and the pipe Supersedes unexecuted round-5 orders. Folds in the live first-eyes review findings (silence, void floor, onboarding cliff, quiet payoffs) + John's hidden-radio idea. Contracts v6: AudioFX (THE SPEAKER - all Web Audio synthesis, no asset files), RelicState + excavate + relicFound (the optical-fossil hidden pipe: find it and the game grows a tuner - FKTRY FM, Correction propaganda subtitled by THE SCREEN's chyron, a shortwave numbers station reading hidden-seam coordinates, dead air). main.ts wires audio + first-gesture unlock; src/audio stub awaits LANE-SCREEN. Lane orders: SIM = wildlife + seams-become-real + the relic + research time; RENDER = the floor (seam glints, strata whispers), relic shimmer (no signposting), juice, break-room cabinet -> match.html; UI = mercy toast, tuner dock, SCREEN click-to-enlarge, camera hints; SCREEN = era feed + THE SPEAKER (3 audio layers, 4 stations, spectral verification); DATA = the commission-ladder tutorial, stations.json broadcast schedule, seams.json final. Ship gate: after review, redeploy partly.party/glytch. Co-Authored-By: Claude Fable 5 --- fktry/CONTRACTS.md | 13 +++++++ fktry/MASTERPLAN.md | 8 ++++ fktry/lanes/LANE-DATA.md | 75 ++++++++++++++++++++++---------------- fktry/lanes/LANE-RENDER.md | 62 ++++++++++++++++++------------- fktry/lanes/LANE-SCREEN.md | 75 +++++++++++++++++++++++++------------- fktry/lanes/LANE-SIM.md | 67 ++++++++++++++++++---------------- fktry/lanes/LANE-UI.md | 61 +++++++++++++++++++------------ fktry/src/audio/index.ts | 12 ++++++ fktry/src/contracts.ts | 24 +++++++++++- fktry/src/main.ts | 7 ++++ 10 files changed, 265 insertions(+), 139 deletions(-) create mode 100644 fktry/src/audio/index.ts diff --git a/fktry/CONTRACTS.md b/fktry/CONTRACTS.md index 61f55f8..fbacfc9 100644 --- a/fktry/CONTRACTS.md +++ b/fktry/CONTRACTS.md @@ -68,3 +68,16 @@ batches them between rounds. Until granted, work around locally inside your lane whose heatPerTick ≤ its coolPerTick can never overheat — the validator enforces it. - Research gating: ids appearing in any `TechDef.unlocks` are locked until researched; ids referenced by no tech are always available. + +## v6 notes + +- **THE SPEAKER**: `AudioFX` (src/audio/**, LANE-SCREEN) — all sound is Web Audio + synthesis, no asset files. main.ts wires init/unlock/onEvents/frame. Pre-discovery + only the diegetic minimum plays; the band unlocks on `relicFound`. +- **Sanctioned narrow imports** (read-only, documented at the export site): + `src/sim/constants.ts` (tuning truth) and `src/audio`'s `radio` control (UI's + tuner dock). Everything else stays contract-mediated. +- **Relics are secrets.** `snapshot.relics` is always present, but nothing may + signpost an unfound relic: RENDER draws the subtle shimmer, UI shows nothing until + `found`. Discoverability budget: wandering + curiosity. That's the design. +- New jam reason string: `'no seam'` (extractor off-seam). diff --git a/fktry/MASTERPLAN.md b/fktry/MASTERPLAN.md index 62be0bc..a101d61 100644 --- a/fktry/MASTERPLAN.md +++ b/fktry/MASTERPLAN.md @@ -102,3 +102,11 @@ diff each lane against its owned paths (ownership violations get reverted), veri claimed features actually work, reconcile cross-lane questions, update CONTRACTS if (and only if) two lanes need it, write next CURRENT ORDERS in each lane doc, commit `[orchestrator] round N orders`. + +## ROUND 5 (MEGA) addendum + +The original round-5 orders were superseded before execution by the MEGA round +(same goals + the paint-and-pipe layer from the live first-eyes review: sound via +the hidden optical-fossil radio, the commission-ladder tutorial, the floor, juice, +click-to-enlarge SCREEN, the break-room cabinet). After this round's review passes: +**redeploy to partly.party/glytch via ../deploy.sh** — that's the ship gate. diff --git a/fktry/lanes/LANE-DATA.md b/fktry/lanes/LANE-DATA.md index 2a22155..3943e09 100644 --- a/fktry/lanes/LANE-DATA.md +++ b/fktry/lanes/LANE-DATA.md @@ -25,41 +25,52 @@ REQUESTs. --- -## CURRENT ORDERS — Round 5 (goal: the world gets teeth and ground) +## CURRENT ORDERS — ROUND 5 (MEGA) — supersedes the earlier round-5 text -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. +Review verdict you're executing: the onboarding cliff. The fax IS the tutorial — +you write the ladder. Plus the radio's entire broadcast schedule and the ground +truth for the floor. -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. +### PART A — carried from round 5 +1. **Constants**: import from `src/sim/constants.ts`, retire hand-copies. +2. **Wildlife data**: MOSQUITO TRAP machine (§5: bug-zapper shaped like a + high-contrast edge target) + catch recipe (canned `mosquito-swarm`), pile/hatch + tuning if SIM makes it data-driven, `vaporwave-sunset` unblocked, FORWARD_CANON + pruned, fax celebrates in voice. +3. **Cooler numbers final** under the additive ruling; duty table 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. +### PART B — the ladder, the ground, the broadcast schedule (new) +4. **THE COMMISSION LADDER — the game's real tutorial.** Rewrite the opening queue + as an escalating teaching ladder, one mechanic per rung, all in voice: + - Rung 1: ~20 MDAT-ORE, raw ("Do not wash it. Do not ask why.") — teaches + extractor→belt→uplink. MUST be completable inside 3 minutes with 4 machine + types; prove it with a sim probe test like your tick-0 melt guard. + - Rung 2: LUMA BARS (teaches demuxer + byproduct pain — the slurry has to go + somewhere and the fax does not care). + - Rung 3: HF DUST (teaches the quantizer + that waste is product — DUST + ALLOWANCE fits here). + - Rung 4: one MELT, fresh (first-taste, reward bumped — it's a graduation now). + - Thereafter: your existing escalation. Ladder intent documented in CANON-DELTA. +5. **`data/seams.json` FINAL** (SIM consumes this round): seam regions per stratum + era with richness; the reference factory's ground coordinated with SIM; include + 2-3 `hidden: true` bonus seams — the numbers station reads their coordinates. +6. **`data/stations.json`**: the broadcast schedule — station ids, band, frequency, + name, format tag, flavor line, and the copy pools: THE STANDARD's propaganda + chyron lines (polite, clean, horrifying — this is The Correction with a + transmitter; 20+ lines so it doesn't loop fast), NUMBERS digit-group config + (incl. the hidden-seam pointers), DEAD AIR's one ghost line. Schema agreed with + SCREEN in NOTES. +7. **Relic canon**: CANON-DELTA the optical-fossil mechanics reading (§1 fossils + "sing when excavated" → the severed audio bus) + one mid-ladder fax margin that + *hints* without pointing ("someone has drawn a sine wave in the corner of this + fax. it is not our department."). +8. **Validator**: seams/stations schemas, ladder invariants (rung-1 buildability at + tick 0), relic never on a seam (cross-check vs SIM's placement rule). + +**Definition of done:** cold-start rung 1 provably completable fast; every station +has enough copy to broadcast for 10 minutes without repeating; seams validated +against both SIM's consumption and RENDER's rendering; CANON-DELTA tells the whole +story of the round. --- diff --git a/fktry/lanes/LANE-RENDER.md b/fktry/lanes/LANE-RENDER.md index f89ba7b..e1e7b9c 100644 --- a/fktry/lanes/LANE-RENDER.md +++ b/fktry/lanes/LANE-RENDER.md @@ -25,35 +25,45 @@ you never simulate. If motion looks wrong, interpolate better or note it — don --- -## CURRENT ORDERS — Round 5 (goal: a living, inspectable world) +## CURRENT ORDERS — ROUND 5 (MEGA) — supersedes the earlier round-5 text -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. +The review verdict driving this round: *the game about place has no place.* You own +fixing that. Everything from the previous round-5 orders is carried, plus the floor, +the juice, and one secret you must draw without signposting. -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. +### PART A — carried from round 5 +1. **Inspect mode**: highlight the inspected entity via `{mode:'inspect'}`; cooler + aura rectangle on inspect (the placed-and-selected case). +2. **Wildlife visuals**: piles = §2 glitter heaps growing with `size`; swarms = + pixel-gnat clouds clustering on `target` (harassed machines flicker); trap zap + read from wildlife-state deltas. +3. **Fixture hygiene**: commissionQueue/research/wildlife/relics in fabricated snapshots. -**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. +### PART B — the floor, the juice, the secret (new) +4. **THE FLOOR.** Kill the void: seam regions from `data/seams.json` render as + subtle glinting mineral patches (the player should *discover* that extractors + want them — the glint is the tell); large-scale ground-tone variation (fbm + patches, barely-there) so the plane reads as terrain; era hinting toward the + world edges per §1 strata (sepia/phosphor whispers at the rim). Restraint rule: + the floor must never compete with belts/machines — it's a stage, not a set. +5. **THE RELIC SHIMMER.** `snapshot.relics` unfound → a faint animated static-patch + decal at its pos: visible to a player actually looking, invisible to one who + isn't. NO signpost, NO minimap ping, NO tutorial arrow — this is a Mario hidden + pipe; its discoverability budget is 'wandering + curiosity'. Found → the patch + becomes a small excavated monument (fossil on a plinth, §1: it *sings*). +6. **JUICE**: placement thunk (scale-pop + dust puff), demolition burst, shipment + flash at the uplink (read shippedTotal deltas from the snapshot). Cheap, + instanced, everywhere. +7. **THE BREAK ROOM.** A decorative arcade cabinet prop near the start zone, marquee + glowing 'GLYTCH', render-owned raycast: click → `window.open('./match.html')`. + The two games finally meet. Coordinate the cursor affordance with UI. +8. **CAMERA + CLICKS (review findings)**: default framing centers the start zone + clear of the build bar (my first machines spawned half-behind it and were + unclickable); investigate entity picking near UI chrome and report what you find. + +**Definition of done:** before/after screenshots of the floor; relic shimmer shown +at two zoom levels (proving subtle-but-findable); cabinet opens match.html; thunk +felt; perf rule holds with floor + 50 piles + 10 swarms. --- diff --git a/fktry/lanes/LANE-SCREEN.md b/fktry/lanes/LANE-SCREEN.md index 0ca37d5..a1967ba 100644 --- a/fktry/lanes/LANE-SCREEN.md +++ b/fktry/lanes/LANE-SCREEN.md @@ -27,34 +27,59 @@ seed stock — port, don't move). --- -## CURRENT ORDERS — Round 5 (goal: the sky learns eras and insects) +## CURRENT ORDERS — ROUND 5 (MEGA) — supersedes the earlier round-5 text -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. +You are now two organs: THE SCREEN (sky) and **THE SPEAKER** (`src/audio/**` is +yours — replace the orchestrator stub; contracts v6 `AudioFX` is wired in main.ts +with a first-gesture `unlock()`). House law: **every sound is synthesized with Web +Audio. No asset files, ever.** A game about broken media makes its own noise. -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. +### PART A — carried from round 5 (THE SCREEN) +1. **Era base feed**: the clean programming ages with the highest ratified era + (sepia/gate-weave reel → phosphor/interlace broadcast → rainbow-on-black disc → + current SMPTE stream → blinding fortress). Corruption stays era-agnostic; the + SUBSTRATE ages. Sterile guarantee holds per era (test one). +2. **Jammed-rot**: jammed entities accelerate boredom decay; clean idle stays gentle. +3. **Wildlife shimmer**: swarms drive regional mosquito-noise via the growth-mask + template, seeded from the swarm pos. +4. **Title card**: WIMVEE FKTRY burn-in ~2s on boot (the game never says its own + name — fix that), then NOTHING IS WRONG. First-ever shipment's awakening upgrades + from 1 frame to a ~0.5s full-canvas moment. -**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. +### PART B — THE SPEAKER (new, the big build) +5. **Layer 1 — diegetic minimum (always on, pre-discovery)**: placement thunk (low + sine + noise burst), fax dot-matrix chatter, brownout pop-crackle + power-down + whine (the codex's catastrophic underrun sound — finally), scram klaxon, + ratification sting (a clean consonant triad — The Correction resolves; you don't). + NOTHING else before discovery: the silence is the tease. +6. **Layer 2 — THE BAND (unlocks on relicFound)**: a tuner. Frequency sweep with + band-appropriate inter-station noise (AM = brown crackle, FM = white hiss, SW = + warble + heterodyne whistles near carriers); station lock via gaussian around + each station's freq. Stations from `data/stations.json` (DATA authors; schema + yours to agree in NOTES): + - **88.1 FKTRY FM** — generative music: seeded 16-step sequencer, square+triangle + voices, pentatonic minor ~92bpm, pattern mutates every 32 bars, flavored by the + current era (reel era gets wow/flutter + crackle). + - **AM 640 THE STANDARD** — Correction propaganda: ring-mod mumble cadence, and + the flagship trick: **THE SCREEN's chyron subtitles the radio** when tuned + (DATA writes the lines). The sky captions the audio. Do this one properly. + - **SW 6955 NUMBERS** — cold formant-synth digit groups; chyron shows the digits; + some groups encode coordinates of DATA's `hidden: true` bonus seams — the radio + is a treasure map for players with a pencil. + - **108.0 DEAD AIR** — room tone, breathing, one reversed-chime ghost per ~10min, + seeded. +7. **Layer 3 — world ambience (post-discovery)**: factory hum scaled by draw/gen, + tape-chunk per shipment stamp, heat adds a strained whine near scram. Subtle; + ducked under the radio. +8. **The control surface**: export a narrow `radio` API (setFrequency/setBand/ + volume/mute/getState/subscribe) — UI's tuner dock drives it (sanctioned import, + document it). Master chain: gain → soft limiter; zero per-frame allocations. + +**Definition of done:** spectral verification (AnalyserNode: station lock vs static +show distinct spectra; brownout pop is a real transient); pre-discovery silence +guarantee tested; subtitles verified on the chyron; era feed contact sheet; CPU +budget stated. This round, verification includes LISTENING — describe what it +sounds like in NOTES, in words. --- diff --git a/fktry/lanes/LANE-SIM.md b/fktry/lanes/LANE-SIM.md index 62995d1..ffac40f 100644 --- a/fktry/lanes/LANE-SIM.md +++ b/fktry/lanes/LANE-SIM.md @@ -21,40 +21,45 @@ playable through unit tests. You are the lane the other four trust with their li --- -## CURRENT ORDERS — Round 5 (goal: M2 closes — the Bitstream comes alive) +## CURRENT ORDERS — ROUND 5 (MEGA) — supersedes the earlier round-5 text -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). +This is the big one: everything from the previous round-5 orders PLUS the world's +first secret. Read it all before starting; the parts interlock. -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. +### PART A — carried from round 5 (unchanged in spirit) +1. **`grantResearch` command** — idempotent, save/load safe. Tell me in NOTES when it + lands; main.ts's `__fktryDemo` swaps off testkit. +2. **Research time** — `RESEARCH_TICKS_PER_PACK` (export it); a tech is an + investment, not a purchase. Fractional vs stepped progress: your call, note it. +3. **Wildlife v1** (contracts v5 shapes): dust piles accumulate near running + hf-dust-emitting machines (seeded RNG finally rolls); size 1.0 hatches a MOSQUITO + SWARM that drifts to the nearest running machine and degrades its speed while + attached (`target`); cleared by DATA's trap machine (suggested shape: trap crafts + its empty-input catch recipe only while a swarm is in radius, producing the canned + `mosquito-swarm` item). Deterministic; save/load carries it; spawn/clear events. + Include the design-point test: a factory that sinks its dust never hatches. -**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. +### PART B — the ground and the pipe (new) +4. **SEAMS BECOME REAL.** Consume `data/seams.json` (DATA authors this round — + coordinate the schema in NOTES early): extractors only extract while their + footprint overlaps a seam region; off-seam extractors idle with a new jam reason + `'no seam'` (edge-triggered like the others; UI will voice it). Reference factory + v5 must sit on seams — coordinate layout coordinates with DATA. +5. **THE RELIC (the hidden pipe).** One `optical-fossil` per world (contracts v6: + `RelicState`, `excavate`, `relicFound`): position derived from the seed — + deterministic, 18-28 tiles from origin in a quiet corner, never on a seam, never + under the reference factory. `excavate` within 1 tile of the unfound relic flips + `found`, emits `relicFound`, persists through save/load forever. `relics` always + present in the snapshot (found or not — RENDER draws the shimmer, UI must not be + able to cheat-detect it any other way). No bandwidth cost, no research gate: the + only lock is *finding it*. +6. **Reference factory v5**: research rig (bottler + archaeology lab off quantizer + surplus) so research runs in-demo; sits on seams per #4; one swarm hatches and is + trapped within the 20k window if DATA's trap lands. Zero-brownout regression stays. + +**Definition of done:** relic lifecycle tested (excavate too far = dropped; found +persists through save→load→20k ticks); off-seam extractor refuses with 'no seam'; +wildlife full lifecycle; research takes time; reference v5 green and on-seam. --- diff --git a/fktry/lanes/LANE-UI.md b/fktry/lanes/LANE-UI.md index 3fc4158..cadd4f5 100644 --- a/fktry/lanes/LANE-UI.md +++ b/fktry/lanes/LANE-UI.md @@ -24,33 +24,46 @@ dispatch Commands via the bus and read state from `update(snap, events)`. --- -## CURRENT ORDERS — Round 5 (goal: the pressure has a face) +## CURRENT ORDERS — ROUND 5 (MEGA) — supersedes the earlier round-5 text -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. +Review verdicts you're executing: first-click punishment without guidance, camera +controls taught nowhere, THE SCREEN's payoff too quiet, and one suspect tab click. +Plus the radio — the game's sound is a *discovery*, and you build its face. -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. +### PART A — carried from round 5 +1. **Scrolling tab strip** (your design, approved). +2. **Publish inspect mode**: inspector open → `setSelection({mode:'inspect', + entity})`; clear on close; Esc order preserved (drop tool → close inspector). +3. **Wildlife surfacing**: infestation toasts in voice; MOSQUITO EXPOSURE line + + speed penalty in the inspector; one hatch-warning toast per pile, not a nag. +4. **Migrate research tests** to `grantResearch`; **verify RENDER's demolition tint + + inspect highlight with screenshots** (third round owed — close it). -**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. +### PART B — onboarding + the radio face (new) +5. **First-brownout mercy toast** (once per session): "NOTHING IS GENERATING. THE + COMMITTEE SUGGESTS: POWER." — fires on the first brownout event only. The alarm + currently teaches nothing; this is the one hint the opening needs. +6. **Camera hint** in the idle line ("MMB/WASD PAN · WHEEL ZOOM") and a **tab + hitbox check**: in review, a direct click on the POWER tab did nothing once + (keyboard paging worked; may be harness — reproduce with a real mouse, fix or + file it as harness with evidence). +7. **New jam voice**: `'no seam'` lands from SIM — give it the line it deserves + ("SEAM EXTRACTOR: THERE IS NOTHING DOWN THERE. MOVE."). +8. **THE SCREEN click-to-enlarge**: click toggles a centered ~2.5× overlay (CSS + transform + backdrop dim), Esc/click closes. The sky is the score; let people + look at it. +9. **THE TUNER DOCK (the radio's face).** Hidden until `relicFound`/`relics.found`. + Then: a dock slides in (bottom-right) — frequency dial (drag + arrow keys), band + AM/FM/SW, volume + mute, station readout in voice ("88.1 FKTRY FM — SIGNAL + LOCKED"). Controls talk to THE SPEAKER via its exported `radio` control (narrow + sanctioned import from `src/audio` — SCREEN documents the API; coordinate in + NOTES). Discovery ceremony: relicFound → "AUDIO BUS RESTORED. THE BITSTREAM HAS + A VOICE." toast + dock slide-in. Radio state persists per session only (the + fossil's `found` is the durable part, sim-side). + +**Definition of done:** cold-start first-brownout shows the mercy toast; tuner dock +appears only after excavation and drives the real audio engine; enlarge verified; +tint/highlight screenshots in NOTES; tab click adjudicated. --- diff --git a/fktry/src/audio/index.ts b/fktry/src/audio/index.ts new file mode 100644 index 0000000..52b6001 --- /dev/null +++ b/fktry/src/audio/index.ts @@ -0,0 +1,12 @@ +/** LANE-SCREEN territory (THE SPEAKER). Stub — replace per lanes/LANE-SCREEN.md. + * All sound is Web Audio synthesis; no asset files, ever. */ +import type { AudioFX, GameData, SimEvent, SimSnapshot } from '../contracts'; + +export function createAudioFX(): AudioFX { + return { + init(_data: GameData) {}, + unlock() {}, + onEvents(_events: SimEvent[]) {}, + frame(_timeMs: number, _snap?: SimSnapshot) {}, + }; +} diff --git a/fktry/src/contracts.ts b/fktry/src/contracts.ts index 7a9410e..e12fbcc 100644 --- a/fktry/src/contracts.ts +++ b/fktry/src/contracts.ts @@ -132,6 +132,14 @@ export interface SimSnapshot { commissionProgress: Record; research?: ResearchState; // v4: research progress (hardens in v6) wildlife?: ReadonlyArray; // v5: piles & swarms (M2's living hazards) + relics?: ReadonlyArray; // v6: buried secrets (the hidden pipe) +} + +export interface RelicState { + id: number; + kind: 'optical-fossil'; // v6: the severed audio bus — one per world + pos: Vec2; + found: boolean; // excavated; persists through save/load } export interface WildlifeState { @@ -160,6 +168,7 @@ export type Command = | { kind: 'grantResearch'; tech: string[] } // v5: instant unlock — the // sandbox/demo verb, also a // legitimate creative mode + | { kind: 'excavate'; pos: Vec2 } // v6: dig at a relic site | { kind: 'setPaused'; paused: boolean }; export type SimEvent = @@ -173,7 +182,8 @@ export type SimEvent = | { kind: 'scram'; entity: number; on: boolean; tick: number } // v2: heat shutdown edge | { 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 + id: number; pos: Vec2; tick: number } // v5: hazard lifecycle + | { kind: 'relicFound'; relic: 'optical-fossil'; id: number; tick: number }; // v6 // ---------------------------------------------------------------- module interfaces @@ -223,6 +233,18 @@ export interface UI { update(snap: SimSnapshot, events: SimEvent[]): void; } +/** v6: THE SPEAKER — everything audible. All sound is synthesized (Web Audio), no + * asset files. Pre-discovery: minimal diegetic UI sounds only. After the optical + * fossil is found (snapshot.relics / relicFound), the full band unlocks: tuner, + * stations, world ambience. Owned by LANE-SCREEN (src/audio/**). */ +export interface AudioFX { + init(data: GameData): void; + /** call on first user gesture — browsers gate AudioContext on interaction */ + unlock(): void; + onEvents(events: SimEvent[]): void; + frame(timeMs: number, snap?: SimSnapshot): void; +} + export interface ScreenFX { init(canvas: HTMLCanvasElement, data: GameData): void; onEvents(events: SimEvent[]): void; // shipped items feed the corruption diff --git a/fktry/src/main.ts b/fktry/src/main.ts index 7de0156..43df33a 100644 --- a/fktry/src/main.ts +++ b/fktry/src/main.ts @@ -7,6 +7,7 @@ import { createSim } from './sim'; import { createRenderer } from './render'; import { createUI } from './ui'; import { createScreenFX } from './screen'; +import { createAudioFX } from './audio'; import items from '../data/items.json'; import machines from '../data/machines.json'; @@ -20,6 +21,7 @@ const sim = createSim(); const renderer = createRenderer(); const ui = createUI(); const screen = createScreenFX(); +const audio = createAudioFX(); // v4 bridge: _sel keeps the legacy {def,dir} shape (including the deprecated // '__remove' sentinel) so unmigrated lanes keep working; selection()/setSelection() @@ -52,6 +54,9 @@ async function boot() { await renderer.init(document.getElementById('game')!, data); ui.init(document.getElementById('ui')!, data, bus); screen.init(document.getElementById('screen') as HTMLCanvasElement, data); + audio.init(data); + // browsers gate AudioContext on a user gesture — unlock on the first one + addEventListener('pointerdown', () => audio.unlock(), { once: true }); // click-to-place / hover ghost glue const game = document.getElementById('game')!; @@ -86,6 +91,8 @@ async function boot() { ui.update(snap, events); screen.onEvents(events); screen.frame(now, snap); + audio.onEvents(events); + audio.frame(now, snap); requestAnimationFrame(loop); } requestAnimationFrame(loop);