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>
15 KiB
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.jsonbefore 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 asviz/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.htmlexcept (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 --deleteto the box; never pushgodstrument_users.db,auth_secret, orpatches/.
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 (0x180DHR 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 callDeviceOrientationEvent.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 50–140); 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 15–30s 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 =
brightnessparam - slow particle drift around it; Moment bursts emit 20–40 particles
- background: a vertical gradient that tracks
sky.elevwhen 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.
--sportsoff: Game Day asleep state.- Reduced motion (
prefers-reduced-motion): particles off, orb breathes gently.
11. Acceptance checks (run all before calling it done)
python3 -m http.server 8899 --directory viz+ openhttp://localhost:8899/life.html?ws=ws://localhost:8790against a running hub (godstrument-testlaunch config) → sources flow, Daylight plays, zero console errors.- Kill the hub → local-only banner, tilt/tap still make sound, no errors.
- Chrome DevTools device mode iPhone 13: layout holds at 390×844; begin button ≥ 56px tall; knobs usable with a thumb.
- Each of the 8 Vibes: switch to it, confirm audibly distinct within 3s, no errors, crossfade smooth.
- Moments: fake
sky.elevcrossing and asport.kickoffframe in the console (expose a tinywindow.__lifeDebug.inject(key, val)hook, dev-only) → toast- chime + burst, cooldowns respected.
- Reload → same Vibe and knob positions return.
- Lighthouse mobile performance ≥ 90 (it's one file with no deps — this is easy; if it isn't, the canvas loop is misbehaving).
- Leave one runnable self-check in-repo:
test_lifestrument.py(stdlib only) assertinglife.htmlparses as balanced HTML, whitelist keys ⊂config.jsonsignals ∪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.