perf(sim): preload all clips into memory for instant altitude swaps

Each altitude change reset the single <video> element's src and re-fetched
the scale's footage over the network, compounded by the dev no-store policy
applying to /media too — so every scale change paid a full fetch+decode.

- simulator/app.py: split the cache middleware — app shell/JS/CSS stay
  no-store (by-eye dev iteration), but /media is now cacheable
  (public, max-age, immutable) since the clips are static.
- simulator/static/app.js: preload every ring clip (19 base + 5 transition,
  ~350 MB) into in-memory blob object URLs at startup — non-blocking, the
  first scale plays immediately while the rest stream in, ordered current
  scale outward. mediaUrl() serves the cached blob when ready, else falls
  back to the network. A small progress chip self-removes when done.
- tests: assert app assets stay no-store and /media is cacheable.

269 passed, 2 skipped. Frontend verified by-eye by the operator (no headless
browser in repo yet).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
BenStullsBets
2026-06-25 07:47:32 -07:00
parent 9a83c0132d
commit d19fe4e8fa
3 changed files with 111 additions and 6 deletions
+11 -5
View File
@@ -234,12 +234,18 @@ def create_app(manifest_path: Optional[Path] = None) -> FastAPI:
return {"ok": True, "clip": merged}
@app.middleware("http")
async def _no_cache(request, call_next):
# Dev preview server: never let a browser serve a stale app.js/style.css.
# The whole point is by-eye iteration, so a plain refresh must always get
# the latest assets (the heuristic cache otherwise hides JS/CSS edits).
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.
response = await call_next(request)
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
if request.url.path.startswith("/media/"):
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
else:
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
return response
if MEDIA_DIR.exists():