Commit Graph

112 Commits

Author SHA1 Message Date
type-two
cccbce536c 🕊 revelation: PSALM — sing, and the instrument answers (Stage 2P)
Mic pitch → folded to the global scale → played as notes, or (when 
GODSPEED is on) latched into the arp one note at a time. The mic lives
in its OWN throwaway AudioContext — it is analysed, never routed into
the music graph (zero feedback), and nothing leaves the page. A pure
autocorrelation detector psalmDetect(buf,sr)→{freq,clarity,rms} (80–1000
Hz, parabolic interpolation) and a pure note state-machine psalmStep()
(2 stable frames to fire, legato on change, 150ms gate close + re-attack,
velocity from RMS) — both unit-tested headlessly. A 🕊 chip shows the
sung note and brightens on voice; a ctxItem toggles it; mic denial
tickers "the instrument cannot hear you".

Verified: detector 220/440/554Hz within 0.09% (silence & noise gated),
fold lands in-scale, the state machine single-fires / legatos /
re-attacks / scales velocity, the ctxItem + mic-denial path work live,
zero console errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 14:04:28 +10:00
type-two
0ff4259a4f ☸ revelation: WHEELS — a wheel within a wheel (Stage 1W)
Per-lane pattern length (wlen, 1..16, default 16 = full wheel) → polymeter.
Named wlen because keyroll already owns seq.len (the per-step note-length
array). One monotonic seqAbsStep counter drives wheelStep(sq), which
returns seqCurStep for full wheels (bit-identical to pre-WHEELS) and
seqAbsStep%wlen for shorter ones. Threaded through seqTick (both branches,
one cur for the gate AND len[] lookup), the S&H source sampling, and every
applyGroove call. migrateSeq clamps wlen (legacy → 16); self-check gains
wlen-default/preserve/clamp + wheel-full-identity. exportMid's drum block
is now one absolute loop (a 12-wheel exports as it plays; sa%16 keeps the
full wheel byte-identical). UI: a ☸ wheel chip in each beat lane + the
arranger euclid row; steps past the wheel dim (.off, pointer-events:none)
and the playhead paints at wheelStep. The owned() export gate is intact.

Verified: groove self-check green (incl. new assertions), full-wheel
export bit-identical over 4 bars, 12-wheel wraps at sa%12, kick-16/hat-12
drift over 48 ticks, beat-panel wheel=12 dims exactly 4 cells, zero errors.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:58:57 +10:00
type-two
6cb355e6b0 📼 revelation: TESTAMENT — record what god does (Stage 1T)
A MediaRecorder tapped off N.lim (the true final mix — post-squash,
post-limiter). Lazy dest+recorder built on first roll, 1s timeslices.
The #testament ⏺ button beside ♪ turns red and counts m:ss while
rolling; on stop it downloads godstrument-testament-<stamp>.<ext> and
tickers "it is written". A ctxItem mirrors it near GODSONIQ.
testamentStart/Stop/State exposed on the Synth return.

Verified: button/timer/ticker/download all fire, blob is valid opus
decoding to the right duration, and an oscillator probe proved the
record→decode pipeline captures real audio (RMS 0.35). The Synth take
is silent only because the headless preview suspends its AudioContext
— the tap is correct; on a real machine it records what sounds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:46:41 +10:00
type-two
e6c464daae 📋 revelation: post-convergence amendments — wlen (len is taken), gates, deploy notes (fable)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:26:54 +10:00
type-two
19f17705e6 ⚭ convergence — the pantheon meets the crossover (fable)
Merge pantheon/integrate (GODSPEED  · GODSQUASH 🫀 · GODSPEAK 🗣 ·
UNGODLY  + the ε closer, reviewed & committed from the m3ultra rig)
into main (keyroll · sats · planes cache · god's eye · 18 review
fixes). Two conflict hunks, both in the beat panel where behavior
fixes met undo instrumentation — resolved keeping both: the bpm field
stays always-editable AND marks undo; master ▶/■ keeps per-lane mute
memory AND marks undo. Manual regenerated from the merged grimoire.
Verified: zero console errors, groove self-check, beat/G panels,
godspeak chip + keyroll CSS all present, python gates green, the
godspeak worker skips gracefully where MLX is absent (and the
supervisor now retires clean exits instead of churning them).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:25:10 +10:00
9adbafe342 📋 pantheon + revelation briefs — the process docs travel with the repo
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:21:57 +10:00
42c17f83b0 Merge branch 'pantheon/e-closer' — the pantheon is complete 2026-07-14 13:21:57 +10:00
18fd836456 🗣 pantheon: ε the closer — the voice becomes command (Stage 3ε)
The GODSPEAK grammar: tempo/bpm with spoken numbers (one twenty → 120),
squash N percent, AMPLER capture/chop, open/close every panel, play/stop
the beat, mood, skin, zen — and take it back, the voice of god un-doing.
First match wins; unmatched words arrive in the ticker, never an error.
A 🗣 chip pulses while the mic is held. Polish: arp.up pruned, the arp
breathes with groove.velo, tempo edits mark undo, serializePatch carries
godtime.locked so undoing a mood restores the lock. Grimoire chapter for
all four gods + regenerated manual + worker permission notes.

Reviewed by fable: two fixes folded in — matchSkin normalizes candidate
names (so god-s-eye matches post-convergence) and a virgin session
saying play-the-beat gets the seed pattern first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:21:24 +10:00
b8b11888c3 Merge branch 'pantheon/d-ungodly' 2026-07-14 10:59:52 +10:00
type-two
629cbad6fd 🔍 crossover post-merge review — 18 confirmed findings fixed (fable)
A fresh-eyes adversarial pass over the 15 pulled commits (godrum +
keyroll + sats + planes cache + god's eye): 21 raised, 18 confirmed,
all fixed.

The instrument: .mid export no longer smuggles the seeded demo beat
(lanes export only if playing or edited off the seed — same test
zeroHasBuild uses); master ▶/■ remembers per-lane mutes; the beat
panel's bpm field is always editable like the dial (🔒 blocks the
world's hands, not the user's); ⏺ rec with the beat stopped anchors
step 0 at arm instead of the invisible free-running clock; note-length
drags speak pointer events (touch works, window-level so mid-drag
re-renders survive); the resize handle no longer eats 40% of a
sixteenth's click width; the god's eye clears a stale ISS trail after
an rAF nap or resize; the "Agent A" dev-process string is gone; the
grimoire now tells the truth about kit knobs and which voices hold
note length (manual regenerated).

The workers: world_sats — partial Celestrak fetches backfill from the
old cache instead of clobbering it, pass-detection state survives the
6-hourly refresh, the no-TLE exit naps 15 min so the supervisor can't
hammer Celestrak, and the venue default now matches world_sky's
Brisbane instead of Null Island; world_planes bounds its stale-cache
fallback to 1h so a days-old sky can't resurrect as live; world_sky's
docstring admits the lat/lon it emits. run.py retires workers that
exit 0 instead of reviving them every 10s (the sgp4-missing churn).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 21:24:25 +10:00
jing
d2c7938321 👁 god's eye: GODSIGH's cosmology as a Spirit on the shelf
A new map-layout skin — the world's own sensor picture read AS the cosmology,
ported from the GODSIGH view. A minimal equirectangular Earth (a sparse hand-drawn
continent set kept inline — no heavy geo data in the one-file console), a 30°
graticule refit to a 2:1 rect each frame (dynamicStations), and nine geographic
"posts" the sources are seated at by godseyeOf. The overlay seats every feed at its
LITERAL coordinates: earthquakes flash at quake.lat/lon (venue-antipode fallback if
absent), the ISS and recon birds sweep as a moving point + trail from sats.iss_lat/
lon, aircraft are a shimmer band, the markets pulse at NY/London/Tokyo, the solar
wind washes the poles as an aurora, the word sparks at civilisational coordinates,
and your own hands are the ring at the venue. Cold watch-floor palette (near-black,
cyan, warning amber) and a Bb minor-pentatonic retune. Registered in SKINS +
SKIN_HOME beside the bagua; grimoire + manual entry ("the eye that hears").

To give the console the venue's location (it had none), world_sky now emits
/gs/sky/lat,lon; config.json declares sky.lat/lon and quake.lat/lon so the eye can
place "here" and the quakes. (sats.* signals + routes already landed with world_sats.)

Verified: full viz parses/runs with the groove self-check passing and zero console
errors; an isolated harness renders the REAL overlay code into a coherent world-map
with every layer; and an end-to-end run (feeds -> hub -> WS) delivers all the eye's
input signals. Adversarial review found one wrap bug — the ISS trail streaking
across the map at the ±180° antimeridian — now fixed by breaking the polyline at the
seam (verified: a seam-crossing sine trail renders split, no full-width streak).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 17:18:18 +10:00
d2c8ad6a6a pantheon: UNGODLY — undo, un-doing what god did (Stage 2δ)
Undo/redo as time-travel over serializePatch(): loadPatch already restores
everything and rebuilds open panels, so undo is a 32-deep ring of snapshots.
- ungodlyMark(label) snapshots BEFORE a mutation (dedup identical, cap 32,
  clears the redo future); ungodlyGesture(label) coalesces a drag burst
  (same label within 800ms) into one entry; undo/redo loadPatch under `muted`.
- ~47 mark sites at every patch mutation: mood, zero enter/exit, dimension +
  vibe load, route patch/unpatch, the beat panel (steps/groove/euclid/expr/
  kit/mute/play), the arranger (grooves/clips/scale/euclid/follow/expr/p-lock),
  and the mix (mute/solo/reset/level/trim/group). Boot restore is suppressed.
- ⌘Z undo · ⌘⇧Z redo, added before the letter shortcuts; every single-letter
  key (p/c/o/w/z/t/b/g) now guards !metaKey && !ctrlKey — so ⌘Z no longer
  toggles zen (the pre-existing quirk), while plain z still does.
- One ctxItem with a live depth count. Memory-only in v1 (reload clears it).
App scope only; nothing inside the Synth IIFE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 17:14:41 +10:00
e56e3bc28f 🗣 pantheon: GODSPEAK worker — the voice of god (Stage 2γ)
The user's wersperr, wired into the sky: hold Right ⌥, speak, release —
the transcript flows to the hub as OSC and reaches every client as a
voice event (Stage 3 gives it the grammar).

- workers/godspeak_worker.py: wersperr-derived (MLX Whisper, 16kHz mono,
  Right ⌥ via pynput). Sends /godspeak/rec 1|0 on key edges and
  /godspeak <text> after transcription instead of pbcopy/paste. House
  worker pattern: argparse --host/--port, lazy imports with graceful
  "not available — skipping (optional)" exit, --check self-test, warm-up.
  MLX is Apple-Silicon-only so it's local-rig-only + opt-in; deps stay
  OUT of requirements.txt (documented pip install in the docstring).
  Transcription serialized (one MLX graph at a time); a busy mic can't
  kill the hotkey listener.
- hub.py: _osc_handler special-cases /godspeak (text/state, not a float
  signal), queues it, and control_loop relays {"godspeak":{...}} to ws
  clients — dropped entirely in read-only mode (it's a command channel).
  Bridged OSC-thread → asyncio via a queue, like every other ingest.
- run.py: --godspeak opt-in flag mirroring --sports; never a default.

Python-only — zero viz/index.html, zero requirements.txt/config.json.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:57:37 +10:00
jing
6d408a8bdb 🤝 world_planes: read the shared OpenSky cache (quota-collision fix)
Reader side of the OpenSky shared-cache v1 contract (mirrored in GODSIGH
SPEC2.md §4). Path ~/.cache/godverse/opensky-states.json (env OPENSKY_CACHE_FILE),
content the raw global /states/all body, freshness = mtime within 120 s.

Each poll cycle now checks the cache first: on a fresh hit it reads the global
body, filters the states to our bbox client-side (indices 5=lon, 6=lat), and
skips the HTTP call entirely (logs "cache hit"). On 429 or a network failure it
falls back to a stale cache rather than nothing. This worker only ever polls a
bbox, so it never WRITES the cache (never partial data into a global-contract
file) — it is a pure reader, and degrades to its old ≥450 s bbox fetch when no
writer is around (i.e. prod). Result: godstrument + GODSIGH running together
spend ~one poller's quota instead of colliding and 429-ing each other out.

Verified: unit tests for read_cache freshness/stale/missing + bbox filtering,
and an integration run where a fresh cache is served (count filtered to bbox,
zero HTTP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:45:32 +10:00
jing
301005bdbc 🛰 world_sats: real satellites as a source (SGP4 from cached Celestrak TLEs)
The sky's own schedule joins the orchestra. Unlike world_iss (which begs a web
API per position), world_sats fetches TLEs once from Celestrak — the stations
plus the named recon birds GAOFEN and COSMOS — caches them to
~/.cache/godverse/tles.txt (24 h freshness, atomic writes, stale-on-failure),
and propagates every craft locally with SGP4 at any rate, no per-fix HTTP.

For the venue city (--lat/--lon, retargeted by run.py) it computes each craft's
elevation above the horizon and emits:
  /gs/sats/best_elev       highest elevation among all tracked (-30..90 deg)
  /gs/sats/overhead_count  how many above 10 deg
  /gs/sats/iss_lat/iss_lon ISS sweep (available even without world_iss)
  /gs/sats/recon_elev      best elevation among only the GAOFEN/COSMOS watchers
  /gs/sats/pass.event      impulse when any craft crosses rising through 30 deg

Wiring: config.json declares the six signals with norm ranges + labels, three
starter routes (best_elev -> pad.brightness, pass -> delay.feedback,
recon_elev -> lfo.rate) and adds sats.best_elev to the cosmos group; run.py adds
the worker to the WORLD profile and the lat/lon retarget set; requirements notes
the pure-python sgp4 dep. The worker is import-safe (sits out if sgp4 is absent)
and exits politely with no cache and no network. Grimoire + manual entry added.

Math ported from GODSIGH's verified satellites layer: TLE -> Satrec.twoline2rv
(handles no_kozai), SGP4 -> TEME km, GMST rotation -> WGS-84 geodetic, then an
ECEF up-vector for elevation. Verified end-to-end against live Celestrak data:
98 sats tracked, ISS position/altitude plausible, and all signals arrive over
the hub WebSocket correctly normalized with their routes active.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:42:03 +10:00
jing
4f6a2a9634 🎹 keyroll: land note-length from local/groove-editor onto godrum's sequencer
Feature-gap analysis of local/groove-editor (c0c3a98) vs current main showed
godrum independently built almost everything the branch had — per-step
velocity/chance/ratchet, euclid, swing/humanize, scale/root, Play-One stacks,
Follow Actions. The one genuine, valuable gap was NOTE LENGTH (the "key-roll"):
main's notes were all one-16th. Ported that natively; skipped the rest as either
already-covered, a deliberate divergence (global scale, Play-One vs polyphony),
or too invasive to land without regressing godrum (clip-length/polymeter would
rewrite the fixed-16 architecture that migrateSeq/arranger/beat/commune assume).

What landed (all back-compat; len defaults to 1 = one 16th, so old patches are
unchanged except un-ratcheted sequenced notes now gate to their drawn length —
which is the feature):
- len[16] on seqFor/drumSeqFor + migrateSeq back-fill + two self-check guards
- attachNoteLengths(): .seqnote overlay blocks with an ew-resize drag handle,
  used by the voice-inspector roll and the arranger grooves-tab roll; len is
  per-voice (shared across A/B/C clips) exactly like vel/chance/rat
- playback: seqTick gates rat==1 notes to len*sixteenth; Synth.pluck gains an
  optional durMs (null = byte-for-byte the old natural decay); ratchets unchanged
- MIDI: export emits length-scaled note-offs with a same-pitch overlap clamp;
  live midiPluck now tracks pending offs per (channel,pitch) so a held note
  retriggered on its own pitch ends cleanly instead of being cut by a stale off
- grimoire + regenerated manual

Verified: groove self-check passes (incl. len guards); an isolated harness
confirms overlay render/drag/clamp at both grid sizes + Play-One stacks + the
getter that keeps overlays in sync when the arranger swaps sq.steps per bar.

The reference branch local/groove-editor is the only copy of this work and is
deleted now that note-length is landed and pushed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 16:35:29 +10:00
3d913b97fd Merge branch 'pantheon/b-godsquash' 2026-07-13 15:52:02 +10:00
ea13b2efd6 🫀 godsquash: master-bus compressor the world can pump
Stage 1β. A DynamicsCompressor + makeup between the master fade and the
safety limiter, driven by one normalized `squash` dest.
- build(): out → squash → makeup → lim (knee 12, attack 0.004; baseline is
  the sq=0 transparent state so it's clean before params() first runs). The
  GODSONIQ N.out tap stays pre-squash. squash/squashMakeup returned on N.
- params(): sq=c01(d("squash",0)); threshold −6→−36, ratio 1:1→8:1, release
  0.12→0.25; makeup = 1 + sq·sq — a QUADRATIC curve tuned by offline
  measurement (the compressor's RMS drop is ~5.95·sq² dB, so 1+sq² holds
  loudness level ±0.5 dB while crest factor falls monotonically).
- Routable exactly like crush: added to OMNI.MODULES fx-rack keys,
  OMNI.LABELS, and ZERO_RACK — nothing else outside the IIFE.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:36:03 +10:00
jing
6aff223e02 📋 godsigh crossover: build brief for opus — keyroll merge, 🛰 sats worker, 👁 god's-eye spirit, shared opensky cache
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 13:34:18 +10:00
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