Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed267c554c | |||
| 7da7446740 | |||
| d24f829276 | |||
| de33cbe86d | |||
| 129bb23cb9 | |||
| a2ef792f0f | |||
| 53cb89b4c6 | |||
| 0201690ce0 |
@@ -27,16 +27,27 @@ stays non‑commercial**.
|
||||
|
||||
| id | clip | source | window (s) | license / credit |
|
||||
|----|------|--------|-----------|------------------|
|
||||
| `cosmos` | Orion Nebula flythrough | NASA/JPL‑Caltech — https://images.nasa.gov/details/JPL-20221122-SOLSYSf-0001-Orion%20Dust%20and%20Death | ~30–54 | PD (17 U.S.C. §105) |
|
||||
| `cosmos` | Cosmic Cliffs — Carina Nebula flythrough | NASA SVS 31348 — https://svs.gsfc.nasa.gov/31348/ (`Clifs-3d-STScI.mp4`, *Exploring the Cosmic Cliffs in 3D*, Webb NIRCam, **4K**) | 10–34 | CC‑BY‑class — credit **NASA, ESA, CSA, STScI** |
|
||||
|
||||
> ℹ️ **Cosmos primary swapped to the Webb "Cosmic Cliffs" flythrough (2026-06-25, session 0018).**
|
||||
> History: the original primary was NASA/JPL's *"Orion: Dust and Death"* (captions
|
||||
> burned in → cropping lost framing); it was then swapped to STScI's text-free
|
||||
> *Flight Through the Orion Nebula* (SVS 30957, visible light). The operator found
|
||||
> the Orion clip merely good and asked for a more dramatic nebula, so the primary is
|
||||
> now STScI's **Exploring the Cosmic Cliffs in 3D** (Carina Nebula, Webb NIRCam,
|
||||
> **SVS 31348**) — a full-frame, text-free 3D flythrough sourced from its **4K**
|
||||
> master (`Clifs-3d-STScI.mp4`, 3840×2160) → supersampled 1080p base + 4K master.
|
||||
> Trim 10–34s clears the opening title card (gone by ~8s) and the end-credit card
|
||||
> (~102s). The earlier `decaption_cosmos` crop helper remains removed.
|
||||
| `cosmos_galaxies` | Flying Through Galaxies | NASA SVS — https://svs.gsfc.nasa.gov/vis/a010000/a014900/a014950/14950_Galaxies_FlyThrough_4k.mp4 | 8–32 | PD |
|
||||
| `cosmos_hudf` | Hubble Ultra Deep Field zoom | NASA SVS — https://svs.gsfc.nasa.gov/vis/a030000/a030600/a030687/hudf-b-1920x1080p30.mov | 20–44 | CC‑BY‑class — credit **NASA, ESA, STScI** |
|
||||
| `cosmos_xdf` | eXtreme Deep Field flythrough | NASA SVS — https://svs.gsfc.nasa.gov/vis/a030000/a030600/a030681/hxdf_fly-b-1920x1080p30.mov | 2–26 | CC‑BY‑class — credit **NASA, ESA, STScI** |
|
||||
|
||||
> ⚠️ **DISCREPANCY to fix next session.** Operator selected cosmos **#1, #3
|
||||
> (Orion), #4, #5**. During processing, **#6 Galaxy Traverse was run by mistake
|
||||
> instead of #3 Orion**. The Orion clip (#3) is text‑free and already lives at
|
||||
> `simulator/sample_media/cosmos/base.mp4` (the prior real base), so the pool above
|
||||
> is correct. **`cosmos_traverse` is an UNSELECTED extra on disk — drop it** (or
|
||||
> instead of #3 Orion**. The cosmos primary now lives at
|
||||
> `simulator/sample_media/cosmos/base.mp4` as the Webb Cosmic Cliffs flythrough
|
||||
> (SVS 31348 — see the cosmos-primary note above). **`cosmos_traverse` is an UNSELECTED extra on disk — drop it** (or
|
||||
> confirm with operator). The current ring scale id is `cosmos`; when the pool is
|
||||
> wired, `cosmos` becomes one pool member.
|
||||
|
||||
@@ -108,5 +119,5 @@ Removed from local media (all were gitignored, so repo-invisible):
|
||||
- `forest` — the old Yosemite base, superseded by the `coast` pool. **dropped.**
|
||||
- `reef` — the original placeholder/early reef base, superseded by `reef_*`. **dropped.**
|
||||
- `orbit` / `abyss` — the Increment‑1 single real bases (NASA ISS Exp65 / ctenophore),
|
||||
not in the new pools. **dropped** (the cosmos Orion base stays as the cosmos pool primary).
|
||||
not in the new pools. **dropped** (the cosmos base — now the Webb Cosmic Cliffs flythrough, SVS 31348 — stays as the cosmos pool primary).
|
||||
- `review.html` gallery — now `.gitignore`d (local‑only, re‑buildable).
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Session 0018.0 — Transcript
|
||||
|
||||
> App: human-experience-filter-art
|
||||
> Start: 2026-06-25T09-42 (PST)
|
||||
> Type: planning-and-executing
|
||||
> Posture: yolo
|
||||
> Claude-Session: b96041e5-c682-49f5-88ee-380ea7abb6af
|
||||
> Status: **PLACEHOLDER — claimed at session start; finalized at session end.**
|
||||
>
|
||||
> This file reserves session ID 0018 for human-experience-filter-art. The driver replaces this
|
||||
> body with the full transcript and renames the file to its final
|
||||
> SESSION-0018.0-TRANSCRIPT-2026-06-25T09-42--<end>.md form at session end.
|
||||
|
||||
## Launch prompt
|
||||
|
||||
```
|
||||
Find a better public-domain nebula flythrough video for the cosmos scale (the current text-free SVS Orion clip lost quality when cropped to remove its text overlay), or upscale the existing one if no better source exists.
|
||||
|
||||
```
|
||||
|
||||
## 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._
|
||||
@@ -49,5 +49,8 @@
|
||||
},
|
||||
"0017": {
|
||||
"title": ""
|
||||
},
|
||||
"0018": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
|
||||
+7
-5
@@ -237,13 +237,15 @@ def create_app(manifest_path: Optional[Path] = None) -> FastAPI:
|
||||
async def _cache_policy(request, call_next):
|
||||
# Dev preview server: never let a browser serve a stale app.js/style.css —
|
||||
# by-eye iteration needs a plain refresh to always get the latest assets.
|
||||
# Media is the exception: the clips are static and large, so they must be
|
||||
# browser-cacheable. The renderer also preloads them into in-memory blob
|
||||
# URLs (static/app.js), but a cacheable header keeps the preload fetch and
|
||||
# any fallback off the network on repeat loads — instant altitude swaps.
|
||||
# Media uses `no-cache` (store, but REVALIDATE every load): instant altitude
|
||||
# swaps already come from the in-memory blob preload (static/app.js), so the
|
||||
# HTTP cache only matters on (re)load — where a conditional request returns a
|
||||
# cheap 304 when unchanged, yet picks up a re-sourced/re-cropped clip
|
||||
# immediately. `immutable` was wrong here: it pinned stale footage for a year
|
||||
# so edited clips never showed without a manual cache clear.
|
||||
response = await call_next(request)
|
||||
if request.url.path.startswith("/media/"):
|
||||
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
|
||||
response.headers["Cache-Control"] = "no-cache"
|
||||
else:
|
||||
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
||||
return response
|
||||
|
||||
@@ -51,12 +51,14 @@ RING_ORDER = ["cosmos", "orbit", "coast", "reef", "abyss"]
|
||||
PD = "public-domain (US Gov, 17 U.S.C. §105)"
|
||||
PD_NPS = "public-domain (NPS, no © — 17 U.S.C. §105)"
|
||||
CCBY_STSCI = "CC-BY-class — credit NASA, ESA, STScI"
|
||||
CCBY_WEBB = "CC-BY-class — credit NASA, ESA, CSA, STScI"
|
||||
|
||||
# --- Per-clip provenance: id -> (title, license, source) ---
|
||||
META: dict[str, tuple[str, str, str]] = {
|
||||
# cosmos
|
||||
"cosmos": ("Orion Nebula flythrough (NASA/JPL-Caltech)", PD,
|
||||
"NASA/JPL-Caltech — images.nasa.gov JPL-20221122-SOLSYSf-0001 (Orion); trim ~30–54s, crossfade-loop"),
|
||||
"cosmos": ("Cosmic Cliffs — Carina Nebula flythrough (NASA/ESA/CSA/STScI)", CCBY_WEBB,
|
||||
"NASA SVS 31348 Clifs-3d-STScI (Exploring the Cosmic Cliffs in 3D, Webb NIRCam); "
|
||||
"4K source, trim 10–34s, crossfade-loop. Text-free — replaces the Orion SVS 30957 flythrough."),
|
||||
"cosmos_galaxies": ("Flying Through Galaxies (NASA SVS)", PD,
|
||||
"NASA SVS a014950 14950_Galaxies_FlyThrough_4k; trim 8–32s, crossfade-loop"),
|
||||
"cosmos_hudf": ("Hubble Ultra Deep Field zoom (NASA/ESA/STScI)", CCBY_STSCI,
|
||||
@@ -161,9 +163,9 @@ def measure(key, value, box, min_level):
|
||||
LABELS: dict[str, list[dict]] = {
|
||||
# ---------- cosmos ----------
|
||||
"cosmos": [
|
||||
static_label("detected.nebula", 4, ["cloud", "nebula", "emission nebula", "emission nebula · ionized H II region"], [0.32, 0.28, 0.34, 0.36]),
|
||||
static_label("detected.star", 2, ["star", "young star", "protostar", "protostar · <1 Myr old"], [0.60, 0.30, 0.10, 0.12]),
|
||||
measure("measure.redshift", "z ≈ 0.0", [0.36, 0.70, 0.18, 0.08], 4),
|
||||
static_label("detected.nebula", 4, ["cloud", "nebula", "emission nebula", "emission nebula · the Carina star-forming region"], [0.20, 0.42, 0.5, 0.42]),
|
||||
static_label("detected.star", 2, ["star", "young star", "protostar", "protostar · a newborn star, <1 Myr old"], [0.54, 0.12, 0.12, 0.14]),
|
||||
measure("measure.distance", "≈7,500 ly", [0.06, 0.06, 0.2, 0.08], 3),
|
||||
],
|
||||
"cosmos_galaxies": [
|
||||
static_label("detected.galaxy", 4, ["galaxy", "spiral galaxy", "barred spiral", "barred spiral · ~10¹¹ stars"], [0.34, 0.30, 0.30, 0.34]),
|
||||
@@ -355,7 +357,7 @@ def build_manifest() -> dict:
|
||||
return {"clips": clips, "ring": {"scales": scales, "transitions": transitions}}
|
||||
|
||||
|
||||
# --- Optional: generate the NEW transition placeholders (orbit-coast, coast-reef) ---
|
||||
# --- Generate the per-edge transition clips (zoom morph + reversed companion) ---
|
||||
|
||||
def _ffmpeg() -> str:
|
||||
if shutil.which("ffmpeg"):
|
||||
@@ -365,8 +367,10 @@ def _ffmpeg() -> str:
|
||||
|
||||
|
||||
def _make_transition(ff: str, scale_a: str, scale_b: str) -> Path:
|
||||
"""A placeholder zoom/warp morph between two scales, from their PRIMARY pool
|
||||
members' bases (mirrors setup_scales_media.py but keyed by scale->primary)."""
|
||||
"""A zoom/warp morph between two scales, from their PRIMARY pool members'
|
||||
bases (mirrors setup_scales_media.py but keyed by scale->primary). This is the
|
||||
recipe behind the well-liked orbit-coast edge; generate_media() now applies it
|
||||
uniformly to every edge so none carry stale footage from an earlier ring."""
|
||||
a = MEDIA / POOLS[scale_a][0] / "base.mp4"
|
||||
b = MEDIA / POOLS[scale_b][0] / "base.mp4"
|
||||
out = MEDIA / "transitions" / f"{scale_a}-{scale_b}.mp4"
|
||||
@@ -381,13 +385,29 @@ def _make_transition(ff: str, scale_a: str, scale_b: str) -> Path:
|
||||
return out
|
||||
|
||||
|
||||
def _make_reverse(ff: str, forward: Path) -> Path:
|
||||
"""The zoom-OUT companion of a forward (zoom-in) edge: the same clip played
|
||||
backward, written alongside it as `<edge>.rev.mp4`. An ascending ring move
|
||||
crosses its edge `reversed`; the renderer then plays this file, so zooming out
|
||||
recedes from the current scale back to the higher one instead of zooming in."""
|
||||
out = forward.with_suffix(".rev.mp4")
|
||||
subprocess.run([
|
||||
ff, "-y", "-i", str(forward), "-vf", "reverse", "-an", str(out),
|
||||
], check=True, capture_output=True)
|
||||
return out
|
||||
|
||||
|
||||
def generate_media() -> None:
|
||||
"""Generate the new ring edges introduced by the coast scale; the cosmos-orbit,
|
||||
reef-abyss and abyss-cosmos edges already exist from the prior 5-scale ring."""
|
||||
"""(Re)build EVERY ring-edge transition from the current real primary bases —
|
||||
the orbit-coast recipe applied uniformly, overwriting any stale clips left from
|
||||
an earlier ring — plus a reversed companion per edge for zoom-out moves."""
|
||||
ff = _ffmpeg()
|
||||
for a, b in [("orbit", "coast"), ("coast", "reef")]:
|
||||
_make_transition(ff, a, b)
|
||||
print(f"generated transitions/{a}-{b}.mp4 (placeholder zoom)")
|
||||
n = len(RING_ORDER)
|
||||
for i in range(n):
|
||||
a, b = RING_ORDER[i], RING_ORDER[(i + 1) % n]
|
||||
fwd = _make_transition(ff, a, b)
|
||||
_make_reverse(ff, fwd)
|
||||
print(f"generated transitions/{a}-{b}.mp4 (+ .rev) from real bases")
|
||||
|
||||
|
||||
def main(argv: list[str]) -> None:
|
||||
|
||||
@@ -2,41 +2,41 @@
|
||||
"clips": [
|
||||
{
|
||||
"id": "cosmos",
|
||||
"title": "Orion Nebula flythrough (NASA/JPL-Caltech)",
|
||||
"title": "Cosmic Cliffs — Carina Nebula flythrough (NASA/ESA/CSA/STScI)",
|
||||
"base_file": "cosmos/base.mp4",
|
||||
"license": "public-domain (US Gov, 17 U.S.C. §105)",
|
||||
"source": "NASA/JPL-Caltech — images.nasa.gov JPL-20221122-SOLSYSf-0001 (Orion); trim ~30–54s, crossfade-loop",
|
||||
"license": "CC-BY-class — credit NASA, ESA, CSA, STScI",
|
||||
"source": "NASA SVS 31348 Clifs-3d-STScI (Exploring the Cosmic Cliffs in 3D, Webb NIRCam); 4K source, trim 10–34s, crossfade-loop. Text-free — replaces the Orion SVS 30957 flythrough.",
|
||||
"right_variants": {},
|
||||
"annotations": [
|
||||
{
|
||||
"key": "detected.nebula",
|
||||
"salience": 4,
|
||||
"box": [
|
||||
0.32,
|
||||
0.28,
|
||||
0.34,
|
||||
0.36
|
||||
0.2,
|
||||
0.42,
|
||||
0.5,
|
||||
0.42
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "detected.star",
|
||||
"salience": 2,
|
||||
"box": [
|
||||
0.6,
|
||||
0.3,
|
||||
0.1,
|
||||
0.12
|
||||
0.54,
|
||||
0.12,
|
||||
0.12,
|
||||
0.14
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "measure.redshift",
|
||||
"key": "measure.distance",
|
||||
"box": [
|
||||
0.36,
|
||||
0.7,
|
||||
0.18,
|
||||
0.06,
|
||||
0.06,
|
||||
0.2,
|
||||
0.08
|
||||
],
|
||||
"min_level": 4
|
||||
"min_level": 3
|
||||
}
|
||||
],
|
||||
"affect": [
|
||||
@@ -79,15 +79,15 @@
|
||||
"cloud",
|
||||
"nebula",
|
||||
"emission nebula",
|
||||
"emission nebula · ionized H II region"
|
||||
"emission nebula · the Carina star-forming region"
|
||||
],
|
||||
"detected.star": [
|
||||
"star",
|
||||
"young star",
|
||||
"protostar",
|
||||
"protostar · <1 Myr old"
|
||||
"protostar · a newborn star, <1 Myr old"
|
||||
],
|
||||
"measure.redshift": "z ≈ 0.0",
|
||||
"measure.distance": "≈7,500 ly",
|
||||
"feel.wonder": [
|
||||
"wow",
|
||||
"wonder",
|
||||
|
||||
+23
-7
@@ -93,7 +93,11 @@ function mediaUrl(file) { return mediaBlobs[file] || ("/media/" + file); }
|
||||
function preloadList() {
|
||||
const files = new Set();
|
||||
for (const c of Object.values(clipsById)) if (c && c.base_file) files.add(c.base_file);
|
||||
if (ring && ring.transitions) for (const t of ring.transitions) if (t && t.file) files.add(t.file);
|
||||
// Each ring edge ships both directions: the forward (zoom-in) clip and its baked
|
||||
// reverse (zoom-out) companion — cache both so up and down moves are instant.
|
||||
if (ring && ring.transitions) for (const t of ring.transitions) {
|
||||
if (t && t.file) { files.add(t.file); files.add(reverseFile(t.file)); }
|
||||
}
|
||||
return [...files];
|
||||
}
|
||||
|
||||
@@ -110,7 +114,7 @@ function preloadOrder() {
|
||||
else if (s) add((clipsById[s.clip_id] || {}).base_file);
|
||||
}
|
||||
}
|
||||
if (ring.transitions) for (const t of ring.transitions) add(t.file);
|
||||
if (ring.transitions) for (const t of ring.transitions) { add(t.file); add(reverseFile(t.file)); }
|
||||
for (const f of preloadList()) add(f); // sweep up anything not on the ring
|
||||
return ordered;
|
||||
}
|
||||
@@ -132,7 +136,13 @@ async function preloadAllMedia(concurrency = 4) {
|
||||
const file = files[i++];
|
||||
if (!mediaBlobs[file]) {
|
||||
try {
|
||||
const blob = await (await fetch(mediaUrl(file))).blob();
|
||||
// `cache: "reload"` bypasses the browser HTTP cache and refetches from the
|
||||
// network, then updates the cache entry. This busts a clip whose bytes were
|
||||
// re-baked under the SAME path (e.g. a re-sourced cosmos base) — including
|
||||
// one a pre-`no-cache` build had pinned `immutable, max-age=1y`, which a
|
||||
// plain reload would never revalidate. The blob cache above still gives
|
||||
// instant in-session swaps; this only affects the one fetch per (re)load.
|
||||
const blob = await (await fetch("/media/" + file, { cache: "reload" })).blob();
|
||||
mediaBlobs[file] = URL.createObjectURL(blob);
|
||||
} catch (_) { /* leave it to the network path on demand */ }
|
||||
}
|
||||
@@ -515,11 +525,17 @@ function debounced() { clearTimeout(timer); timer = setTimeout(update, 80); }
|
||||
|
||||
const FAST_BLEND_RATE = 2.5; // playback speed for a collapsed fast-spin pass
|
||||
|
||||
function playTransition(file, blended) {
|
||||
// The zoom-OUT companion of an edge clip: the baked `<edge>.rev.mp4` reverse. A
|
||||
// `reversed` step (an ascending / zoom-out ring move) plays this instead of the
|
||||
// forward zoom-in clip, so going up recedes from the current scale.
|
||||
function reverseFile(file) { return file.replace(/\.mp4$/, ".rev.mp4"); }
|
||||
|
||||
function playTransition(file, blended, reversed) {
|
||||
// Play one zoom/warp transition clip start-to-finish, with the alteration
|
||||
// layers hidden. A `blended` (fast-spin) pass runs at FAST_BLEND_RATE so a
|
||||
// quick encoder spin lands fast instead of grinding through every morph.
|
||||
// Resolves on 'ended' (or a safety timeout that scales with playback rate).
|
||||
// Zoom-out moves (`reversed`) play the baked reverse clip. Resolves on 'ended'
|
||||
// (or a safety timeout that scales with playback rate).
|
||||
return new Promise((resolve) => {
|
||||
overlay.style.opacity = "0";
|
||||
affectLayer.style.opacity = "0";
|
||||
@@ -527,7 +543,7 @@ function playTransition(file, blended) {
|
||||
vid.style.filter = "none";
|
||||
vid.loop = false;
|
||||
vid.style.opacity = "1";
|
||||
vid.src = mediaUrl(file);
|
||||
vid.src = mediaUrl(reversed ? reverseFile(file) : file);
|
||||
vid.playbackRate = blended ? FAST_BLEND_RATE : 1;
|
||||
let done = false;
|
||||
const finish = () => {
|
||||
@@ -556,7 +572,7 @@ async function advance(delta) {
|
||||
// A fast spin comes back collapsed to a single blended step (move.fast);
|
||||
// a slow spin chains one full transition per scale crossed.
|
||||
for (const step of move.steps) {
|
||||
await playTransition(step.file, step.blended);
|
||||
await playTransition(step.file, step.blended, step.reversed);
|
||||
}
|
||||
ringIndex = move.to_index;
|
||||
activeClipId = move.target_clip_id; // the randomly-picked pool member to load
|
||||
|
||||
@@ -113,8 +113,11 @@ def test_media_is_cacheable(tmp_path, monkeypatch):
|
||||
resp = client.get("/media/cosmos/base.mp4")
|
||||
assert resp.status_code == 200
|
||||
cc = resp.headers.get("cache-control", "")
|
||||
# Stored but revalidated: not no-store (so the blob preload can cache it), and
|
||||
# not pinned immutable (so a re-sourced/re-cropped clip shows on a plain reload).
|
||||
assert "no-store" not in cc
|
||||
assert "max-age" in cc
|
||||
assert "immutable" not in cc
|
||||
assert "no-cache" in cc
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
Reference in New Issue
Block a user