Commit Graph

232 Commits

Author SHA1 Message Date
BenStullsBets 0752746f7b fix(content): strip NOAA title card from reef_snapper base + re-bake its morphs
Operator eyeball feedback: the red snapper clip opened with a black 'Northern Red
Snapper / fishwatch.gov' title card. Re-trimmed reef_snapper/base.mp4 to drop the
first 3.0s (the card; footage is clean from 3.0s), 23s->20s, and re-baked the 7
reef_snapper morph pairs (+ reverses) all-intra from the clean base. Verified the
snapper-dominant morph frames are text-free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:32:14 -07:00
BenStullsBets 614411b5e4 fix(sim): loop base from morph-tail offset so landed morphs don't jitter
Operator eyeball feedback: landing on an altitude jump-cut the loop back to frame 0.
Each morph reproduces the destination clip's first 3s (pipeline trim=0:3) and ends on
dst@~3s, so the steady-state loop now starts at LOOP_TAIL_S=3.0 and loops within
[3.0, duration] — the morph's last frame hands straight off to the loop, and the
morph-covered intro never re-shows at rest (removes the overlapping 'altitude video
that is part of the morph'). E2E asserts a landed clip loops from ~3s, not 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 16:29:22 -07:00
BenStullsBets 70fc9a46ab feat(pipeline): re-bake 154 morphs all-intra for smooth scrub seeking (LFS)
Per plan Task 7. Every frame now a keyframe (verified 93/93 I-frames on a sample),
so the scrub interaction seeks to arbitrary frames smoothly. Total transitions
173M -> 504M; largest clip ~5.8M (well under the 25MB LFS/proxy ceiling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:51:53 -07:00
BenStullsBets f2f242a411 feat(pipeline): all-intra ffmpeg builders for seekable morph re-bake
Per plan Task 7. Extracts transition_cmd/reverse_cmd argv builders and adds the
ALL_INTRA flag set (-g 1 -keyint_min 1 -sc_threshold 0) so every baked frame is a
keyframe — smooth arbitrary-frame seeking for the scrub interaction. Unit-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:50:31 -07:00
BenStullsBets 31a3fd733f test(e2e): scrub tier — currentTime + gains track dial, reverse, commit-lock, wheel
Per plan Task 5. Replaces the .rev-asserting zoom-out test with a turn-back test
(same forward morph seeked backward, audio recedes, re-locks the start clip); adds
a detent-crossing commit+lock test via a new window.__hefState diagnostic seam.
Full tier (6 tests) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:47:25 -07:00
BenStullsBets 8458ab59eb feat(sim): wheel + label-tap auto-scrub then lock; retire discrete advance()
Per plan Task 4. onWheel + jumpToScale now animate pos via autoScrub() (drives the
same setPos scrub path, ~600ms, lands exactly on the integer + locks). Removes the
now-dead advance()/playTransition()/FAST_BLEND_RATE (the server /api/ring/advance
round-trip is no longer used by the client). Post-landing morph warm-up folded into
setPos's settle branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:45:37 -07:00
BenStullsBets 20f49e936a feat(sim): scrub engine — drag drives pos, seeks morph, commits+locks+re-rolls
Per plan Task 3. A continuous float pos is the knob: setPos() seeks the segment's
morph currentTime by frac (throttled one seek/rAF), crossfades audio, sweeps the
needle live, and commits+locks+re-rolls on each integer crossing. Drag live-scrubs
(no more commit-on-release); release holds the blend (no auto-complete). Client-side
pool pick replaces the server round-trip for drag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:43:48 -07:00
BenStullsBets 0548e9a82b feat(sim): two-element audio crossfade keyed by scrub fraction
Per plan Task 2. Adds <audio id=aud-b>; blendAudio(lo,frac) mixes the two
adjacent scale soundtracks by knob fraction (element reuse across crossings so
only the genuinely-new scale reloads); restAudio settles a single scale at rest.
Diagnostic readout now reports whichever element is audible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:40:04 -07:00
BenStullsBets 3dc3e3491f feat(sim): pure altitude-scrub math module + node unit suite
Per plan Task 1. position->segment, frac->currentTime, audio gains, and
integer-crossing detection; 9 node --test cases green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:37:27 -07:00
BenStullsBets 937461e3ba docs(plan): scrub-driven altitude transitions implementation plan
Plan for the approved design at
docs/superpowers/specs/2026-06-27-scrub-driven-altitude-transitions-design.md.
Two phases: (1) scrub interaction against current morphs, (2) all-intra re-bake.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:35:23 -07:00
BenStullsBets 6781e40c94 add sessions/0024/SESSION-0024.0-TRANSCRIPT-2026-06-26T21-12--2026-06-27T15-23.md + replace placeholder/variant SESSION-0024.0-TRANSCRIPT-2026-06-26T21-12--INPROGRESS.md 2026-06-27 15:25:28 -07:00
BenStullsBets 66c65e78b8 update sessions/0024/SESSION-0024.0-TRANSCRIPT-2026-06-26T21-12--INPROGRESS.md 2026-06-27 15:24:43 -07:00
BenStullsBets 528fb5c438 Merge origin/main (session-history commits 0023-0025) into 0024 feature work 2026-06-27 15:22:13 -07:00
BenStullsBets c2cc503a9c Merge session-0024: altitude clip-pair morph + lock; scrub-driven design spec
Ships the altitude clip-pair morph transition system (154 directional morphs,
pick-then-morph, chained, lock-per-altitude), graduated experience media into
git-LFS, the Playwright E2E tier, and the dial-needle sweep. Also lands the
approved design spec for the next increment (scrub-driven transitions).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:21:31 -07:00
BenStullsBets 972ad8832b spec(design): scrub-driven altitude transitions (knob position drives morph + audio)
Approved design for the next increment: continuous knob position scrubs the morph
video + crossfades audio (hold-partway, reversible, re-roll-on-fresh-approach,
wheel auto-scrubs one altitude). Includes morph all-intra re-bake for smooth
seeking. To be planned + built in a separate session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 15:19:32 -07:00
BenStullsBets 9b4884b466 feat(sim): sweep the Altitude needle in sync with the morph (no pre-move)
The dial needle now sweeps one detent per crossed altitude driven by the morph
video's playback progress, landing exactly when the morph ends and the clip
locks. Drops the live finger-follow during drag (which moved the needle to the
target ahead of the zoom and caused a snap-back on commit) so the needle moves
ONLY with the transition. Verified in Chromium + WebKit, both directions, wheel
and drag.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 14:47:19 -07:00
BenStullsBets d7e9fea1d3 feat(media): graduate used experience media into git via LFS (coast_birdrock transcoded)
The curated clip bases, per-scale audio, and 154 baked altitude morphs are the
actual experience content, so they're committed via git-LFS (self-hosted Gitea
LFS store). coast_birdrock/base.mp4 was re-encoded 68MB->22MB (25Mbps->8Mbps,
1080p30 unchanged) to fit the git.benstull.org front-end nginx upload limit; its
16 morphs were re-baked. Stale right_variants stay gitignored. Adds .gitattributes
(LFS) and negates the used media back into git in .gitignore. Also records the
LFS media-graduation decision in the plan doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 14:21:55 -07:00
BenStullsBets e8cd783258 test(e2e): Playwright tier asserts altitude lock + matching morph (in & out)
Task 8. New simulator/e2e Playwright tier (§9 browser tier). altitude-lock.spec
proves: zooming plays the morph matching the landed clip (forward + .rev), and
the clip is LOCKED after landing (no change while parked). advance() records
played morph paths on window.__hefMorphs (diagnostic seam; reliable under the
blob cache).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 07:33:33 -07:00
BenStullsBets 5382995931 feat(sim): play chosen morph, lock clip per altitude, drop secondary swap
Task 7. advance() threads the current clip as from_clip_id, plays the
server-resolved chained morphs (each already lands on its destination clip),
then locks activeClipId until the next altitude change — no fade-to-black
secondary swap, no re-roll while parked. Builds a (from->to) morph lookup
from the ring; preloads only the morphs reachable from the locked clip,
refreshed per landing (154 morphs is too many to preload up front). Drops
reverseFile + fadeThroughBlack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 07:15:03 -07:00
BenStullsBets 7855718f74 feat(ring,api): pick destination clip then resolve matching chained morph
Tasks 3-6. Transition is now a directed clip-pair morph; ScaleRing.morph_for
looks up (from,to)->file. advance_ring returns structural steps (no file);
resolve_move picks each crossed altitude's clip THEN its morph, chained, and
reports the locked target. Fast spin keeps the whole chain (blended) instead
of collapsing. /api/ring/advance takes from_clip_id, draws picks, returns the
resolved chain. Dropped ring_move_to_dict + _rev_file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 07:12:25 -07:00
BenStullsBets e2b54dd0cc add sessions/0025/SESSION-0025.0-TRANSCRIPT-2026-06-27T06-05--2026-06-27T07-05.md + replace placeholder/variant SESSION-0025.0-TRANSCRIPT-2026-06-27T06-05--INPROGRESS.md 2026-06-27 07:05:51 -07:00
BenStullsBets cc469b5298 feat(pipeline): per-clip-pair morph manifest + member-pair baking (154 clips)
Tasks 1-2 of the altitude clip-pair morph plan. Manifest emits a morph
entry for every video pair in adjacent altitudes, both directions
(<src>__<dst>.mp4 zoom-in + .rev zoom-out); generate_media() bakes them
all via the existing xfade=zoomin recipe keyed by clip member.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 07:04:29 -07:00
BenStullsBets 3b3d30e8e0 plan(0024): add zoom-out E2E case; resolve review-gate open items
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 06:17:42 -07:00
BenStullsBets 441645ae7a plan(0024): altitude clip-pair morph + lock per altitude
Choose the destination clip before transitioning, play the matching
member->member morph (154 clips, all adjacent video pairs, both
directions), chain through each crossed altitude, and lock the clip
until the altitude is changed (no secondary swap, no re-roll).

Session 0024 (writing-plans). Stops at the review gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 06:09:06 -07:00
BenStullsBets a581fa41ed claim human-experience-filter-art session 0025 (placeholder) + sessions.json entry 2026-06-27 06:05:49 -07:00
BenStullsBets afec816bb0 claim human-experience-filter-art session 0024 (placeholder) + sessions.json entry 2026-06-26 21:13:13 -07:00
BenStullsBets df869d6978 add sessions/0023/SESSION-0023.0-TRANSCRIPT-2026-06-26T09-05--2026-06-26T09-20.md + replace placeholder/variant SESSION-0023.0-TRANSCRIPT-2026-06-26T09-05--INPROGRESS.md 2026-06-26 18:59:46 -07:00
BenStullsBets 8aa5ee4019 claim human-experience-filter-art session 0023 (placeholder) + sessions.json entry 2026-06-26 18:59:05 -07:00
BenStullsBets 6427ab4a49 chore(audio): tuck audio diagnostics (status + native player) under Dev Mode
The 'no sound' debugging is resolved (stale-server resilience). Move the audio
status readout + native test player into the Dev Mode panel — kept for future
'can't hear' debugging, out of the installation UI. 7 E2E pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 18:58:22 -07:00
BenStullsBets 56d08d5c60 fix(video): client tolerates a stale server (old /api/alteration shape + payload)
The operator's video wouldn't turn on: their stale uvicorn (old Python) requires a
7-way 'content' and 422s the {visual,audio} payload, and returns {content:{video}}
not {render:{video:{shown}}}. So the alteration POST failed and video never updated
(audio works because it's client-driven). Fix: controls() also sends a back-compat
'content' field (current server ignores it; old one ignores visual/audio), and
update() reads video-shown from either response shape. Verified end-to-end against a
simulated fully-stale server: video shows + audio plays, zero errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:46:49 -07:00
BenStullsBets 97d2ddd573 fix(audio): client soundtrack fallback when /api/ring lacks audio (stale server)
Root cause of 'no sound', found via the live readout: the operator's /api/ring
returned the ring WITHOUT the per-scale audio field (a server process predating the
audio code) → soundtrackUrl() was null → the element never got a src (readyState 0,
no error). The native test player proved the file itself plays. Fix: client falls
back to a scale-id→soundtrack map (mirrors build_pool_manifest.SCALE_AUDIO) so audio
works without a server restart. Verified: stale-ring sim now plays; audio-first-then-
video leaves both on. +2 E2E regression tests (7 total). Chromium + WebKit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:42:12 -07:00
BenStullsBets 56e352fe65 diag(audio): status shows resolved url + ring source + scale (pinpoints null soundtrack url)
The operator's 'PAUSED readyState 0' (no error) means soundtrackUrl() returned null
— the running app's /api/ring has no audio for the scale, the signature of a STALE
uvicorn server (Python predates the audio code; static reload doesn't restart it).
Status now shows url=<…|NONE>, ring=server|SYNTH, scale id so it's unambiguous.
Fresh server verified: ▶ playing rs=4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:34:16 -07:00
BenStullsBets 2564168ac3 Merge origin/main (0022 transcript) into 0023 diagnostics 2026-06-26 13:28:37 -07:00
BenStullsBets deb7c34df9 Merge session-0023: audio diagnostics (status readout + native test player) 2026-06-26 13:28:19 -07:00
BenStullsBets 625d32c0f3 diag(audio): live audio-status readout + native test player; stop swallowing play errors
Three blind fixes failed because headless browsers relax autoplay (can't reproduce)
and play() errors were swallowed. Instrument instead:
- #audio-status: live readout (playing/paused/blocked/media-error) under the toggle
- surface the play() rejection reason + element error (was .catch(()=>{}))
- 'Audio check' fieldset with a native <audio controls> on the soundtrack file, to
  test the file directly with zero app code in the path
Diagnostic only — no behavior change. 37 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:28:17 -07:00
BenStullsBets 58a6ee4644 add sessions/0022/SESSION-0022.0-TRANSCRIPT-2026-06-26T08-40--2026-06-26T08-50.md + replace placeholder/variant SESSION-0022.0-TRANSCRIPT-2026-06-26T08-40--INPROGRESS.md 2026-06-26 13:20:59 -07:00
BenStullsBets fd1da791bf claim human-experience-filter-art session 0022 (placeholder) + sessions.json entry 2026-06-26 13:20:20 -07:00
BenStullsBets 4ac39c9034 Merge session-0022: Loading Universe splash + Safari-safe single-element audio + video/audio coupling
Removes the tap-to-start wall (Loading Universe splash instead), fixes audio still
silent on real Safari (single <audio> played synchronously in the toggle gesture),
both toggles default off, first Video-on couples Audio. Verified Chromium + WebKit.
290 tests + 5 Playwright E2E pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 13:19:39 -07:00
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 3d46b96ecb add sessions/0021/SESSION-0021.0-TRANSCRIPT-2026-06-26T08-12--2026-06-26T08-20.md + replace placeholder/variant SESSION-0021.0-TRANSCRIPT-2026-06-26T08-12--INPROGRESS.md 2026-06-26 12:57:38 -07:00
BenStullsBets 3b19d3eb92 claim human-experience-filter-art session 0021 (placeholder) + sessions.json entry 2026-06-26 12:56:57 -07:00
BenStullsBets fddb6d65d4 Merge session-0021: off/on Audio+Video toggles; Safari autoplay + GPU video-off fixes
Operator-driven revision of the audio feature: Dev-Mode-style Video/Audio toggles,
white-noise deferred (Audio = off/soundtrack), and fixes for two real-browser bugs
(video-off not blanking GPU-composited layers; no soundtrack on Safari/iOS).
Verified in headless Chromium + WebKit. 288 tests + 3 Playwright E2E pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 12:56:20 -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 d446238b79 add sessions/0020/SESSION-0020.0-TRANSCRIPT-2026-06-26T06-47--2026-06-26T07-58.md + replace placeholder/variant SESSION-0020.0-TRANSCRIPT-2026-06-26T06-47--INPROGRESS.md 2026-06-26 08:06:49 -07:00
BenStullsBets c51fdc422a update sessions/0020/SESSION-0020.0-TRANSCRIPT-2026-06-26T06-47--INPROGRESS.md 2026-06-26 08:06:25 -07:00
BenStullsBets aa76cd1c9f Merge session-0020: Audio + separated Visual/Audio controls
Splits bundled Content into orthogonal Visual×Audio; per-altitude soundtracks +
synthesized pink white-noise; server-resolved render.audio; client A/B crossfade
layer; control-surface spec reframed. 288 tests + 3 Playwright E2E pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 08:02:45 -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 fd0aad6610 docs(audio): reframe control-surface spec content->visual+audio; record §12 decisions + roadmap
- networked-control-surface §3/§4/§5/§6: Content -> Visual + Audio; transitions.content
  -> transitions.visual; render.content -> render.video + render.audio; SSE notes
- audio spec §12: resolved (audio=live ~0.6s; noise=pink; tap-to-start overlay);
  status -> in implementation
- ROADMAP: session-0020 audio increment; music layer deferred

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 07:50:34 -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