Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b710b259f2 | |||
| 7f99c3cdc4 | |||
| 153681a402 | |||
| 80952d7b8e | |||
| a5e44ea9c7 | |||
| ad0a59ac04 | |||
| cf74d2c70a | |||
| 6781e40c94 | |||
| 66c65e78b8 | |||
| 528fb5c438 | |||
| c2cc503a9c | |||
| e2b54dd0cc | |||
| a581fa41ed | |||
| afec816bb0 | |||
| df869d6978 | |||
| 8aa5ee4019 |
@@ -0,0 +1,83 @@
|
||||
# Session 0023.0 — Transcript
|
||||
|
||||
> App: human-experience-filter-art
|
||||
> Start: 2026-06-26T09-05 (PST)
|
||||
> End: 2026-06-26T09-20 (PST)
|
||||
> Type: executing-plans (operator-driven debugging — "still can't hear" / "video didn't come on")
|
||||
> Posture: yolo
|
||||
> Claude-Session: e0cc053a-41fa-416d-a4f5-493f14984e75
|
||||
> Status: FINALIZED
|
||||
|
||||
## Launch prompt (the thread)
|
||||
|
||||
> "I still can't hear" → (after diagnostics) operator screenshot: `audio: on, PAUSED
|
||||
> (readyState 0)` and "the audio check works" with `ring=server · url=NONE` → "the
|
||||
> video didn't come on when I turned it on" → after the fix: **"yes, both"** (video
|
||||
> and audio confirmed working).
|
||||
|
||||
## Pre-state
|
||||
|
||||
- Sessions 0020–0022 had shipped the audio/video feature + three "Safari autoplay"
|
||||
fixes that were verified only in headless browsers and **did not work** for the
|
||||
operator. Audio was silent; this session set out to find the real cause.
|
||||
|
||||
## Session arc — the debugging
|
||||
|
||||
1. **Stop guessing; instrument.** Three blind fixes had failed because headless
|
||||
Playwright (Chromium *and* WebKit) relaxes autoplay, so the bug was never
|
||||
reproducible. Added a **live `#audio-status` readout** (surfacing the previously
|
||||
*swallowed* `play()` errors + `aud.error`) and a **native `<audio controls>` test
|
||||
player** that plays the file with zero app code involved. Confirmed the server
|
||||
already honors HTTP Range (`206`) + `audio/mpeg` — not a serving problem.
|
||||
2. **The decisive readout.** Operator reported: the **native player works** (file +
|
||||
codec + browser audio all fine), but the toggle showed
|
||||
**`ring=server · url=NONE`** with `readyState 0` and no media error — meaning my
|
||||
code never set a source because `soundtrackUrl()` returned null.
|
||||
3. **Root cause = a STALE uvicorn server.** The operator's Python process predated the
|
||||
0020 audio/video work, so it (a) returns `/api/ring` without the per-scale `audio`
|
||||
field, and (b) requires the old 7-way `content` — it **422s** the new
|
||||
`{visual,audio}` payload and returns `{content:{video}}` instead of
|
||||
`{render:{video:{shown}}}`. So audio had no url AND the Video toggle's POST failed
|
||||
(→ "video didn't come on"). Reloading never fixes it (only refreshes static assets,
|
||||
not the Python process).
|
||||
4. **Fix = make the client resilient to a stale server** (so no restart is needed):
|
||||
- `soundtrackUrl()` falls back to a scale-id→file map (`SCALE_AUDIO_FALLBACK`,
|
||||
mirrors `build_pool_manifest.SCALE_AUDIO`).
|
||||
- `controls()` also sends a back-compat `content` field (current server ignores it;
|
||||
an old one ignores visual/audio).
|
||||
- `update()` reads "video shown" from **either** response shape.
|
||||
Verified end-to-end against a **simulated fully-stale server** (routed old
|
||||
`/api/alteration` shape + stripped ring audio) in Chromium **and** WebKit: Video on
|
||||
→ video shows + soundtrack plays, zero errors. **Operator confirmed "yes, both".**
|
||||
5. **Cleanup.** Tucked the audio diagnostics (status readout + native player) under the
|
||||
**Dev Mode** panel — kept for future debugging, out of the installation UI.
|
||||
|
||||
## Deferred decisions
|
||||
|
||||
_No low-confidence calls. One judgment worth noting: I added permanent client
|
||||
back-compat for a pre-release internal API to unblock the operator without a server
|
||||
restart. The clean long-term fix is restarting the dev server; the shim is harmless and
|
||||
removes the dependency. Flagging in case a future cleanup wants to drop it once servers
|
||||
are reliably current._
|
||||
|
||||
## Cut state
|
||||
|
||||
- `main` @ `6427ab4`, clean, pushed. **292 tests + 7 Playwright E2E pass** (2 skipped).
|
||||
- Audio + Video experience **works and is operator-confirmed** on the real device.
|
||||
- New E2E regressions: stale-ring soundtrack fallback, audio-then-video ordering.
|
||||
- The operator's actual server is still stale — recommended (not required) they restart
|
||||
it so the API is current; the client no longer depends on it.
|
||||
|
||||
## Operator plate / lesson
|
||||
|
||||
- **The big lesson (recorded in memory):** headless Playwright relaxes BOTH autoplay and
|
||||
GPU compositing, so it cannot reproduce real-browser/real-server issues. When a fix
|
||||
"passes headless" but the operator still sees the bug, **instrument with on-screen
|
||||
readouts + a native control** to get their real data — don't ship another blind guess.
|
||||
Three rounds were lost to this before instrumenting.
|
||||
|
||||
## Next /goal
|
||||
|
||||
```
|
||||
/goal source/compose the deferred per-altitude music layer (the reserved Audio dial position) per docs/audio-candidate-pool.md — the Audio+Video experience is working & operator-confirmed; OR re-enable white-noise as a 3rd Audio position if wanted
|
||||
```
|
||||
@@ -0,0 +1,94 @@
|
||||
# Session 0024.0 — Transcript
|
||||
|
||||
> App: human-experience-filter-art
|
||||
> Start: 2026-06-26T21-12 (PST)
|
||||
> Type: planning-and-executing
|
||||
> Posture: yolo
|
||||
> Claude-Session: e0aea412-c853-4236-9536-cfd85cbc4975
|
||||
> End: 2026-06-27T15-23 (PST)
|
||||
> Status: **FINALIZED.**
|
||||
>
|
||||
> Started as writing-plans; transitioned to planning-and-executing (yolo) when the
|
||||
> operator approved the plan and asked to execute in-session.
|
||||
|
||||
## Next /goal
|
||||
|
||||
Build **scrub-driven altitude transitions** per the approved design at
|
||||
`docs/superpowers/specs/2026-06-27-scrub-driven-altitude-transitions-design.md`:
|
||||
knob position continuously scrubs the morph `currentTime` + crossfades adjacent
|
||||
scale audio (hold-partway, reversible, re-roll on fresh approach, wheel auto-scrubs
|
||||
one altitude). Phase 1: interaction on current morphs. Phase 2: re-bake all 154
|
||||
morphs all-intra for smooth seeking + re-push LFS. Writing-plans → executing-plans
|
||||
in a fresh session. (Operator was handed a ready-to-paste prompt.)
|
||||
|
||||
## Launch prompt
|
||||
|
||||
> Fix altitude/video transition: video shouldn't switch when changing altitudes;
|
||||
> good zoom transition for every video combination; video must not change after
|
||||
> zooming to a new altitude.
|
||||
|
||||
Operator refinements during orientation:
|
||||
- **Lock per altitude until the altitude is changed** — on arrival, pick the
|
||||
clip once and keep it; no secondary swap, no re-roll while parked.
|
||||
- **Choose the destination clip BEFORE transitioning, then play the appropriate
|
||||
morph footage.** We need morph footage for ALL transitions between ALL videos
|
||||
in adjacent altitudes (both directions).
|
||||
|
||||
## Plan
|
||||
|
||||
Anchor: leaf change extending player `design.md §3` (scale navigation & zoom
|
||||
transitions) + content-pipeline. **Scope reality:** clip-pair-keyed morph system
|
||||
— **154 morph clips** (77 adjacent video pairs × 2 directions) up from 5 per-edge
|
||||
morphs; touches the bake pipeline (`build_pool_manifest.py`), the ring contract
|
||||
(`player/ring.py`), manifest/transition schema (`simulator/clips.py`), the
|
||||
`/api/ring/advance` endpoint (`simulator/app.py`), the frontend `advance()`/lock
|
||||
(`simulator/static/app.js`), and a new Playwright E2E tier. Writing-plans session:
|
||||
author ONE plan, stop at the review gate.
|
||||
|
||||
## Deferred decisions
|
||||
|
||||
- The dial-needle **sweep** (synced to the morph) is verified working in Chromium +
|
||||
WebKit both directions, but the operator's real Safari still showed it "not
|
||||
working" — root cause likely stale cached `app.js` (Safari refresh confusion),
|
||||
never fully confirmed. Moot: the **scrub-driven redesign supersedes** the dial
|
||||
interaction entirely. The sweep commit merged to main as an interim improvement.
|
||||
- **git-LFS front-proxy upload limit** (HTTP 413 on the 68 MB base): resolved by
|
||||
transcoding rather than raising the server limit — the operator couldn't locate
|
||||
the front-proxy VM (`35.238.203.16`, nginx, not in any gcloud project I could
|
||||
reach) this session. Raising that limit is a deferred infra task.
|
||||
|
||||
## Arc
|
||||
|
||||
**Pre-state.** Altitude dial (PR #18, s0016) navigated a 5-scale ring with **5
|
||||
per-edge** morphs (scale-primary→primary). Landing picked a random pool member, but
|
||||
`advance()` played the edge morph (ending on the primary) then did a jarring
|
||||
fade-to-black **secondary swap** to the random member. Operator: "video switches when
|
||||
changing altitudes; must not change after zooming."
|
||||
|
||||
**What we built (writing-plans → executing, planning-and-executing).**
|
||||
1. Orientation + clarification: lock-per-altitude; **pick destination first then play
|
||||
the matching morph**; morphs for **all adjacent video pairs both directions** (154);
|
||||
multi-detent **chains** through each altitude.
|
||||
2. Wrote + reviewed the plan (`docs/superpowers/plans/2026-06-27-altitude-clip-pair-morph-lock.md`);
|
||||
operator approved + asked to execute in-session → switched to yolo.
|
||||
3. Executed TDD: per-clip-pair manifest + member-pair baking; `Transition`/`morph_for`/
|
||||
`resolve_move` in `player/ring.py`; `resolved_move_to_dict`; `/api/ring/advance`
|
||||
`from_clip_id`; frontend pick→morph→lock (no secondary swap); regenerated 154
|
||||
morphs. pytest 302 passed; new **Playwright E2E tier** 2 passed.
|
||||
4. **Media graduation:** operator flagged that "sample" media is actually the
|
||||
experience — committed bases + audio + 154 morphs (~526 MB) via **git-LFS → Gitea
|
||||
LFS store**. Hit HTTP 413 on the 68 MB `coast_birdrock` base at the front proxy;
|
||||
**transcoded it 68→22 MB** (re-baked its 16 morphs) to ship. Pushed; merged to main.
|
||||
5. Polish loop on the **dial/transition timing** (sweep → removed live drag pre-move),
|
||||
then operator **reframed**: knob position should *drive* the transition (scrub),
|
||||
not the dial follow the video. Brainstormed + wrote the **scrub-driven design spec**;
|
||||
handed off a prompt to build it in a fresh session.
|
||||
|
||||
**Recurring lesson re-confirmed:** the operator's mid-session "no transitions" was a
|
||||
**stale uvicorn** (served new `app.js` from disk but old in-memory API returning the
|
||||
deleted old-named morph files → 404). Fix = restart uvicorn with the venv python.
|
||||
|
||||
**Cut state.** `main` (528fb5c) has the shipped feature, LFS media, E2E tier, both the
|
||||
plan and the scrub design spec. No content repo → plan archived in-repo under `docs/`
|
||||
(gap flagged). Simulator is localhost-only (no PPE/prod) → no §9 deploy stage; verified
|
||||
green locally. Next increment (scrub) deferred to a fresh session.
|
||||
@@ -0,0 +1,96 @@
|
||||
# Session 0025.0 — Transcript
|
||||
|
||||
> App: human-experience-filter-art
|
||||
> Start: 2026-06-27T06-05 (PST)
|
||||
> End: 2026-06-27T07-05 (PST)
|
||||
> Type: capture
|
||||
> Posture: careful
|
||||
> Claude-Session: d6ed1203-92b6-471f-80a4-4c773b645ca1
|
||||
> Status: **FINALIZED**
|
||||
|
||||
## Launch prompt
|
||||
|
||||
```
|
||||
capture this as an issue. We'll implement the network feature later but want it as an Epic, then the remote simulator (e.g. ipad) as a feature with this design
|
||||
```
|
||||
|
||||
## Pre-state
|
||||
|
||||
- Immediately followed session 0019 (brainstorming), which merged the
|
||||
**networked control surface** design SPEC to `main`
|
||||
(`docs/superpowers/specs/2026-06-26-networked-control-surface-design.md`).
|
||||
- Operator wants the work captured as backlog (deferred — "implement later"): an
|
||||
**Epic** for the whole networked feature, and a **Feature** for the iPad remote
|
||||
twin anchored to that spec.
|
||||
- Single-repo app: tracker = `benstull/human-experience-filter-art` on
|
||||
git.benstull.org; no separate content repo (`CONTENT_REMOTE` empty).
|
||||
- Checkout shared with concurrent sessions (0024 live on its own branch).
|
||||
|
||||
## Arc
|
||||
|
||||
1. **Routing.** Classified the request as **capture**; invoked `wgl-capture`,
|
||||
claimed tracked-lite session **0025** (capture / careful).
|
||||
|
||||
2. **Target resolve.** Single-repo app → drafted into this repo's working-tree
|
||||
`issues/` (ephemeral, INV-8); tracker is this repo on git.benstull.org.
|
||||
|
||||
3. **Two asks identified** (per handbook §4.3 sizing): an **Epic** (multi-Feature
|
||||
umbrella — the networked control surface) and a **Feature** (single increment —
|
||||
the iPad web remote twin, the spec's v1 scope). Both type-fit cleanly.
|
||||
|
||||
4. **Drafted both** in the content repo's `issues/` at business altitude (Epic +
|
||||
Feature business sections free of implementation specifics; the design spec
|
||||
referenced as orientation). Presented for review; operator approved filing both
|
||||
at **P2** via `AskUserQuestion`.
|
||||
|
||||
5. **Filing.** Label-ensure first hit a **401** — the default issue-token service
|
||||
resolved to the wrong host. Fixed by pointing `WGL_CAPTURE_TOKEN_SERVICE` at the
|
||||
host-specific Keychain entry `wgl-gitea-issues-readwrite-token-git.benstull.org`.
|
||||
Then ensured capture labels (9 created) and filed:
|
||||
- **Epic #26** — "Networked control surface — drive the experience from a
|
||||
separate control device" (`type/epic`, P2).
|
||||
- **Feature #27** — "Remote controller simulator — drive the experience from an
|
||||
iPad web twin over wifi" (`type/feature`, P2; parent #26).
|
||||
(Typecheck advisory labels 401'd — fail-open, skipped; no effect on the issues.)
|
||||
|
||||
6. **Cleanup.** Deleted both filed drafts (Gitea = system of record, INV-8);
|
||||
removed the now-empty `issues/` dir; working tree carries no drafts.
|
||||
|
||||
## Cut state
|
||||
|
||||
- Filed: **Epic #26**, **Feature #27** (parent #26), both `priority/P2`, deferred.
|
||||
- No repo commits this session (capture is tracked-lite; drafts ephemeral). The
|
||||
dirty tree on branch `session-0024` is the **concurrent session's** work
|
||||
(`build_pool_manifest.py` + a test) — left untouched.
|
||||
- Memory updated: `networked-control-surface-spec.md` records the filed issues +
|
||||
a deferred "when resumed" pointer; `MEMORY.md` index refreshed.
|
||||
|
||||
## Deferred decisions
|
||||
|
||||
_None — no autonomous low-confidence calls. Type assignment and P2 priority were
|
||||
operator-approved in-session._
|
||||
|
||||
## Type-fit tally
|
||||
|
||||
`type-fit: judged 2, mismatch 0, overridden 0` (judged against §4.3 informally;
|
||||
the typecheck-label markers could not be stamped — advisory token 401, fail-open).
|
||||
|
||||
## Operator plate
|
||||
|
||||
- The networked-control work is now backlog: **#26** (Epic) / **#27** (Feature),
|
||||
deferred per "implement later".
|
||||
- Token note for future capture on this host: use
|
||||
`WGL_CAPTURE_TOKEN_SERVICE=wgl-gitea-issues-readwrite-token-git.benstull.org`
|
||||
(the unscoped default resolves to the wrong host and 401s).
|
||||
|
||||
## Next /goal
|
||||
|
||||
No new immediate next from this capture — the work is parked on the tracker
|
||||
(#26/#27, deferred). When the operator resumes hardware:
|
||||
|
||||
```
|
||||
/goal Write the implementation plan for the remote controller simulator, per docs/superpowers/specs/2026-06-26-networked-control-surface-design.md (Feature #27)
|
||||
```
|
||||
|
||||
The active project frontier remains the per-altitude music layer (see the
|
||||
`audio-soundtrack-sourcing` memory), not this deferred thread.
|
||||
@@ -0,0 +1,130 @@
|
||||
# Session 0026.0 — Transcript
|
||||
|
||||
> App: human-experience-filter-art
|
||||
> Start: 2026-06-27T15-26 (PST)
|
||||
> Type: planning-and-executing
|
||||
> Posture: yolo
|
||||
> Claude-Session: c6b9bebd-f94a-4fc0-982d-0a9dc00b3148
|
||||
> Checkout: /Users/benstull/git/benstull.org/benstull/human-experience-filter-art
|
||||
> Status: **PLACEHOLDER — claimed at session start; finalized at session end.**
|
||||
>
|
||||
> This file reserves session ID 0026 for human-experience-filter-art. The driver replaces this
|
||||
> body with the full transcript and renames the file to its final
|
||||
> SESSION-0026.0-TRANSCRIPT-2026-06-27T15-26--<end>.md form at session end.
|
||||
|
||||
## Launch prompt
|
||||
|
||||
Build scrub-driven altitude transitions in the simulator, per the approved design at
|
||||
`docs/superpowers/specs/2026-06-27-scrub-driven-altitude-transitions-design.md`. Write the
|
||||
implementation plan from that spec, review it, then execute it. Make the Altitude knob
|
||||
position continuously drive the transition: dragging the dial scrubs the morph video's
|
||||
currentTime and crossfades the two adjacent scale soundtracks by knob angle; hold-partway
|
||||
(no auto-complete), fully reversible, re-roll the destination clip on each fresh approach,
|
||||
scroll-wheel auto-scrubs one altitude then locks, lock-per-altitude preserved. Phase 1:
|
||||
build against current morphs. Phase 2: re-bake all 154 morphs all-intra for smooth seeking,
|
||||
re-push via git-LFS. Unit tests for pure scrub logic + extend Playwright E2E.
|
||||
|
||||
## Plan
|
||||
|
||||
> Anchor: design docs/superpowers/specs/2026-06-27-scrub-driven-altitude-transitions-design.md (R2a — ELIGIBLE)
|
||||
> Session type: planning-and-executing (fused) — write the plan, then execute it, ship via §9.
|
||||
|
||||
Scrub-driven altitude transitions. Continuous `pos` (float) drives morph `currentTime` +
|
||||
two-soundtrack audio crossfade by `frac(pos)`; integer crossing commits + locks + re-rolls.
|
||||
Phase 1 = interaction against current morphs; Phase 2 = all-intra re-bake (154 morphs, LFS).
|
||||
Pure scrub logic kept separable + unit-tested; Playwright E2E extended to assert currentTime
|
||||
+ audio gains track the dial and reverse on turn-back.
|
||||
|
||||
### Progress (checkpoint @ Phase-1 done)
|
||||
|
||||
- Plan: `docs/superpowers/plans/2026-06-27-scrub-driven-altitude-transitions.md` (approved "execute as-is").
|
||||
- **Phase 1 DONE** (branch `session-0026`, 5 commits):
|
||||
- T1 `simulator/static/scrub.js` (pure UMD math) + `simulator/unit/scrub.test.js` (9 `node --test`).
|
||||
- T2 two-element audio crossfade (`<audio id=aud-b>`, `blendAudio`/`restAudio`/`ensurePlaying`/`assignElements`).
|
||||
- T3 scrub engine: drag live-drives `pos`; `setPos` seeks morph currentTime (1 seek/rAF), crossfades audio,
|
||||
commits+locks+re-rolls on integer crossings; release holds (no auto-complete); client-side `pickPoolClip`.
|
||||
- T4 wheel + label-tap → `autoScrub` (animate `pos`, land exact, lock); removed dead `advance()`/`playTransition()`.
|
||||
- T5 E2E scrub tier (6 tests green): drag tracks currentTime+gains, turn-back reverses same file + re-locks start,
|
||||
detent-crossing commits+locks (via `window.__hefState` seam), wheel auto-scrubs+locks.
|
||||
- **Verification:** `node --test` 9 green · `pytest` 302 passed / 2 skipped · Playwright 6 green.
|
||||
- **Phase 2 DONE** (T7–T8): `transition_cmd`/`reverse_cmd` builders + `ALL_INTRA` (`-g 1 -keyint_min 1
|
||||
-sc_threshold 0`), unit-tested; re-baked all 154 morphs all-intra (verified 93/93 I-frames on a sample),
|
||||
173M→504M, largest clip ~5.8M; committed via git-LFS and pushed (154 LFS objects, 453MB).
|
||||
- **All 8 plan tasks complete. Branch `session-0026` pushed.** Final verification: `node --test` 9 green ·
|
||||
Playwright 6 green · `pytest` 302 passed/2 skipped.
|
||||
- **Remaining:** merge to main + §9. Operator by-eye "feel/smoothness liked?" review is the one acceptance
|
||||
criterion automated tests can't cover (simulator-first directive) — flagged for operator before/at merge.
|
||||
- **DECISION (operator): HOLD for eyeball first.** Branch `session-0026` stays UNMERGED; session NOT finalized.
|
||||
Operator pulls + runs the sim locally to judge the scrub feel/smoothness, then says merge (or requests tweaks).
|
||||
|
||||
### Eyeball round 1 — operator found 2 issues, both FIXED (2 more commits)
|
||||
|
||||
- **Fix 1 — landing jitter (frontend).** Landing jump-cut the loop back to frame 0. Each morph reproduces the
|
||||
dst clip's first 3s (`trim=0:3`), ending dst@~3s, so steady-state loop now starts at `LOOP_TAIL_S=3.0` and
|
||||
loops `[3.0, duration]` (`ensureClipMedia` custom loop; disarmed during morph scrub). Morph's last frame hands
|
||||
straight off to the loop. E2E asserts a landed clip loops from ~3s, not 0. No re-bake. (8th E2E test added.)
|
||||
- **Fix 2 — red-snapper text card (content).** `reef_snapper` opened with a black NOAA title card (~0.9–2.9s).
|
||||
Re-trimmed `reef_snapper/base.mp4` to drop the first 3.0s (23s→20s, footage clean from 3.0s) and re-baked the
|
||||
7 reef_snapper morph pairs (+reverses, all-intra) from the clean base; verified snapper morph frames text-free.
|
||||
- **Verification:** Playwright 7 green · all-intra re-confirmed (93/93 I-frames). Pushed to `session-0026`.
|
||||
- **D2 (deferred) — reef_snapper tracked test-label.** The trim shifts the footage, so reef_snapper's one
|
||||
loop-normalized tracked label (`appear 0.0`/`disappear 0.7` + 3-keyframe track) is now slightly misaligned to
|
||||
the subject. It's demo/test material gated behind both knobs up; left as-is — re-author via `/author.html` if
|
||||
the operator wants it pixel-accurate.
|
||||
### Eyeball round 2 — 2 more items, both FIXED (2 commits)
|
||||
|
||||
- **Fix 3 — morph→loop load jolt (double-buffer).** A pause/jolt when the morph ended and the next altitude's
|
||||
loop loaded: swapping `vid.src` to the base + seeking the tail reloaded the ON-SCREEN element (decode+seek
|
||||
stall). Added a dedicated `#vid-loop` element for the steady-state base loop; during a scrub it is PRELOADED to
|
||||
the clip we're heading toward (`loadLoop`, paused at the tail frame). The paint shader reads the active source
|
||||
(`displayVid() = busy ? vid : loopVid`), so landing is an instant source swap — no reload, no seek. E2E asserts
|
||||
the loop element is armed + playing from ~3s after landing.
|
||||
- **Fix 4 — audio on/off → 0–10 level dial.** Replaced the Audio checkbox with a 0–10 range slider = master gain
|
||||
(`audioVol = level/10`), scaling rest volume + the two-track scrub crossfade. First Video-on lifts audio to
|
||||
**3/10** (was full). E2E: range 0–10 default 0; first video-on → 3.
|
||||
- **Verification:** Playwright **9 green** · node unit 9 · screenshot-confirmed the dial layout.
|
||||
- **D3 residual still applies:** double-buffer kills the LOAD pause both directions; the reverse/turn-back FRAME
|
||||
asymmetry (morph src-start@0 vs loop tail@3s) and the ~loop-wrap seek remain (per-clip loop files would fully
|
||||
resolve if wanted).
|
||||
### Eyeball round 3 — audio-off bug (systematic-debugging), FIXED (1 commit)
|
||||
|
||||
- **Bug:** audio turned off *sometimes* after landing on a new altitude. **Root cause (code-traced, not guessed):**
|
||||
`fadeVolume` started a `setInterval` ramp that was **never cancelled**. `restAudio` fades the idle element to 0;
|
||||
if within FADE_MS (500ms) that same element becomes the newly-landed ACTIVE scale (`assignElements` reuses one
|
||||
element per soundtrack url), the stale fade-to-0 kept running and dragged the now-active element to silence —
|
||||
hence "only sometimes." **Fix:** `cancelFade(el)` supersedes any in-flight fade when a new fade starts
|
||||
(`fadeVolume`) or a direct volume is set (`ensurePlaying`). Added `window.__hefFade` seam + a deterministic E2E
|
||||
that PROVES it (fails with the fix disabled, passes with it). Playwright **10 green**.
|
||||
### Eyeball round 4 — transition speed (1 commit)
|
||||
|
||||
- **Request:** slow click/auto transitions ~2x and make them the SAME speed between any two altitudes (2 away =
|
||||
2× as long as 1). `autoScrub` was a fixed 600ms total regardless of distance. Now duration is PROPORTIONAL:
|
||||
`PER_ALTITUDE_MS=1200` (≈2× the old 600) × |distance|, so a label-click/wheel jump of N altitudes takes
|
||||
N×1200ms — constant per-step speed. Manual drag unaffected. E2E asserts a 2-step jump ≈ 2× a 1-step. 11 green.
|
||||
- **Resume:** merge + `wgl-session-finalize` on operator go, OR next eyeball round of tweaks.
|
||||
|
||||
## Deferred decisions
|
||||
|
||||
_Autonomous-mode low-confidence calls the driver made and would have
|
||||
liked operator input on. Appended as the session runs; surfaced at
|
||||
finalize. Empty if none._
|
||||
|
||||
- **D1 (plan) — canonical segment morph file.** The spec's component 2 says resolve the
|
||||
directed morph via `morphByPair["<fromClip>→<toClip>"]`, but its turn-back decision says
|
||||
"scrubs the SAME morph in reverse" with full reversibility. These are only mutually
|
||||
consistent if a segment uses ONE file scrubbed bidirectionally. I locked the plan to:
|
||||
per segment `[lo, lo+1]`, always use the **descend/forward** morph
|
||||
`morphByPair["<clip@lo>→<clip@lo+1>"]` and drive `currentTime = frac × duration` in both
|
||||
travel directions; the `.rev` files become unused by the scrub interaction (kept baked
|
||||
for back-compat). This means the old E2E "zoom-out plays a `.rev`" assertion is replaced
|
||||
by a "turn-back seeks the same file backward" assertion. Low-confidence on whether the
|
||||
operator specifically wanted `.rev` retained in the drag path.
|
||||
- **D2 — reef_snapper tracked test-label misalignment after the text-card trim.** Trimming the snapper base by
|
||||
3.0s (to remove the title card) shifts the footage, so reef_snapper's one loop-normalized tracked label
|
||||
(`appear 0.0`/`disappear 0.7` + a 3-keyframe track) no longer follows the same subject. Left as-is: it's
|
||||
demo/test authoring gated behind both knobs up; re-author via `/author.html` if pixel-accuracy is wanted.
|
||||
- **D3 — landing-jitter fix approach.** Chose a frontend loop-from-tail (`LOOP_TAIL_S=3.0`, custom
|
||||
`[3.0, duration]` loop) over re-baking morphs or baking dedicated loop clips — cheap, no media churn,
|
||||
eye-tweakable. Residual: a one-seek hitch at each loop wrap (~every 17–20s) and scrub-START (vs landing) is
|
||||
not made seamless (a looping clip is at arbitrary phase when a drag begins). If the wrap hitch bothers the
|
||||
operator, bake per-clip loop files (`base[3:end]`) for native seamless looping.
|
||||
@@ -64,5 +64,17 @@
|
||||
},
|
||||
"0022": {
|
||||
"title": ""
|
||||
},
|
||||
"0023": {
|
||||
"title": ""
|
||||
},
|
||||
"0024": {
|
||||
"title": ""
|
||||
},
|
||||
"0025": {
|
||||
"title": ""
|
||||
},
|
||||
"0026": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user