[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 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-18 12:56:09 +10:00
parent e3d1a724e9
commit 8dd4616580
10 changed files with 265 additions and 139 deletions

View File

@ -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).

View File

@ -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.

View File

@ -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.
---

View File

@ -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.
---

View File

@ -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.
---

View File

@ -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.
---

View File

@ -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.
---

12
fktry/src/audio/index.ts Normal file
View File

@ -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) {},
};
}

View File

@ -132,6 +132,14 @@ export interface SimSnapshot {
commissionProgress: Record<string, number>;
research?: ResearchState; // v4: research progress (hardens in v6)
wildlife?: ReadonlyArray<WildlifeState>; // v5: piles & swarms (M2's living hazards)
relics?: ReadonlyArray<RelicState>; // 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

View File

@ -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);