Commit Graph

357 Commits

Author SHA1 Message Date
BenStullsBets d161db8110 feat(audio): add CC0 ethereal Music loop (freesound #808032, via LFS)
The Music-option asset produced by build_audio_media.py: deadrobotmusic
'Ambient F Sharp Minor Ethereal Choir Pad' (CC0), sourced from the freesound
hq preview and looped/loudness-normalized to ethereal.loop.mp3 (3:34). Both
the raw source and the served loop are committed via git-LFS, matching the
per-scale soundtracks; served at /media/audio/music/ethereal.loop.mp3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 22:26:14 -07:00
BenStullsBets ee7190bb4a feat(audio): Audio source dropdown (None/Soundtrack/Music) + Volume rename
Split the single 0–10 'Audio' slider into two controls:
- Audio  — a dropdown selecting the source: None / Soundtrack / Music
- Volume — the renamed 0–10 slider (master gain for the active source)

Music is one ethereal, relaxing ambient loop played the same at every
altitude (no per-altitude crossfade), per the innerflo.me brief. Sourced
CC0: deadrobotmusic 'Ambient F Sharp Minor Ethereal Choir Pad' (freesound
#808032); added to build_audio_media.py, the candidate-pool doc, and the
public credits colophon. Media stays gitignored; run build_audio_media.py.

Routing lives in a pure, node-tested HEFScrub.audioPlan(source, level, frac);
app.js's restAudio/blendAudio consult it. None greys out Volume. The welcome
screen's mirror level slider is relabeled Volume too. i18n for all 4 langs.

Spec: docs/superpowers/specs/2026-07-01-audio-music-option-design.md
Tests: +5 node unit (audioPlan), +4 e2e (audio-source.spec.ts); 32 related
e2e green, 38 node unit green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:55:23 -07:00
BenStullsBets 6816e4ce75 docs(spec): Audio source dropdown (None/Soundtrack/Music) + Volume rename
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:41:40 -07:00
BenStullsBets 7ec8b607d2 Merge remote-tracking branch 'origin/main' into worktree-welcome-screen-controls 2026-06-30 15:06:59 -07:00
BenStullsBets b135cb5f5e fix(deploy): redirect the bare legacy root too (/* splat misses the empty case)
Add an explicit /human-experience-simulator/ → /human-machine-strata/ 301 (the
subpath splat doesn't match the trailing-slash root).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:02:29 -07:00
BenStullsBets 950c1e6c8c feat(deploy): move app URL to /human-machine-strata + 301 from the legacy path
Build default app-path → /human-machine-strata; deploy.sh APP_PATH default too.
_write_redirects now emits a permanent redirect /human-experience-simulator/* →
/human-machine-strata/:splat so old links still land. Media (R2 bucket) + Pages
project names are unchanged (not in the benstull.art URL). Updated the build
test (asserts the legacy redirect) + static-build e2e path + README paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:00:40 -07:00
BenStullsBets 958c3060d2 feat(rename): retitle the project to "Human/Machine Strata"
Page <title>, on-page <h1> (app.title — kept as the proper title, same across
all 4 languages), the About + Credits headings/prose, and the author dev tool.
"Strata" = the layered cosmos→abyss descent; the slash = the human↔machine
membrane the work is about.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 15:00:40 -07:00
BenStullsBets c36f995463 feat(panel): move Altitude to the top of the panel (mobile: reachable with video on screen)
On mobile the panel wraps below the (sticky) stage; putting the Altitude knob
first means it sits right under the video instead of behind Output + knobs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:29:02 -07:00
BenStullsBets b63518bfbb chore(title): h1 header → "Human Experience Filter Simulation" (4-lang, matches page title)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:23:43 -07:00
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 54cad607fb add sessions/0038/SESSION-0038.0-TRANSCRIPT-2026-06-30T12-25--2026-06-30T13-20.md + replace placeholder/variant SESSION-0038.0-TRANSCRIPT-2026-06-30T12-25--INPROGRESS.md 2026-06-30 13:19:40 -07:00
BenStullsBets 053bb5c1a3 claim human-experience-filter-art session 0038 (placeholder) + sessions.json entry 2026-06-30 13:17:25 -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