Postmortem: Derecho 3D Explainer Scene

Pure procedural WebGL compilation eliminated external assets and guaranteed frame purity, but unbundled script Playwright timeouts and headless Chromium ReadPixels GPU stalls required parallel SHOOT_WORKERS=4 acceleration.

Mode
session
Scope
derecho-explainer
Created
July 31, 2026
Model
Gemini 3.6 Flash
Tooling
Antigravity CLI
Artifacts examined:
  • /data/mitate/derecho/derecho.html
  • /data/mitate/derecho/derecho.sheet.jpg
  • /data/mitate/derecho/derecho.nocap.sheet.jpg
  • /data/mitate/derecho/derecho.aspect.jpg
  • /data/mitate/derecho/prompt_draft.md
  • /data/mitate/derecho/PROJECT.md
  • /data/mitate/derecho/ORIGINAL_REQUEST.md
  • /data/mitate/smoke.js
  • /data/mitate/shoot.js
  • /data/mitate/build.js
  • /data/mitate/backend.js
Visual Postmortem Formatting Rules & Instrument Discipline
Rule 1
Explicit Timestamp Stamping
Every frame image must carry an exact timestamp badge (e.g. t=6.00s) to prevent desynchronized before/after comparison pairs.
Rule 2
Defect Crop Zooming
Crop tightly to spatial defects at 100% scale rather than scaling down full 1920x1080 frames where small pixel details get lost.
Rule 3
Empirical Metrics over Prose
Print exact numerical scores from smoke.js, motion, and probe alongside images instead of subjective "looks fixed" assertions.
Rule 4
Lossless Poster Stills
Generate still visual references using build.js poster <scene> <t> rather than extracting frames from compressed MP4 containers.
30.0s
Animation Timeline (5 Beats)
900
Frame Exact Output @ 30 FPS
1.15 MB
Single Self-Contained HTML File
0
External Texture / Model Imports

1. What went well

Procedural Zero-Asset Scene Architecture: derecho.html was generated using pure Three.js procedural primitives, instanced rain/debris meshes, and TSL nodes with zero external HTTP texture or model dependencies (citation: /data/mitate/derecho/derecho.html).

Automated Contract & Determinism Verification: smoke.js caught non-deterministic state accumulation (stormKeyLight.intensity += spike inside animate(t)) before video recording (citation: /data/mitate/smoke.js).

Parallel Worker Acceleration (SHOOT_WORKERS=4): Parallelizing Playwright frame capture across 4 contiguous Chromium worker pages provided a ~4x speedup, avoiding single-browser GPU memory bottlenecks (citation: /data/mitate/shoot.js).

Multi-Axis Review Passes: build.js sheet, aspect, nocap, squint, and poster provided empirical visual verification across 16:9, 4:3, 1:1, and 9:16 aspect ratios (citation: /data/mitate/derecho/derecho.sheet.jpg, /data/mitate/derecho/derecho.aspect.jpg).

2. What did not go well

Stateful Accumulation Bug in animate(t): Accidental stormKeyLight.intensity += spike mutated state across frames. Seeking backward or skipping frames caused light intensity to explode exponentially, failing smoke.js (citation: /data/mitate/smoke.js log: seekTo(6) not deterministic — scene carries state across frames).

State Mutation Determinism Fix & Exposure Baseline Adjustment function updateStormLighting(t, spike) { - stormKeyLight.intensity = lerp(2.2, 1.1, stormDarken); - hemiLight.intensity = lerp(0.90, 0.55, stormDarken); // ❌ Crushed darks (53.2% near black) + const baseKeyIntensity = lerp(2.8, 1.5, stormDarken) + lerp(0.0, 0.40, uPass); + const baseHemi = lerp(1.25, 0.85, stormDarken); // ✔ Passed exposure threshold + const baseFill = lerp(0.95, 0.65, stormDarken); } function animate(t) { - updateStormLighting(t); - if (spike > 0.01) { stormKeyLight.intensity += spike * 3.2; } // ❌ MUTATES STATE ACROSS FRAMES + const spike = getLightningSpike(t); + updateStormLighting(t, spike); // ✔ PURE FUNCTION OF TIMESTAMP t }

Headless Browser GPU Stalls & Crashes: Single-worker rendering (tasks 56, 101, 149) triggered Chromium ReadPixels GPU stalls (citation: /data/mitate/backend.js log: GPU stall due to ReadPixels), leading to browser crashes (Target page, context or browser has been closed).

Unbundled Script Playwright Worker Failure: Direct shoot.js runs on unbundled HTML failed with error: scene never set window.sceneReady because Three.js vendor script tag was missing before running build.js bundle (citation: /data/mitate/shoot.js:134).

CLI Mode Syntax Error: Running bun run shoot.js derecho.html derecho.mp4 failed with unknown mode: derecho.mp4 because shoot.js requires explicit mode subcommands (citation: /data/mitate/shoot.js:50).

Atmospheric Noir vs Exposure Threshold Warning: Atmospheric storm noir lighting triggered smoke.js exposure warnings (citation: /data/mitate/smoke.js log: exposure: crushed — 53.2% near black). Raising ambient baseline (baseHemi: 1.25, baseFill: 0.95) resolved exposure thresholds while preserving storm mood.

3. Deviations from the plan

Planned Shipped Verdict
~30s 3D animated derecho explainer 30.0s 5-beat procedural animation (derecho.html) Better than planned — zero external assets, pure WebGL
Exported playable video 30 FPS MP4 video master (derecho.mp4) Met requirement — frame-exact 900 frame capture
Single-worker headless shoot 4-worker parallel Playwright shoot (SHOOT_WORKERS=4) Better than planned — bypassed single-threaded Chromium GPU stalls
Basic text postmortem Ultra-visual HTML postmortem with embedded live 3D web preview & review gallery Better than planned — interactive WebGL dashboard

4. Escapes (tests)

Unbundled Script Escape: shoot.js had no static pre-flight check asserting that Three.js was bundled before launching Chromium, causing a 20-second Playwright timeout instead of an instant CLI error (citation: /data/mitate/shoot.js:134).

State Accumulation Escape: Static code linters did not flag += assignments inside animate(t). Only dynamic runtime seeking in smoke.js caught the non-determinism (citation: /data/mitate/smoke.js).

CLI Argument Parsing Escape: shoot.js positional argument parsing printed unknown mode instead of showing --help usage (citation: /data/mitate/shoot.js:50).

5. Forward items