293 lines
19 KiB
Markdown
293 lines
19 KiB
Markdown
# ZERO MODE · VIBES · DIMENSIONS · the OMNI panel
|
||
### Build brief for the implementing model (Opus). Read fully before writing code.
|
||
|
||
John's ask, verbatim spirit: *the full UI is an intense sensory assault. Give people a
|
||
**zero mode** — 7 sources left, 7 right, click a slot to see the feeds in that grouping
|
||
(same colors/chakra energy as the main UI), pick one or more, route them by hand to an
|
||
output, right-click to assign it (synth role / MIDI / …). They build everything from
|
||
zero. Save the whole thing as a **dimension** (a full hardcore scene save); save a
|
||
specific source→route→output combo as a **vibe** — "build a vibe, save a vibe, share a
|
||
vibe". And give OMNI a NICE UI panel.*
|
||
|
||
---
|
||
|
||
## 0. Ground rules (unchanged from every Godstrument session)
|
||
|
||
- **Canon:** `viz/index.html` is truth; the grimoire prose inside it must be updated to
|
||
match whatever ships (never the other way). Regenerate `GODSTRUMENT_MANUAL_SOURCE.md`
|
||
with the builder afterwards (`build_grimoire.py` pattern — carry-forward + dedupe).
|
||
- **Honesty rule:** nothing decorative. Every slot, knob, and control maps to a real
|
||
feed/dest/tweak. If a thing isn't wired, it doesn't render.
|
||
- **Verify with your own eyes:** preview server + login (invite + `/api/signup` recipe —
|
||
see memory `godstrument-local-verify`), console clean, then screenshot proof.
|
||
- **Deploy:** rsync git-tracked files to botchat (`humanjing@100.71.119.27`, app dir
|
||
`/home/humanjing/godstrument`), **never `--delete`**, never push `godstrument_users.db`
|
||
/ `auth_secret` / `patches/`; `sudo -n systemctl restart godstrument`; curl-verify
|
||
cache-busted. First load post-deploy is slow (cold start).
|
||
- **auth.py changes must be additive** (new table / new column with default) — the
|
||
server's SQLite holds real user accounts. Never a destructive migration.
|
||
- Commit per milestone with the Co-Authored-By line. Adversarially review at the end
|
||
(a Workflow, as in previous sessions) and fix confirmed findings before deploy.
|
||
|
||
## 1. What already exists — build on it, don't reinvent
|
||
|
||
| Thing | Where (verify at build time) | Use it for |
|
||
|---|---|---|
|
||
| **14 concept groups** with `label`, `orbit`, `members` | `config.json:31-46` ("groups"); grimoire "The vibes — grouping by feeling" (~line 848-900, has the emojis: 💰 wealth, 💧 water, …) | The 7+7 zero slots. They are ALREADY called vibes in canon. |
|
||
| MOODS + applyMood | `viz/index.html:2275-2322` | The pattern for entering/leaving a named state; zero joins this family |
|
||
| First-visit flow (`gs_seen`, `gs_mood`) | `viz/index.html:2841-2842` | Zero as the new-user front door |
|
||
| serializePatch / loadPatch | `viz/index.html:2532-2567` | Dimensions ARE this; a blank dimension = `{routes:[], …}` |
|
||
| addRouteLocal / registerRoute / removeRouteLocal | `viz/index.html:2452-2468, 2921` | Zero-mode routing + vibe apply/remove |
|
||
| "patch to →" context submenu | `viz/index.html:5521` | Reuse for assigning a node to an output |
|
||
| Node tweaks (mute/gain/offset), setParamLocal | `viz/index.html:2036+, 2451` | OMNI panel knobs = views over these |
|
||
| MY PATCHES (account presets, one JSON blob) | `auth.py:46 (presets table), :249+ (endpoints)`; ⚙ panel UI | Rename surface to **Dimensions**; vibes get a sibling store |
|
||
| CHAKRAS (colors) | `viz/index.html:3427` | The zero-slot color language |
|
||
| Arranger panel (`arrbtn`, resizable, tabbed) | `viz/index.html:1497+, 2325-2330` | The UI pattern for the OMNI panel |
|
||
| KEY_ACTIONS + remappable keys | `viz/index.html:~6060-6110` | Key bindings for zero + OMNI panel |
|
||
| quantize option on routes | `viz/index.html:1398, 2415` | The "assign like a synth roll" — note outputs get scale/root/octaves |
|
||
|
||
**Extraction discipline:** before each milestone, re-verify the exact lines above (they
|
||
drift) and extract any formula/shape you depend on into code comments citing
|
||
`viz/index.html:LINE`.
|
||
|
||
## 2. ZERO MODE — dimension zero
|
||
|
||
**Concept:** a fourth entry in the mood family, but deeper: not muted — **empty**.
|
||
No cables, no voices sounding, the field silent and almost blank. The user builds the
|
||
entire instrument by hand, one cable at a time. Leaving zero (to any mood / the storm)
|
||
restores the full factory experience.
|
||
|
||
### Entry & exit
|
||
- Right-click sky → **`0 zero — build from nothing`** (top of the mood submenu), plus a
|
||
key (suggest `F11` or a free slot in DEFAULT_KEYS; check collisions).
|
||
- **First-visit choice card** (replaces the current silent default into first light):
|
||
three big options — 🌱 **start from zero** (build it yourself) · 🌅 **first light**
|
||
(a gentle song) · ⛈ **the full storm** (everything at once). Persist choice like
|
||
`gs_mood`. Returning users keep whatever they last used.
|
||
- Entering zero: snapshot the current patch to a holding slot (localStorage
|
||
`gs_prezero`) so exiting zero can offer "restore what you had" vs "keep my build".
|
||
Implementation: `loadPatch({routes: [], tweaks: {}, seqs: {}, …})` + a `zeroMode`
|
||
UI flag. Do NOT touch the hub or other users — this is all client-local, like moods.
|
||
|
||
### The zero UI (the whole point: calm)
|
||
- **Hide the full node field.** Render instead:
|
||
- **7 slots down the left, 7 down the right** — the 14 groups from config.json in a
|
||
fixed, curated order (suggest: left = the elements/feelings: light, dark, fire,
|
||
water, earth, air, spirit · right = the domains: planet, cosmos, heavens, human,
|
||
market, wealth, summer). Each slot: a colored ring (its chakra/vibe hue — extract
|
||
or define a `GROUP_HUES` map; the grimoire's vibe emojis are the seed), the label,
|
||
and a live "breathing" glow driven by the group's average member value (already
|
||
computed for group nodes) — alive but quiet.
|
||
- **A center stage** — empty at first. Nodes the user builds land here.
|
||
- **An output rack** along the bottom (or right-center column): the destinations,
|
||
grouped and labeled in plain language:
|
||
- **OMNI voices:** lead (sings) · bass (roots) · pad (glows) · drone (hums) · perc (taps)
|
||
- **OMNI space & grit:** reverb.size · delay.feedback · saturation · glitch ·
|
||
granular.density · crush · wavetable.morph · lfo.rate · master.space · filter.cutoff
|
||
- **🌀 earth echo** knobs (echo.*) — shown only when earth echo is on
|
||
- **MIDI** (when an out port is enabled): the CC map + lead/bass note channels
|
||
Each output is a small labeled socket, dark until something is patched into it.
|
||
- **Slot → picker drawer:** clicking a slot slides open a drawer listing that group's
|
||
member feeds: name, one-line plain-English description (extract from the grimoire
|
||
source blurbs / `signals` labels), live value bar, chakra color. Each row has ⊕.
|
||
Picking 1+ feeds **creates a node on the stage** (or adds to the slot's existing
|
||
node). The node renders like a mini group-node: the slot's color, member count badge.
|
||
- **Routing:** drag from a node to an output socket → lays a cable **per member feed**
|
||
to that dest at a sensible default amount (0.5, curve lin) — reusing addRouteLocal.
|
||
Alternatively right-click node → the existing "patch to →" submenu filtered to the
|
||
rack outputs. Cables render exactly like the main UI (same renderer — don't fork it).
|
||
- **Right-click assign (the "synth roll" ask):** right-click an output socket or a
|
||
cable →
|
||
- note outputs (lead.note/bass.note): **quantize picker** — scale (the 11 SCALES),
|
||
root (note name picker), octaves (1-3); presets "minor pent @C4" etc.
|
||
- CC/level outputs: amount slider + curve picker (lin/exp/exp3/log/scurve/inv) —
|
||
the existing route controls, surfaced.
|
||
- MIDI: choose CC number / channel (when midi enabled).
|
||
- **Guidance, not tutorial:** use the existing eventTicker for 3-4 contextual nudges
|
||
("pick a source from a ring", "now drag it onto a voice", "press ♪ — that's YOUR
|
||
instrument"). No modal tutorial. The calm IS the feature.
|
||
- **Graduation:** a small "⛈ enter the storm" affordance; leaving zero shows the full
|
||
field with everything the user built still wired (their routes persist — zero is not
|
||
a sandbox, it's the same matrix).
|
||
|
||
### Acceptance criteria (Z-AC)
|
||
From a fresh account in zero mode, with no instructions beyond the ticker nudges, this
|
||
sequence works in under a minute and makes sound: click a ring → pick `sun.speed` →
|
||
drag node to `filter.cutoff`; click another → pick `crypto.price` → drag to `lead` →
|
||
right-click lead → minor pent @ 60 → press ♪ → bitcoin sings through a sun-opened
|
||
filter. Verified in the preview with a screenshot; console clean; leaving zero → first
|
||
light works; re-entering zero offers restore.
|
||
|
||
## 3. VIBES — build a vibe, save a vibe, share a vibe
|
||
|
||
A **vibe** = a small, additive, shareable bundle of wiring:
|
||
|
||
```json
|
||
{ "v": 1, "name": "sun filter", "emoji": "☀️",
|
||
"routes": [{"source": "sun.speed", "dest": "filter.cutoff", "amount": 0.85, "curve": "exp"},
|
||
{"source": "crypto.price", "dest": "lead.note", "amount": 1.0,
|
||
"quantize": {"scale": "minor_pent", "root": 60, "octaves": 2}}] }
|
||
```
|
||
|
||
- **Apply = additive** (registerRoute upserts; nothing else touched). Tag applied
|
||
routes with `viaVibe: "<name>"` so a vibe can be **toggled off** (remove exactly the
|
||
routes it added — removeRouteLocal). Multiple vibes stack; a route present in two
|
||
stays until both are off.
|
||
- **Save a vibe:** in zero mode, "💾 save as vibe" on the stage selection (or the whole
|
||
current zero build); in the full UI, right-click a node → "save these cables as a
|
||
vibe". Named + emoji.
|
||
- **Store:** account-side, in auth.py — **additive** migration: either a new `vibes`
|
||
table mirroring presets, or a `kind` column on presets defaulting `'dimension'`.
|
||
Endpoints mirror the preset ones (list/save/delete). Local fallback to localStorage
|
||
when signed out.
|
||
- **Share:** two mechanisms, both v1:
|
||
1. **Vibe code:** compact string `GSV1.<base64url(json)>` — "copy vibe code" puts it
|
||
on the clipboard; an "import vibe" box (⚙ panel + zero mode) accepts a pasted code.
|
||
2. **URL fragment:** `godstrument.pro/#vibe=GSV1....` — on load (post-login), offer
|
||
"apply the vibe you were sent?" Never auto-apply; show its name + route count
|
||
first. (Fragment never reaches the server — clean with the invite-only model.)
|
||
- **Validation:** parse defensively — unknown sources/dests in an imported vibe are
|
||
listed and skipped, not errors. Cap: 32 routes per vibe.
|
||
|
||
### Vibes AC
|
||
Save a 2-route vibe, toggle it off/on, copy its code, hard-reload, import the code,
|
||
apply — identical wiring returns. A vibe code from another account applies cleanly.
|
||
|
||
## 4. DIMENSIONS — the full scene save
|
||
|
||
- **Rename the user-facing surface**: ⚙ "MY PATCHES" → **"DIMENSIONS"** ("a dimension
|
||
is the whole world you built — every cable, mix, groove, tempo, key"). Same
|
||
serializePatch blob, same endpoints — this is a naming + framing change; do NOT
|
||
break existing saved presets (they simply appear as dimensions).
|
||
- Zero mode ships with the blank state as **"dimension zero"** (a virtual, always-
|
||
available entry at the top of the list — not stored, generated).
|
||
- Grimoire: rewrite the "Saving your instrument" section for the dimension/vibe split:
|
||
*a dimension is everything; a vibe is a feeling you can hand to a friend.*
|
||
|
||
## 5. The OMNI panel
|
||
|
||
A dedicated synth panel, sibling to the tracks panel (same open/close/resize pattern —
|
||
`arrbtn` precedent). Button: **🎹 OMNI** bottom-right next to 🎛 tracks; key-bindable.
|
||
|
||
- **Layout — a synth, not a mixer:** module strips:
|
||
- **VOICE:** the pluck voice selector — FM tine · 🎸 string · 🎛 GODSONIQ (with a
|
||
"sample 3s" button + sampling state) — wired to `Synth.stringVoice()` /
|
||
`Synth.godsoniq()` / `Synth.sampling()`; plus 🌐 tab feed toggle.
|
||
- **LEAD** (wavetable.morph knob) · **BASS** · **PAD** (pad.brightness) ·
|
||
**DRONE** (drone.voices; a small "7.83 Hz" schumann lamp when earth echo is on) ·
|
||
**PERC** (perc.density).
|
||
- **FX RACK:** filter.cutoff · reverb.size · delay.feedback · saturation · glitch ·
|
||
granular.density · crush · lfo.rate · master.space (+ chorus shown as a fixed,
|
||
labeled, non-decorative indicator — it IS always on; label it honestly).
|
||
- **🌀 EARTH ECHO:** on/off + the six echo.* knobs (only enabled when on).
|
||
- **TRANSPORT:** ♪ toggle, mood selector, the current scale (from gScale, click →
|
||
scale picker), BPM readout linked to godtime.
|
||
- **Knob semantics (the crucial design):** every knob shows the **live world-driven
|
||
value** as an animated arc (from the dest's current value — the world's hand).
|
||
Dragging a knob adjusts the dest's **tweak gain/offset** (existing setParamLocal
|
||
machinery) — you're riding a fader the world is also pushing. Double-click resets
|
||
the tweak to neutral (the ⌘⇧-click semantic). A small dot shows when a knob has a
|
||
user tweak. Right-click a knob = the node's existing context menu (mute/solo/level/
|
||
cables/patch to). **No new audio paths — the panel is a view over dests + tweaks.**
|
||
- Muted dests render dim; a grooved/p-locked dest shows a tiny grid icon.
|
||
- Works inside zero mode too (it doubles as zero's output rack detail view — if this
|
||
is cleanly achievable, the rack and the panel share components; if not, keep them
|
||
separate and simple).
|
||
|
||
### OMNI AC
|
||
Open the panel: every knob's arc moves with live data; drag reverb.size up — audibly
|
||
more reverb and the dest tweak persists in the saved dimension; voice selector swaps
|
||
FM→string→GODSONIQ and plucks confirm; screenshot for the session log; console clean.
|
||
|
||
## 6. Milestones (each: build → verify in preview → commit)
|
||
|
||
- **Z0** — blank dimension + zero entry/exit + first-visit choice card + prezero restore.
|
||
- **Z1** — 7+7 slots + picker drawer + stage nodes (no routing yet). Colors/labels/live glow.
|
||
- **Z2** — routing to the output rack + right-click assign (quantize/curve/amount) +
|
||
ticker nudges. **Z-AC passes.**
|
||
- **Z3** — vibes: tag/apply/toggle, save to account (additive auth.py migration),
|
||
vibe code export/import + #vibe= fragment. **Vibes AC passes.**
|
||
- **Z4** — dimensions rename + dimension-zero entry + grimoire "Saving" rewrite.
|
||
- **Z5** — OMNI panel. **OMNI AC passes.**
|
||
- **Z6** — grimoire sections (zero mode gets its own tech-manual section in the style
|
||
of OMNI/Earth Echo/GODSONIQ: numbered how-to + spec table), regenerate the manual,
|
||
adversarial review workflow over the whole feature set, fix confirmed findings,
|
||
deploy to botchat, curl-verify live, update memory.
|
||
|
||
## 7. Risks & taste notes
|
||
|
||
- `viz/index.html` is ~7.5k lines of interlocking IIFE — add new sections as their own
|
||
IIFEs near their dependencies; do not restructure existing code to "make room".
|
||
- The 14-group order and hues are a **taste call** — propose them, note them as
|
||
one-line consts John can retune by ear/eye (the skins pattern).
|
||
- Zero mode must never fork the matrix/renderer: same routes, same cables, same synth.
|
||
If zero starts needing its own compute path, stop — the design has gone wrong.
|
||
- Don't gate existing users into zero — it's a door, not a wall. `gs_mood` returning
|
||
users land where they left off.
|
||
- Keep the first-visit card copy short and Godstrument-voiced. No onboarding-speak.
|
||
|
||
---
|
||
|
||
# STATUS + CONTINUATION (Fable review, 2026-07-10)
|
||
|
||
**Z0–Z2 verified and approved** (commits 2a705e3, d30cea4, 4e44533, 9a7f947): the three-door
|
||
card, the calm 7+7 ring field, spinning orbs, drag-to-route, the synth-roll assign menu —
|
||
all live-verified, Z-AC met (bitcoin sang through a sun-opened filter from zero), console
|
||
clean, syntax clean, field-draw correctly guarded. Genuinely good work.
|
||
|
||
Continue from here, in this order:
|
||
|
||
## C1. Fix: leaving zero silently DISCARDS the user's build (real bug)
|
||
|
||
`exitZeroToMood` always restores `gs_prezero`, replacing routesArr — so a user who built
|
||
cables in zero and clicks "first light" loses their build with no warning. The brief's
|
||
intent (§2 graduation): *"leaving zero shows the full field with everything the user
|
||
built still wired."* Fix: on exit, if the current (zero-built) patch has ≥1 route, offer a
|
||
small three-way choice (same visual language as the door card, but compact):
|
||
- **keep my build** — don't restore prezero; just drop the overlay + apply the mood's
|
||
mutes/gains only (the build stays as the whole patch);
|
||
- **bring the old world back** — current behavior (restore prezero);
|
||
- **weave them together** — restore prezero THEN re-registerRoute the zero-built routes
|
||
on top (registerRoute dedups by source|dest).
|
||
If the zero build has 0 routes, skip the prompt entirely (current behavior is right).
|
||
|
||
## C2. Zero build must survive a reload
|
||
|
||
Routes persist via the matrix, but `zeroUI.nodes` (picked feeds / stage orbs) don't — a
|
||
returning zero-user (gs_mood="0 zero") reloads into an empty stage even though their
|
||
cables exist. Persist the picks to localStorage (`gs_zeronodes`: {group: [srcKeys]}) on
|
||
every pick/route; restore in `zeroUI.open()`. Also reconstruct socket lit-state (already
|
||
done via refreshSockets) — verify with a reload test.
|
||
|
||
## C3. Orb interactions beyond drag (touch + depth)
|
||
|
||
HTML5 drag doesn't exist on touch, and orbs currently have no menu:
|
||
- **Tap-to-patch fallback:** click/tap an orb → it "arms" (glow ring, cursor hint);
|
||
click a socket → routeGroup(armed, dest); click elsewhere → disarm. Keep drag working.
|
||
- **Right-click an orb** → small menu: member list with × to remove a feed, "unpatch
|
||
everything from this orb", and (stub for Z3) "💾 save as vibe". Removing the last
|
||
member removes the orb.
|
||
- **⛈ graduation affordance:** a small fixed "⛈ enter the storm" pill inside the zero UI
|
||
(bottom-right above the rack) → runs the C1 exit flow with mood "the full storm".
|
||
|
||
## C4 = Z3 (vibes), Z5 (OMNI panel), Z4 (dimensions), Z6 (ship) — per the brief above
|
||
|
||
Follow the brief sections 3, 4, 5, 6 as written, with these additions learned since:
|
||
- The orb right-click "save as vibe" (C3) is the natural zero-mode entry to Z3 — a vibe
|
||
is exactly one orb's members + the routes they carry.
|
||
- auth.py vibes store: use the existing `CREATE TABLE IF NOT EXISTS` idiom (auth.py:44-46
|
||
style) so the server migrates itself on restart — additive only. Test locally against a
|
||
COPY of godstrument_users.db, never the live file, and remember the deploy never ships
|
||
the db.
|
||
- Vibe import parses user-pasted data → validate hard (source/dest against known sets,
|
||
numeric clamps, 32-route cap, try/catch the base64/JSON). This is a trust boundary.
|
||
- **Before the Z6 deploy, run the `/ship-check` skill** (pre-deploy security sweep) —
|
||
this feature set touches auth.py and user-pasted input, which is exactly its territory.
|
||
- Nit (low): while in zero, the chakra-view key (F3/C) flips chakraMode with nothing
|
||
drawn — make it a no-op in zero (or have it exit zero first via the C1 flow).
|
||
|
||
Keep the cadence: extraction-check → build → verify in the logged-in preview (z0test /
|
||
zeropass123 account exists) → screenshot → commit per milestone. Deploy only at Z6 after
|
||
the adversarial review. The grimoire is truth — Z6 writes zero/vibes/dimensions/OMNI-panel
|
||
sections in the tech-manual style (numbered how-to + spec table) and regenerates the manual.
|