# REVELATION — build brief · TESTAMENT · WHEELS · PSALM · CANON > **You are an execution agent (Opus).** One lane of a four-feature wave. Read **Part 0**, execute ONLY your lane, end with the §0.8 report (defined in `GODRUM_BRIEF.md`, our process root — GODRUM and THE PANTHEON both shipped through this loop). The planning model (Fable) reviews every stage before merge. --- ## ⚠ AMENDMENTS — read BEFORE your lane (Fable, 2026-07-14, post-convergence) This brief was drafted at `main@b8b1188`, **before** the pantheon merged with the crossover wave (keyroll note-length · world_sats · OpenSky cache · the god's-eye Spirit · 18 post-merge review fixes). The convergence landed as `19f1770`. These amendments override anything they contradict below. 1. **GATE (replaces §0.2's):** branch from `main` ≥ `19f1770`. Confirm your base has BOTH waves: `grep -c godspeakCommand viz/index.html` ≥ 1 (ε) AND `grep -c attachNoteLengths viz/index.html` ≥ 1 (keyroll) AND `git log --oneline -3` includes the ⚭ convergence commit. If not, STOP. 2. **WHEELS — the `len` name is TAKEN.** Keyroll landed `seq.len` as a **per-step ARRAY of note lengths** (see `seqFor`: `len: new Array(16).fill(1)`, plus `migrateSeq`'s `len-default`/`len-badlen` self-checks). WHEELS's per-lane pattern length must be a **different field: `wlen` (int 1–16, default 16)**. Everywhere W.1–W.5 below says `len`, read `wlen`. Your `migrateSeq` additions must coexist with the existing len-array migration (add `wlen` clamping beside it; new self-check assertions for `wlen`, don't touch the len ones). 3. **WHEELS × keyroll indexing:** the note branch of `seqTick` now reads `sq.len[cur]` (per-step note length) — with wheels, `cur` becomes `wheelStep(sq)` for BOTH the `steps[]` gate and the `len[]` lookup (one `cur`, used twice — do not let them diverge). 4. **WHEELS × exportMid:** the drum block now has an `owned(v, sq)` gate (lanes export only if playing or edited off `DRUM_SEED`) — your absolute-loop rewrite in W.4 must keep that gate exactly. The note track is length-aware with a same-pitch overlap clamp; don't disturb it. 5. **TESTAMENT:** the master chain gained nodes since your anchor was written (squash from β; the Schumann layer predates). Grep the actual tail of `build()` for the final limiter node and its name — do not trust `N.lim` as a name, verify it. Also `Synth.pluck` now takes an optional `durMs` — irrelevant to your tap, but don't be surprised by the signature. 6. **PSALM:** `Synth.pluck(midi, vel, durMs)` exists — sustained sung notes may pass a duration instead of relying on the fixed decay; treat that as an allowed (flag-it-in-report) enhancement, not scope creep. The mic-analysis AudioContext isolation rule stands. 7. **CANON:** factory patches must survive `migrateSeq` with the NEW fields (len arrays, `wlen` once WHEELS lands, `locked`, `kit`) — author them by exporting real patches from a post-WHEELS build, never by hand-writing JSON. 8. **Regression additions to §0.7:** the groove self-check must stay green (it now asserts len-array migration), and the god's-eye skin must still cycle (right-click → skin) with zero console errors — it shares the overlay/render path some of you will be near. 9. **Deploy note for ω (the closer):** the production box is currently TWO waves behind and its venv still lacks `sgp4` — the deploy checklist you prepare must start with `/home/humanjing/godstrument/.venv/bin/pip install sgp4`, then the CLAUDE.md filtered rsync, then restart. GODSPEAK stays local-rig-only (inert on the VPS); `mlx-whisper` must never enter `requirements.txt`. --- ## Part 0 — shared context ### 0.1 The four revelations | name | what | maps to (the DAW-study gap) | |---|---|---| | **📼 TESTAMENT** | record the performance — the master mix to a file, one button | a live instrument that can't record itself | | **☸ WHEELS** | per-lane pattern lengths (1–16) — polymeter, the wheel within a wheel | flexible MIDI/pattern editing | | **🕊 PSALM** | sing to the instrument — mic pitch, quantized to the scale, played as notes (or latched into GODSPEED) | vocal integration, Godstrument-style | | **✦ CANON** | factory dimensions — curated patches that ship with the instrument | Logic's stock-content advantage | ### 0.2 Ground truth & hard gate - **GATE: do not branch until PANTHEON Stage 3ε has merged to `main`.** Confirm your base has ε's work (`git log --oneline -5` shows the ε commit; grep `godspeak` reaches a `handleMessage` branch). If ε hasn't landed, STOP and report. All line anchors below were verified at `main@b8b1188` (pre-ε) — **they WILL have drifted; grep for names, never trust raw numbers.** - One-file app `viz/index.html` (~9.4k lines): app IIFE with the `Synth` IIFE near the bottom (`const Synth = (function () {`). Python side: `hub.py` (OSC→ws), `run.py`, `workers/`. Conventions, worktrees, commit style, §0.7 regression (zero console errors; `♪ T O B G` + ⌘Z all work), temp-hook discipline, never-deploy/never-main: all per `GODRUM_BRIEF.md` Part 0 — read it. - Relevant machinery you inherit: the groove clock (`frame()` advances `seqPhase`; on each new 16th `seqCurStep = st16; seqTick()`), `seqs` (`{on, steps[16], vel, chance, rat, lock}` via `seqFor`/`drumSeqFor`, normalized by `migrateSeq`, serialized wholesale by `serializePatch`), the beat panel (`openBeat`, lanes, `beatRefresh`), the arranger grooves tab, `exportMid()` (ch-9 drum block added by GODRUM-E), `Synth.pluck(midi, vel)`, GODSPEED's `arp`/`arpLatch(note, vel)`, UNGODLY's `ungodlyMark`/`ungodlyGesture` (mark BEFORE mutating), the master chain `out → squash → squashMakeup → lim → destination` (~8585 pre-ε), the `Recorder` AudioWorklet (fixed-length captures — NOT what TESTAMENT uses), `gScale`/`SCALES`/`scaleRows`, and the hub's `_scan_patches` (serves named template patches from a dir to the client's template select). - Branches: `revelation/t-testament` · `revelation/w-wheels` · `revelation/p-psalm` · `revelation/c-canon` · `revelation/o-closer` (ω), each from post-ε `main`. ### Stage map | stage | lanes | parallel-safe because | |---|---|---| | 1 | **T 📼 ∥ W ☸** | T = Synth IIFE + one header button; W = app-scope sequencer/panels/exportMid — disjoint, and neither touches ε's `handleMessage` | | 2 (after 1 merges) | **P 🕊 ∥ C ✦** | P = index.html only (new self-contained block); C = Python + a new `factory/` dir + JSON — file-disjoint | | 3 (after 2 merges) | **ω the closer** | grimoire chapter (all four), manual, cross-feature QA, deploy checklist | Reviews between stages. Do not start later-stage work. --- # Stage 1T — 📼 TESTAMENT ("it is written") **Territory:** inside the `Synth` IIFE, PLUS: one header button (`#testament`, created app-scope beside the ♪ button — note the ♪ button itself is DOM built *inside* the Synth IIFE at its tail; mirror that pattern and build yours there too, keeping you 100% IIFE-resident), one `ctxItem`, one small CSS block. Nothing else. **T.1 The tap.** Recording captures the TRUE final mix — post-squash, post-limiter. In `build()`: expose `lim` on the returned `N` (one word added to the return object). The recorder itself is lazy: ```js let testDest = null, testRec = null, testChunks = [], testT0 = 0, testTimer = null; function testamentStart() { if (!ctx) start(); // wake the graph — a recording can begin the session if (testRec) return false; // already rolling if (!testDest) { testDest = ctx.createMediaStreamDestination(); N.lim.connect(testDest); } const mime = ["audio/webm;codecs=opus", "audio/webm", "audio/mp4"] .find(m => window.MediaRecorder && MediaRecorder.isTypeSupported(m)); if (!mime) return false; // (Safari fallback lands on audio/mp4) testChunks = []; testRec = new MediaRecorder(testDest.stream, { mimeType: mime, audioBitsPerSecond: 256000 }); testRec.ondataavailable = e => { if (e.data && e.data.size) testChunks.push(e.data); }; testRec.start(1000); // timesliced — a crash loses ≤1s testT0 = ctx.currentTime; return true; } function testamentStop() { ... } // → Promise<{blob, secs, ext}> resolved in onstop function testamentState() { return { on: !!testRec, secs: testRec ? ctx.currentTime - testT0 : 0 }; } ``` Export `testamentStart/Stop/State` on the Synth return object. The worklet `Recorder` (GODSONIQ/AMPLER) is untouched — different machinery, different purpose. **T.2 The button.** `#testament` next to `#sound` (♪): shows `⏺`; while rolling it turns red (`.on`), pulses gently, and shows elapsed `m:ss` (update via a 1s interval only while rolling). Click toggles. On stop: build the file name `godstrument-testament-.` (derive the timestamp from `new Date()` at stop time), trigger the download (the `a[download]` + `URL.createObjectURL` idiom), and ticker: `📼 testament: — it is written`. Failure (no MediaRecorder) → ticker `📼 this browser cannot bear witness`, button stays off. Add `ctxItem("📼 TESTAMENT — record what god does", …)` near GODSONIQ. **T.3 Verify.** Live: record ~6s of the sounding world (drums playing), stop → blob size > 0, elapsed matches ±0.5s, and **prove the audio is real**: load the blob into an `