feat(sim+player): scale-ring navigation (endless encoder) + cosmos/abyss scales
Add scale-ring navigation to the simulator per the scales-library design §3: an endless rotary-encoder control (relative, vs the absolute 0-4 experience knobs) walks a closed ring of neutral "scales of nature" clips, with placeholder AI zoom/warp transitions between adjacent scales and a small->large wrap (the infinite-zoom payoff). - player/ring.py (new, canonical pure logic): ScaleRing + advance_ring; one transition clip per edge, played forward zooming inward / reversed outward; modulo wrap both ways; chained steps for a multi-detent spin. Mirrors player/content.py conventions (frozen dataclasses, pure functions). - simulator: load_ring + ring_to_dict/ring_move_to_dict; GET /api/ring and POST /api/ring/advance (Python owns step/wrap/transition math; the browser only plays the returned transition(s) then settles on the target scale). - frontend: endless-encoder control (zoom in/out buttons + stage scroll), current-scale readout, multi-clip active-scale selection, transition playback. - media: two cheap true-PD scales so the ring is demonstrable -- cosmos (NASA/Hubble) + abyss (NOAA Ocean Exploration), provenance recorded in the manifest, bytes generated as labelled placeholders by setup_scales_media.py. The expensive multi-strength flow-stabilized Right re-bake is DEFERRED (new scales carry a raw base only) until the ring is liked; Pi renderer + serial/firmware remain deferred (simulator-first). - docs: ROADMAP slice-3 done; USER_GUIDE scale-ring gesture; plan doc. Verified: pytest 215 passed / 2 skipped (+22 new); sim boots, /api/ring + /api/ring/advance correct incl. wrap, media served, and a headless-Chrome CDP drive walked cosmos -> forest -> abyss -> (wrap) -> cosmos with the active clip reloading correctly. Spec: docs/superpowers/specs/2026-06-07-scales-library-and-right-axis-pipeline-design.md (§3) Plan: docs/superpowers/plans/2026-06-07-scale-ring-navigation.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+20
-4
@@ -139,6 +139,23 @@ Design:
|
||||
plan:
|
||||
[`2026-06-07-reconciled-simulator-alteration-slice.md`](./superpowers/plans/2026-06-07-reconciled-simulator-alteration-slice.md).
|
||||
|
||||
**Slice 3 — scale-ring navigation (simulator) ✅ Done.** Merged to `main`
|
||||
(session 0011). Adds the **endless rotary-encoder** control — *relative* (vs the
|
||||
absolute 0–4 experience knobs) — that walks a **closed ring** of neutral
|
||||
"scales of nature" clips, with placeholder AI zoom/warp **transitions** between
|
||||
adjacent scales and a small→large **wrap** (the infinite-zoom payoff, scales
|
||||
design §3). The navigation math is canonical Python (`player/ring.py`:
|
||||
`ScaleRing` + `advance_ring`, one transition clip per edge, played forward
|
||||
inward / reversed outward); the sim exposes `GET /api/ring` + `POST
|
||||
/api/ring/advance` and the browser only plays the returned transition(s) then
|
||||
settles on the target scale, keeping the live knob alteration on top. Two cheap
|
||||
**true-PD** scales were added so the ring is demonstrable — `cosmos` (NASA/Hubble)
|
||||
and `abyss` (NOAA Ocean Exploration), provenance recorded in the manifest, bytes
|
||||
generated as labelled placeholders by `simulator/setup_scales_media.py`. The
|
||||
expensive real multi-strength flow-stabilized Right re-bake is **deferred** (new
|
||||
scales carry a raw base only) until the ring is liked. Plan:
|
||||
[`2026-06-07-scale-ring-navigation.md`](./superpowers/plans/2026-06-07-scale-ring-navigation.md).
|
||||
|
||||
**Remaining slices (not started):**
|
||||
|
||||
- **Runtime renderer** — drive the single panoramic projector via mpv/ffmpeg;
|
||||
@@ -152,10 +169,9 @@ plan:
|
||||
- **Offline v2v variant pipeline** — author the pre-baked Right restyle variants
|
||||
via the local flow-stabilized SD pipeline (now ~free, not a paid API — see the
|
||||
scales-library/right-axis design §1/§4); a real multi-strength flow-stabilized
|
||||
re-bake per base clip + the multilingual label/string tables + TTS.
|
||||
- **Scale-ring navigation** — the endless rotary encoder + short pre-baked AI
|
||||
zoom/warp transitions between neutral "scales of nature" clips on a closed ring
|
||||
(scales-library design §3); a new control + offline pipeline element.
|
||||
re-bake per base clip + the multilingual label/string tables + TTS. The
|
||||
**scale-ring transitions** (forward/reverse per-edge morphs) are part of this
|
||||
offline pipeline too — the simulator currently uses placeholders.
|
||||
- **Catalog model changes** — audio *source* + "neutral base" vs "altered
|
||||
variant" flag (sub-project 2 territory, design §13).
|
||||
|
||||
|
||||
+15
-4
@@ -298,12 +298,16 @@ piece moved from *selecting* clips to *altering* them.)
|
||||
**One-time setup — populate the sample footage** (look-tuning only; not shipped
|
||||
content):
|
||||
|
||||
python simulator/setup_sample_media.py
|
||||
python simulator/setup_sample_media.py # the forest scale (real POC base + Right variants)
|
||||
python simulator/setup_scales_media.py # the cosmos + abyss scales + ring transitions
|
||||
|
||||
This copies the session-0008 POC artifacts (`~/hef-poc/out/`) into
|
||||
`simulator/sample_media/forest/` — the neutral base clip and the real
|
||||
`setup_sample_media.py` copies the session-0008 POC artifacts (`~/hef-poc/out/`)
|
||||
into `simulator/sample_media/forest/` — the neutral base clip and the real
|
||||
flow-stabilized Right restyle — and generates placeholder intermediate Right
|
||||
strengths. The `.mp4` binaries are gitignored.
|
||||
strengths. `setup_scales_media.py` makes the scale **ring** demonstrable: cheap
|
||||
synthetic placeholder bases for the two true-PD scales (`cosmos` = NASA/Hubble,
|
||||
`abyss` = NOAA Ocean Exploration) plus the per-edge zoom/warp transition clips.
|
||||
The `.mp4` binaries are gitignored.
|
||||
|
||||
**Run it (Docker):**
|
||||
|
||||
@@ -320,6 +324,13 @@ then open http://localhost:8000.
|
||||
|
||||
- **Content dial** — picks audio/video channel; "off" and audio-only positions go
|
||||
to black walls.
|
||||
- **Scale ring (endless encoder)** — `⊖ out` / `in ⊕` (or scroll the stage) walk a
|
||||
*closed ring* of neutral "scales of nature" clips — cosmos → forest → abyss and
|
||||
back around (diving past the smallest **wraps** to the largest). It is *relative*
|
||||
(an endless encoder), distinct from the absolute 0–4 knobs: each step plays a
|
||||
short placeholder zoom/warp **transition**, then settles on the next scale, with
|
||||
the current knob alteration still applied. The current scale is named beside the
|
||||
buttons (`name (i/N)`).
|
||||
- **Four experience knobs (0–4):**
|
||||
- **Dark / Light** — a live runtime color grade (cool/dark ↔ warm/bright; equal
|
||||
or zero = the raw footage).
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# Scale-Ring Navigation (Simulator) — Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans or
|
||||
> superpowers:subagent-driven-development to implement task-by-task. Steps use
|
||||
> checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add scale-ring navigation to the simulator — an endless rotary-encoder
|
||||
control (relative, vs the absolute 0–4 experience knobs) that walks a *closed
|
||||
ring* of neutral "scales of nature" clips, with placeholder AI zoom/warp
|
||||
transitions between adjacent scales. Add 1–2 cheap, true-PD neutral base clips
|
||||
(NASA/Hubble cosmos + NOAA Ocean Exploration deep sea) so the ring is
|
||||
demonstrable. **Defer** the expensive real multi-strength flow-stabilized Right
|
||||
re-bake until the ring is liked; keep deferring the Pi renderer + serial/firmware.
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-06-07-scales-library-and-right-axis-pipeline-design.md` §3 (scale navigation & zoom transitions), §2 (scales library), §2.1 (strict-PD sourcing map).
|
||||
|
||||
**Architecture:** Python stays the single source of truth. The ring *navigation
|
||||
math* — step, wrap, which transition clip to play and in which direction — lives
|
||||
in a new canonical pure module `player/ring.py` (mirrors `player/content.py` /
|
||||
`player/alteration.py`: frozen dataclasses, pure functions). The simulator's data
|
||||
layer (`simulator/clips.py`) parses a `ring` section of the manifest into the
|
||||
ring types; the API exposes the ring and a stateless `advance` move; the browser
|
||||
holds the current ring index and only *renders* (plays the returned transition
|
||||
file, then the target scale's clip, with the existing per-knob alteration on top).
|
||||
|
||||
**Ordering convention (canonical):** ring index 0 = the *largest* scale (cosmos);
|
||||
increasing index zooms **inward** toward the smallest. `+1` detent = zoom in,
|
||||
`-1` = zoom out — both wrap (past the smallest wraps to the largest, the
|
||||
infinite-zoom payoff, §3). Edge `i` connects `scales[i] → scales[(i+1) % N]`; the
|
||||
last edge is the small→large wrap seam. One transition clip per edge plays
|
||||
**forward** when zooming inward and **reversed** when zooming outward, so N scales
|
||||
need only N transition clips (§3: "one clip per ring edge").
|
||||
|
||||
**Deferral, made explicit:** the new cosmos/abyss scale clips carry a raw base
|
||||
only (no pre-baked Right variants); the `Clip` model already falls back any
|
||||
unauthored strength to the raw base, so Right on a new scale is a no-op until the
|
||||
multi-strength flow-stabilized re-bake happens (deliberately deferred). Forest
|
||||
keeps its existing POC variants.
|
||||
|
||||
**Tech Stack:** Python 3.13, FastAPI + pydantic, pytest, frozen dataclasses;
|
||||
vanilla JS + CSS/SVG for the browser; ffmpeg (system or `imageio-ffmpeg`) for
|
||||
placeholder media.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
**Engine (create):**
|
||||
- `player/ring.py` — `Scale`, `Transition`, `ScaleRing`, `RingMove`,
|
||||
`TransitionStep`, `scale_at`, `advance_ring`. Pure logic, no I/O/JSON.
|
||||
|
||||
**Simulator (modify):**
|
||||
- `simulator/clips.py` — add `load_ring(manifest)` building a `ScaleRing` from the
|
||||
manifest `ring` section + `ring_to_dict` / `ring_move_to_dict` serializers.
|
||||
- `simulator/app.py` — `GET /api/ring`, `POST /api/ring/advance`.
|
||||
- `simulator/static/{index.html,app.js,style.css}` — endless-encoder control
|
||||
(zoom-in/zoom-out buttons + mouse-wheel on the stage), current-scale readout,
|
||||
multi-clip active-clip selection, transition playback.
|
||||
- `simulator/sample_media/manifest.json` (committed) — add cosmos + abyss clips
|
||||
and the `ring` section; media binaries stay gitignored.
|
||||
- `simulator/setup_sample_media.py` (or sibling) — generate cheap cosmos/abyss
|
||||
base placeholders + per-edge transition placeholders (ffmpeg); record true-PD
|
||||
provenance in the manifest. Real PD fetch is a best-effort `--fetch` bonus.
|
||||
|
||||
**Tests (create/extend):**
|
||||
- `tests/test_player_ring.py` (create) — ring navigation logic (step, chain, wrap,
|
||||
direction/reverse, degenerate N≤1).
|
||||
- `tests/test_clips.py` (extend) — `load_ring`, serializers.
|
||||
- `tests/test_simulator_api.py` (extend) — `/api/ring`, `/api/ring/advance`.
|
||||
|
||||
**Docs:**
|
||||
- `docs/ROADMAP.md` — scale-ring status.
|
||||
- `docs/USER_GUIDE.md` — simulator "scale ring" gesture (if present).
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Engine — `player/ring.py` (pure ring navigation)
|
||||
- [ ] TDD `Scale`/`Transition`/`ScaleRing` construction + validation (N edges for N scales; ≥1 scale; degenerate N≤1 ring).
|
||||
- [ ] TDD `scale_at` (mod indexing) and `advance_ring(ring, from_index, delta)` → `RingMove` (to_index by mod; per-unit `TransitionStep` list; `reversed` on outward; `wrapped` flag on crossing the seam; chained steps for |delta|>1).
|
||||
|
||||
## Task 2: Simulator data layer — `load_ring` + serializers
|
||||
- [ ] TDD `load_ring(manifest)` parsing the `ring` section into a `ScaleRing`.
|
||||
- [ ] TDD `ring_to_dict` / `ring_move_to_dict`.
|
||||
|
||||
## Task 3: API — `/api/ring`, `/api/ring/advance`
|
||||
- [ ] TDD `GET /api/ring` (scales + transitions, with each scale's title) and `POST /api/ring/advance` (validates from_index/delta; returns the move + target clip_id).
|
||||
|
||||
## Task 4: Media — cosmos + abyss bases + transitions; manifest
|
||||
- [ ] Add cosmos + abyss clips and the `ring` section to the committed manifest with true-PD provenance.
|
||||
- [ ] Extend the media-setup script to generate cheap base + transition placeholders (and optional real PD fetch).
|
||||
|
||||
## Task 5: Frontend — endless encoder + multi-clip + transition playback
|
||||
- [ ] Active-clip selection by ring index; zoom-in/out buttons + wheel; current-scale readout; play returned transition file(s), then settle on target clip; keep live alteration on top.
|
||||
|
||||
## Task 6: Verify + docs
|
||||
- [ ] Full `pytest -q` green; boot the sim and exercise the ring by eye/screenshot.
|
||||
- [ ] Update ROADMAP / USER_GUIDE.
|
||||
Reference in New Issue
Block a user