feat(sim): sky scale + auto-start experience + drop reef_hawaiian

Expanded-pools sky-scale work plus two experience tweaks:

- New `sky` altitude (airplane-window / low-flight layer) between orbit and
  coast; RING_ORDER cosmos·orbit·sky·coast·reef·abyss. New clip pools across
  all scales, re-baked the full adjacent-pair morph set (all-intra, LFS), and
  removed the now-stale cosmos↔abyss transitions the old ring order produced.
- Auto-start: once the preload finishes the experience turns Video on and
  raises the audio dial to 3/10 with no user gesture (app.js autoStart()).
- Dropped reef_hawaiian (text overlay — violates the text-free pool rule);
  reef pool 7→6, manifest transitions 586→558.

Tests: rewrote tests/test_e2e_audio.py for the dial UI + auto-start; updated
the manifest morph-count assertions and the altitude-scrub target for the new
ring order. 302 pytest + 11 Playwright e2e + 9 node unit all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-06-29 06:56:25 -07:00
parent 108e6207ba
commit 73d92b90d0
630 changed files with 6906 additions and 1123 deletions
+89 -7
View File
@@ -38,21 +38,32 @@ MANIFEST = MEDIA / "manifest.json"
# --- Pools: scale id -> ordered clip ids (primary first) (content-candidate-pool.md) ---
POOLS: dict[str, list[str]] = {
"cosmos": ["cosmos", "cosmos_miri", "cosmos_galaxies", "cosmos_hudf", "cosmos_xdf"],
"orbit": ["orbit_planetearth", "orbit_crewobs", "orbit_bluemarble"],
"coast": ["coast_birdrock", "coast_surfgrass", "coast_elkbeach", "coast_drakesbeach"],
"reef": ["reef_lionfish", "reef_spawning", "reef_hawkfish", "reef_snapper", "reef_coralspacific"],
"abyss": ["abyss_wow", "abyss_midwaterexp", "abyss_hiding"],
"cosmos": ["cosmos", "cosmos_galaxies", "cosmos_orion", "cosmos_tarantula",
"cosmos_westerlund", "cosmos_southernring", "cosmos_carina_eso"],
"orbit": ["orbit_planetearth", "orbit_bluemarble", "orbit_aurora2025",
"orbit_citylights", "orbit_helene", "orbit_epic"],
"sky": ["sky_grca_templesa", "sky_greenland_landice", "sky_greenland_suture",
"sky_jungle_amazon", "sky_jungle_waterfall", "sky_coast_cliffspain",
"sky_mtn_castlecrags", "sky_mtn_rocky"],
"coast": ["coast_birdrock", "coast_surfgrass", "coast_kelp", "coast_otters",
"coast_kalaloch", "coast_seals", "coast_mist"],
"reef": ["reef_lionfish", "reef_spawning", "reef_hawkfish", "reef_coralspacific",
"reef_redsea", "reef_flowergarden"],
"abyss": ["abyss_wow", "abyss_midwaterexp", "abyss_hiding", "abyss_bigfin",
"abyss_dandelion", "abyss_octopus", "abyss_seapig"],
}
# Ring order (large -> small, wraps): cosmos -> orbit -> coast -> reef -> abyss -> cosmos.
RING_ORDER = ["cosmos", "orbit", "coast", "reef", "abyss"]
# Ring order (large -> small, wraps): cosmos -> orbit -> sky -> coast -> reef -> abyss -> cosmos.
# `sky` (the airplane-window / low-flight layer) sits between orbit (space) and coast (ground).
RING_ORDER = ["cosmos", "orbit", "sky", "coast", "reef", "abyss"]
# Per-scale soundtrack (audio spec §5.1) — the production-pass output of
# simulator/build_audio_media.py, served at /media/audio/<path>.
SCALE_AUDIO: dict[str, str] = {
"cosmos": "cosmos/pillars.loop.mp3",
"orbit": "orbit/spaceamb.loop.mp3",
# TODO(sky): wants its own wind/air ambience — reusing the orbit space-amb loop as a placeholder.
"sky": "orbit/spaceamb.loop.mp3",
"coast": "coast/waves.loop.mp3",
"reef": "reef/soundscape.loop.mp3",
"abyss": "abyss/whale.loop.mp3",
@@ -113,6 +124,71 @@ META: dict[str, tuple[str, str, str]] = {
"NOAA Ocean Exploration midwater-exploration-1920x1080-1; trim 1640s, crossfade-loop"),
"abyss_hiding": ("“Hiding in the Dark” (NOAA Ocean Exploration)", PD,
"NOAA Ocean Exploration dark-1280x720-1; trim 2044s, crossfade-loop"),
# --- Round-4 curated additions (2026-06-28, branch feat/sky-scale-expanded-pools) ---
# cosmos
"cosmos_orion": ("Orion Nebula — visible/IR flythrough (NASA SVS)", PD,
"NASA SVS 30957 orion_vis_ir_zoom_xfade-1920x1080p30; trim 4095s, crossfade-loop. Text-free."),
"cosmos_tarantula": ("Tarantula Nebula (30 Doradus) zoom (NASA/ESA/CSA/STScI)", CCBY_WEBB,
"ESA/Webb weic2212b; 1080p, trim 159s, crossfade-loop."),
"cosmos_westerlund": ("Flight through star cluster Westerlund 2 (ESA/Hubble)", "CC-BY 4.0 — credit ESA/Hubble",
"ESA/Hubble heic1509f; 1080p, trim 559s, crossfade-loop."),
"cosmos_southernring": ("Southern Ring Nebula zoom (NASA/ESA/CSA/STScI)", CCBY_WEBB,
"ESA/Webb weic2207a; 1080p, trim 159s, crossfade-loop."),
"cosmos_carina_eso": ("Carina Nebula zoom (ESO)", "CC-BY 4.0 — credit ESO",
"ESO eso1828c; 1080p, trim 048s, crossfade-loop."),
# orbit
"orbit_aurora2025": ("ISS aurora — Nov 2025 geomagnetic storm (NASA JSC)", PD,
"NASA JSC ESRS ISS_20251112_071350; 1080p, trim 560s, crossfade-loop."),
"orbit_citylights": ("ISS night pass — West Coast city lights (NASA JSC)", PD,
"NASA SVS 30180 iss028_nighttime_20110819; 720p, trim 054s, crossfade-loop."),
"orbit_helene": ("Hurricane Helene from the ISS — storm + atmospheric limb (NASA JSC)", PD,
"NASA JSC jsc2024m000168; trim 559s, crossfade-loop."),
"orbit_epic": ("DSCOVR/EPIC full-disk Earth time-lapse (NASA GSFC)", PD,
"NASA SVS 12118 EPIC_timelapse; trim 060s, crossfade-loop."),
"orbit_illuminated": ("“Earth Illuminated” — aurora + Milky Way over the limb (NASA JSC)", "CC0 / public domain",
"NASA JSC Earth_Illuminated_120808; trim 68113s, crossfade-loop."),
# sky (NEW scale — airplane-window / low-flight, between orbit and coast)
"sky_grca_templesa": ("Grand Canyon — banking aerial over cliffs & temples (NPS)", PD_NPS,
"NPS Grand Canyon b-roll hd10 0121cbe5; 1080p, trim 024s, crossfade-loop."),
"sky_greenland_landice": ("IceBridge — low flight over N. Greenland snowy land ice (NASA SVS)", PD,
"NASA SVS 13472 Panasonic 2016 Airborne LandIce; trim 2060s, vidstab-stabilized, crossfade-loop."),
"sky_greenland_suture": ("IceBridge — overhead N. Greenland sea-ice patterning (NASA SVS)", PD,
"NASA SVS 13472 4K SeaIce 2017 Suture; trim 1050s, vidstab-stabilized, crossfade-loop."),
"sky_jungle_amazon": ("Amazon rainforest canopy — amazon→canopy splice", "Pexels License",
"Pexels 31291128 (amazon canopy) → 29780524 (jungle canopy), 1s xfade splice; crossfade-loop."),
"sky_jungle_waterfall": ("Waterfall plunging out of dense jungle canopy", "Pexels License",
"Pexels 8334944; 4K source, trim 646s, crossfade-loop."),
"sky_coast_cliffspain": ("Dramatic Atlantic sea cliffs, Northern Spain — aerial", "Pexels License",
"Pexels 36668415; 4K source, trim 125s, crossfade-loop."),
"sky_mtn_castlecrags": ("Castle Crags — gliding past jagged granite spires (US Forest Service)", PD,
"USFS/DVIDS 998726 DOD_111563303; trim 120174s, crossfade-loop."),
"sky_mtn_rocky": ("Rocky Mountain NP — aerial over alpine summits & tundra (NPS)", PD_NPS,
"NPS Rocky Mountain b-roll 188046ff; trim 60110s, crossfade-loop."),
# coast
"coast_kelp": ("Giant kelp forest, underwater", "Pexels License",
"Pexels 30285973; 4K source, trim 046s, crossfade-loop."),
"coast_otters": ("Wild sea otters, Elkhorn Slough CA (USFWS)", PD,
"USFWS Pacific Southwest b-roll (Wikimedia 28833083527); trim 048s, crossfade-loop."),
"coast_kalaloch": ("Slow surf on rock at sunset, Kalaloch WA", "CC-BY-SA 4.0 — credit Joe Mabel",
"Wikimedia Waves crashing on rocks off Beach 4, Kalaloch; trim 046s, crossfade-loop."),
"coast_seals": ("Harbor seals hauled out on a rock", "Pexels License",
"Pexels 6497564; 4K source, trim 854s, crossfade-loop."),
"coast_mist": ("Sea mist over a rocky coastline", "Pexels License",
"Pexels 31036891; 4K source, trim 357s, crossfade-loop."),
# reef
"reef_redsea": ("Red Sea coral reef — clear blue, colorful", "CC-BY-SA 4.0 — credit Kora27",
"Wikimedia Korallenriff im Roten Meer DSCF1654; trim 6286s, crossfade-loop."),
"reef_flowergarden": ("Sunlit reef + sergeant majors + jack/scad schools (NOAA Fisheries)", PD,
"NOAA Fisheries b-roll VIDEO_ID 1553921798001; trim 868s, crossfade-loop."),
# abyss
"abyss_bigfin": ("Bigfin squid drifting in black water (NOAA Ocean Exploration)", PD,
"NOAA OER Windows to the Deep 2021 dive10-bigfin-squid; trim 3476s, crossfade-loop."),
"abyss_dandelion": ("Dandelion siphonophore — glowing orb + threads (NOAA Ocean Exploration)", PD,
"NOAA OER 2021 North Atlantic Stepping Stones dive13-dandelion; trim 1270s, crossfade-loop."),
"abyss_octopus": ("Deep-sea octopus (Graneledone) crawling (NOAA Ocean Exploration)", PD,
"NOAA OER 2023 EXPRESS West Coast; Wikimedia Graneledone_boreopacifica_close-up; trim 660s, crossfade-loop."),
"abyss_seapig": ("Swimming sea cucumber (Enypniastes), CC0 (NOAA)", "CC0 / public domain",
"NOAA Okeanos Gulf of Mexico 2014 Ex1402-dive06; trim 044s, crossfade-loop."),
}
# --- Affect vocabulary per SCALE (shared by all the scale's pool members) -------
@@ -131,6 +207,12 @@ AFFECT: dict[str, list[tuple]] = {
("feel.unity", [0.66, 0.58], 3, ["one", "unity", "belonging", "a borderless belonging"]),
("feel.distance", [0.42, 0.82], 4, ["far", "distance", "remoteness", "an exquisite remoteness"]),
],
"sky": [
("feel.exhilaration", [0.50, 0.44], 1, ["whee", "exhilaration", "elation", "a soaring elation"]),
("feel.freedom", [0.22, 0.68], 2, ["free", "freedom", "release", "a boundless release"]),
("feel.lightness", [0.66, 0.58], 3, ["light", "lightness", "buoyancy", "a weightless buoyancy"]),
("feel.vertigo", [0.42, 0.82], 4, ["whoa", "vertigo", "giddiness", "a giddy, falling vertigo"]),
],
"coast": [
("feel.ease", [0.50, 0.44], 1, ["nice", "ease", "calm", "an unhurried calm"]),
("feel.nostalgia", [0.22, 0.68], 2, ["miss", "nostalgia", "wistfulness", "a salt-air wistfulness"]),
+7 -7
View File
@@ -72,22 +72,22 @@ test("a new volume fade supersedes a stale one (audio not silenced after landing
expect(vol).toBeGreaterThan(0.7); // ended near 0.8, not dragged to 0
});
test("audio is a 0-10 level dial, defaulting to 0", async ({ page }) => {
test("audio is a 0-10 level dial", async ({ page }) => {
await boot(page);
const ctl = await page.evaluate(() => {
const c = document.querySelector("#audio") as HTMLInputElement;
return { type: c.type, min: c.min, max: c.max, value: c.value };
return { type: c.type, min: c.min, max: c.max };
});
expect(ctl.type).toBe("range");
expect(ctl.min).toBe("0");
expect(ctl.max).toBe("10");
expect(ctl.value).toBe("0"); // silent until raised
});
test("turning video on for the first time lifts audio to 3/10", async ({ page }) => {
test("the experience auto-starts once loaded: video on + audio at 3/10", async ({ page }) => {
await boot(page);
expect(await page.evaluate(() => (document.querySelector("#audio") as HTMLInputElement).value)).toBe("0");
await enableVideo(page); // first video-on couples audio at a gentle level
// No gesture needed — autoStart() flips Video on and lifts audio to a gentle level
// the moment the preload finishes.
await expect.poll(() => page.evaluate(() => (document.querySelector("#visual") as HTMLInputElement).checked)).toBe(true);
await expect.poll(() => page.evaluate(() => (document.querySelector("#audio") as HTMLInputElement).value)).toBe("3");
// The level label reflects it.
expect(await page.evaluate(() => document.querySelector("#audio-level-val")?.textContent)).toBe("3");
@@ -134,7 +134,7 @@ test("auto-scrub speed is constant per altitude (a 2-step jump takes ~2x a 1-ste
return (await page.evaluate(() => performance.now())) - t0;
}
const t1 = await timeJump(60, "orbit"); // cosmos -> orbit (1 altitude)
const t2 = await timeJump(120, "reef"); // orbit -> reef (2 altitudes)
const t2 = await timeJump(120, "coast"); // orbit -> coast (2 altitudes; ring is cosmos·orbit·sky·coast·reef·abyss)
const ratio = t2 / t1;
expect(ratio).toBeGreaterThan(1.5); // proportional to distance, not a fixed total
expect(ratio).toBeLessThan(2.7); // ~2x, not more
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More