Commit Graph

346 Commits

Author SHA1 Message Date
BenStullsBets 4555bb29a8 chore(title): page title → "Human Experience Filter Simulation"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:22:51 -07:00
BenStullsBets 8c63b14967 chore(deploy): no-store the whole app path (per-HTML override didn't stick)
Cloudflare Pages let the /seg/* no-cache rule win over the per-HTML no-store, so
the index was still served no-cache. no-store the whole app path instead — the
shell is tiny and media is on R2 (separate, immutable). Guarantees a fresh index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:20:53 -07:00
BenStullsBets ecd444b53b fix(reduce-motion): re-roll the clip on an instant-cut altitude change
With reduce motion on, selecting a new altitude jumps (autoScrub's reduced
branch → setPos) and skips the segment that would re-roll the destination clip,
so activeClipId stayed on the previous altitude's clip and the video didn't
change. Self-heal at the landing point: if the landed altitude's pool doesn't
contain activeClipId, pickPoolClip a fresh one. TDD: new altitude-lock e2e.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:18:16 -07:00
BenStullsBets fafb498d6f feat(panel): move Experience knobs above Altitude; drop dial hint + '(0–4)'
Per operator: Think/Feel/Mood group sits above the Altitude knob; remove the
'Turn the knob…' altitude hint (and its dead i18n key); 'Experience knobs (0–4)'
→ 'Experience knobs' (all 4 langs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:12:58 -07:00
BenStullsBets e06191db32 chore(deploy): no-store the app-shell HTML (stop Safari serving a stale index)
no-cache (revalidate) wasn't enough — Safari served a stale cached index from
disk/bfcache, which loaded old versioned JS and broke the page (black, silent).
HTML entry points now no-store (always fresh index → always fresh hashed assets).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:12:57 -07:00
BenStullsBets ccd40103a2 fix(speed): cap Video speed at 2x (>2x stutters — base loops not all-intra)
Same root cause that retired reverse playback: the resting loop clips aren't
all-intra, so playbackRate >2x makes Safari stutter on inter-frame seeks. Narrow
both sliders (welcome + panel) + the tick datalist to 0–2x; default still 1x.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:08:11 -07:00
BenStullsBets 79f64e3f7c feat(welcome): rename "Speed" control to "Video speed" (4-lang)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:57:13 -07:00
BenStullsBets 7cdf594794 chore(e2e): untrack + gitignore Playwright test-results artifacts
These were accidentally committed (tracked on main); they are regenerated on
every run and shouldn't be in git.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:27:25 -07:00
BenStullsBets 5f9ae6b212 feat(welcome): reduce-motion toggle for morph transitions; decouple loop from it
Per operator: Speed only controls the resting loop, so reduce-motion is still
needed to turn off the morph/transition videos. Re-add a visible 'Reduce motion'
toggle on BOTH the welcome screen and the panel (mirrored, default from OS
prefers-reduced-motion, remembers the choice). When ON, altitude changes are
instant cuts instead of an animated morph tween (autoScrub's reduced branch);
the resting loop is unaffected — it follows the Speed slider (0x freezes it).

Decouples the loop from reduce-motion (was: main froze both videos + disabled
the slider under OS-reduced). playLoop()/landing/applyPlaySpeed no longer check
isReduced(); isReduced() now only gates the morph tween. Welcome controls are
now Language / Speed / Audio / Reduce motion. i18n: restore rm.label (4 langs),
reword warn.body to point at the on-screen control.

Tests: a11y Task 5 reworked (speed independent of reduce-motion; new instant-cut
behavior test); welcome defaults assert reduce-motion off. node 35, e2e 32,
static 2, pytest 301 (3 pre-existing ffmpeg/cv2 env fails).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:27:18 -07:00
BenStullsBets c176ae7231 Merge remote-tracking branch 'origin/main' into worktree-welcome-screen-controls
# Conflicts:
#	simulator/e2e/tests/a11y.spec.ts
#	simulator/static/app.js
2026-06-30 12:55:42 -07:00
BenStullsBets 6647390cb1 Merge remote-tracking branch 'origin/main' into worktree-welcome-screen-controls
# Conflicts:
#	simulator/e2e/tests/a11y.spec.ts
#	simulator/static/app.js
#	simulator/static/i18n.js
2026-06-30 12:54:06 -07:00
BenStullsBets e064a2e684 feat(speed): switch Playback Speed to 0x–4x forward (drop reverse)
Real-browser eyeball: below-0x reverse jittered because the base loop
clips aren't all-intra (the flagged risk). Narrow the slider to 0x–4x
forward (still continuous, step=any); 0x still freezes; OS reduced-motion
behavior unchanged. Removes the manual rAF reverse loop and the
HEFScrub.reverseStep helper + its unit tests.

Tests: node unit 10/10; a11y e2e 12/12 (Task 5 now 3 slider tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:48:14 -07:00
BenStullsBets df8b245af3 feat(speed): Playback Speed slider replaces Reduce motion toggle
Continuous -2x..2x slider drives ONLY the resting altitude loop video
(#vid-loop); the morph/transition video is scrub-driven and untouched.
Forward = native playbackRate; ~0x = freeze; below 0x = a manual rAF
reverse loop (native negative playbackRate is unreliable). Reverse math
is the pure, node-tested HEFScrub.reverseStep helper.

Reduced-motion accessibility is preserved but now derives live from the
OS prefers-reduced-motion setting (no visible toggle): when active the
loop stays frozen and the slider is disabled. Retires hef.reduceMotion.
i18n: speed.label (en/es/fr/ja); warn.body reworded to point at the OS
setting.

Tests: +4 node unit cases for reverseStep (14/14 green); a11y e2e Task 5
rewritten for the slider (13/13 green); static-build e2e green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:39:06 -07:00
BenStullsBets 539618b1b8 docs(spec): Playback Speed slider replaces Reduce motion toggle
Continuous -2x..2x speed control for the resting altitude loop video
(#vid-loop); manual rAF reverse loop below 0x; reduced-motion a11y
behavior preserved invisibly via OS prefers-reduced-motion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 12:39:06 -07:00
BenStullsBets f077193df9 feat(welcome): pre-load Welcome screen with controls + Launch gate
Per docs/superpowers/specs/2026-06-30-welcome-screen-design.md. Fold the bare
#loading splash, the #motion-warning modal, and the #run-sim button into one
#welcome overlay — the universal entry gate:

- State A (.welcoming): heads-up notice + four output controls (Video ON,
  Reduce motion OFF, Audio 2, Language) + 'Launch Simulator' button, with the
  'Loading Universe' progress pinned at the bottom (media preloads behind it).
- State B (.loading): pressing Launch while still loading hides the messaging +
  button, keeps the controls, and centers the loader; entry happens automatically
  once phase-1 finishes. Launch when already loaded enters directly.
- The right .panel is hidden until entry (body:has(#welcome)); welcome control
  values carry into the panel inputs on entry. Language + Reduce motion drive the
  shared state live (both selects/checkboxes mirror); Video + Audio apply on entry.
- Launch is the single audio-unlock gesture (beginExperience runs in-gesture even
  when entry is deferred). Retire WARN_KEY (welcome shows every load); entry uses
  the chosen Audio level rather than forcing 2.
- i18n: new welcome.launch key + es/fr/ja for the heads-up (warn.title/body),
  reworded to point at the on-screen Reduce-motion control; drop dead run.button.

Tests: new welcome.spec.ts (defaults, live language re-render, State B
deferred-entry, straight-in when loaded, control carry-over, panel hidden);
update altitude-lock/a11y/i18n/loop-recovery/static-build e2e + the Python
audio/credits e2e to the welcome flow; add a window.__hefReady diagnostic seam.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:14:35 -07:00
BenStullsBets 22bd152f33 add sessions/0037/SESSION-0037.0-TRANSCRIPT-2026-06-30T10-42--2026-06-30T10-52.md + replace placeholder/variant SESSION-0037.0-TRANSCRIPT-2026-06-30T10-42--INPROGRESS.md 2026-06-30 10:54:38 -07:00
benstull 5696b94328 Merge pull request 'feat(dial): snap to the closest altitude on drag release' (#31) from worktree-session-0037-altitude-snap-on-release into main 2026-06-30 17:51:39 +00:00
BenStullsBets 8e790b4a95 feat(dial): snap to the closest altitude on drag release
A drag is a way of grabbing the altitude dial, not a free-standing encoder
detent. Until now onDialUp held the live blend wherever the knob stopped (the
"no auto-complete" model), so a grab-and-release could leave the experience
parked mid-morph between two altitudes.

Now a real turn (moved >= 6) auto-scrubs the short way to Math.round(pos) and
settles there (frac 0 locks) — a grab-and-release always lands on a discrete
altitude, exactly like a tap or label click. The tap path (moved < 6 ->
jumpToScale) is unchanged, and reduced motion is handled by autoScrub (jump to
target, no tween).

Adds an altitude-lock e2e asserting a partial drag settles on an integer
altitude after release. Full altitude-lock (14) + a11y + i18n suites green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:50:51 -07:00
BenStullsBets b9de6695a0 docs(welcome): entry button is 'Launch Simulator' (new welcome.launch key)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:48:43 -07:00
BenStullsBets 05328b1b5e docs(welcome): brainstorming spec for the Welcome screen + controls
Unify #loading / #motion-warning / #run-sim into one #welcome overlay:
heads-up notice + four output controls (Video on, Reduce motion off,
Audio 2, English) + Loading Universe progress. Two states (welcome →
centered loading); right panel hidden until entry; Continue is the single
entry gesture. Forward note: extra welcome copy lands later.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:47:14 -07:00
BenStullsBets 1b646c8ade claim human-experience-filter-art session 0037 (placeholder) + sessions.json entry 2026-06-30 10:45:10 -07:00
BenStullsBets 00fd9aab17 add sessions/0036/SESSION-0036.0-TRANSCRIPT-2026-06-30T06-30--2026-06-30T12-00.md + replace placeholder/variant SESSION-0036.0-TRANSCRIPT-2026-06-30T06-30--INPROGRESS.md 2026-06-30 10:39:44 -07:00
BenStullsBets 0334fc8bc4 claim human-experience-filter-art session 0036 (placeholder) + sessions.json entry 2026-06-30 10:36:01 -07:00
BenStullsBets a4d3d831b0 Merge remote-tracking branch 'origin/main' into chore/land-isolation 2026-06-30 10:20:33 -07:00
BenStullsBets d711db6a2c fix(guard): scope the edit-block to files INSIDE the repo
The PreToolUse guard blocked ALL edits while in the main checkout — including files
outside the repo (auto-memory, /tmp, scratchpad). Now it only denies edits to files
under the main checkout's working tree; external files are allowed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:18:39 -07:00
BenStullsBets 17856cac32 chore(dev): enforce isolated worktrees outside main for every session
Multiple sessions sharing this one checkout clobbered each other (branch switches,
edits landing on the wrong branch). Enforce per-session isolation:
- worktree.bgIsolation=worktree + baseRef=fresh (native bg-session isolation)
- PreToolUse(Edit|Write|NotebookEdit) hook DENIES edits while in the main checkout
- SessionStart hook directs each session to EnterWorktree (fresh worktree, off main)
.claude/hooks/worktree-guard.sh detects main-vs-worktree via git-dir != git-common-dir.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:15:57 -07:00
BenStullsBets 34478bb3a3 add sessions/0035/SESSION-0035.0-TRANSCRIPT-2026-06-30T08-45--2026-06-30T10-08.md + replace placeholder/variant SESSION-0035.0-TRANSCRIPT-2026-06-30T08-45--INPROGRESS.md 2026-06-30 10:15:43 -07:00
BenStullsBets f7abb5c29b claim human-experience-filter-art session 0035 (placeholder) + sessions.json entry 2026-06-30 10:10:03 -07:00
BenStullsBets 86ce291056 add sessions/0034/SESSION-0034.0-TRANSCRIPT-2026-06-30T07-24--2026-06-30T10-01.md + replace placeholder/variant SESSION-0034.0-TRANSCRIPT-2026-06-30T07-24--INPROGRESS.md 2026-06-30 10:06:07 -07:00
BenStullsBets e0964eae6c claim human-experience-filter-art session 0034 (placeholder) + sessions.json entry 2026-06-30 10:03:35 -07:00
BenStullsBets 1a6ffc74a1 feat(load): phase-1 preload gate + eligible-pick pool growth
Gate the universe on one clip per altitude + connecting morphs (~126 MB, ~10-20s),
then grow the pool in the background. The random pick (pickPoolClip/pickRandomMember)
is restricted to fully-loaded clips — base + connecting morphs both ways cached
(HEFPreload.eligibleDestinations) — so a transition never stalls on un-downloaded
media and new clips become reachable only once fully loaded. Pure planner in
preload.js (+ node tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:55:46 -07:00
BenStullsBets 380c2d13e7 Merge origin/main into feat/accessibility-pass (reconcile static-publish + a11y with main)
# Conflicts:
#	simulator/static/app.js
2026-06-30 09:47:40 -07:00
BenStullsBets ff06783173 add sessions/0033/SESSION-0033.0-TRANSCRIPT-2026-06-30T09-38--2026-06-30T09-43.md + replace placeholder/variant SESSION-0033.0-TRANSCRIPT-2026-06-30T09-38--INPROGRESS.md 2026-06-30 09:44:55 -07:00
BenStullsBets 87d605db39 Merge design/cloudflare-static-publish into main: Cloudflare static publish + CC credits
Integrates the fully-static Cloudflare publish line (live at
benstull.art/human-experience-simulator) into main: the static build (Pages + R2,
manifest-only media tree, baked API JSON), the "Run simulation" start button, the
public CC-BY/BY-SA credits page with the "License & reuse" CC BY-SA 4.0 derivative
declaration (Option A + D1), the repeatable Cloudflare deploy script, and assorted
load/audio fixes (progressive boot, scaleAudioUrl via mediaBase).

Clean auto-merge with main's per-clip affect/feelings + i18n work — only app.js and
altitude-lock.spec.ts overlapped, both auto-resolved. Verified green on the merged
tree: node 29, pytest 315 passed / 2 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:42:44 -07:00
BenStullsBets 75961ea18e claim human-experience-filter-art session 0033 (placeholder) + sessions.json entry 2026-06-30 09:39:18 -07:00
BenStullsBets b83758fbca feat(content): about.html — intent, provenance, honest limits; static-build copy
English-first page mirroring credits.html (for-everyone / tech arc
underwater→drones→space / built-with-LLMs / honest-it's-imperfect). Header
link + about.link i18n key; about.html + flash.js added to PUBLIC_ASSETS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:37:00 -07:00
BenStullsBets 61bd4a7943 Merge remote-tracking branch 'origin/main' into worktree-fix+audio-and-tracking-labels 2026-06-30 09:36:17 -07:00
BenStullsBets 79105a3ef9 feat(a11y): wire flash-clamp into autoScrub; document 3-flash/sec audit
Clamp each per-altitude step to the WCAG 2.3.1 floor (no-op at 1200ms, guards
a future lowering). Audit: #black fade + audio crossfade can't exceed 3/sec, no
clamp needed. boot() now seeds the gate-dismissed flag (returning visitor) so
the run-sim test isn't blocked by the new interstitial.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:34:10 -07:00
BenStullsBets 2d54023f94 chore(i18n): re-extract en.json catalog to match per-clip affect
Keeps the translator source-of-record in sync with the manifest's English.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:33:46 -07:00
BenStullsBets bf1013be74 feat(content): per-clip right-brain feelings drawn from each clip's footage
Affect was authored per SCALE — every clip in an altitude shared the same 4
emotion words. Now every clip carries its OWN feelings, read from that specific
footage (mood, motion, light), so e.g. the violent Tarantula nebula evokes "a
violent ferment" while the dying-star Southern Ring evokes "a star's slow dying /
a luminous farewell," and a drowsing seal haul-out evokes "a sun-warmed drowse."

Adds AFFECT_CLIP (per-clip override; falls back to the scale register) covering
all 41 clips — 41 distinct feeling-sets, one per clip. Keeps each scale's
emotional family as a base and swaps in 1-2 footage-specific feelings, reusing the
existing feel.* palette where it fits and adding 19 new nuance keys (sublime,
tenderness, turbulence, radiance, mortality, poignancy, eeriness, purity,
solitude, verdancy, freshness, repose, hush, poise, flow, intricacy, spectral,
alienness, strangeness) with full es/fr/ja. 42 distinct feeling keys now in use.

Drafted by viewing each clip's footage (per-scale passes); first-pass reads that
want operator by-eye review. Data-only (build_pool_manifest.py + manifest + i18n
catalogs). Verified: 34/34 catalog+i18n+build pytest, 16/16 node unit, 4-language
parity for every clip, live render shows distinct feelings per clip (incl. new
keys) with Think/Feel overlap still 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:33:21 -07:00
BenStullsBets 7c8c4b45cd fix(load): preload all bases + morphs before interaction; remove buggy hold-gate
The hold-last-frame settle gated playLoop() behind a canplay event that doesn't
re-fire on an already-loaded element → the next altitude's video never started.
Reverted to the simple swap, and gate the universe on a full preload (every base +
every morph) so navigation is smooth with no on-demand stall. Adds a ?debug on-screen
media-state overlay for diagnosing real-browser playback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:31:10 -07:00
BenStullsBets b9a52b1c41 fix(deploy): bust the app-shell cache — no-cache _headers + content-versioned asset urls
Pages caches .js/.css by type (max-age=14400) and ignores _headers for them, so
returning visitors ran stale JS (no-video/no-audio until a private window). index.html
is no-cache and now references app.js?v=<hash> etc., so a normal reload always gets
fresh code; media on R2 stays immutable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:31:10 -07:00
BenStullsBets f1ce23c4fe feat(a11y): aria-live narration of scale/clip; hide decorative SVGs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:28:19 -07:00
BenStullsBets 0db602ebd0 feat(a11y): keyboard + ARIA slider semantics for the Altitude dial
role=slider + tabindex on the dial; Arrow/Home/End stepping; labels become
Enter/Space buttons; aria-valuenow/valuetext track the committed altitude via
renderDial. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:26:45 -07:00
BenStullsBets fa3bbef0d6 add sessions/0032/SESSION-0032.0-TRANSCRIPT-2026-06-30T09-20--2026-06-30T09-24.md + replace placeholder/variant SESSION-0032.0-TRANSCRIPT-2026-06-30T09-20--INPROGRESS.md 2026-06-30 09:26:01 -07:00
BenStullsBets 717bf5b08b feat(a11y): reduced-motion freeze-to-stills with OS-default toggle
Single guard in playLoop() holds a still frame; autoScrub jumps instantly;
applyReduceMotion pauses/resumes. Default from prefers-reduced-motion, then
the choice persists. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:24:00 -07:00
BenStullsBets ee7c65a0f2 feat(credits): restore the CC BY-SA declaration as "License & reuse" (Option D1)
s0031 removed "About this work" to avoid duplicating it on the separate About
page, but that section WAS the D1 compliance — it declares the altered work is a
derivative offered under CC BY-SA 4.0, which is what satisfies the ShareAlike
obligation for the BY-SA source clips. Without it the live site credited the
BY-SA authors but never declared the derivative share-alike.

Restore the declaration on the credits page, under the heading "License & reuse"
instead of "About this work" so it isn't confused with the separate About page
(which covers the project's intent). Tests assert the declaration is present and
that the heading is NOT "About this work".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:23:57 -07:00
BenStullsBets 64e8f658f6 claim human-experience-filter-art session 0032 (placeholder) + sessions.json entry 2026-06-30 09:21:27 -07:00
BenStullsBets 6cf8fffc51 feat(a11y): one-time photosensitivity warning gate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:18:04 -07:00
BenStullsBets 1f98b7bd8c feat(a11y): flash.js pure WCAG 2.3.1 flash-clamp helper + tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 09:16:02 -07:00