The audit's verdict was that Godstrument is short on REACH, not features:
everything hid behind an invisible right-click, and everything you made
died in one tab. Four builds against exactly that.
☰ EVERYTHING — a visible, grouped index of the whole instrument (view /
play / your world / together / out / start here). Labels lead with the
plain verb and wear the god-name behind: "arpeggiator · GODSPEED",
"record to a file · TESTAMENT". Critically it is ONE data-driven list
with TWO renderers — the ☰ panel and the right-click sky menu both
render skyGroups(), so a feature can never appear in one and not the
other. (The sky menu's 90 hand-written lines became a 17-line loop.)
Submenus expand in place, so touch works.
☄ THE OMENS — the instrument always knew when the world was doing
something rare and never said so. Reads the live feeds and ranks what's
remarkable: a full moon, a harmonious sky, satellites overhead, a quake
that just landed, your solar return. One line whispers into the ticker
on arrival; the full reading is a tap away. Verified against the real
planet — it read a 91%-lit moon, an unusually harmonious sky and a
convulsing bitcoin, all true at the time.
🌍 SHARED WORLDS — the whole instrument (cables, grooves, beat, kit,
scale, tempo) folded into one link, gzipped by the browser's own
CompressionStream: ~10KB of patch becomes a ~620-character URL. It
rides the fragment, so nothing is uploaded, no account or gallery is
needed, and there is nothing to take down. The receiver is always
asked, never overwritten, and ⌘Z restores theirs. Round-trip verified:
a 12-wheel hat and 137bpm came back exactly. Deliberately NOT gated on
the hub handshake — a shared link must open even when the hub is down.
📱 THE PHONE FORK — a touch device is offered lifestrument once
(remembered, never forced), and the top bar folds to fit a narrow
screen either way.
Plus: the expression-lane drag was written out byte-identically in
three places; it is now one shared helper that speaks pointer events,
so velocity/chance/ratchet/p-lock lanes work under a thumb too.
Grimoire documents all four; manual regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Correctness (four were my own regressions from the REVELATION wave):
· ☸ WHEELS polymeter permanently detuned after any skipped frame —
seqAbsStep advanced by ++ per transition, but one frame can cross two
16ths (bpm ≳155, or the clamped dt on every tab refocus). Proven to
drift 12 steps out at 174bpm; now advances by the true distance, so
seqAbsStep % 16 === seqCurStep always holds.
· Sample & hold with exactly one lit step froze on its first sample
forever (guard keyed on the wrapped step, which never changed) — now
keyed on the absolute step.
· PSALM double-activation left the MICROPHONE HOT after "stop": the
re-entrancy guard sat after the getUserMedia await, orphaning the
first stream/interval/context. Guard is now synchronous, a stop
during the prompt is honoured, and a second tap while starting
cancels. Mic off means off — the promise the feature makes.
· Live-recording a drum tap into a short wheel wrote steps past wlen
that never played; the tap now folds onto the lane's own wheel.
· Switching MIDI out port left notes droning on the old port (pending
off-timers fired at the NEW one) — midiFlushOut() releases every held
note + all-notes-off before the swap.
· world_quakes' seen-set grew unbounded on long-running rigs.
Security: stored XSS via the commune room name — the one untrusted
string reaching innerHTML unescaped (hub only lower-cased it). Escaped
client-side at both sinks, and the hub now strips markup chars too.
Robustness: world_ephemeris/world_almanac imported swisseph unguarded,
so a machine without pyswisseph crash-looped them every 10s forever
against the supervisor's exit-0 contract — both now retire quietly
(verified by simulating the missing dep), and pyswisseph is documented
in requirements.
Mobile: the top bar's five separately-pinned chips collided below
~500px — the first thing a phone visitor saw was a broken header. Now
a clean row with the account chip on its own line and the source
counter moved left.
Housekeeping: dead measureSpaced(), dead #account .code selector, and
an 888K stale worktree removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The front door becomes a skin: the first-visit card grows from three doors
to six, the walked door is REMEMBERED (gs_door) and opens the same way on
every arrival, and it's re-choosable any time (right-click sky → 🚪 the
doors, or say "doors"). Each door is one DOORS registry entry + one act —
delete the entry, the door is gone.
The classics (unchanged acts): 🌱 start from zero · 🌅 first light ·
⛈ the full storm. The new gig doors:
- 🎪 the rig — walk on stage: first light applied, beat panel + GODSPEED
already open, and the sleeper fold tightened so only the ~8 liveliest
uncabled hands stay in the field (8th-liveliest flowDisp sets the bar).
- 🃏 the deal — the world deals five cables from its liveliest feeds
(family-diverse) onto a shuffled spread of ten dests, ONE card always
⚡pulsing (÷16 or ÷8); "🃏 deal again" chip; every arrival deals fresh;
each hand is an ungodly mark so ⌘Z takes it back. Voice: "deal" redeal
while the deal is your door.
- 🌒 the dawn — first light applied, then the players take the stage one
at a time (~1.25s apart, cabled first, then by liveliness, 18 arrivals),
each narrated in the ticker; cables only draw once their player is on
stage (drawCables skips hidden sources); when the entrance ends the
sleeper fold reconciles the rest.
Verified live (static serve + temp hook, removed & grep-verified 0):
six cards render; deal → exactly 5 routes, 1 pulsing, chip visible,
gs_door saved; redeal changes the hand, marks undo, ⌘Z restores the
previous hand exactly; rig → beat+GODSPEED open, field folded tight,
deal furniture closed; dawn → 24/24 hidden at curtain, players arrive
one-by-one with ticker narration and their cables, finale reconciles to
0 hidden; zero console errors; T O B G S + ⌘Z regression green;
grimoire + manual regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four hands-on changes from live-play feedback (Jono's "what speed are those
things going at?"):
- ghost cables: a patched cable whose source is silent no longer vanishes —
alpha/width floor (va = max(activeDisp, 0.10)) on the idle line, dashes and
dot; glow still reserved for active cables.
- speed at a glance: flowLabel() speaks each source's rate in bars of the
current godtime ("streaming · ≈ 1/8 bar · ≈ every 5 bars · asleep, still
12m · glacial"); shown in the node right-click live readout and as a second
line in every loupe source row. Sources track _lastMoveT (last real data).
- ⚡ PULSE: per-cable clocked throb — r.pulse ∈ {16,8,4,1} pulses/bar; the
shaped value rides a raised-cosine synced to seqPhase, so a glacial feed
(debt, declination) becomes a ≥1/16 groove whose DEPTH is the slow value.
Cycled via the ⚡ chip on each cable row (off → ÷16 → ÷8 → ÷4 → bar),
serialized with the patch, undo-marked, whitelisted on load.
- 💤 sleeper fold: cableless sources with no signal (norm ≤ 0.02) and no
movement for 45s fold out of the field; they wake THEMSELVES when their
feed speaks (lastMove refresh). Bottom-left chip shows the count and
toggles the fold (localStorage gs_fold). Field view only — skins keep
their full cosmologies, zero stays zero, disconnected dev view unaffected.
Verified live (static serve + temp hooks, removed & grep-verified 0): pulse
active == norm·window to 0 error over 12 same-frame samples and cycles
⚡÷16→÷8→÷4→bar→off; ghost cable visible on a dead venus.dist route;
labels discriminate (mercury "≈ every 5 bars", neptune "glacial", earth.beat
honestly "waiting…" — it never moves); fold hides dead feeds with chip
"💤 2 quiet feeds folded", chip toggle restores, woken feed rejoins field;
zero console errors; T O B G S + ⌘Z regression green; grimoire + manual
regenerated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two live-play fixes:
- Zoom: trackpad pinch (ctrl+wheel) over a DOM overlay (right-click menu,
panels) used to hit the BROWSER's page zoom, which the canvas then made
un-undoable because it preventDefaults every wheel — zoomed-in with no way
out except re-opening a menu. Now a capture-phase window listener catches
pinch everywhere and drives the app's own view zoom (shared zoomAt());
Safari's gesture* events are blocked too. Plain scrolling in menus and
panels is untouched; canvas behavior unchanged (verified single-fire).
- Cables: dash/blip speed was uniform (activity-only). Each source now
carries flowDisp — an EMA of |Δnorm|/s with fast attack + slow release —
and every cable's dashPhase advances at 6 + flow·240 + activity·40, with
the pulse dot riding the same phase. A ticking feed streams, a glacial
feed crawls (venus.dist ~0 flow vs mercury.phase saturated, measured);
event sources surge on fire via firePulse.
Verified: pinch in/out over overlay drives view.z and is defaultPrevented,
plain scroll not hijacked, canvas single-fire, flows measured per-source
via temp hook (removed, grep-verified 0), zero console errors, T O B G S
+ ⌘Z regression green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vendor SOLARGOD@6391273's dependency-free JPL ephemeris (viz/spheres.js) and
make the solar system a modulation source, computed locally at the instrument's
own clock — no network, no server, no sockets.
- viz/spheres.js: ephem.js verbatim + the minimal lib subset inlined; loaded via
a <script type="module"> bridge that self-checks (Mars @ JD 2461236.5 within
0.005 AU of Horizons ground truth) and hangs a frozen window.SPHERES; every
consumer guarded so a missing/broken file still boots the app.
- sphere.<planet>.{phase,dist,beat} sources for the 8 planets, routable like any
S&H source; the great-year clock maps 1 bar = N days of sky (log knob 1..3650,
default 30) off seqPhase alone, so scrub/tempo/reload reproduce the same sky.
- 🪐 spheres panel (S / right-click sky): per-planet live meters, rate knob,
☸ orbital-ratios button (drum wheels → 4:8:16 Io:Europa:Ganymede, 8:13
Venus:Earth pentagram), on/off; godspeak: spheres on/off·faster/slower,
"<planet> drive <dest>". spheres {on,rate} travel in the patch.
- grimoire chapter + regenerated viz/manual.html.
Verified: self-check green; determinism (jupiter.phase@bar16 identical across
reload); pentagram (venus.beat period 1.59 bars ≈ 583.9/365, within 2%); ratios
set exact wlens and ⌘Z reverts; zero console errors; ♪ T O B G + ⌘Z all work;
spheres default off on fresh boot.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Four grimoire entries in the house voice, fact-checked against what was
verified: 📼 TESTAMENT ("it is written"), ☸ WHEELS (Ezekiel 1:16 — "a
wheel in the middle of a wheel"), 🕊 PSALM (the local-listening privacy
line — nothing leaves the machine), ✦ CANON (the read-only shelf of
worked examples). Manual regenerated (27 sections).
Cross-feature QA: all four surfaces coexist with the pantheon and the
crossover — TESTAMENT ⏺ button, WHEELS chips in all 5 beat lanes,
PSALM/TESTAMENT/GODSPEED ctxItems — zero console errors, groove
self-check green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four shipped patches on a read-only shelf: ✦ the heartbeat (boom-bap,
mpc 8-4, kick duck), ✦ the wheel within the wheel (WHEELS polymeter —
kick 16 / hat 12 / clap 10), ✦ the squeeze (GODSQUASH — the market &
quakes pump the master), ✦ quiet arrival (gentle earth-echo ambient,
no drums). git-tracked in factory/, authored in the client's
serializePatch shape.
Course-correction from the brief's stale anchor: the template select
reads /api/presets (account-based, auth.py), NOT the hub's legacy
patchList — so the factory shelf is injected there. list_factory() +
load_factory() prepend the ✦ patches for every signed-in player;
PUT/DELETE on a ✦ name → 403 (read-only); a missing factory/ degrades
to []. test_factory.py (deploy-excluded) validates shape.
Verified: HTTP dispatch serves the 4 ✦ names leading the list, loads
one, rejects writes (403), user saves still work, traversal/missing →
None, zero index.html changes. Taste disclaimer: these are structural
drafts — correct shape is the deliverable, beauty is the human's.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>