Commit Graph

44 Commits

Author SHA1 Message Date
BenStullsBets 30ec0c9c26 fix(audio): Loading Universe splash; single-element in-gesture audio (Safari); first-video-on couples audio
Operator follow-ups:
- Remove the tap-to-start wall; show a 'Loading Universe…' splash until all media
  preloaded (main() awaits preloadAllMedia + a progress bar), then reveal the app.
- Both Video + Audio toggles default OFF (black + silent at start).
- FIX audio still silent on real Safari: the A/B-crossfade-after-gesture didn't
  unlock on Safari (priming srcless elements doesn't unlock them). Use ONE <audio>
  element played SYNCHRONOUSLY inside the toggle's click — the reliable Safari
  unlock; later altitude swaps reuse the unlocked element.
- First time Video turns on, also turn Audio on (one flip = full experience), set+
  played in the same gesture; Audio-on-first stays audio-only.
- Soundtrack follows Altitude via the single element (brief fade swap).
- Verified in headless Chromium AND WebKit: coupling works, soundtrack plays,
  video-off blanks, zero JS errors. 290 tests + 5 Playwright E2E pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:19:15 -07:00
BenStullsBets 2722949805 fix(audio): off/on Audio + Video toggles; fix Safari autoplay + GPU video-off
Operator-driven live-UI revision:
- Video + Audio are now Dev-Mode-style on/off toggles (Audio on = soundtrack);
  white-noise deferred (removed from the live control; pure machinery kept).
- FIX video-off not blanking: the <video>/<canvas> are GPU-composited and could
  paint above the auto-z #black on a real GPU — give #black z-index + its own
  layer AND hide the video layers (opacity 0) on video-off.
- FIX no soundtrack on Safari/iOS: Safari unlocks <audio> only when play() runs
  INSIDE the user gesture; the start gesture now primes both A/B elements
  synchronously, so the async crossfades are allowed to play.
- Toggles wired on 'change' (matches the Dev Mode switch).
- player/audio.py AUDIO_SOURCES -> {off, soundtrack}; tests + E2E updated.
- Verified in headless Chromium AND WebKit: audio plays, video-off blanks both
  layers, zero JS errors. 288 tests + 3 Playwright E2E pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:56:07 -07:00
BenStullsBets c07067492b test(audio): Playwright E2E (3 tests, skip-if-no-browser) + client robustness
E2E asserts in a real headless Chromium: white_noise loads the noise asset,
soundtrack loads the current scale's asset, and Visual=off fades video while
audio keeps playing. Driving it out surfaced + fixed three client issues:
- fadeVolume uses setInterval (rAF stalls when the tab is backgrounded)
- start gesture drops srcless priming (a click grants Chrome autoplay
  document-wide; priming empty <audio> only hung the handler / polluted state)
- applyAudio doesn't await play() (awaiting a srcless/buffering play can hang)
pyproject: [e2e] optional dep (pytest-playwright). Full suite 288 passed, 2 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 08:00:44 -07:00
BenStullsBets 68f9d7342a feat(audio): client — split Content into Visual+Audio controls + audio crossfade layer
- index.html: Content <select> -> Visual toggle + Audio source selector; A/B <audio>
- app.js: controls() emits visual+audio; update() posts altitude_index and reads
  render.video/render.audio; A/B gain-crossfade applyAudio(); one-time tap-to-start
  gesture (autoplay policy); mediaUrl() passes absolute /media urls through
- style.css: #start-gesture overlay
- smoke: page serves controls + audio elements; soundtrack url couples to altitude;
  noise + soundtrack media serve 200

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 07:48:03 -07:00
BenStullsBets 29c6d822bd feat(audio): Visual×Audio Python contract — resolver, controls, state, API
- player/audio.py: pure resolve_visual / resolve_audio_url / resolve_audio (§4/§6)
- retire player/content.py (7-way table) + its test
- player/controls.py: serial contract content -> visual + audio
- player/state.py: Playback carries video:bool + audio_source:str
- simulator/app.py: /api/alteration validates visual+audio, returns render.video
  + server-resolved render.audio.url from altitude_index; NOISE_URL constant
- tests reframed; full suite 285 passed, 2 skipped

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 07:46:22 -07:00
BenStullsBets 1f9b2a0bec feat(audio): Scale.audio field + per-scale soundtrack wiring through the manifest
Scale gains audio:str='' (back-compat). clips.py reads/serializes it; ring_to_dict
exposes it; build_pool_manifest emits SCALE_AUDIO per scale. Manifest regenerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 07:42:40 -07:00
BenStullsBets 39fb9108db feat(audio): ffmpeg production pass — loop/loudnorm builders, runner, build script
Pure arg builders (audio_ops) + thin runner (audio_run) + build_audio_media.py.
Produces the 5 normalized soundtrack loops + a synthesized pink-noise bed under
the gitignored sample_media/audio tree. 6 tests pass (white-noise integration
runs against bundled imageio_ffmpeg).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 07:41:34 -07:00
BenStullsBets a3ec09af7e feat(audio): per-altitude soundtracks + separated audio/visual controls spec
Sound for the experience, plus the design to control it.

Soundtrack layer (ambience), all license-clean (PD/CC0), recorded in
docs/audio-candidate-pool.md (media gitignored; re-sourceable):
- cosmos: Pillars of Creation sonification (NASA, PD)
- orbit:  station room-tone hum (CC0)
- coast:  sea waves + terns (CC0)
- reef:   6-layer NOAA soundscape + CC0 submerged bed (PD + CC0)
- abyss:  blue-whale moan (NOAA, PD)

Solution Design (proposed): split the bundled Content selector into two
orthogonal dials, Visual (on/off) x Audio (off/soundtrack/white-noise),
unlocking white-noise+video. Soundtrack follows the single Altitude dial;
white noise is altitude-independent. Reframes the networked-control-surface
spec SS3/SS5 (still proposed/unbuilt -> clean pre-impl edit). Music layer deferred.

Dev panel: links to the clip + audio review galleries; sticky stage/panel layout.
gitignore: audio media, review_audio.html, .DS_Store.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 06:37:56 -07:00
BenStullsBets e3a9b7cff6 feat(content): add cosmos_miri pool member (Carina mid-infrared pan)
Operator wanted more Cosmic Cliffs footage and a different-color rendering.
Keeps the NIRCam flythrough as the cosmos primary and adds ESA/Webb weic2205c
("Pan of the Carina Nebula, combined NIRCam + MIRI") as a new cosmos pool
member — the same Carina towers in the muted gray/teal/gold MIRI mid-infrared
palette, distinct from the iconic orange/blue flythrough.

- POOLS["cosmos"] grows 4 -> 5 (cosmos, cosmos_miri, galaxies, hudf, xdf);
  primary unchanged.
- META + LABELS authored (nebula/star/distance, salience-gated); affect inherits
  the cosmos scale vocabulary.
- manifest.json regenerated (19 -> 20 clips).
- Source: 4K master, text-free/human-free, trim 4-26s (clears the fade-from-
  black), 1 s crossfade-loop (~21 s) -> 1080p base + 4K master (gitignored).
- docs/content-candidate-pool.md updated (cosmos pool of 5 + sourcing note).

271 passed, 2 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 05:07:13 -07:00
BenStullsBets c3aa419c53 feat(sim): emotions are a right-brain output (Right knob alone)
The affect channel previously gated on BOTH knobs (strength = min(left, right)),
so the analytical Left brain co-controlled the emotion words. Per operator
direction, emotions are now driven by the Right (dreamlike) knob ALONE — Left no
longer gates them. This sharpens the split: Left = measurement (detections +
measurements), Right = the dream AND the feeling it evokes.

- player/alteration.py: `_affect_strength(right) = right` (was min(left,right));
  `_affect_intensity` keyed off Right. AffectOverlay + is_identity docs updated
  (is_identity still holds: right>0 ⇒ dream.strength>0).
- simulator/static/app.js: renderAffect comments (gating is Right via `strength`);
  the client logic already keyed min_level/tier off the passed strength + right.
- Affect design spec: revision note + every min(left,right) → right.
- Tests rewritten: affect is Right-driven (full Right + Left 0 surfaces affect;
  Right 0 + Left 4 surfaces none).

271 passed, 2 skipped. Live API verified: left=0/right=4 → strength 4; left=4/
right=0 → strength 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 04:29:21 -07:00
BenStullsBets 2eb752b5bc fix(sim): content-hash media URLs + fade pool-landing clip swap
Two fixes to the simulator's media handling, both surfaced by the cosmos clip swap.

1. Permanent cache-bust via content hash. New `GET /api/media-versions` returns a
   short sha1 token per served file (clip bases + ring transitions + reverses),
   cached by (mtime, size) so a re-bake is picked up without a restart. The client
   fetches it at boot and appends `?v=<hash>` to every /media URL, so a clip
   re-baked under the same path (e.g. a re-sourced cosmos base) gets a fresh URL
   the browser can't serve stale — fixing the recurring stale-clip problem at the
   root (supersedes the `cache: "reload"` belt-and-suspenders, which stays).

2. Fade the pool-landing swap. The baked ring transition zooms into the scale
   PRIMARY, but the rotating pool picks a random member on landing — so a
   non-primary pick hard-cut from the primary to the chosen clip (e.g. coast
   birdrock -> surfgrass, the artifact the operator saw). On landing, when the pick
   differs from the primary, mask the swap behind a short fade through the existing
   #black overlay (CSS opacity transition); same-clip landings still do a plain
   (re)load. General across all pooled scales.

271 passed, 2 skipped (+2: media-version content-hash + absent-file omission).
app.js syntax-checked; endpoint verified live (29 files; token matches a manual
sha1 of the bytes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 13:03:56 -07:00
BenStullsBets ed267c554c fix(sim): bust HTTP cache on media preload so re-baked clips show
The preload fetched each clip with the default HTTP cache, so a clip re-baked
under the same path (e.g. the cosmos base re-sourced to the Webb Cosmic Cliffs
flythrough) kept showing stale footage after a reload — worst case forever, when
a pre-`no-cache` build had pinned it `immutable, max-age=1y` (a plain reload never
revalidates an immutable entry). Fetch the preload with `cache: "reload"`, which
bypasses the HTTP cache, pulls fresh bytes, and updates the cache entry; the
in-memory blob cache still gives instant in-session altitude swaps. One fetch per
(re)load, so no steady-state cost.

269 passed, 2 skipped. app.js syntax-checked.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 12:40:34 -07:00
BenStullsBets d24f829276 feat(content): swap cosmos primary to the Webb "Cosmic Cliffs" flythrough
Operator found the Orion SVS 30957 flythrough merely good and asked for a more
dramatic nebula. Replaces the cosmos-scale primary with STScI's "Exploring the
Cosmic Cliffs in 3D" (Carina Nebula, Webb NIRCam — NASA SVS 31348), sourced from
its 4K master (Clifs-3d-STScI.mp4, 3840x2160) via the content pipeline:
trim 10-34s (clears the title card ~8s and end-credit card ~102s) + crossfade-loop
-> 4K master + supersampled 1080p base (5.5 Mbps, crisper than the old 2.9 Mbps
1080p-native Orion encode).

- build_pool_manifest META: cosmos -> SVS 31348 Cosmic Cliffs, new CCBY_WEBB
  credit (NASA, ESA, CSA, STScI). Labels retuned for the Carina framing
  (emission nebula / protostar; redshift -> distance ~7,500 ly).
- Regenerated manifest.json (also corrects stale JPL-Orion provenance left when
  129bb23 updated META but not the manifest) + all ring-edge transitions from the
  new base (cosmos-orbit / abyss-cosmos now zoom from the Cosmic Cliffs).
- docs/content-candidate-pool.md: record the swap + history.

269 passed, 2 skipped. New base verified text-free + full-frame by-eye across the
loop; final look to be confirmed by the operator (no Chrome on the box).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 10:48:44 -07:00
BenStullsBets 129bb23cb9 feat(content): swap cosmos Orion to a text-free SVS flythrough
Replaces the JPL "Orion: Dust and Death" primary (captioned throughout — we'd
cropped it, losing framing) with STScI's "Flight Through the Orion Nebula"
(visible light, NASA SVS 30957) — a full-frame, text-free flythrough of the same
nebula. Sourced via the content pipeline (trim 10–34s + crossfade-loop → 1080p
master + base); cosmos-orbit / abyss-cosmos transitions regenerated from it.

- build_pool_manifest META: cosmos -> SVS 30957, license CC-BY-class (NASA/ESA/
  STScI), new trim window.
- Remove the now-obsolete decaption_cosmos() crop helper (the new source needs
  no caption removal).
- docs/content-candidate-pool.md: record the source swap; drop the crop note.

269 passed, 2 skipped. New clip verified text-free + full-frame by-eye;
final look to be confirmed by the operator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:28:10 -07:00
BenStullsBets a2ef792f0f fix(sim): revalidate /media instead of pinning it immutable
The `immutable, max-age=1y` media header pinned stale footage in the browser for
a year, so a re-sourced or re-cropped clip (e.g. the de-captioned cosmos) never
showed without a manual cache clear. Switch /media to `no-cache` (store, but
revalidate every load): instant altitude swaps already come from the in-memory
blob preload, so the HTTP cache only matters on (re)load — where a conditional
request is a cheap 304 when unchanged but picks up edited clips immediately.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:20:14 -07:00
BenStullsBets 53cb89b4c6 fix(content): crop NASA burned-in captions off the cosmos Orion clip
The cosmos primary is NASA/JPL's "Orion: Dust and Death" — an annotated
visualization with lower-third captions ("DUST FILAMENT", "BLUE BUBBLE", …)
burned in throughout, so trimming can't avoid them (only ~4s of our window is
caption-free). The candidate-pool doc wrongly called it text-free.

- build_pool_manifest.decaption_cosmos(): crops the cosmos base/master to the
  top 66% of frame height (16:9 preserved, center-x, ~1.5x tighter framing),
  re-fit to 1920x1080 — the recorded, reproducible form of the fix (media is
  gitignored). Applied in place; re-ran generate_media() so the cosmos-orbit /
  abyss-cosmos transitions use the cropped base.
- docs/content-candidate-pool.md: correct the "text-free" note; document the crop.

269 passed, 2 skipped. De-caption verified by-eye on both caption moments;
final look to be confirmed by the operator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 09:16:57 -07:00
BenStullsBets 0201690ce0 feat(sim): real zoom transitions on every ring edge, both directions
The cosmos→orbit, reef→abyss and abyss→cosmos transition clips were stale
leftovers from an earlier 5-scale ring, built against old/placeholder footage —
so those edges flashed placeholder frames mid-zoom. Only orbit-coast and
coast-reef had been rebuilt from the current real bases.

- build_pool_manifest.generate_media(): now rebuilds EVERY ring edge from the
  current real primary pool bases (the orbit-coast recipe applied uniformly),
  overwriting the stale clips, and bakes a reversed companion (<edge>.rev.mp4)
  per edge via an ffmpeg `reverse` pass.
- app.js: a `reversed` step (an ascending / zoom-OUT ring move) now plays the
  baked .rev clip instead of the forward zoom-in, so going up recedes from the
  current scale. Both directions are added to the in-memory preload set.

Media is gitignored (generated locally / on deploy); regenerated all 10 clips
locally. 269 passed, 2 skipped; node --check clean; clips serve 200.
Visual result to be verified by-eye by the operator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:34:50 -07:00
BenStullsBets 0928988abc fix(sim): collapse Dev Mode panel when disabled
The generic .hidden { display:none } is defined earlier in the stylesheet than
.dev-panel { display:flex }; equal specificity meant source order won and the
panel stayed visible with the toggle off. A two-class .dev-panel.hidden rule
wins, so all dev controls/data truly hide when Dev Mode is disabled.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:26:10 -07:00
BenStullsBets a1c9c5c760 feat(sim): Dev Mode — pool clip picker + live analysis under one toggle
Adds a single "Dev Mode" switch at the bottom of the control panel (off by
default, persisted in localStorage). When on, a panel appears below it with all
dev controls and data; when off the default view is just the experience + the
three control fieldsets.

Dev panel:
- Pool picker: a <select> of the current altitude's pool members — choose any
  clip to override the random landing pick; a "Re-roll random" button re-runs
  the server-canonical pick. Rebuilds on each landing.
- Active clip metadata (id, title, source, license, base_file).
- Annotations & affect: keys with salience/window/tier counts (read-only;
  editing stays in /author.html).
- RenderPlan JSON — relocated here from the always-visible panel.
- Cache & playback: preload status (N/total), memory-vs-network for the active
  clip, live resolution/duration/loop position.

Frontend only — no API changes; everything is read from data the renderer
already has. Factored pickRandomMember() out of landScale() so the initial
landing, ring advances, and the re-roll button share one pick.

Design: docs/superpowers/specs/2026-06-25-simulator-dev-mode-design.md
269 passed, 2 skipped; node --check + server smoke clean. Frontend behavior
to be verified by-eye by the operator (no headless browser in repo yet).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 08:19:39 -07:00
BenStullsBets d19fe4e8fa perf(sim): preload all clips into memory for instant altitude swaps
Each altitude change reset the single <video> element's src and re-fetched
the scale's footage over the network, compounded by the dev no-store policy
applying to /media too — so every scale change paid a full fetch+decode.

- simulator/app.py: split the cache middleware — app shell/JS/CSS stay
  no-store (by-eye dev iteration), but /media is now cacheable
  (public, max-age, immutable) since the clips are static.
- simulator/static/app.js: preload every ring clip (19 base + 5 transition,
  ~350 MB) into in-memory blob object URLs at startup — non-blocking, the
  first scale plays immediately while the rest stream in, ordered current
  scale outward. mediaUrl() serves the cached blob when ready, else falls
  back to the network. A small progress chip self-removes when done.
- tests: assert app assets stay no-store and /media is cacheable.

269 passed, 2 skipped. Frontend verified by-eye by the operator (no headless
browser in repo yet).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 07:47:32 -07:00
BenStullsBets 5fae4f9005 feat(sim): replace ring buttons with a turnable "Altitude" knob
The scale ring's ⊖ out / in ⊕ buttons are replaced by a circular **Altitude**
knob (the endless rotary encoder, drawn as a dial). cosmos (highest) sits at the
top; turning clockwise descends cosmos → orbit → coast → reef → abyss and past
the deepest **wraps back to the top** — the same endless ring the server already
models (advance + wrap unchanged).

- Built dynamically from `ring.scales` (labels + ticks per scale, a needle that
  points at the current scale, an ALTITUDE caption).
- Drag to turn: the rotation accumulates and commits whole detents on release, so
  a big spin becomes one fast blended pass (reuses the proven wheel/detent path);
  scroll the knob to step; tap a label to jump the shortest way around.
- Pure frontend — no engine/API change. JS syntax-checked; live server probed
  (assets serve, dial present, abyss→cosmos wrap intact). 23 sim-API tests green.
  By-eye review deferred to the operator (no Chrome on this box).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 21:44:51 -07:00
BenStullsBets 70fd367c70 tools(pipeline): hybrid track.py + simulator label author mode
Content-pipeline Increment 2, part 2 (the tooling). Implements design §11.5
(docs/superpowers/specs/2026-06-24-content-pipeline-design.md):

- tools/pipeline/track.py — the stage-5 geometry pass. Classical path: a
  hand-seeded normalized box propagated by OpenCV Lucas-Kanade optical flow
  (CSRT used instead when a contrib build provides it), sampled to a SPARSE
  loop-normalized keyframed track + an appear/disappear window. Optional ML
  detect+track path lazy-imports ultralytics (clear ImportError if absent;
  base install needs only cv2). Pure helpers (normalize/denormalize, loop_t,
  infer_window, sample_track, track_to_annotation) are unit-tested; the cv2
  propagation is an opt-in integration test on real abyss_wow footage.
  Semantics are never produced here — geometry only.

- Author mode — /author.html + author.js/.css reuse the preview stage: pick a
  pool clip, scrub, drag a seed box, Run tracker (or Add as static box), author
  the LEFT detail tiers (general -> scientific+fact) + salience, shift-click to
  place affect anchors with RIGHT emotion tiers, and Save to manifest. Backend:
  POST /api/author/track (runs track_seed on the clip's base) + POST
  /api/author/clip (idempotent upsert via tools.pipeline.manifest — keeps media
  + provenance, replaces only authored content, reloads in place). The tracker
  propagates box geometry; all strings/scientific names/facts are hand-typed.

- Repointed the pipeline integration test off the retired forest base to the
  cosmos pool primary. USER_GUIDE simulator section brought current (pools,
  coast, 3-knob Mood, real-time dream, progressive tiers, author mode).

267 passed / 2 skipped (+ track pure + opt-in real-footage + author endpoint
tests). Author UI by-eye review deferred to the operator (no Chrome on this box).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:14:32 -07:00
BenStullsBets edca8a9615 content(sim): rotating clip pools + progressive tracked labels & emotions
Content-pipeline Increment 2, part 1 (the experience). Implements design §11
(docs/superpowers/specs/2026-06-24-content-pipeline-design.md):

- Rotating pool (§11.1): each ring scale references a POOL of vetted clips
  (docs/content-candidate-pool.md); the player picks a random member on landing.
  player.ring.Scale gains `pool` + pure `pick_clip_id(scale, r)` (injected
  randomness); the pick happens at the API boundary (random.random()), a delta=0
  advance is the initial/re-roll pick. clips.py parses/exposes the pool; the
  client lands on move.target_clip_id. Back-compat: a scale with only clip_id is
  a pool of one.
- Rename ring scale forest -> coast (new land<->sea scale, orbit..reef); ring
  order cosmos -> orbit -> coast -> reef -> abyss -> wrap. Cleanup: dropped the
  leftover local media (cosmos_traverse, orbit_westcoast, old forest/reef, the
  Increment-1 orbit/abyss single bases); new coast-edge transition placeholders.
- Time-windowed tracked labels (§11.2): annotations gain appear/disappear
  (loop-normalized, wraps across the seam); a label shows only while on screen
  and its box tracks the subject. abyss + reef pools authored as test material.
- Progressive LEFT detail tiers (§11.3): per-object `salience` (first shows at
  Left 5-salience) + tiered strings (general -> specific -> scientific -> +fact)
  escalating with the Left knob; replaces flat min_level gating. Strings may be a
  tier list or a static string (back-compat). No engine change (client reads
  overlay.level).
- Progressive RIGHT emotion tiers (§11.4): affect vocabulary escalates basic ->
  compound with the Right knob (dream.strength); appearance still gated by
  min(left,right). No engine change.

New simulator/build_pool_manifest.py holds the hand-authored content and emits
the 19-clip pool manifest (the reproducible baseline). setup_scales_media.py
marked superseded. 251 passed / 4 skipped (+ ring pool + API pool/window tests).
By-eye visual review deferred to the operator (no Chrome on this box).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 18:05:13 -07:00
BenStullsBets 6369d0c5ab content(sim): real strict-PD bases for all 5 scales (provenance + license)
Sourced, license-verified, and ran through tools/pipeline/run.py process_clip
(trim → crossfade-loop → 1080p proxy) the five real public-domain neutral bases,
replacing the synthetic placeholders. Media stays gitignored; this records the
real provenance/license + trim window per clip so it is re-sourceable.

- cosmos — NASA/JPL-Caltech, Orion Nebula flythrough (17 U.S.C. §105)
- orbit  — NASA/JSC, Earth from ISS (Expedition 65) (§105)
- forest — NPS Yosemite stock b-roll (PD, no ©)
- reef   — NOAA Fisheries coral b-roll (§105)
- abyss  — NOAA Ocean Exploration comb jelly / ctenophore (§105)

All transcoded in ~2.6 min total (well under the 1-hour budget). Labels/tracks
remain the Increment-1 placeholders (real per-clip authoring is Increment 2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 09:16:21 -07:00
BenStullsBets e812f41ef1 docs: content-sourcing procedure + roadmap update + setup docstring (content pipeline Increment 1)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 08:19:41 -07:00
BenStullsBets 8789d6a6d5 feat(sim): extend scale ring to 5 (orbit + reef) (content pipeline §5)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 08:15:41 -07:00
BenStullsBets 2933498c51 feat(sim): client annotation-track interpolation + hand-authored forest track (content pipeline §4) 2026-06-24 08:11:40 -07:00
Ben Stull f5d8a4f96b feat(sim): collapse Dark/Light into one bipolar Mood dial; drop calibration UI
The engine already models Dark/Light as the two poles of one signed axis
(tone = (light - dark)/4, equal = neutral), so the simulator now exposes a single
bipolar Mood dial (-4 dark .. 0 neutral .. +4 light) that maps onto dark/light in
controls(). Removes the Calibration section entirely: mood_gain and overlay_gain
were always-locked 1.0 dev knobs (full dial = full effect), so they bake in as the
DEFAULT_CALIBRATION constants and the sim sends no calibration. Experience panel is
now just Left, Right, Mood.

Engine/tests unchanged (DEFAULT_CALIBRATION already carries the 1.0 constants).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:58:43 -07:00
Ben Stull cbb7c9f53d feat(sim): Right axis = deterministic real-time painterly dream
Replace the pre-baked SD restyle variants (too fluid — the painting re-rolled
every keyframe and the flow-warp melted frames) with a deterministic, real-time
dream that holds STILL across the loop; only the footage's own motion moves.
Design: docs/superpowers/specs/2026-06-22-right-axis-deterministic-dream-design.md

Engine (player/alteration.py): Restyle(variant) -> Dream(strength, intensity);
Calibration.right_variant_map -> dream_gain. player/state.py: a Right change is a
LIVE_UPDATE now, not a crossfade (only a clip swap crossfades). Plan dict
restyle -> dream. Tests migrated; 233 green. (No new Python behavior for the
client-render swap below.)

Renderer (simulator front-end): the Right dream is a WebGL2 Kuwahara shader on a
<canvas> over the live video — edge-preserving, so motion/structure stay crisp;
the dream is the same footage, just stylized (no blur). Ramped by the knob; max
goes "trippy" (vivid saturation + ~45deg hue drift + posterize, concentrated near
max via t=intensity^2). Phase-1 luminous-haze (CSS+bloom) was built then retired
by eye (blur read as out-of-focus video). Mood grade rides as a CSS filter on the
canvas; bloom layer removed.

Dev ergonomics that fell out of the iteration:
- /dev/version + a 1s client poll = live-reload (open tab never runs stale
  renderer code; reloads on my edits AND server restarts).
- no-cache on the dev server; an on-screen error banner + crash-proof render so a
  silent throw can't masquerade as "nothing is changing".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 09:55:35 -07:00
Ben Stull 6adb93407b feat(sim): analytical Left HUD redesign + affect channel + no-cache
Left-brain HUD look-and-feel pass (judged by eye in the sim):
- corner-bracket targeting reticles (capped arms) replace plain boxes
- translucent label chips, legible over any frame
- two sensor channels: cyan detections (with deterministic confidence
  scores) vs amber measurements (center crosshair)
- bbox-sized "ANALYSIS . L{n} . {k} OBJ" status tag, escalating with Left

Affect channel (emotions in the HUD), per
docs/superpowers/specs/2026-06-22-affect-channel-hud-design.md:
- surfaces only when BOTH knobs up; strength = min(left, right)
- stable per-scene palette, more words appear with combined strength
- soft glowing violet words on their own opacity layer (the dream
  leaking into the machine's read); math in player/alteration.py
- forest/cosmos/abyss palettes in the manifest; 5 new unit tests

Simulator no-cache middleware so by-eye iteration never serves a stale
app.js / api response (fixes the latent "plan has affect but clip.affect
empty" stale-/api/clips bug).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 23:11:02 -07:00
Ben Stull 46064912ed feat(sim): forest carries real multi-strength Right variants
Ran the baker for the forest scale: real flow-stabilized painterly Right variants
1-4 now back the dreamlike axis on real Yosemite footage (was: 1 real + 3 ffmpeg
placeholders). By-eye finding this surfaced: sd-turbo's painterly transform is
sharply non-linear in img2img strength (barely registers <0.5, ramps hard through
~0.5-0.65), so the initial linear 0.28-0.58 ramp left levels 1-3 ~identical to
raw. Re-baked with a clustered 0.46/0.52/0.58/0.64 curve -> a real perceptual
ramp: gentle dream haze -> clearly impressionist -> strongest, each notch visibly
dreamier, temporally calm (flow-stabilized).

- manifest: forest right_variants 1-4 all model 'sd-turbo+farneback-flow'
- setup_sample_media.py: now only stages the neutral base; the baker owns the
  Right variants, so re-running it never clobbers a real bake
- docs: USER_GUIDE setup steps + ROADMAP v2v vertical-slice-done note

Suite 228 passed / 2 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 06:58:29 -07:00
Ben Stull 4e3b326d40 feat(pipeline): forest Right-variant baker (POC flow restyle, productionized)
Right strength curve (level 1-4 -> increasing img2img keyframe strength) is pure
and unit-tested; the restyle engine is a verbatim port of the session-0008 POC
flow_restyle.py (SD img2img keyframes + Farneback optical-flow tweens = calm,
no boiling). torch/diffusers imported lazily so the module + curve test stay light.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 06:33:04 -07:00
Ben Stull f11b9ee72d feat(ring): fast-spin blended pass past a speed threshold (scales design §3)
A multi-detent encoder spin previously chained one full transition per scale
crossed (e.g. +5 ≈ 5 placeholder morphs ≈ 12-15s), which feels sluggish for a
fast spin. Design §3 anticipates this: "transitions chain, or past a speed
threshold a faster blended pass is used."

`player.ring.advance_ring` gains an opt-in `fast_spin_threshold` (canonical
default `DEFAULT_FAST_SPIN_THRESHOLD = 3`). A spin batches its detents into one
advance() call, so `abs(delta)` is the input layer's proxy for spin speed; at or
above the threshold the move collapses to a single blended pass — one
`TransitionStep` (the arrival edge, marked `blended`, landing straight on the
destination, `RingMove.fast=True`) instead of the full chain. Landing index,
seam-crossing (`wrapped`), and arrival edge/direction are exactly the full
chain's; only the in-between transitions are dropped.

The policy is opt-in (default off) because the pure function cannot observe
wall-clock spin speed — the simulator/firmware, which can, enables it. This keeps
all existing complete-chain behavior and tests intact. The simulator passes the
default threshold and plays the blended step at 2.5× (FAST_BLEND_RATE); a
dedicated fast-blend clip can replace the accelerated arrival-edge placeholder
when real transition media exists.

- player/ring.py: TransitionStep.blended, RingMove.fast, threshold param + policy
- simulator/clips.py: serialize fast/blended in ring_move_to_dict
- simulator/app.py: apply DEFAULT_FAST_SPIN_THRESHOLD at /api/ring/advance
- simulator/static/app.js: play a blended step at FAST_BLEND_RATE
- docs: scales design §3, USER_GUIDE, ROADMAP slice-3 note

Tests: +9 (ring policy, serializer, API). Suite 224 passed / 2 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 00:17:47 -07:00
Ben Stull 7a50ae41bb 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>
2026-06-07 23:40:21 -07:00
Ben Stull 9c347d580f feat(sim+engine): lock alteration calibration + fix psychedelic dark grade
Tuning the look by eye in the simulator (design §8) surfaced that the dark
mood pole rendered a full-frame hue-rotate(-200deg) — rock orange, trees
purple: the disorienting look rejected in session 0008, not the peaceful POC
dark_frame. Replace it with darken + slight desaturate on the video filter
plus a multiply-blended deep-blue wash (#tint, below the SVG HUD so the
overlay stays legible). Dark now reads cool/somber with natural greens, like
the approved POC look; light/left/right unchanged and confirmed peaceful.

With full tilt now tasteful on every axis, LOCK DEFAULT_CALIBRATION to unity
gains + linear variant map as a deliberate by-eye choice (not placeholders),
closing the open session-0006 knob->strength decision: knobs run 0=off..4=max,
equal Dark/Light = identity, the 5 notches map 1:1 to the 5 discrete Right
bakes. Guard the locked constants with test_default_calibration_is_locked.

Resolves design §8 open questions (calibration curve shape; grade-vs-overlay
ordering) and records the dark-grade fix. Full suite 193 passed / 2 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 23:19:53 -07:00
Ben Stull d2d63c0184 feat(simulator): alteration preview UI (grade + variant crossfade + Left overlay)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:57:13 -07:00
Ben Stull c3f9262a73 feat(simulator): sample-media manifest + POC setup/placeholder generator
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:56:15 -07:00
Ben Stull eb3aa0949d feat(simulator): /api/alteration + /api/clips; retire selection endpoints
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:55:10 -07:00
Ben Stull c316309fc6 feat(simulator): clips.py manifest model; retire selection fixtures
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:54:09 -07:00
Ben Stull 80cde37b98 build(simulator): Dockerfile, compose, and make targets
Per docs/superpowers/plans/2026-06-04-experience-simulator.md Task 6.
Dockerfile also copies tools/ (a declared setuptools package) so the
editable install resolves inside the image; the plan's Dockerfile omitted it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 03:50:34 -07:00
Ben Stull c1745f0021 feat(simulator): X-ray frontend (dials, pool, brain/mood maps)
Per docs/superpowers/plans/2026-06-04-experience-simulator.md Task 5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 03:43:34 -07:00
Ben Stull cdf36c9b57 feat(simulator): FastAPI app with /api/select and /api/catalog/meta
Per docs/superpowers/plans/2026-06-04-experience-simulator.md Task 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 03:42:13 -07:00
Ben Stull aadfccf32d feat(simulator): deterministic synthetic fixture catalog
Per docs/superpowers/plans/2026-06-04-experience-simulator.md Task 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 03:41:35 -07:00
Ben Stull a260b68d2b build(simulator): add simulator package and sim optional-deps
Per docs/superpowers/plans/2026-06-04-experience-simulator.md Task 2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 03:41:05 -07:00