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:
@@ -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 16–40s, crossfade-loop"),
|
||||
"abyss_hiding": ("“Hiding in the Dark” (NOAA Ocean Exploration)", PD,
|
||||
"NOAA Ocean Exploration dark-1280x720-1; trim 20–44s, 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 40–95s, crossfade-loop. Text-free."),
|
||||
"cosmos_tarantula": ("Tarantula Nebula (30 Doradus) zoom (NASA/ESA/CSA/STScI)", CCBY_WEBB,
|
||||
"ESA/Webb weic2212b; 1080p, trim 1–59s, crossfade-loop."),
|
||||
"cosmos_westerlund": ("Flight through star cluster Westerlund 2 (ESA/Hubble)", "CC-BY 4.0 — credit ESA/Hubble",
|
||||
"ESA/Hubble heic1509f; 1080p, trim 5–59s, crossfade-loop."),
|
||||
"cosmos_southernring": ("Southern Ring Nebula zoom (NASA/ESA/CSA/STScI)", CCBY_WEBB,
|
||||
"ESA/Webb weic2207a; 1080p, trim 1–59s, crossfade-loop."),
|
||||
"cosmos_carina_eso": ("Carina Nebula zoom (ESO)", "CC-BY 4.0 — credit ESO",
|
||||
"ESO eso1828c; 1080p, trim 0–48s, crossfade-loop."),
|
||||
# orbit
|
||||
"orbit_aurora2025": ("ISS aurora — Nov 2025 geomagnetic storm (NASA JSC)", PD,
|
||||
"NASA JSC ESRS ISS_20251112_071350; 1080p, trim 5–60s, crossfade-loop."),
|
||||
"orbit_citylights": ("ISS night pass — West Coast city lights (NASA JSC)", PD,
|
||||
"NASA SVS 30180 iss028_nighttime_20110819; 720p, trim 0–54s, crossfade-loop."),
|
||||
"orbit_helene": ("Hurricane Helene from the ISS — storm + atmospheric limb (NASA JSC)", PD,
|
||||
"NASA JSC jsc2024m000168; trim 5–59s, crossfade-loop."),
|
||||
"orbit_epic": ("DSCOVR/EPIC full-disk Earth time-lapse (NASA GSFC)", PD,
|
||||
"NASA SVS 12118 EPIC_timelapse; trim 0–60s, crossfade-loop."),
|
||||
"orbit_illuminated": ("“Earth Illuminated” — aurora + Milky Way over the limb (NASA JSC)", "CC0 / public domain",
|
||||
"NASA JSC Earth_Illuminated_120808; trim 68–113s, 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 0–24s, 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 20–60s, 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 10–50s, 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 6–46s, crossfade-loop."),
|
||||
"sky_coast_cliffspain": ("Dramatic Atlantic sea cliffs, Northern Spain — aerial", "Pexels License",
|
||||
"Pexels 36668415; 4K source, trim 1–25s, crossfade-loop."),
|
||||
"sky_mtn_castlecrags": ("Castle Crags — gliding past jagged granite spires (US Forest Service)", PD,
|
||||
"USFS/DVIDS 998726 DOD_111563303; trim 120–174s, crossfade-loop."),
|
||||
"sky_mtn_rocky": ("Rocky Mountain NP — aerial over alpine summits & tundra (NPS)", PD_NPS,
|
||||
"NPS Rocky Mountain b-roll 188046ff; trim 60–110s, crossfade-loop."),
|
||||
# coast
|
||||
"coast_kelp": ("Giant kelp forest, underwater", "Pexels License",
|
||||
"Pexels 30285973; 4K source, trim 0–46s, crossfade-loop."),
|
||||
"coast_otters": ("Wild sea otters, Elkhorn Slough CA (USFWS)", PD,
|
||||
"USFWS Pacific Southwest b-roll (Wikimedia 28833083527); trim 0–48s, 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 0–46s, crossfade-loop."),
|
||||
"coast_seals": ("Harbor seals hauled out on a rock", "Pexels License",
|
||||
"Pexels 6497564; 4K source, trim 8–54s, crossfade-loop."),
|
||||
"coast_mist": ("Sea mist over a rocky coastline", "Pexels License",
|
||||
"Pexels 31036891; 4K source, trim 3–57s, 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 62–86s, crossfade-loop."),
|
||||
"reef_flowergarden": ("Sunlit reef + sergeant majors + jack/scad schools (NOAA Fisheries)", PD,
|
||||
"NOAA Fisheries b-roll VIDEO_ID 1553921798001; trim 8–68s, 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 34–76s, crossfade-loop."),
|
||||
"abyss_dandelion": ("Dandelion siphonophore — glowing orb + threads (NOAA Ocean Exploration)", PD,
|
||||
"NOAA OER 2021 North Atlantic Stepping Stones dive13-dandelion; trim 12–70s, 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 6–60s, crossfade-loop."),
|
||||
"abyss_seapig": ("Swimming sea cucumber (Enypniastes), CC0 (NOAA)", "CC0 / public domain",
|
||||
"NOAA Okeanos Gulf of Mexico 2014 Ex1402-dive06; trim 0–44s, 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"]),
|
||||
|
||||
@@ -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.
+4486
-768
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.
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.
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.
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.
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.
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
Reference in New Issue
Block a user