Commit Graph

93 Commits

Author SHA1 Message Date
0e17dbf793 pantheon: GODSPEED — the arpeggiator (Stage 1α)
Hold a chord in mind mode, the machine runs with it — on godtime, with
the groove's swing (it fires from seqTick's tail, so it inherits the
swung onset for free; measured: off-beats land 0.31 late under mpc 8-4).
Notes latch at mindStrike when the arp is on (a strike toggles the note
in/out; MIDI-in routes through there too, so hardware latches for free);
mindStrike is byte-identical when the arp is off. rate 1/4→1/32 (1/32
double-fires mid-16th), up/down/updown/random, oct ×1–3, gate → note
length. Fires through the same pluck() + midiPluck(ch 2) every mind note
uses. A compact #godspeed panel on G and one sky-menu entry.

App-scope only — never touches the Synth engine (β's turf), OMNI, or
squash. seqTick/mindStrike/keydown edits are additive one-liners.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:27:17 +10:00
0fb6410be7 📋 godrum: build brief + deploy-filter update (all *_BRIEF.md excluded)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 12:15:38 +10:00
5314090e84 📖 godrum: grimoire chapter (🥁 beat · keys 1–5 & ⏺ rec · 🌾 AMPLER · MIDI/export) + manual
Stage 4 docs. Four entries added to the grimoire's living-patch list, alongside
the groove-grids/tracks/Groove entries, in the house voice:
- 🥁 the beat — five drum lanes on the godtime clock; draw/▶; velocity/chance/
  ratchet; ⬢ euclid; the feel shared with tracks; the 🎛 kit drawer incl. duck
  ("the kick parts the sea"); saves with the patch; moods never silence it; a
  beat drawn in zero counts as a build.
- keys 1–5 & ⏺ rec — finger-drum the kit (⇧ ghost); rec quantizes taps to the
  nearest sixteenth.
- 🌾 AMPLER — "there is ample, for god is abundant": capture the sounding world
  (a fed tab counts), chop 8/16, audition, ⇄ reverse, seat slices onto voices;
  slices ride level/pan/duck/choke; session-only.
- the beat leaves the building — GM drums on MIDI channel 10 (36/38/39/42/46)
  and a ch-10 track in export .mid.

viz/manual.html regenerated via build_manual.py (27 sections, self-check ok).
GODSTRUMENT_MANUAL_SOURCE.md left untouched (viz/index.html is canon).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 12:04:39 +10:00
9bfa0eec30 Merge branch 'godrum/e-performance' into godrum/integrate
# Conflicts:
#	viz/index.html
2026-07-13 11:43:26 +10:00
bd3340d4a2 🥁 godrum: finger-drumming keys, live record, drums in the MIDI export
Stage 3 / Agent E — the performance layer.
- fireDrum(name,vel,durMs): one shared fire path for the sequenced beat and
  the played keys; seqTick's drum branch routed through it (behavior identical).
- Keys 1–5 play kick/snare/clap/hat/ohat while the beat panel is open (vel 0.9,
  ⇧ = 0.5 ghost); ev.code with an ev.key fallback for code-less environments.
- ⏺ rec toggle (beat.rec, red when armed): each tap quantizes to the nearest
  16th (round seqPhase % 16) and writes steps[n]/vel[n], repaint via beatRefresh.
- exportMid: a channel-9 GM drum track (36/38/39/42/46), the beat looped across
  the arrangement's bars, one 16th long; ratchets omitted in v1. Added before the
  bail check so a beat-only export still produces a file.
Territory: openBeat header, keydown, seqTick, exportMid + one #beat CSS rule.
Nothing inside the Synth IIFE; no ctx-menu entries.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 02:10:01 +10:00
d2ddec1521 🌾 godrum: AMPLER — chop the captured world onto the drum voices (D)
The SP-404 move, Godstrument-style. GODSONIQ already captures ~3s of the
live master; AMPLER routes its own capture into a separate amplerBuf
(recTarget-tagged per-capture, so GODSONIQ's keys-replay stays byte-
identical), chops it into 8 or 16 equal slices, reverses any slice (lazy
cached copy), and seats a slice onto any drum voice. An assigned voice
PLAYS its slice through that voice's level→pan→drumBus chain, so the kit
knobs and patch persistence keep acting; a slice-kick still ducks and a
slice-hat/ohat still obeys the TR choke (both factored to be source-
agnostic). Eight frozen ampler* methods + an amplerPeaks waveform helper.

UI: a right-side #ampler panel (capture · waveform · chop · slice grid
with per-slice reverse · per-voice assignment strip), opened by one sky-
menu ctxItem after GODSONIQ. Engine lives in the Synth IIFE; the panel
sits after openBeat's brace — E's territory (openBeat body, seqTick,
keydown, exportMid) untouched. AMPLER state is session-only in v1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 01:05:55 +10:00
b0fad08556 🥁 godrum: 2.1 — choke anchor, euclid guard, proto-pollution, zero-mode beat
Four defects from the independent 4-lens adversarial review of Stage 2.

R8  choke same-task noise pop — DEVIATION from the brief's fix, verified by
    measurement. Read-before-cancel does NOT work: reading gain.value right after
    setValueAtTime(0,t) in the same task returns the 1.0 default (the envelope
    hasn't rendered), both offline AND live — so the anchor is 1.0 and it still
    pops (measured 0.45 vs a 0.15 ohat). Correct primitive is cancelAndHoldAtTime,
    which holds the timeline value at t (0 same-task, decaying level mid-ring);
    read-value kept as the legacy fallback. Same idiom applied to fireKick's duck.
    Verified live: same-task double-ohat 0.152 ~= single 0.149 (no pop); normal
    different-task choke still cuts the tail 70%.

R9  euclid junk input — trim, split /[\s,]+/, drop empty tokens, Number(), bail
    unless the first token is finite. " 4"/",4" -> E(4,16); "abc"/"" -> untouched;
    "0" -> cleared; "5,2" -> E(5,16) rot 2. (Empty-token filter also fixes the ""
    vs "0" distinction a bare Number() can't.)

R10 prototype pollution via kit data (MAJOR — patches/vibes are cross-user) —
    own-property guard (hasOwnProperty) everywhere a caller-supplied name indexes
    KIT / N.drums / DRUM_DEFS: drum, drumParam, drumParamList, drumKit, drumKitLoad,
    drumSync. Verified: drumKitLoad({"__proto__":{toString:0.5}}) and
    drumParam("__proto__",…) leave Object.prototype intact; legit keys still apply;
    normal round-trip works.

R11 a beat is a build — hoist the canonical seed into DRUM_SEED (one source of
    truth for seedBeat + zeroHasBuild); zeroHasBuild() now returns true when any
    drum lane is on or its gates differ from the seed. Untouched seed still exits
    quietly; ▶ or a drawn step now shows the keep/weave card so the beat survives.

Temporary verify hooks removed (grep-clean). No merge to main, no deploy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 23:57:31 +10:00
fa5aa434d1 🥁 godrum: integrate — merge A+B, review fixes R1–R7, mix pass + kick-duck
Stage 2 (Agent C). Real merge of godrum/a-engine + godrum/b-beat-panel onto
godrum/integrate (zero conflicts; extracted app script passes node --check),
then the seven review findings:

R1  kick tail — stop the osc at t+0.01+8τ (τ computed once); no click at max decay.
R2  seed — clear closed hats on steps 14/15 so the seeded open hat rings into the downbeat.
R3  stale paint — paintGrid now drives paintExpr (lit-bars track the grid); groove-slider drags relight the preset buttons.
R4  groove — add the fourth "velocity" slider (groove.velo), matching the arranger's grooves tab.
R5  moods — applyMood's storm + clearGrooves loops skip drum.* lanes; a mood never silences the beat.
R6  kick-duck — duck param on the kick (default .35); pad+drone reroute through a new duckG; fireKick dips it and lets it flood back ("the kick parts the sea").
R7  mix — closed/open hat internal peak scalars 0.70/0.80 → 0.35/0.45 so the hats sit at/just under the ambient perc.density shimmer (§0.6 contract defaults unchanged).

Verified end-to-end on a static serve via offline-render measurements plus a live
analyser tap on the real graph: first-press wake with ♪ off, every voice audible,
choke (95% tail cut), duck −3.07 dB → recover, chance/ratchet, R5 through a storm,
kit+beat persistence incl. duck, zero-mode round-trip, arranger↔beat shared groove,
mutual-exclusion, zero console errors. Temporary verify hooks removed (grep-clean).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 22:44:41 +10:00
4990e9244f Merge branch 'godrum/b-beat-panel' into godrum/integrate 2026-07-12 22:02:54 +10:00
9435c78feb 🥁 godrum: drum lanes + the 🥁 beat panel
Stage 1 / Agent B. Drum lanes fire from seqTick under "drum.*" seq keys
(gate + chance + ratchet + velocity, inheriting swing/humanize from the
groove clock), guarded Synth.drum + GM MIDI on channel 9. New #beat panel
modelled on the arranger: master play, shared groove, 5×16 grid with
playhead, vel/chance/ratchet expression lane, per-lane mute + euclid,
guarded kit drawer. Kit rides serializePatch/loadPatch; drum patterns ride
seqs. B key + sky menu + launcher button. Nothing touched inside the Synth
IIFE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:47:57 +10:00
28be1de3b8 🥁 godrum: five synthesized drum voices in the Synth engine
Kick / snare / clap / closed-hat / open-hat as one-shot node graphs
summed onto a drumBus → preSat, so drums ride the world's saturation →
crush → chorus/reverb/echo like every other voice. Each voice keeps a
persistent level→pan chain; hits connect into it. TR-style choke: a
closed hat OR a new open hat cuts the ringing open hat.

Frozen API (§0.6): drum(name,vel), drumParam get/set, drumParamList,
drumKit / drumKitLoad. All params normalized 0..1 at the boundary and
mapped to real units inside each voice; non-finite inputs rejected.
Entirely inside the Synth IIFE — no other territory touched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 21:34:19 +10:00
type-two
4e72592cd3 🧹 no wordmark inside the instrument
The app no longer announces GODSTRUMENT to someone already playing it —
the landing page and auth modal keep the brand; the console keeps only
the counts and the header line. The grimoire trigger slides up into the
vacated corner, and drawSpacedText goes with the wordmark it existed for.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 15:57:13 +10:00
type-two
232475e0ce 🌱 lifestrument v1.0.1 — post-ship review fixes (fable)
Three finds Opus's pre-ship review missed, all verified in-browser:
the heart-connect button was unclickable (pointer-events inherited
none from #readout), orb taps rang the 2.5s Moment bell per tap (now
a short pluck via the newly exposed Synth.pluck), and Game Day's
asleep copy leaked --sports jargon to civilians. Plus the Grimoire
now names the gentle door, and the brief gains §14: review verdict
and the v1.1 punch list (hub Cache-Control for .html, manual section,
knob double-tap, per-vibe info sheets, PWA manifest, wake lock).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 14:52:04 +10:00
type-two
09abd33901 🐛 lifestrument: show the login form by default for signed-out visitors
On production /api/me fails when signed out, so setUser was never called
and the auth form (hidden by default) never appeared — a dead landing.
Default authArea to visible; setUser still swaps to the begin button
once signed in (or via ?dev=1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:46:40 +10:00
type-two
350ad72782 🌱 LIFESTRUMENT — the gentle client (viz/life.html)
A second, self-contained client for the same hub, mobile-first: only
the sources about living (bodies, births, weather, sun & moon, play),
eight one-tap Vibes instead of a matrix, two macro knobs (ENERGY /
DREAM), a breathing orb, and dopamine Moments (somewhere a baby, the
sun where you stand, a kickoff). Its own compact Web Audio synth, all
routing client-side so it plays against the read-only public hub.
Reuses the invite-only /api auth; local sensors (heart BT, device tilt,
shake, mic, tap) never touch the hub. No money, no machinery, no
cables. Links back to the full instrument; added a "🌱 lifestrument"
item to godstrument's sky menu. Built understand→build→review with an
adversarial pass; 21 findings fixed (a11y sliders, zoom, contrast,
warm-dawn background, reachable heart connect, pluck de-click,
scheduler catch-up cap, moment tuning). test_lifestrument.py guards the
whitelist and the money/machinery ban.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 13:43:48 +10:00
type-two
dab04294ef 🌱 LIFESTRUMENT_BRIEF.md — build brief for the fun one
A second client for the same hub: viz/life.html, mobile-first, life
sources only (no money, no machinery), eight one-tap Vibes, two knobs,
a breathing orb, and Moments — somewhere a baby, sunset where you
stand, kickoff. Godstrument is build-anything; Lifestrument is
feel-something-immediately. Brief added to the deploy exclusion list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 12:42:36 +10:00
type-two
9ab1b0f5ae 🧹 health-sweep tidy-up: 5 low-severity loose ends (0 broken found)
A 13-agent adversarial whole-app sweep (client controls / runtime / server-auth /
new features / feed-config-honesty, each finding reproduced-or-refuted) found ZERO
broken functions — verdict healthy. Fixed the reachable low-severity edges it did
confirm:

- viz/index.html:1804 — arranger 'grooves' tab threw TypeError (nSel.isNote) when
  dests is empty in the cold-connect window (post-sign-in, pre-WS-hello). Guard:
  dests.get(arrSelKey) || {isNote:false} (the 'clips' tab already tolerates empty).
- viz/index.html — picking a skin from the sky menu / F4 next-skin / F4 prev while
  in ZERO mode force-enabled an invisible, un-toggleable chakraMode (every clear
  path is zeroMode-guarded). Added the same !zeroMode guard to all three sites
  (byte-identical outside zero — no regression).
- hub.py:117 — GODSTRUMENT_READONLY parse treated any non-empty non-'0' value as
  true (so 'false'/'no'/'off' enabled read-only). Now an explicit truthy set
  {1,true,yes,on}; prod uses '1' → stays read-only (verified before deploy).
- viz/index.html:2996 — removed a dead else branch in zero 'save as vibe' (its
  'vibes land in the next update' placeholder was unreachable; saveVibe is a real
  sibling method) → call this.saveVibe(gk) directly.
- transform.py:40 — collapsed a no-op ternary (both branches float(v)).
- viz/manual.html — regenerated (was stale vs build_manual.py; picked up the
  admin-panel CSS added in 764e28b; grimoire prose was already in sync).

Left as documented cosmetic: sanitizeVibe caps emoji at 2 code points (can split a
ZWJ/flag glyph on a shared vibe — display only, no XSS). Verified: node --check,
all .py compile, auth --selftest green, app boots into zero clean, console clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 10:21:03 +10:00
type-two
47b51b98ca 🔒 session-token credential binding + admin-guard/CSRF hardening (adversarial review)
Fable flagged that stateless session tokens bound only to uid+expiry — so an
admin action couldn't actually invalidate a live 30-day cookie. Reproduced 3
facets, all one root cause, all fixed by binding the token to the account's
credential state:
- make_token/read_token/_bind: token = uid:exp:bind:sig where
  bind = hmac(secret, 'uid:created:salt')[:16]; read_token re-derives it from the
  user's CURRENT row and rejects on mismatch/missing. So a token dies on account
  DELETE (no ghost session/writes), password RESET (salt rotates → admin can truly
  lock someone out), and SQLite id-REUSE (different created → no identity takeover).
  Old 3-part tokens fail to parse → a one-time re-login. No schema change.

A follow-on adversarial review (4 security lenses × find→refute×2, GO gate) then
surfaced 3 low-severity issues, all fixed here too:
- self-de-admin guard used  (identity) — {admin:0}
  / '' / 'false' / [] slipped past and could strand a sole db-admin. Now only a
  real JSON bool toggles admin (closes the bypass AND the truthy-string mis-grant).
- signup invite claim was check-then-write (double-spend under concurrency) — now
  an atomic  +
  rowcount check; signup/admin-edit wrap the UNIQUE writes in try/except
  IntegrityError → friendly message instead of a 500.
- login-CSRF (Lax cookie only): hub.py _api now blocks cross-site mutating requests
  by Origin (allowlist godstrument.pro/localhost + Host-match fallback so legit
  traffic always passes; safe GETs never blocked).

Verified: auth.py --selftest (delete/reset/id-reuse kill the token; guard bypass
closed for every falsy value); live curl (CSRF 401/401/401/403, GET never blocked);
real-browser flow (new-format cookie authenticates through a page load, admin table
renders, same-origin admin POST passes CSRF). Console clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 09:51:10 +10:00
type-two
764e28b1c3 👤 admin user management — list / edit / delete accounts + reset passwords
The admin panel could mint invite codes and read stats but not manage the people.
Adds, all under the existing admin-gated /api/admin/ block (default-closed:
no session → 401, non-admin → 403):

- GET  /api/admin/users        — every account: id, email, admin flag, patch count, joined
- POST /api/admin/user/<id>    — rename / change email / grant-or-revoke admin
                                 (validated like signup: format + uniqueness)
- POST /api/admin/user/<id>/reset — set a fresh random temp password, returned ONCE
                                 (never stored plaintext or logged) to relay to the user
- DELETE /api/admin/user/<id>  — delete the account + its saved patches (feedback +
                                 used invite kept as history)

Self-lockout guards: you can't delete your own account or revoke your own admin.
Admin granted via the GODSTRUMENT_ADMIN env is marked env_admin and its toggle is
hidden (can't be removed in the db). auth.py migration-free — uses existing tables;
the is_admin column was already migrated.

Client: the ⚙-account admin panel now lists every user with inline edit (username +
email), 🔑 reset-pw (shows a copyable temp), ★ make/revoke admin, and 🗑 delete
(confirm); the panel widens for the table. Names/emails escaped on render.

Verified: auth.py --selftest (list/edit/reset/delete + endpoint guards); live curl
against the local dev db (rename, bad-email/dup rejected, admin toggle, self-guards
400, non-admin 403, delete cascades presets); and the UI end-to-end (rows render
with correct per-row actions, edit expands, reset shows a temp password). Console
clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 07:01:58 +10:00
type-two
297ca8457f 📖 add a polished standalone user manual (viz/manual.html) + generator
The public site only had the in-app grimoire (behind the ✦ overlay); there was no
shareable, linkable user manual. build_manual.py extracts the grimoire (the canon,
per CLAUDE.md) + its styles from viz/index.html into a self-contained, always-
visible page with a generated 27-section table of contents and a 'play the
instrument →' back-link. Single source → no drift; re-run after editing the grimoire.

Served at godstrument.pro/manual.html (docroot is viz/). Verified: renders as a
normal page (overlay/close-button overridden away), TOC anchors resolve, all
feature sections present (zero, OMNI panel, vibes, dimensions), console clean.

Also refine the deploy convention: exclude dev docs/scripts (CLAUDE.md, README,
ZERO_OMNI_BRIEF.md, GODSTRUMENT_MANUAL_SOURCE.md, godstrument.txt, build_manual.py,
tests) so working notes never land on the public box.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 06:49:25 +10:00
type-two
fb32d10991 🐛 seed the synth idle (not 0) for cableless tweaked voices — fix-verify regression
The fix-verify pass flagged one CONFIRMED regression in the #1 floor loop: it
pinned any tweaked cableless non-note dest to a hard 0, but two dests have a
non-zero synth idle — filter.cutoff (d(...,0.3)) and tempo.nudge (d(...,0.5)).
So touching the filter knob in zero (or, before this, merely opening OMNI, which
created a neutral tweak) pinned filter.cutoff to 0 → 250 Hz, darkening the lead.

- new DEST_IDLE map; the floor loop seeds destVals[k]=DEST_IDLE[k]||0, so a
  neutral/reset knob returns a voice to its idle (filter 0.3), an offset rides
  from the idle, and mute still → 0. (verified: reset filter → 0.3 not 0;
  drag up → 0.767; mute → 0)
- OMNI update() + knob pointerdown now read tweaks[k] directly instead of tw(k),
  so rendering/opening the panel no longer creates phantom neutral tweaks (which
  also kept the tweaks map — and saved dimensions — clean). (verified: open OMNI
  → filter has no tweak)

A clean instrument (no tweaks) stays byte-identical; the public house patch was
already masked (filter.cutoff is cabled to sun.speed). Deploy gate: GO.
Console clean, syntax clean, 21 knobs, field healthy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 01:26:01 +10:00
type-two
fa948823a7 🐛 fix 8 adversarial-review findings (zero/vibes/OMNI)
A 30-agent adversarial review (5 dimensions × find→refute×2) confirmed 8 defects;
all fixed and live-verified:

BLOCKERS
- #1 unwired OMNI trim couldn't be silenced: the offset-floor only seeded a dest
  while offset>0.0015, so reset/mute/lower stopped emitting the key and the last
  floored value stuck in the synth (Synth.update merges). Now ANY tweaked cableless
  non-note dest is seeded 0 so the processing loop writes its real value, incl. 0.
  (verified: crush norm 0.5 on drag → 0 on reset)
- #2 leaving zero with an OMNI/offset-only build (no cables) was silently discarded
  — exitZeroToMood gated on routesArr.length===0. New zeroHasBuild() also counts
  picked orbs + any non-neutral tweak, so the keep/old/weave card always shows.
  (verified: offset-only build → 'you shaped this instrument by hand. keep it?')
- #3 vibe scale whitelist used truthiness (SCALES[q.scale]) → 'constructor'/'__proto__'
  passed → NaN into a Web Audio AudioParam → threw ~40×/s (remotely triggerable via
  public #vibe= links). Now Array.isArray(SCALES[...]) + quantize only on note dests;
  mxQuantize fallback hardened too. (verified: fx quantize dropped, note scale→minor_pent)

SHOULD-FIX
- #4 finishZeroExit keep/weave clobbered the kept mix (applyMood resets MOOD_KEYS):
  re-lay the build's tweaks after applyMood; weave merges build tweaks over prezero.
- #5 crush knob was fully dead (never in dests): OMNI.render ensureDest()s every
  voice/FX key → arc lives, floor works, right-click opens the menu. (verified)
- #6 OMNI drags in zero weren't persisted: knob pointerup/dblclick call zeroUI.persist()
  when zeroMode → survives reload. (verified: offset 0.467 in gs_zerobuild)
- #7 reloaded vibes showed OFF: persist activeVibes + per-route _vibes/_base to
  gs_zerovibes, restoreVibeState() on enterZero(true). (verified: vibe restores ◉)
- #8 GODSONIQ chip lit ~3s late: update() re-lights engine/♪ chips each frame.

Console clean, syntax clean, 21 knobs, field healthy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 01:10:30 +10:00
type-two
fc1bb307bc 🔒 harden vibe import trust boundary — strip markup from name/emoji (ship-check)
The vibe name + emoji arrive from user-pasted GSV1 codes and #vibe= URL fragments
and are rendered via innerHTML in the vibes panel + the shared-vibe offer card.
name was <-escaped but emoji was raw. sanitizeVibe now strips [<>&"'`] from both
at the trust boundary, so nothing dangerous reaches any render path.

Verified live: a crafted code with name='<img onerror=…>' + emoji='<svg onload=…>'
imported via #vibe= → no <img>/<svg> in the DOM, no script fired, both rendered as
inert text. (ship-check item 3: user strings → HTML must be escaped.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:41:40 +10:00
type-two
93c5e20ccf 📖 Z6 — grimoire + manual: zero-build, OMNI panel, dimensions & vibes
Canon (viz/index.html grimoire) is truth; manual synced to match.

- grimoire: two new tech-manual sections in the OMNI/Earth-Echo/GODSONIQ style
  (numbered how-to + spec table) — 'Building from zero — the empty instrument'
  (rings → orbs → drag/tap routing → synth-roll → keep/weave exit → persistence)
  and 'OMNI — the synth panel' (live arcs, trim-riding knobs, engine selector,
  earth echo, transport). Rewrote 'Saving your instrument' as dimensions & vibes.
- manual (had drifted — still said MY PATCHES): added Building-from-zero + the
  🎹 OMNI panel; 'Saving' → dimensions & vibes; keymap now lists F11 zero + O.

Verified: grimoire renders all three new h2 sections, no broken markup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:37:09 +10:00
type-two
5e6e257ec0 🎹 Z5 — OMNI: a NICE synth panel over the dests + tweaks
A 🎹 OMNI button (sibling of 🎛 tracks, key O) opens a floating panel — the
planet's synth, laid out like a synth. NO new audio paths: a view over the same
dests + tweaks the field already drives.

- knobs: each knob's ARC is the dest's live value (n.normDisp, the same number
  the field meters — it breathes with the world); dragging rides that dest's
  tweak OFFSET (setParamLocal) and a dot marks your hand; double-click resets to
  neutral; right-click opens the node's full context menu. Note voices (lead/bass)
  show a live pitch arc — display-only (their character is the engine selector).
- modules: VOICES (lead/bass/pad/drone/perc), FX RACK (filter/reverb/delay/
  saturation/glitch/granular/crush/morph/lfo/space), 🌀 EARTH ECHO (a toggle;
  its six echo.* knobs appear only when it's on).
- header: voice-engine selector (FM tine / 🎸 string / 🎛 GODSONIQ — the pluck's
  character, via Synth.stringVoice/godsoniq/sampling), ♪ start-stop, live BPM,
  🔒 godtime lock.

Matrix honesty fix (computeDests): a hand-set positive offset now gives a voice a
floor even with no cable — so an OMNI (or the existing 'trim') knob can make a
voice audible from nothing. Only affects dests a user explicitly offset; a clean
instrument (all offsets 0) is bit-identical to before.

Verified live (z0test, via a manual matrix pump since the headless preview's rAF
is paused): panel opens with 21 knobs + 3 modules; idle arcs track live data
(filter 0.41); dragging reverb up floored its value 0.09→0.59 with the tweak dot
showing; double-click reset it; engine selector swapped FM→string; earth-echo
toggle revealed its knobs; right-click opened the 'filter cutoff' node menu.
Tweaks persist in the saved dimension (serializePatch carries tweaks). Console
clean, syntax clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:32:59 +10:00
type-two
3654b2f6ec 🌌 Z4 — dimensions: MY PATCHES → DIMENSIONS (framing) + dimension zero
A dimension is everything (the whole scene: routes, tweaks, grooves, tempo);
a vibe is a feeling you can hand a friend. Same /api/presets storage — this is
a framing rename, existing saves appear unchanged.

- ⚙ panel: 'MY PATCHES' → 'DIMENSIONS', copy reworked; input placeholder
  'name this dimension…'.
- the load dropdown always leads with '✦ dimension zero — the empty field';
  loading it calls enterZero() (the place you build from nothing).
- grimoire 'Saving your instrument' rewritten (canon = truth): the dimension /
  vibe distinction, the GSV1 share code + #vibe= link, 'build a vibe, save a
  vibe, share a vibe'.

Verified live (z0test): panel shows DIMENSIONS +  VIBES, MY PATCHES gone,
dropdown leads with dimension zero. Console clean, syntax clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:11:59 +10:00
type-two
55defaa695 Z3 — vibes: build a feeling, save it, share it
A vibe = a small bundle of cables {v:1,name,emoji,routes[≤32]}. Distinct from a
dimension (the whole scene): a vibe is a feeling you can hand a friend.

- apply is additive + reversible: routes a vibe creates are tagged (route._vibes)
  so toggling it off removes exactly those; a route two vibes share survives until
  both are off; a hand-built cable (route._base) a vibe merely rides is NEVER
  deleted. Tags live in-memory only — they don't leak into saved patches.
- save from a zero orb (right-click → 💾), from any node's right-click in the full
  field (💾 save these as a vibe), or 'save current wiring' in the panel (≤32 cap).
-  vibes panel (zero pill + ⚙ gear): list, toggle on/off, ⧉ copy share code,
  × delete, paste-to-import. localStorage library (gs_vibes).
- share: GSV1.<base64url(json)> clipboard code + godstrument.pro/#vibe=… fragment
  that OFFERS on load (add it / just save) and never auto-applies.
- trust boundary: sanitizeVibe hard-validates every import — unknown source/dest
  skipped (not thrown), amounts/roots/octaves clamped, curves/scales whitelisted,
  32-route cap, try/catch the base64/JSON. Garbage codes are rejected cleanly.
- per-account cloud storage deferred: the code + local library already deliver
  build/save/share across devices and friends. (add a vibes table if users ask.)

Verified live (z0test): built a 2-cable orb → saved 'sunrise duo' (quantize
preserved) → panel listed it ◉ → share code round-tripped through import →
garbage code rejected → hard-reload kept the library → toggle ON created exactly
2 routes (lead.note lit), toggle OFF removed them → #vibe= fragment offered
'shared gift' in the full app, hash cleared, not auto-applied. Console clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 00:08:36 +10:00
type-two
00503b14c3 🌱 C1-C3 — keep-your-build exit, reload persistence, orb tap/menu/storm
C1: leaving zero with a build no longer silently discards it — a compact
    three-way card (keep my build / bring the old world back / weave them
    together); esc stays in zero. 0-route exits stay quiet (restore prezero).
C2: the zero build survives a reload — picks + cables persist to
    gs_zeronodes/gs_zerobuild; enterZero(true) on the reload path restores them.
C3: tap-to-patch (arm an orb, tap a voice) as a touch fallback for drag;
    right-click an orb → member list (× to drop a feed + its cables),
    unpatch-this-orb, and a save-as-vibe stub (Z3); a ⛈ enter-the-storm pill.
Nit: the chakra-view key/menu is a no-op in zero (nothing to draw there).

Verified live (z0test): pick→orb→tap-patch lit lead.note; hard reload restored
the ☀ orb + lit socket; the exit card's keep-my-build landed in a healthy full
field on the storm mood with zero-storage cleared; orb menu drops members.
Console clean, syntax clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:57:14 +10:00
type-two
b6f1c59e15 📖 brief: STATUS + CONTINUATION — Z0-Z2 approved, C1-C4 instructions (Fable review)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 23:42:05 +10:00
type-two
9a7f94759c 🌱 Z2 — zero mode routing: drag an orb to a voice, right-click to assign
The functional heart — build the instrument by hand from zero.

- Orbs are draggable; the output-rack sockets are drop targets. Drop an orb on a
  voice/FX socket → lays a cable per member feed (registerRoute), auto-adding a
  default quantize (minor_pent / root 60 or 36 for bass) when the target is a
  note voice. The socket lights green when it carries routes; a ticker line
  confirms the patch and points to ♪.
- Right-click a socket → the "synth roll" assign menu: for note voices a scale
  (the 11 SCALES) / root / octaves picker; for CC voices an amount slider + curve
  picker (lin/exp/exp3/log/scurve/inv). Plus "× unpatch all". Edits the live routes.
- refreshSockets() reflects existing routes on open; hover-highlight on drag.

Z-AC verified live: pick sun.speed (cosmos ring) → drop on filter · pick
crypto.price (wealth ring) → drop on lead → both sockets light · right-click lead
→ scale picker (11 scales, root, octaves) · press ♪ → OMNI plays (button green).
Bitcoin sings through a sun-opened filter, built from nothing. Console clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:38:03 +10:00
type-two
4e44533cdd 🌱 Z1.5 — stage nodes are spinning orbs (John's vision)
Each built group becomes a spinning orb on the stage, not a flat chip — a glowing
core in the group's hue with its picked feeds as petals orbiting around it (CSS
zspin/zpulse). They accumulate as you build: pick from fire → one orb; add cosmos
→ two; and on. Orbs breathe live — the core glows by the group's average value,
each petal brightens with its own feed. More feeds widen the orbit ring.

Verified live: fire (3 petals) + cosmos (2 petals) render as two spinning glowing
orbs; console clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:33:43 +10:00
type-two
d30cea469c 🌱 Z1 — zero mode: the calm 7+7 field (rings, picker drawer, stage)
The overlay that replaces the sensory flood. In zero mode the node field is not
drawn (frame() skips drawCables/drawSourceNode/drawDestNode); a DOM overlay
takes its place over the starfield:

- 7 vibe-rings down each side — the 14 config groups (left = elements/feelings:
  light·dark·fire·water·earth·air·spirit; right = domains: planet·cosmos·heavens·
  human·market·wealth·summer). Each: a hued dot (GROUP_META — taste consts,
  retune freely), label, live feed count, and a breathing glow scaled by the
  group's live average value. Members come from groupsInfo (the hub's config).
- Picker drawer: click a ring → it slides in from that side listing the group's
  feeds (name, key, live value bar, ⊕ that toggles in place). Pick 1+ → a stage
  node (chip, colored by group) lands on the center stage.
- Output rack along the bottom: OMNI voices (lead·bass·pad·drone·perc) + space &
  grit (filter·reverb·delay·…) as labeled sockets. Visual only — routing is Z2.

Veil (z-8) sits below the ♪/tracks/grimoire controls (z10-13) so they stay
usable. Verified live: rings render with hues + live glow, heavens drawer opens
with its 5 feeds + value bars, picking lands stage chips, ⊕ toggles in place,
console clean. Routing to the rack + right-click assign = Z2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 23:29:19 +10:00
type-two
2a705e3a35 🌱 Z0 — zero mode: blank dimension + first-visit choice card
The calm front door. Zero is not a muted world (moods do that) — it's an EMPTY
one you build by hand, the same matrix started from nothing.

- enterZero(): snapshots the current patch → gs_prezero, then loadPatch({}) empties
  the field; sets clientMatrix so the hub can't re-populate the cables; gs_mood="0 zero".
- exitZeroToMood(name): restores gs_prezero (the full factory) then applies the mood —
  leaving zero brings the whole world back.
- First-visit choice card ("one world, three doors"): 🌱 start from zero · 🌅 first
  light · ⛈ the full storm. Replaces the silent default-into-first-light; backdrop/esc =
  first light. Returning users keep their last mood; a returning zero-user re-enters zero.
- Mood submenu gains "0 zero — build from nothing"; F11 toggles zero (KEY_ACTIONS).

Verified live (logged-in preview): card renders with 3 doors; "start from zero" empties
the field (56 factory cables cleared, snapshotted); F11 restores all 56 + first light
(92 BPM locked); returning-zero reload re-enters zero with no card; console clean.

The 7+7 calm slot UI is Z1. Not deployed yet (deploy is Z6, post-review).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 22:53:14 +10:00
type-two
35c094f8d8 📖 ZERO_OMNI_BRIEF — zero mode, vibes/dimensions, OMNI panel (build brief for Opus, by Fable)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:40:58 +10:00
type-two
5db568d9da 📖 name the softsynth OMNI + tech-manual sections (OMNI / Earth Echo / GODSONIQ)
Names the built-in browser synth OMNI (♪ tooltip, intro prose) and adds three
scannable "tech manual" reference sections to the grimoire — OMNI (full voice
roster + how-to), the Earth Echo (six world-driven tape knobs + Schumann), and
GODSONIQ (resample + tab feed) — each a numbered how-to plus a two-column
control/dest spec table (new .spec / ol.steps CSS; auto-indexed in the TOC).

Every label, dest key and world→voice mapping extracted from code and verified.
Fixes two drifted claims in the old Earth Echo prose the audit caught: tape
wear drives hiss only (grit/saturation is static, not world-driven), and the
Schumann sub is ×4 = two octaves (not "four times").

Manual: sections flow into PART I (extractor taught .spec/.row → markdown
bullets) + an OMNI umbrella entry in Appendix J.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 10:02:40 +10:00
type-two
78718e9459 🌐 feed a tab into the world — external audio via getDisplayMedia
Right-click sky → "🌐 feed a tab": getDisplayMedia({video:true, audio:{…}})
lets the user share another tab's audio (a YouTube tab, a stream). Wired via
createMediaStreamSource into preSat → the full FX chain + master, so it's
audible live, crush-able, and resampleable by GODSONIQ from one wiring. Video
track stopped immediately; a track 'ended' listener handles Stop-sharing.

Audio constraints (verified against MDN/W3C/Chrome docs): suppressLocalAudio
Playback so the tab isn't heard twice, and echoCancellation/noiseSuppression/
autoGainControl off so the WebRTC voice-DSP doesn't mangle music. Chromium-only
(feature-detected; graceful ticker hints for unsupported / no-audio-track);
macOS grabs a tab not all-system; DRM services capture silent, YouTube fine.

Grimoire (truth) + manual Appendix J updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 09:38:33 +10:00
type-two
5ffa3a4bfb 🎛 GODSONIQ — resample the world onto the keys (ASR-10 nod)
Record ~3s of the live master mix into a buffer via a recorder AudioWorklet
tapping the full post-FX bus, then replay it pitched across the keyboard
(playbackRate = 2^((midi-60)/12), middle C = unity). Runs through the same
FX chain as every voice, so `crush` gives it the classic 12-bit sampler grit.
Right-click sky → 🎛 GODSONIQ to sample / re-sample; 🎸 string voice exits
back to the FM tine. Capture path verified: full-amplitude sawtooth recorded
clean (peak 0.98, rms 0.49) over the realtime port round-trip.

Grimoire (truth) + manual Appendix J updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 09:30:25 +10:00
type-two
b884cf9642 🎛 make wavetable.morph & granular.density literal (honesty fix)
The last two names that lied are now true:
- wavetable.morph — was a filter-Q sweep. Now a REAL wavetable: the lead is two
  band-limited PeriodicWaves (a mellow one + a bright saw-rich one) crossfaded by
  morph. Spectrally verified: smooth brightness sweep (centroid 462→880→1174
  across morph 0→0.5→1), no aliasing.
- granular.density — was one occasional noise burst every 4th step. Now a REAL
  grain cloud: a density-scaled scatter of short (12–40ms) pitch-varied grains
  per step (0 at rest → a dense cloud when the world writes). noiseHit takes a
  scheduled start time so grains scatter within the step.

The grimoire's existing claims ("sweeps the oscillator through its bank of
waveforms" / "a cloud of tiny grains") were aspirational — now they're accurate,
so no doc softening needed. Every clickable/routable thing now does what it says.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 09:13:40 +10:00
type-two
adde3bc326 🎸🔊 worklet batch — Karplus string + 12-bit bitcrush (spectrally verified)
The two voices native Web Audio can't do safely, done as one AudioWorklet module
(loaded from a Blob URL, so still single-file):
- 🎸 Karplus-Strong string — a sample-accurate 8-voice plucked-string worklet
  (noise burst → damped delay loop). Right-click sky → "string voice" swaps the
  FM tine for it. Verified offline: STABLE (rms 0.04, not the billions the native
  version exploded to), in-tune (A3→221, A4→436), harmonic.
- 🔊 12-bit bitcrush — a world-drivable lo-fi stage on the master bus (true
  bit-depth quantise + sample-rate reduction). Route anything to `crush`; clean
  by default (16-bit → 4-bit + downsample). Verified: transparent at 16-bit,
  hi-freq grit 0.18 at 4-bit.

Loaded async on synth start, spliced glitch→crusher→out and karplus→preSat.
Verified live: both load with no errors, string-voice toggle flips correctly.
Grimoire + manual document the whole synth-voice suite.

This closes the timbre work: FM tine · supersaw · chorus · string · bitcrush —
every one tuned by the offline-render + FFT harness before it made a sound.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 07:37:31 +10:00
type-two
2db0d5945f 🔍 pinch/scroll zoom — get right into the orbits
The whole visual now zooms: scroll or trackpad-pinch to zoom the canvas toward
the cursor (1×–6×), drag empty space to pan when zoomed, double-click to snap
home. Implemented as a CSS transform on #stage (smooth, no draw-loop surgery);
all four hit-test chokepoints (mousedown/move/up/contextmenu) invert the zoom so
clicking/dragging/right-clicking nodes still lands exactly — provably correct
(the inverse of the transform). Pan clamps so the canvas always covers the view.
Grimoire + manual note the gesture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 03:01:06 +10:00
type-two
d1fa247ad8 🎹 supersaw pad + brighter FM tine (spectrally verified)
Supersaw: the pad's 3 chord tones each become a detuned saw cluster (9 saws,
±14¢ spread) — the lush JP-8000 wall. Verified offline: lush (133 partials),
no clip (peak 0.29), no aliasing, no DC; padAmp trimmed 0.10→0.07 for the extra
voices. Retuning loop + level updated for the new {o,iv} structure.

FM finish: bumped FM_RATIO 2→3 (the classic bright DX7 tine). Verified alias-free
across the whole register — even ratio 4 at A6/hard aliases <0.2% — so brightness
is pure taste, not a correctness risk. One-line const if you want warmer (2) or
glassier (4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 02:41:15 +10:00
type-two
41d2c762a9 🔔 FM pluck — DX7-style tine on every plucked note (spectrally verified)
pluck() (used by the sequencer, hand notes, and mind mode) is now a sine carrier
phase-modulated by a sine operator (ratio 2), with the mod index enveloped —
a bright "tine" attack decaying to a mellow tail — and scaled by velocity, so
harder = brighter (the DX7 magic). PM not FM, for stable pitch.

Built via the new spectral-verification method: rendered offline in an
OfflineAudioContext and FFT-analysed BEFORE shipping. Verified: in-tune
(A4→439Hz, A2→110Hz), harder-is-brighter (centroid 570→858), zero aliasing,
zero DC, stable. The harness earned its keep immediately — it caught a
Karplus-Strong feedback explosion (rms in the billions) that would've shipped
blind; KS + bitcrush deferred to an AudioWorklet batch (native feedback + filters
is unstable under Web Audio block-processing).

Taste (the FM ratio/index) is the one thing left for real ears. FM_RATIO is a
one-line const to tune.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 02:30:03 +10:00
type-two
9e5de4f55f 📖 grimoire front door — quick-start card + clickable contents
The grimoire got big, so give it a proper entrance:
-  Quick-start card up top: a 7-step "make it sing in a minute" on-ramp for the
  generative sequencer (scale → euclid → Play One → ratchet → p-lock → play).
- ☰ contents: a floating, jump-anywhere table of contents built from the 22 h2
  sections (delta-based scroll so it lands reliably in any layout). Sits beside
  the × close, drops a clean index panel, closes on pick or outside-click.
Verified: quick-start renders (7 steps), TOC lists all 22 sections and jumps
distinctly (Getting in→top, Playing it→far). Manual regenerated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 01:33:16 +10:00
type-two
8938424db6 📖 sync manual to HEAD — p-lock prose in Part I (grimoire mirror)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 01:23:03 +10:00
type-two
c5997163b5 🎨 hand-tuned skin home keys — 14 deliberate, distinct moods
Replace the auto-derived skin scale (which gave 3× C wholetone and a sparse
hirajoshi default) with a curated SKIN_HOME map — a considered {root,scale} per
cosmology, every pair unique so turning the wheel always changes the key.
Verified: 14/14 distinct. Default (chakra) is now warm C minor pentatonic.

chakra C min-pent · planes A lydian · tree D minor · lodge G major ·
ladder E phrygian · yggdrasil F# minor · lataif B dorian · wuxing F# pentatonic ·
tarot A minor · zodiac C mixolydian · futhark G dorian · wheelyear F major ·
artist C wholetone · bagua E min-pent. (Fallback to derived scale for any future skin.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 01:14:39 +10:00
type-two
8dd551017a ⬗ Parameter locks — the Pocket-Operator move on the texture voices
Steal the one clever idea from the PO/Elektron world: p-locks. A CC/texture
voice (filter, glitch, reverb, drive) now gets a per-step LOCK lane — pin its
value to a blocky level on any step, overriding the world there; drag a step
below the lane to unlock it and hand it back to the data. The filter jumping to
a different cutoff on every hit, disjointed and alive — a full groovebox.

- applyGroove(sq, step, world) unifies the gate + p-lock; computeDests calls it.
- lock[] rides on seqs (serialised, migrated). Note voices keep velocity/chance/
  ratchet; CC voices get the p-lock lane (amber).
- self-check guards all four cases (no-groove / lit-unlocked / dark / locked).
- grimoire + manual updated (also documents the Juno chorus).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 01:02:22 +10:00
type-two
1938874707 🌊 Juno-106 chorus — the whole synth, warmer and wider
The safest, highest-value vintage borrow: an always-on subtle BBD-style chorus
on the master bus — two short delays each warbled by its own slow LFO, panned
wide, ~0.3 wet. Universally warms and widens the dry oscillators; can't sound
bad, can't error. Parallel send off `post`, chorusWet exposed for future
world-modulation. Verified: graph builds clean on synth start, no errors.

(FM mode, supersaw, and the 12-bit bitcrush are the next step — deferred to a
listening pass since they reshape core timbre and want real ears to tune, not
a backgrounded preview tab.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 00:48:33 +10:00
type-two
75dbe81749 🎚🌀 skins re-tune the world — Source → Spirit → Matter, made true
The flagship fix from the wired audit: skins were visual-only (the mix was
identical across all 14). Now the active Spirit re-tunes the Matter — each
skin's home tonality (its first station's scale) becomes the global scale, so
BOTH the sequencer and the ambient data-melodies re-harmonise when you turn the
wheel of cosmologies. Verified: 10 distinct tonalities across 14 skins
(chakra→B hirajoshi, wuxing→A dorian, wheelyear→F major, futhark→G# pentatonic…).
- skinHomeScale() derives {root,scale} from the skin's own station data (no
  hardcoded map); applySkinScale() sets gScale on skin change + at init.
- computeDests overrides the ambient note quantize to follow gScale.
- the sequencer already followed gScale, so it re-tunes for free.
- grimoire updated: "choose your spirit and the same planet plays through it"
  is now literally true. Manual regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 00:42:36 +10:00
type-two
e2170060c7 🔌 honesty pass — every control now does what it says
Retire the wired-audit debt so nothing is decorative-pretending-to-be-wired:
- FREEZE now works: a group freeze snapshots each member and holds it (was a
  set-but-never-read flag). Verified: holds 0.2 while the source moves to 0.9.
- .mid export is full-fidelity: real per-step velocity + ratchets expanded to
  sub-notes (was hardcoded vel 100, single hits). Chance/Play-One stay live-only
  — honest for a deterministic file.
- Hide the level/trim sliders on note voices (they never applied to a pitch —
  no more dead sliders).
- Grimoire honesty: soften "every source is a real measurement" to name the
  honest models (demographic tide, room-sensor sim); drop the phantom ⚙ SPELLS
  panel claim. Manual regenerated to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 00:34:57 +10:00
type-two
74df16c90f Ratchets — per-step sub-hits, and the glitch loop is closed
The last piece from the glitch-drum workflow: a per-step ratchet lane (1–4) —
each step can re-fire that many sub-hits within its slot, for instant rolls and
32nd-note glitch from a single step. Fired via short setTimeouts inside seqTick;
combined with Play One, every sub-hit re-rolls the note. Verified live:
rat3→3 hits, rat4→4, rat1→1, empty step→0. Self-check guards the rat default.

Now the generative loop is complete: Euclidean picks the rhythm, Play One the
pitch, ratchets the subdivision, the Groove the feel, follow-actions the
structure — and the global Scale keeps it all in key. Grimoire + manual synced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 20:43:37 +10:00