Godstrument/PANTHEON_BRIEF.md
2026-07-14 13:21:57 +10:00

22 KiB
Raw Permalink Blame History

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 ~39473956 · 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 ~68906907 (taken: p c o w z t b + digits 15; 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

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)

  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.

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 (13) · 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 15 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 futuremuted = 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 (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 40180 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)

  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 <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

  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 15 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 12, 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.