diff --git a/fktry/lanes/LANE-SCREEN.md b/fktry/lanes/LANE-SCREEN.md index a1967ba..163b2ff 100644 --- a/fktry/lanes/LANE-SCREEN.md +++ b/fktry/lanes/LANE-SCREEN.md @@ -410,3 +410,131 @@ NEXT (not self-assigning): the era→standard table means THE SCREEN now knows a strata skins (sepia Reel, phosphor Broadcast, rainbow-on-black Disc) could drive the *base feed's* palette per the player's current era, so the clean programming itself ages as they tech up. That plus the mask template would give era-flavoured biomes on the sky. + +### Round 5 (MEGA) — 2026-07-18 — Opus 4.8 + +SHIPPED: all 8 orders, both organs. THE SPEAKER exists. +- PART A: `eras.ts` + `eras.test.ts` (new), `baseTexture.ts` (era substrate + general chyron), + `glitchStack.ts` (wildlife pass, title composite), `boredom.ts` (jammed-rot), `index.ts`. +- PART B: `graph.ts`, `sfx.ts`, `stations.ts`, `radio.ts`, `ambience.ts`, `subtitleBus.ts`, + `band.test.ts` (all new), and `index.ts` replacing the stub. +- 78 tests in my two lanes, all green. Repo-wide 443/443, tsc clean. + +**A NOTE ON "VERIFICATION INCLUDES LISTENING": I cannot hear.** I have no ears, and I won't +pretend a waveform is a listening session. What I did instead is render every sound through an +`OfflineAudioContext` and measure it — spectra, peak/RMS, attack times, onset rates — and the +description below is derived from those measurements plus the synthesis I wrote. It is an +honest account of what the numbers say the speaker is doing, not of what it feels like in a +room. **Please put headphones on and tell me what's wrong** — mix balance and "is this +annoying after ten minutes" are exactly what measurement can't answer, and I'd rather get that +from you than guess. + +WHAT IT SOUNDS LIKE (measured, then described): +- **Brownout** — the important one. A 0.29 ms attack: a genuine broadband click, not a fade. + Then ~0.5 s of bandpassed brown crackle and a sawtooth whine sliding 420→40 Hz under a + closing lowpass. It should read as a rack losing its rails: snap, spit, sag. +- **Placement thunk** — 2.3 ms attack, a sine dropping 150→55 Hz (the mass) under a 1.4 kHz + noise slap (the contact). Detuned per placement so a row of belts isn't a machine gun. +- **Scram klaxon** — 620/440 Hz square alternating four times, lowpassed at 1.8 kHz. Peak 0.29: + deliberately not the loudest thing, because it fires often. +- **Ratification sting** — 85 ms attack, F–A–C sines with an octave shimmer. The *only* + consonant, in-tune, untroubled sound in the game. Everything the player makes is filth; the + enemy sounds lovely. That's the joke, and it's why its attack is slow when everything else + is percussive. +- **FKTRY FM 88.1** — 4.9 note onsets/sec against 6.13 sixteenths at 92 bpm, i.e. the ~34% + rests are audible as space rather than a wall. Square lead over triangle bass, pentatonic + minor, pattern mutating every 32 bars. Spectrum is bass-dominant (60 Hz: 0.49) with nothing + above 4 kHz. +- **THE STANDARD 640** — 2.9 syllables/sec, which is real speech cadence. Ring-modulated + carrier through two moving formants, lowpassed at 4.5 kHz to AM bandwidth. Sits at 500 Hz + and nowhere else. It is deliberately just short of intelligible — you can hear that it's + speech and not what it says. **THE SCREEN tells you what it says.** That gap is the trick. +- **NUMBERS 6955** — 4.0 digits/sec, a formant voice with a per-digit vowel shape so numerals + are learnable by ear, through a narrow SW bandpass (sharp 2 kHz peak, 0.50). No affect, no + melody, no acknowledgement. +- **DEAD AIR 108.0** — lowpassed brown room tone breathing at 0.21 Hz (projector pace), plus + one reversed chime — swelling up out of nothing then cut dead — every ten minutes, seeded. + +DECISIONS: +- **Everything takes `(ctx, dest, t0)` and nothing assumes a live context.** That one choice is + why the whole speaker can be rendered in an `OfflineAudioContext` and measured deterministically + — same code the player hears, no test-only path. Noise buffers use a seeded PRNG for the same + reason: identical spectra every run. +- **The limiter is a `tanh` WaveShaper, not a DynamicsCompressor.** A compressor's makeup gain + would pump the entire mix every time the klaxon fires. tanh just refuses to exceed 1, which is + what "don't clip" actually means, and it adds the saturation a blown factory PA should have. +- **Pre-discovery silence is structural, not a volume setting.** The radio's master gain is 0 + and ambience is never started until `relicFound`. Layer 1 is all one-shots, so a quiet factory + is *actually* quiet between events rather than humming under a noise floor. +- **Inter-station noise is band-specific** because that's what makes a band feel like a place: + AM brown crackle, FM white hiss, SW a wandering bandpass plus a heterodyne whistle whose pitch + is the beat frequency — so it swoops *down to nothing* as you zero in. Hunting is the mechanic. +- **The era ages the substrate, never the corruption.** Same shader, same params; only what + they're painted onto changes. The contact sheet shows identical melt drips across all five eras. +- **Boot era is `reel`.** With nothing ratified the world starts in the deepest stratum and + climbs to the surface (stream) and beyond (fortress). The old SMPTE look is now mid-game. +- Title timed in seconds (1.53 s hold, gone by 2.07 s), awakening 0.47 s. Both are *moments*; + the scram wipe stays frame-counted because it's a tape artifact. Three clocks, three fictions. + +VERIFIED (offline render + pixel reads + contact sheets): +- **Pre-discovery silence: peak 0.0 and RMS 0.0, exactly zero**, with the dial swept across all + three bands and ambience asked for max load+fever. Same graph post-discovery: peak 0.285. +- **Station lock vs static are distinct spectra, and correctly so per band.** AM static has + **70× the 60 Hz energy of AM locked** (1.40 vs 0.02 — the brown crackle). FM *inverts*: locked + is bass-dominant (0.49 @60 Hz, nothing @14 k), static is treble-only hiss (0.10 @14 k, nothing + @60 Hz). SW locked shows a 2 kHz formant peak (0.50) that static (0.09) lacks. +- **Subtitles end-to-end**: tuned AM → The Correction's line on the chyron; off by 4 kHz (lock + 0.906) → still captioned; off station → caption clears; SW → `5 3 0 7 9` on the sky, which is + the real coordinate for the nitrate vein. FM and DEAD AIR caption nothing (no words). Screenshot + in the sheet. +- **Era contact sheet**: reel (sepia + sprocket holes), broadcast (phosphor + interlace), disc + (rainbow-on-black gloss), stream (classic SMPTE), fortress (blinding white) — all under the + same 20× melt. +- **Budget: SCREEN 0.040 ms/frame** (GPU sync, 2000 entities, every artifact + wildlife + strain + + brownout). **SPEAKER 0.0063 ms/frame** on the worst case (SW numbers + heterodyne + ambience) + — the per-frame path only writes AudioParams; Web Audio does synthesis on its own thread. + +BLOCKED/BROKEN: nothing blocking. +- **I caught a balance bug by measuring rather than hearing**: DEAD AIR rendered at RMS 0.072, + *louder than the music station* (0.032), which made "dead air" the loudest thing on the dial — + exactly backwards. Dropped its tone gain 0.32→0.10 and the breath depth with it; it now sits at + 0.023, under the music. This is the clearest argument for you actually listening: I only found + it because I happened to print RMS side by side. +- Harness gotcha, mine not the product's: importing a module with a `?v=` cache-buster creates a + **second module instance**, so my first subtitle test wrote to a different singleton than THE + SCREEN reads and the chyron looked broken. Verified `busQ !== busC`. Re-ran canonically and it + works. Same family as round 3's clock rewind: the harness lied, not the code. + +FINDING FOR LANE-DATA (not touching their file): `stations.json`'s `_doc` says the decoys +"decode to empty ground", but **two of them decode onto real seams** — `08127` → (-24,-20) lands +in `reel-beds-deep`, and `27503` → (-5,18) lands in `broadcast-shelf-south`. Both are visible, +non-hidden seams so no secret leaks and the treasure hunt is intact — it's a doc/data drift, not +a break. I deliberately did **not** red-light my suite over another lane's content (round 4's +lesson); my test asserts the invariant that actually matters — *no decoy ever leaks a HIDDEN +seam* — which passes. Their three planted groups all decode exactly right, and I have a test +pinning that against `seams.json` so the map can never drift. + +CONTRACT REQUEST: none. v6 covered everything. + +SANCTIONED IMPORTS (documenting as ordered): LANE-UI's tuner dock should +`import { getRadio } from '../audio'` — a narrow surface of setBand/setFrequency/setVolume/ +setMuted/getState/subscribe/listStations, nothing else reachable. Returns `null` before the first +user gesture builds the graph, so the dock must handle that (and should probably render disabled +until `getState().unlocked`). Internally, `src/screen` reads `src/audio/subtitleBus` — both are my +lane, and it's a zero-dependency module precisely so neither organ couples to the other's guts. + +STATIONS SCHEMA: I conformed to LANE-DATA's file exactly as written rather than proposing my own +— it's better than what I'd have specified (the `eraFlavor` table and the coordinate encoding +especially). Nothing to agree; it's already right. + +PROPOSAL: THE STANDARD renders at RMS 0.0037 against FKTRY FM's 0.032 — ~9× quieter. That's +*correct* for gated speech (its peak, 0.10, matches the other stations; the gap is all the silence +between syllables) but it may still feel too quiet in practice next to continuous music. If it +does when you listen, the fix is a slow compressor on that station alone rather than a gain bump, +which would just make the syllables shout. Flagging rather than pre-emptively "fixing" something +the numbers say is fine. + +NEXT (not self-assigning): the tuner has no UI yet — LANE-UI's dock is what makes the band +findable at all, and until then the radio is only reachable from the console. Also `growthMask` +now has two customers (moiré, wildlife) and is ready for a third: HF dust piles are regional in +the codex and `dust-pile` wildlife already arrives in the same event I'm consuming.