Godstrument/LIFESTRUMENT_BRIEF.md
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

15 KiB
Raw Blame History

LIFESTRUMENT — build brief

Coding instructions for a fresh Claude (Opus 4.8) session working in this repo. Read this whole file before writing code. Where this brief and the live code disagree, the code wins — verify against viz/index.html, hub.py, config.json before assuming.

1. What you are building

LIFESTRUMENT is a second, self-contained client for the existing godstrument hub: one page, viz/life.html, served by the same server at godstrument.pro/life.html. Where godstrument is the expert console — 80+ sources, a patchable modulation matrix, fourteen belief-system skins, MIDI/OSC/CV out — Lifestrument is the fun one: only the sources about living (bodies, births, weather, the sun and moon, play), a handful of one-tap Vibes instead of a patch matrix, two big knobs, immediate sound, and little dopamine Moments (a chime when somewhere a baby is born; a golden flash at sunset; a whistle at kickoff). A visitor should hear something beautiful within five seconds of tapping one button, and never see a cable.

Philosophy in one line: godstrument (and its ZERO mode) is build anything; Lifestrument is feel something, immediately.

What it is NOT: no money (no crypto/fx/market/econ/debt — banned outright), no transport machinery (planes, ISS), no quakes/deaths in the sound (life, not mortality), no matrix editing, no skins, no arranger/grooves, no commune, no MIDI/OSC/CV out, no grimoire. It links back to the full instrument for people who outgrow it.

2. Non-negotiables

  • One file: viz/life.html, self-contained (inline CSS/JS, no frameworks, no CDN, no build step) — same house style as viz/index.html. Target ≤ 3,000 lines.
  • Zero changes to hub.py / run.py / config.json / workers. Everything is client-side. If you believe you need a server change, stop and leave a note instead.
  • Do not modify viz/index.html except (optionally) one additive line: a "🌱 lifestrument" item in the right-click sky menu that opens /life.html. Nothing else.
  • Mobile-first. Design for a phone held in one hand (portrait, ~390px); desktop is the adaptation, not the base. Godstrument is desktop-first; Lifestrument is the opposite.
  • Audio starts only after a user gesture (browser rule): one big button.
  • This brief and all planning docs stay out of the deploy (see §12).
  • Never rsync --delete to the box; never push godstrument_users.db, auth_secret, or patches/.

3. Architecture (all decided — don't relitigate)

Data in. Connect to the same hub websocket the main viz uses. Copy the URL derivation from viz/index.html connect() (~line 2240): localhost pages use ws://localhost:8765, hosted pages use wss://<host>/ws, and a ?ws= query-param override wins. Consume only msg.sources frames: each key carries {raw, norm, label, event}. Ignore dests, routes, macros — Lifestrument has no use for the shared matrix. Keep a small per-key history ring (~120 values) like index.html does for its sparklines; the orb and meters draw from it.

Routing. A local mini-matrix, entirely client-side: each Vibe is a static list of {source, param, amount, curve} wirings applied at audio-frame rate (requestAnimationFrame is fine; the synth reads a params object). The hub's shared routes are never touched — this also means Lifestrument works against the read-only public hub exactly as well as a local one.

Sound. Its own compact Web Audio synth (§6). Do not try to reuse the index.html synth — it is closure-scoped and expert-shaped. ~300 lines is enough.

Auth. Same invite-only accounts. Reuse the exact /api/* endpoints index.html calls (read its auth IIFE, ~line 6400: me/login/signup/logout with a session cookie) — do not invent new endpoints. Landing = one screen with the LIFESTRUMENT wordmark, a one-line poem, login/signup (reuse the flow, restyle to §9), and after auth one giant begin button that starts audio and drops you into the default Vibe already playing.

Local sources (client-generated, never sent to the hub; they exist only in the local matrix, prefixed me.*):

  • me.heart — Web Bluetooth heart rate. Lift the pattern from index.html's ♥ implementation (0x180D HR service). Chrome/Edge/Android only — feature- detect and show "not on this browser" honestly on iOS Safari.
  • me.tilt.x / me.tilt.y — DeviceOrientation beta/gamma normalized 0..1. On iOS call DeviceOrientationEvent.requestPermission() from the begin button's gesture. This is the universal free sensor: every phone becomes a controller with zero hardware.
  • me.shake — DeviceMotion impulse (accel magnitude over threshold, 400ms cooldown), an event source.
  • me.voice — mic RMS via getUserMedia + AnalyserNode, opt-in per Vibe.
  • me.tap — taps on the orb: sets tempo (rolling average of last 4 intervals) and fires a pluck. Fallback "heartbeat" for iOS.

Hub sources whitelist (the ONLY hub keys Lifestrument may use — exact keys, all verified present in config.json signals): clock.births, clock.pop, clock.popvel, world.lifeexp, world.fertility, weather.temp, weather.wind, weather.precip, weather.humidity, sky.day, sky.elev, season.north, season.south, astro.moon, astro.harmony, almanac.fertile, almanac.waxing, light.lux, light.flash.event, audio.rms, audio.centroid, tof.near, tof.cx, tof.cy, hand.open, sport.events_today, sport.inplay, sport.kickoff, wiki.rate. (tof.*/light.* flow when the sensor rig — or workers/sim_esp32.py — runs; sport.* when the hub runs --sports. Absent keys must degrade gracefully: a card shows a gentle "asleep" state, never an error.)

4. The Vibes (presets) — build exactly these eight

Each Vibe is: an emoji + name + one-line promise on a card; a wiring list; a palette tint for the orb; and one or two "moment" hooks (§7). Switching Vibes crossfades over ~2s (ramp old wirings' amounts to 0, new ones in). Store the last Vibe + knob positions in localStorage (ls_vibe, ls_energy, ls_dream).

# Vibe Promise Core wiring (source → param · amount · curve)
1 💓 Heartbeat (default) your pulse is the tempo me.heart raw BPM → tempo (clamped 50140); me.heart norm → brightness ·0.6·lin; no heart? me.tap sets tempo; kick ticks every beat, hat every half
2 🌅 Daylight the sun plays the room sky.elev → brightness ·1.0·scurve; sky.day → pad warmth ·0.8; inverse sky.day → space ·0.6 (night opens reverb); weather.temp → root note (see §5 key map)
3 🌧 Rain Room the weather writes the melody weather.precip → pluck density ·1.0·exp (rain literally plays notes); weather.wind → pan-wobble rate ·0.7; weather.humidity → wash level ·0.5; weather.temp → root note
4 👶 The Living somewhere, a baby clock.births → chime rate (§7 Poisson, throttled); world.lifeexp norm → pad brightness ·0.7; world.fertility → warmth ·0.5; clock.popvel → drone level ·0.4
5 🌕 Moon Bath slow silver astro.moon → shimmer level ·0.9 (full = maximal); almanac.fertile → pad bloom ·0.5; astro.harmony → consonance (major-ness) ·0.6; tempo fixed 60, everything legato
6 🏟 Game Day the world at play sport.inplay → percussion density ·0.9; sport.events_today → base energy ·0.4; sport.kickoff → riser+crash moment; hub without --sports → card asleep: "the stadium sleeps — ask the keeper"
7 🎤 The Room your space sings back me.voice → swell ·1.0·scurve; audio.centroid (or local mic centroid) → filter ·0.5; light.lux → brightness ·0.5; light.flash.event → sparkle hit; tof.near → pluck when hand approaches
8 🤲 In Hand tilt, and it bends me.tilt.x → filter sweep ·1.0; me.tilt.y → space ·0.8; me.shake → big hit + orb burst; hand.open/tof.cx/cy join identically when the rig is present

Curves are godstrument's: lin, scurve (ease in/out), exp (hugs the floor).

5. The two knobs (the only "mixer")

  • ENERGY — one macro scaling: tempo ±20%, percussion density, pluck probability, brightness ceiling. Low = ambient wash, high = it dances.
  • DREAM — space macro: reverb size/mix, delay feedback, pad release, detune. Low = dry and close, high = cathedral.

Both are big thumb-friendly dials (or vertical sliders on desktop), default 0.5, persisted. Nothing else is user-adjustable in v1.

Key map (for weather.temp → root): map norm 0..1 across [D minor pent, F major pent, G mixolydian(pent subset), A major pent] roots 38/41/43/45 — cold is minor and low, warm is major and higher. All melodic content in Lifestrument is pentatonic — the no-wrong-notes rule is what makes it un-mess-up-able.

6. The synth (compact, ~300 lines)

Voices (all Web Audio primitives, no worklets):

  • kick — sine drop 120→45Hz, 90ms, on the beat when ENERGY > 0.2
  • hat — filtered noise burst, offbeats, level ~ percussion density
  • pluck — 4-voice poly: triangle osc → lowpass → exp-decay gain; pentatonic notes chosen by the active Vibe's melody driver
  • pad — 2 detuned saws → lowpass (brightness) → slow attack/release; chord = root + 5th + 9th; "warmth" morphs triangle↔saw mix
  • drone — one sine + one sine at the 5th, sub register, level per Vibe
  • shimmer — high sine cluster with slow random pan, for Moon Bath
  • chime — the Moment sound: 2-osc bell (sine + 3.01× partial), 2.5s decay
  • master: delay (0.375s, feedback = DREAM·0.5) + reverb (generated impulse: 2s noise burst with exp decay into a ConvolverNode) + DynamicsCompressor as a safety limiter.

The params object (what wirings write into, smoothed with setTargetAtTime): tempo, brightness, warmth, space, wash, density, pluckDensity, droneLevel, shimmerLevel, consonance, panWobble, swell, root.

7. Moments — the ding-ding engine

A Moment = toast (emoji + one line, ~4s), a chime (pitch varies), and an orb burst (particles in the Vibe's tint). Cooldowns are what keep them precious — enforce per-moment minimum gaps:

Moment Trigger Cooldown
👶 somewhere, a baby Poisson draw against clock.births raw (≈4/s worldwide — draw every frame, p = rate·dt·THROTTLE with THROTTLE tuned so it fires ~every 1530s in The Living, ~2min elsewhere) 12s
🌅 / 🌇 the sun rises/sets where you are sky.elev raw sign crossing (needs two frames straddling 0) 6h
🌕 the moon is full tonight astro.moon norm > 0.97, once per session session
kickoff — N matches begin sport.kickoff event arrives 30s
🌧 rain has started weather.precip raw 0 → >0 1h
💓 new calm / new fire me.heart sets a session min/max (after 3min warmup) 5min
🎂 your day (v2, with natal)

First-visit onboarding is also a Moment sequence: three toasts introducing the orb, the Vibes, the knobs. Never a modal tutorial.

8. The orb (the whole visual)

One canvas, one big central orb (~55% viewport width on phone):

  • radius breathes with the master output RMS; a ring pulses on each kick
  • hue = active Vibe's tint; inner gradient brightness = brightness param
  • slow particle drift around it; Moment bursts emit 2040 particles
  • background: a vertical gradient that tracks sky.elev when available (night → deep navy, day → pale warm) — the page itself knows what time it is
  • tapping the orb = me.tap (pluck + tempo in Heartbeat)
  • under the orb: current Vibe name + the live fact it runs on, in plain words, updated ~10s: "144 hearts born this minute" / "the sun is 12° up" / "3 matches in play". Real numbers, human phrasing — this is Lifestrument's version of godstrument's data-honesty.

9. Look & feel

Dawn, not deep space: near-white warm background in day, dusky in night mode (driven by sky.elev, not a toggle), one accent hue per Vibe, big rounded type (system stack is fine; 17px minimum body), generous spacing, cards with soft shadows, everything reachable by thumb. No tables, no panels, no right-click anything. The wordmark is lowercase: lifestrument · "played by being alive". Keep godstrument's honesty of voice ("the ECB fixes once a day" energy) but warmer and shorter.

10. Degradation matrix (build these states, they are most of the UX)

  • No Bluetooth API (iOS): Heartbeat card shows "🫱 tap your pulse" mode.
  • Hub unreachable: local-only mode — me.* sources still play (tilt/tap/voice); banner "the world is quiet — playing your half".
  • Not signed in: landing; signup needs an invite code exactly like godstrument.
  • Sensor rig absent: In Hand uses tilt only; The Room uses mic only.
  • --sports off: Game Day asleep state.
  • Reduced motion (prefers-reduced-motion): particles off, orb breathes gently.

11. Acceptance checks (run all before calling it done)

  1. python3 -m http.server 8899 --directory viz + open http://localhost:8899/life.html?ws=ws://localhost:8790 against a running hub (godstrument-test launch config) → sources flow, Daylight plays, zero console errors.
  2. Kill the hub → local-only banner, tilt/tap still make sound, no errors.
  3. Chrome DevTools device mode iPhone 13: layout holds at 390×844; begin button ≥ 56px tall; knobs usable with a thumb.
  4. Each of the 8 Vibes: switch to it, confirm audibly distinct within 3s, no errors, crossfade smooth.
  5. Moments: fake sky.elev crossing and a sport.kickoff frame in the console (expose a tiny window.__lifeDebug.inject(key, val) hook, dev-only) → toast
    • chime + burst, cooldowns respected.
  6. Reload → same Vibe and knob positions return.
  7. Lighthouse mobile performance ≥ 90 (it's one file with no deps — this is easy; if it isn't, the canvas loop is misbehaving).
  8. Leave one runnable self-check in-repo: test_lifestrument.py (stdlib only) asserting life.html parses as balanced HTML, whitelist keys ⊂ config.json signals me.*, and banned prefixes (crypto., fx., market., econ., debt., quake., planes., iss.) appear nowhere.

12. Deploy

viz/life.html deploys with the normal flow (it must ship — it's product). This brief does NOT: it is in the deploy exclusion list in CLAUDE.md. Deploy = rsync git-tracked files minus that exclusion list (never --delete), then sudo -n systemctl restart godstrument on the box, then verify https://godstrument.pro/life.html returns 200 and plays.

13. Explicitly deferred to v2 (do not build now)

Natal/birthday integration (needs the chart caster), a shareable "postcard" image of your session, preset remixing/saving, Lifestrument-specific accounts or theming per user, Apple Watch workarounds, camera-based pulse detection, any server-side anything.