22 KiB
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.mdfor 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, theSynthIIFE inside it near the bottom. Vanilla JS + raw Web Audio, no libraries. Python side:hub.py(OSC 9000 in → normalize → WebSocket out, port fromconfig.json),run.py(starts hub + workers; note the--sportsopt-in-worker precedent),workers/*.py(house pattern: argparse--host/--port, lazy imports with graceful "skipping (optional)" exit,SimpleUDPClientto OSC 9000). - Line numbers drift — grep for names. Verified anchors as of
main@0fb6410:OMNI.MODULES/LABELS~3947–3956 ·handleMessage~4357 ·mindStrike~5019 (resolvesmindKeys.notes[i], firesSynth.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 GODRUMfireDrumprecedent inside it) · main keydown handler ~6890–6907 (taken:p c o w z t b+ digits1–5;gis 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 menuctxItem(label, fn, kbd)entries ~7100s ·SynthIIFE 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#landingclient-side), §0.7-style regression every time (zero console errors;♪ T O B Gand panels all work), never deploy, never commit db/secrets/patches/, never touchmain. - 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
cd /Users/m3ultra/Documents/godstrument
git worktree add ../godstrument-<LANE> -b pantheon/<branch> 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)
- 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.
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():
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 <text> 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": <str>}} 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:
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 <name>") · 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 (squashdest + OMNI knob), the GODSPEAK worker (OSC/godspeak+/godspeak/rec→ ws{"godspeak":{text|rec}}), and UNGODLY (⌘Z/⌘⇧Z,ungodlyMark/ungodlyGesture/ungodlyUndoin app scope). You close the pantheon: the voice grammar, the polish list, the grimoire chapter, the QA matrix, the deploy checklist. Branchpantheon/e-closerfrommain(confirmarpTick,squash,godspeak_worker.py, andungodlyUndoare 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🗣 <what happened>; unmatched text falls through to the ticker as🗣 "<text>"— 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 |
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 ungodlyMarks naturally (applyMood already marks; don't double-mark).
E.2 The polish list (small, reviewed carry-overs — all in-scope)
- Prune GODSPEED's dead
arp.upstate field (α flagged it). - Apply
groove.velovelocity-deviation to arp notes inarpFire(mirror the drum branch's line) — the arp should breathe like the lanes do. ungodlyMark("tempo")on the beat panel's BPM input commit (the one known unmarked mutation).- 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 <li> 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
- Voice→everything (drive
handleMessagedirectly 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. - 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).
- GODSPEED × GODRUM: arp + beat playing together, both swung, keys 1–5 don't latch,
G/Bpanels coexist. - GODSQUASH × GODRUM: squash at 0.5 while the beat plays — offline-render crest drop, no limiter clip.
- Full §0.7 regression (
♪ T O B G+ ⌘Z/⌘⇧Z + zen-not-on-⌘Z) — zero console errors. recindicator 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.