🌱 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>
This commit is contained in:
parent
9ab1b0f5ae
commit
dab04294ef
@ -1,6 +1,6 @@
|
||||
# Godstrument — Claude context
|
||||
|
||||
Music-instrument web app; hosted at godstrument.pro on the **botchat** VPS (`humanjing@100.71.119.27`, exposed via Cloudflare Tunnel — NOT dealgod, despite earlier notes). App dir `/home/humanjing/godstrument` (not a git checkout). Deploy = rsync git-tracked files only, **minus dev docs/scripts** (`--files-from=<(git ls-files | grep -vE '^(CLAUDE\.md|README\.md|ZERO_OMNI_BRIEF\.md|GODSTRUMENT_MANUAL_SOURCE\.md|godstrument\.txt|build_manual\.py|test_.*\.py)$')`, never `--delete`, never push `godstrument_users.db`/`auth_secret`/`patches/`) then `sudo -n systemctl restart godstrument`. The **user manual** ships as `viz/manual.html` (served at godstrument.pro/manual.html), generated from the in-app grimoire by `python3 build_manual.py` — re-run it after editing the grimoire. Working notes/planning docs stay OUT of the deploy. Origin is Gitea `ssh://git@100.71.119.27:222/monster/Godstrument.git`. See [[godstrument-pro-deploy]].
|
||||
Music-instrument web app; hosted at godstrument.pro on the **botchat** VPS (`humanjing@100.71.119.27`, exposed via Cloudflare Tunnel — NOT dealgod, despite earlier notes). App dir `/home/humanjing/godstrument` (not a git checkout). Deploy = rsync git-tracked files only, **minus dev docs/scripts** (`--files-from=<(git ls-files | grep -vE '^(CLAUDE\.md|README\.md|ZERO_OMNI_BRIEF\.md|LIFESTRUMENT_BRIEF\.md|GODSTRUMENT_MANUAL_SOURCE\.md|godstrument\.txt|build_manual\.py|test_.*\.py)$')`, never `--delete`, never push `godstrument_users.db`/`auth_secret`/`patches/`) then `sudo -n systemctl restart godstrument`. The **user manual** ships as `viz/manual.html` (served at godstrument.pro/manual.html), generated from the in-app grimoire by `python3 build_manual.py` — re-run it after editing the grimoire. Working notes/planning docs stay OUT of the deploy. Origin is Gitea `ssh://git@100.71.119.27:222/monster/Godstrument.git`. See [[godstrument-pro-deploy]].
|
||||
|
||||
- **Canon gotcha:** the real spec prose lives in `viz/index.html` (grimoire text around line ~950); `GODSTRUMENT_MANUAL_SOURCE.md` claims to be exhaustive but has drifted before (Earth Echo / Schumann layer was missing). When they disagree, **viz/index.html is truth** — update the manual to match, not the other way.
|
||||
- Secrets (Cloudflare token etc.): read from `.env`/local files, never from chat. A CF token was pasted in chat once and rolled — don't repeat that.
|
||||
|
||||
248
LIFESTRUMENT_BRIEF.md
Normal file
248
LIFESTRUMENT_BRIEF.md
Normal file
@ -0,0 +1,248 @@
|
||||
# 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 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 = `brightness` param
|
||||
- slow particle drift around it; Moment bursts emit 20–40 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.
|
||||
Loading…
Reference in New Issue
Block a user