From 9adbafe34282b9328cc52197bb2b900ed8e8222a Mon Sep 17 00:00:00 2001 From: monsterrobotparty Date: Tue, 14 Jul 2026 13:21:57 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=8B=20pantheon=20+=20revelation=20brie?= =?UTF-8?q?fs=20=E2=80=94=20the=20process=20docs=20travel=20with=20the=20r?= =?UTF-8?q?epo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- PANTHEON_BRIEF.md | 189 ++++++++++++++++++++++++++++++++++++++++++++ REVELATION_BRIEF.md | 130 ++++++++++++++++++++++++++++++ 2 files changed, 319 insertions(+) create mode 100644 PANTHEON_BRIEF.md create mode 100644 REVELATION_BRIEF.md diff --git a/PANTHEON_BRIEF.md b/PANTHEON_BRIEF.md new file mode 100644 index 0000000..a8e11b8 --- /dev/null +++ b/PANTHEON_BRIEF.md @@ -0,0 +1,189 @@ +# THE PANTHEON — build brief · GODSPEED · GODSQUASH · GODSPEAK · UNGODLY + +> **You are an execution agent (Opus).** You have been assigned one lane of a four-feature build. Read **Part 0**, then execute ONLY your lane. Your output is reviewed by the planning model (Fable) before merge; end your run with the §0.8 report. This brief follows the GODRUM process (see `GODRUM_BRIEF.md` for the full history — GODRUM shipped through it in four staged, reviewed lanes). + +--- + +## Part 0 — shared context (every agent reads this) + +### 0.1 The four gods + +| name | what | where it lives | +|---|---|---| +| **⚡ GODSPEED** | the arpeggiator — hold a chord, the machine runs with it, on godtime with the groove's swing | client, app scope | +| **🫀 GODSQUASH** | the master-bus compressor — one world-modulatable knob that makes the whole planet pump | client, Synth engine | +| **🗣 GODSPEAK** | the voice of god — the user's own wersperr (local MLX Whisper) speaks commands into the sky | Python worker + hub + client | +| **⏪ UNGODLY** | undo — *un-doing what god did*. ⌘Z time-travels the patch | client, app scope | + +### 0.2 Ground truth + +- Repo `/Users/m3ultra/Documents/godstrument`, branch base = **`main`** (post-GODRUM, ≥ `0fb6410`). The app is one file, `viz/index.html` (~9,100 lines): grimoire prose up top, one giant IIFE, the `Synth` IIFE inside it near the bottom. Vanilla JS + raw Web Audio, no libraries. Python side: `hub.py` (OSC 9000 in → normalize → WebSocket out, port from `config.json`), `run.py` (starts hub + workers; note the `--sports` opt-in-worker precedent), `workers/*.py` (house pattern: argparse `--host/--port`, **lazy imports with graceful "skipping (optional)" exit**, `SimpleUDPClient` to OSC 9000). +- **Line numbers drift — grep for names.** Verified anchors as of `main@0fb6410`: `OMNI.MODULES`/`LABELS` ~3947–3956 · `handleMessage` ~4357 · `mindStrike` ~5019 (resolves `mindKeys.notes[i]`, fires `Synth.pluck(note, vel)` + `midiPluck(2, note, vel, 320)` — the mind console's own MIDI channel) · `seqTick` ~2922 (16th-note tick, **swing already applied at the clock**, see the GODRUM `fireDrum` precedent inside it) · main keydown handler ~6890–6907 (taken: `p c o w z t b` + digits `1–5`; **`g` is free**; NOTE the single-letter handlers currently have **no meta/ctrl guards** — ⌘Z toggles zen today, a pre-existing quirk UNGODLY fixes) · `serializePatch`/`loadPatch` ~3160s · ctx menu `ctxItem(label, fn, kbd)` entries ~7100s · `Synth` IIFE opens ~8400s (`const Synth = (function () {`). +- **GODRUM conventions carry over verbatim**: worktree per agent, emoji commit style, surgical edits only, match the house voice in comments, params normalized 0..1, temporary verify hooks allowed but removed + grep-verified before commit, static-serve `viz/` to verify (`python3 -m http.server 89xx --directory viz`, dismiss `#landing` client-side), §0.7-style regression every time (**zero console errors; `♪ T O B G` and panels all work**), never deploy, never commit db/secrets/`patches/`, never touch `main`. +- **Hard territorial rule from GODRUM's lesson:** if your lane and a parallel lane could both insert at the same seam, they WILL conflict — your brief names your regions; stay inside them, and if you must cross, STOP and flag it in your report instead. + +### 0.3 Git + +```sh +cd /Users/m3ultra/Documents/godstrument +git worktree add ../godstrument- -b pantheon/ main +``` +Branches: `pantheon/a-godspeed` · `pantheon/b-godsquash` · `pantheon/c-godspeak-worker` · `pantheon/d-ungodly` · `pantheon/e-closer`. + +### 0.8 Report format (same as GODRUM) + +1. Branch + worktree + commit(s). 2. What was built + decisions where the brief left room. 3. `git diff main --stat` + the full diff. 4. How you verified (deterministic tests + measurements + regression results). 5. Deviations & open questions. 6. Territory confirmation (hooks removed, grep-clean, nothing outside your regions). + +### Stage map + +| stage | lanes | parallel-safe because | status | +|---|---|---|---| +| 1 | **α GODSPEED ∥ β GODSQUASH** | α is app-scope-only; β is Synth-IIFE + three named one-line touches α never makes | ✅ APPROVED — α @ 0e17dbf, β @ ea13b2e; trial-merge clean, zero conflicts. β's quadratic makeup `1+sq²` is a reviewer-endorsed deviation (measured: reduction ≈ 5.95·sq² dB); the linear `1+sq·1.4` in B.2 below is superseded — do not re-implement it | +| 2 | **γ GODSPEAK-worker ∥ δ UNGODLY** | γ is Python-only (zero index.html); δ is index.html-only (zero Python) | ✅ APPROVED — γ @ e56e3bc, δ @ d2c8ad6; merged to main @ b8b1188. Reviewer live-verified undo/redo + ⌘Z-no-longer-zen + worker graceful-skip | +| 3 (current) | **ε the closer** | GODSPEAK client grammar + grimoire chapter for all four + manual + cross-feature QA + deploy checklist | full brief below | + +Reviews between every stage. Do not start a later stage's work. + +--- + +# Stage 1α — GODSPEED ⚡ (the arpeggiator) + +**Territory:** viz/index.html app scope, OUTSIDE the Synth IIFE. You may touch: `mindStrike`, `seqTick` (additive hook, GODRUM's `fireDrum` style), the main keydown handler (the `G` key), one `ctxItem`, your own panel block + CSS block (place the panel function after `closeAmpler`/the AMPLER block; CSS after `#ampler`'s block). You may NOT touch: the Synth IIFE, `OMNI.MODULES`/`LABELS`, anything `squash` (that's β), exportMid, serializePatch. + +**A.1 State & capture.** +```js +const arp = { on:false, held:[], rate:1, dir:"up", oct:1, gate:0.8, idx:0, up:true }; +// rate: 4=1/4 · 2=1/8 · 1=1/16 · 0.5=1/32 dir: up|down|updown|random oct: 1..3 held: [{note, vel}] cap 8 +``` +Capture point is `mindStrike(i, vel)`: when `arp.on`, a strike **toggles** `{note: mindKeys.notes[i], vel}` in `arp.held` (latch — strike again to remove; keep the `mindKeys.flash[i] = 1` feedback) and does **not** pluck immediately. When `arp.on` is false, `mindStrike` is byte-identical to today. Since MIDI-in already routes note-ons through `mindStrike`, hardware gets latch capture for free — note that in your report; true hold-mode from MIDI note-offs is a flagged future upgrade, not v1. + +**A.2 The clock.** Ride `seqTick()` — one additive call at its tail (like `beatRefresh`): `arpTick()`. Inside: bail unless `arp.on && arp.held.length`; a 16th-counter fires when `counter % arp.rate === 0` for rates ≥ 1; for 1/32, fire twice (immediate + `setTimeout(sub/2)`, `sub = 60000/bpm/4`, the ratchet idiom). Because seqTick fires at the swung onset, **GODSPEED inherits the groove** — that's the whole soul of the feature; verify it. + +**A.3 Note selection.** Pool = `held` sorted ascending, expanded across `oct` octaves (`note + 12*o`). `up`/`down` cycle; `updown` ping-pongs without repeating the turnaround note; `random` picks uniformly. Fire via `Synth.pluck(note, vel)` + `midiPluck(2, note, vel, durMs)` where `durMs = sub * arp.rate * arp.gate` (clamped ≥ 30) — channel 2, the mind console's channel, exactly like `mindStrike`. + +**A.4 UI.** `G` key + `ctxItem("⚡ GODSPEED — hold a chord, the machine runs", …)` toggle a compact `#godspeed` panel (house style, model on the beat header row): **on/off** (`.midibtn.on`) · rate select (1/4 1/8 1/16 1/32) · dir select · oct (1–3) · gate slider · a held-notes readout (note names — there's a `midiToName` in scope; grep it) · **clear** button. Arp toggled off → `held` cleared, `idx` reset. Add the `G` handler beside `B` (~6906); verify `g` is truly unbound first (grep, incl. the capture-phase keymap handler — digits precedent from GODRUM lane E). + +**A.5 Verify.** Deterministic: drive `seqTick` (hook the internals temporarily), spy `Synth.pluck` — assert the exact note order for up/down/updown/random over 2 bars at each rate; 1/32 double-fires; gate scales `durMs`; swing: with "mpc 8-4" the off-16th fires late (assert via the seqTick call time, or by construction + note it). Live: open panel via `G` and the sky menu, latch three mind-mode notes, ▶ nothing needed — the arp runs on the clock; confirm audible + zero console errors + §0.7 regression + **GODRUM untouched** (beat panel plays, keys 1–5 still finger-drum — they must NOT feed the arp). + +--- + +# Stage 1β — GODSQUASH 🫀 (the master squeeze) + +**Territory:** inside the Synth IIFE, PLUS exactly three named external touches: (1) the `OMNI.MODULES` "fx rack" `keys` array — append `"squash"`; (2) `OMNI.LABELS` — add `"squash": "squash"`; (3) wherever `crush` is structurally registered as a destination in viz/index.html (grep `"crush"` and mirror EVERY structural appearance — dest declaration/registry, matrix nodes if any — so `squash` is routable by the world exactly like `crush`; `crush` is NOT in config.json, so config.json stays untouched unless your grep proves otherwise). Nothing else outside the IIFE. + +**B.1 The chain.** In `build()`, where the master currently does `out.connect(lim)`: insert `out → comp → makeup → lim` (`comp = ctx.createDynamicsCompressor()`, `makeup = ctx.createGain()` at 1). Return `comp` and `makeup` on `N` (as `squash`, `squashMakeup`). Do NOT move the GODSONIQ tap (`N.out.connect(recNode)` happens later in `loadWorklets` and correctly stays pre-squash — captures are pre-limiter by design; say so in a comment). Set static: `knee 12`, `attack 0.004`. + +**B.2 The one knob.** In `params()`: +```js +const sq = c01(d("squash", 0)); // 0 = transparent (no route, no squeeze) +S(N.squash.threshold, -6 - sq * 30, 0.1); // −6 → −36 dB +S(N.squash.ratio, 1 + sq * 7, 0.1); // 1:1 → 8:1 +N.squash.release.setTargetAtTime(0.12 + sq * 0.13, t, 0.2); +S(N.squashMakeup.gain, 1 + sq * 1.4, 0.1); // gain-compensate the squeeze — tune this by measurement +``` +Tune the makeup curve so perceived loudness stays roughly level while crest factor drops — measure, don't guess. + +**B.3 Verify.** Offline renders (the house method): a busy program (pad + drums via `Synth.drum` if merged base has them — it does) at `squash` 0 / 0.5 / 1.0 → show **crest factor (peak/RMS) falls monotonically**, RMS roughly held by makeup, no clipping into the limiter (peak ≤ pre-squash peak + ε). **Null-ish test at 0:** ratio 1 → output within ~0.5 dB of a bypass render (DynamicsCompressor has fixed lookahead latency — compare envelopes, not sample-aligned nulls, and note the latency in your report). Live: OMNI shows the new `squash` knob in the fx rack; dragging it audibly pumps; a world route onto `squash` modulates it. §0.7 regression + GODRUM regression (drums still balanced — squash defaults to 0). + +--- + +# Stage 2γ — GODSPEAK worker 🗣 (Python only — ZERO viz/index.html) + +**Territory:** new file `workers/godspeak_worker.py`, a small addition to `hub.py`'s `_osc_handler` (+ broadcast), an opt-in flag in `run.py`. Nothing else. **This feature is local-rig only**: MLX Whisper is Apple-Silicon-only, so it runs where the user's Mac runs the hub — the VPS simply never has it (like the ToF/vision workers). Say this in the file's docstring. + +**C.1 The worker.** Adapt the user's wersperr (`ssh://git@100.71.119.27:222/monster/wersperr.git`, 99 lines — clone it read-only and study it; model: `mlx-community/whisper-large-v3-turbo`, 16 kHz mono, hold **Right ⌥** via pynput, transcribe on release, ignore < ⅓ s). Changes from wersperr: instead of pbcopy/paste, send OSC to the hub — `/godspeak/rec 1` on key-down, `/godspeak/rec 0` on release, `/godspeak ` after transcription. House worker pattern throughout: argparse `--host 127.0.0.1 --port 9000`, **lazy imports** (`mlx_whisper`, `sounddevice`, `pynput`, `numpy`) with the graceful `"godspeak worker: … not available — skipping (optional)"` exit, warm-up transcribe of silence on boot, `--check` self-test. Deps do NOT go in requirements.txt (VPS must stay clean) — document `pip install mlx-whisper sounddevice pynput` (into the venv, Mac only) in the docstring. + +**C.2 The hub.** In `_osc_handler`, special-case the `/godspeak` addresses (they're text/state, not norm signals — mirror how non-signal messages like the patch list reach clients): broadcast `{"godspeak": {"text": }}` and `{"godspeak": {"rec": 0|1}}` to all ws clients immediately. Respect the hub's **read-only mode** (~line 114): in read-only, drop godspeak entirely (it's a command channel). + +**C.3 run.py.** `--godspeak` opt-in flag exactly like the `--sports` precedent (~line 132): starts the worker, prints a one-liner. Never in the default set. + +**C.4 Verify.** `--check` passes on this Mac (model loads, silence transcribes). With hub running locally: hold Right ⌥, say something, show the hub log/ws broadcast carrying the text (a 5-line ws test client is fine). Graceful-skip path: run with the imports missing (e.g. `python3 -m venv` bare env) → clean skip, exit 0. Confirm `git diff main --stat` touches ONLY the three Python files. + +--- + +# Stage 2δ — UNGODLY ⏪ (undo — viz/index.html only, ZERO Python) + +**Territory:** viz/index.html app scope, outside the Synth IIFE. Touches: a new snapshot module (place after `loadPatch`), `ungodlyMark(label)` calls at the mutation sites listed below, the keydown handler (⌘Z/⌘⇧Z **plus meta-guards on the existing single-letter keys** — today ⌘Z toggles zen; that quirk dies in this lane), one `ctxItem`. Do NOT touch: the Synth IIFE, GODSPEED's/AMPLER's blocks, exportMid. + +**D.1 The engine.** Undo = time-travel over `serializePatch()` — `loadPatch` already restores everything (routes, tweaks, seqs incl. drums, arr, bpm, groove, scale, kit) and rebuilds open panels. So: +```js +const ungodly = { past: [], future: [], muted: false, MAX: 32, lastT: 0, lastLabel: "" }; +function ungodlyMark(label) { // call BEFORE a mutation applies — the snapshot is the world as it was + if (ungodly.muted) return; + const s = JSON.stringify(serializePatch()); + if (ungodly.past.length && ungodly.past[ungodly.past.length - 1].s === s) return; // no empty history + ungodly.past.push({ s, label }); if (ungodly.past.length > ungodly.MAX) ungodly.past.shift(); + ungodly.future.length = 0; +} +function ungodlyGesture(label) { // continuous drags: one mark per gesture burst (same label within 800ms = same gesture) + const tNow = performance.now(); + if (label === ungodly.lastLabel && tNow - ungodly.lastT < 800) { ungodly.lastT = tNow; return; } + ungodly.lastLabel = label; ungodly.lastT = tNow; ungodlyMark(label); +} +``` +Undo pops `past` → pushes the CURRENT state onto `future` → `muted = true; loadPatch(JSON.parse(p.s)); muted = false` → ticker `"⏪ ungodly: " + p.label`. Redo mirrors. Empty → ticker `"⏪ nothing to take back — god has done nothing yet"`. Memory-only v1 (reload clears history — note it). + +**D.2 The mark sites** (find each by name; `ungodlyMark` for discrete acts, `ungodlyGesture` for drags — always BEFORE the mutation): +route patch/unpatch via the UI (the cable actions + the ctx-menu route items — NOT `registerRoute` itself, which `loadPatch` calls) · `applyMood` entry (`"mood: " + name`) · dimension/patch load + vibe apply (`"loaded "`) · zero enter and exit · groove preset clicks + groove sliders (gesture) in BOTH the beat panel and the arranger grooves tab · euclid fills (both panels) · beat-panel step toggles (gesture, `"beat edit"`) + expression-lane drags (gesture) + kit-knob drags (gesture) · arranger clip-cell cycles (gesture) · scale root/name changes · `resetTweaks` and mute/solo toggles (mark). Anything you find that mutates patch state and isn't listed: add it and report it. + +**D.3 Keys & menu.** In the keydown handler: FIRST, `if (ev.metaKey || ev.ctrlKey) { if z → undo (shift → redo), preventDefault, return; }` — placed before the single-letter block so ⌘Z no longer reaches zen; then add `!ev.metaKey && !ev.ctrlKey` guards to the existing letter handlers (p/c/o/w/z/t/b/g — small, mechanical, in-territory). `ctxItem("⏪ UNGODLY — take it back (⌘Z)" + depth count, …)`. + +**D.4 Verify.** Deterministic script: draw beat steps → apply a mood → patch a route → three undos restore each state exactly (compare `JSON.stringify(serializePatch())` to the recorded snapshots) → three redos return → new action after undo clears `future`. Gesture-debounce: a 20-event slider drag = ONE history entry. `muted` holds: undo itself creates no marks. ⌘Z does NOT toggle zen; plain `z` still does. Depth cap at 32. §0.7 + GODRUM regression, zero console errors. + +--- + +# Stage 3ε — the closer (FINAL BRIEF) + +> **You are Agent ε (Opus).** All four gods are built and merged to `main @ b8b1188`: GODSPEED (arp, `G`), GODSQUASH (`squash` dest + OMNI knob), the GODSPEAK worker (OSC `/godspeak` + `/godspeak/rec` → ws `{"godspeak":{text|rec}}`), and UNGODLY (⌘Z/⌘⇧Z, `ungodlyMark`/`ungodlyGesture`/`ungodlyUndo` in app scope). You close the pantheon: the voice grammar, the polish list, the grimoire chapter, the QA matrix, the deploy checklist. Branch `pantheon/e-closer` from `main` (confirm `arpTick`, `squash`, `godspeak_worker.py`, and `ungodlyUndo` are all in your base). Part 0 rules and §0.8 report bind. + +## E.1 The GODSPEAK client (viz/index.html) + +In `handleMessage` (grep it), add the `godspeak` branch: +- `msg.godspeak.rec` → a small 🗣 indicator pulses while recording (a fixed chip near the ♪ button, or a ticker line — house style, your call; it must appear on 1 and clear on 0). +- `msg.godspeak.text` → normalize (lowercase, strip punctuation, map number-words to digits over the 40–180 range — a small word-number table: "ninety" → 90, "one twenty" → 120) and run the grammar. **First match wins; every executed command lands in the ticker as `🗣 `; unmatched text falls through to the ticker as `🗣 ""` — the word arrives in the world, never an error.** + +Grammar (be forgiving with filler words — match on keywords, not exact phrases): +| utterance contains | action | +|---|---| +| `tempo`/`bpm` + N (digits or words) | `godtime.bpm = clamp(N, 40, 180)` unless `godtime.locked` (then ticker "godtime is locked") | +| `mood` + a fuzzy `MOODS` key (or a bare mood name) | `applyMood(match)` | +| `play`/`start` + `beat` · `stop` + `beat` | flip all five `drum.*` lanes on/off (reuse the beat panel's master-play logic — extract or mirror it) | +| `open`/`close` + `beat`\|`tracks`\|`omni`\|`ampler`\|`keys`\|`godspeed` | the matching panel toggle | +| `capture` (+ N `seconds`)? | `Synth.amplerArm(N || 3)` — if it returns false, ticker the same "press ♪ first" hint AMPLER uses | +| `chop` + `eight`/`sixteen`/8/16 | `Synth.amplerChop(n)` + re-render the AMPLER panel if open | +| `skin` + fuzzy skin name | `setSkin(match)` | +| `zen` | `setZen(!zenMode)` | +| `squash` + N `percent` | set the squash trim: `setParamLocal("squash","offset", N/100)` (register the dest first if needed) | +| `take it back` / `undo` | `ungodlyUndo()` — the voice of god, un-doing | + +Fuzzy matching: lowercase-prefix or substring against the real key lists (`MOODS`, `SKINS`) — never invent names; no match → fall through to the ticker. **Voice commands are user acts: let the actions they call fire their existing `ungodlyMark`s naturally (applyMood already marks; don't double-mark).** + +## E.2 The polish list (small, reviewed carry-overs — all in-scope) + +1. Prune GODSPEED's dead `arp.up` state field (α flagged it). +2. Apply `groove.velo` velocity-deviation to arp notes in `arpFire` (mirror the drum branch's line) — the arp should breathe like the lanes do. +3. `ungodlyMark("tempo")` on the beat panel's BPM input commit (the one known unmarked mutation). +4. GODSPEAK worker docstring: add the two macOS one-time permissions (Microphone + Accessibility/Input Monitoring for pynput) — mirror wersperr's README note. + +## E.3 The grimoire chapter (canon) + manual + +Four `
  • ` entries alongside the GODRUM chapter (same placement pattern Agent F used, before the stage; house voice; fact-check every claim against what you verified): +- **⚡ GODSPEED** — press `G`; enter a chakra, turn it on, strike keys to latch a chord; it runs on godtime and *swings with the groove*; rate/direction/octaves/gate; a MIDI keyboard latches too (it rides channel 2 out). +- **🫀 GODSQUASH** — the master squeeze in OMNI's fx rack; one knob, threshold dives and ratio climbs together, loudness stays level while the peaks kneel; route the world onto it and the planet pumps the mix. +- **🗣 GODSPEAK** — the voice of god (local rig only, Apple Silicon): `run.py --godspeak`, hold Right ⌥, speak — "tempo ninety", "storm mood", "capture six seconds", "chop sixteen", "take it back"; unmatched words arrive in the ticker; nothing leaves the machine (the user's own local Whisper). +- **⏪ UNGODLY** — ⌘Z takes it back, ⌘⇧Z re-does; thirty-two acts deep; every cable, mood, groove, kit knob and beat step; memory-only (a reload forgives everything). + +Then `python3 build_manual.py` and commit the regenerated `viz/manual.html` (the Stage-4/F exception applies to closers). + +## E.4 Cross-feature QA matrix + +1. **Voice→everything** (drive `handleMessage` directly with synthetic `{godspeak:{text}}` messages — the worker needs a mic+model, so the ws path is verified by γ already): tempo, mood, play/stop beat, open/close each panel, capture/chop (audio unlocked first), skin, zen, squash N percent, **"take it back" undoes the mood it just set**. Unmatched → ticker verbatim. Junk/number-word parsing table. +2. **Undo × the new gods:** arp latch isn't in the patch (transient — confirm undo doesn't clear held notes); squash trim set by voice → ⌘Z reverts it (it's a tweak). +3. **GODSPEED × GODRUM:** arp + beat playing together, both swung, keys 1–5 don't latch, `G`/`B` panels coexist. +4. **GODSQUASH × GODRUM:** squash at 0.5 while the beat plays — offline-render crest drop, no limiter clip. +5. Full §0.7 regression (`♪ T O B G` + ⌘Z/⌘⇧Z + zen-not-on-⌘Z) — zero console errors. +6. `rec` indicator appears/clears on synthetic `{godspeak:{rec:1|0}}`. + +## E.5 Deploy checklist (prepare, do NOT execute) & deliverables + +Same shape as GODRUM's F.4: merge main is already done for stages 1–2, so: your branch → `main` (reviewer does it) · rsync per CLAUDE.md (briefs auto-excluded; `manual.html` ships; `workers/godspeak_worker.py` ships but is inert on the VPS — graceful skip) · restart · cold-start wait · smoke-test godstrument.pro (`B`,`G`, ⌘Z, OMNI squash knob, manual chapter) · note GODSPEAK is local-rig-only so the live site's voice test is `run.py --godspeak` on the Mac. Commit(s) on `pantheon/e-closer`; §0.8 report + the E.4 matrix ✓/✗ + the E.2 list each done/deviated. diff --git a/REVELATION_BRIEF.md b/REVELATION_BRIEF.md new file mode 100644 index 0000000..3222bf9 --- /dev/null +++ b/REVELATION_BRIEF.md @@ -0,0 +1,130 @@ +# 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. + +--- + +## 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 `