[screen] round 5 notes: both organs, and what I could not verify

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-18 13:51:24 +10:00
parent fd2aee6c50
commit 70130c9e71

View File

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