# Filmmaker and physical-motion iteration plan

Date: 2026-10-22  
Target: `laptop-ai-datacenter-demo.html`  
Scope: planning only. Create this document; leave the HTML and all other existing files unchanged.

**Recommendation:** retain the nine-scene, 60-second explanation and eight visible infrastructure journeys. Give the camera distinct jobs in each scene, repair the transitions between those jobs, then add physical behavior to the few objects that can plausibly move. Keep packets and token labels under editorial control. Add selective depth of field last, after framing, lighting, and deterministic playback work without it.

The user reports two successful blind review rounds and a successful live LAN preview. Treat that version as the acceptance baseline. This pass read all 392 lines, including construction, cloning, lighting, camera math, travel bridges, and playback handlers. It did not run a new visual review or browser performance benchmark. Code observations below are verified from the file; proposed coordinates, physical parameters, and performance limits are starting specifications to validate in the executor's browser.

Baseline SHA-256: `8ba0d5723b24f8f42baacc58183f3f877dccd5c7f72997b832e08e48050553d7`.

## 1. What is actually there

| Area | Current implementation and consequence | Source |
|---|---|---|
| Delivery | One HTML file, inline module, Three.js **0.186.1** and matching `three/addons/` through jsDelivr importmap. Only `RoundedBoxGeometry` is imported separately. No build pipeline. | [HTML lines 8–10, 89–91](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:8) |
| Rendering | `WebGLRenderer`, antialiasing, PCF soft shadows, sRGB output, ACES filmic mapping, exposure 1.12. Pixel ratio is capped at 1.25 below 700 CSS pixels and 2 otherwise. Global exponential fog is 0.012. There is no compositor, DOF, motion blur, or environment map. | [Renderer setup](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:93) |
| Camera | One `PerspectiveCamera(38, aspect, .1, 180)`. Each `beats` entry has `p0/p1`, `q0/q1`, and `arc`. `update()` smoothsteps position and aim, then adds a sinusoidal position arc. This already creates authored moves, but repeats the same movement grammar. Portrait changes distance and FOV; it is not just a desktop crop. | [Beat definitions](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:234), [camera evaluation](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:369) |
| Geography | Seven original sets plus `pacificReturn`, `homeReturn`, and `streetReturn` occupy offsets from z=0 to z=198. `globe` is a flat Pacific-centered map board, despite its name. `streetReturn` is built but is not a beat and is not selected by the current bridge visibility rule. | [Map](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:156), [clones and offsets](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:245) |
| Travel | `makeTravelBridge()` builds cables, brackets, relay boxes, contextual infrastructure, packet, halo, and trail around a Catmull–Rom path. `updateTravel()` takes over for the last 1.25 seconds of each outgoing beat. Camera and packet sample the same route with different progress offsets. | [Bridge builder](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:274), [travel update](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:338) |
| Lighting | Global hemisphere, shadow-casting `key`, and finite-range `blueLight`/`goldLight`; scene-local `roomGlow`, `rovLamp`, `oceanFill`, `landingLight`, `aisleFill`, `rackFillWarm`, `rackFillCool`, and model lights. Global colored points remain near the origin when sets move far down z. `key` has no authored target/frustum following the sets. | [Global lighting](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:97), [local ocean lighting](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:195), [rack lighting](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:223) |
| Materials | Most hardware uses `MeshStandardMaterial`; routes use emissive or unlit materials. `M` contains shared materials. `rackLights` changes shared `M.cyan`/`M.blue` emissive intensity, so many objects inherit the last LED write. This can persist into later scenes or backward seeks. | [Material helpers](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:101), [rack LEDs](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:222), [LED update](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:377) |
| Motion | `rov` has sinusoidal yaw only. Wires, sea lines, cup, laptop, lamp, rocks, and racks are static. `fans` contains both rack and GPU cylinders but is updated only in the Data center branch. Packets/tokens follow formulas; there is no rigid-body solver or particle simulation. `starGeo` is static randomly initialized map scenery. | [ROV](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:195), [fans and tokens](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:221), [animation](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:372) |
| Playback | `elapsed` drives `update(t)`, pointer seeking, keyboard seeking, pause/play, replay, and end stop. Hidden tabs pause. These are essential constraints on any physics or camera damping. Existing scene branches leave some state dependent on the last visited scene. | [Playback and handlers](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:364) |

### Specific issues to resolve before adding polish

**Travel endpoints do not equal rendered shot endpoints.** `makeTravelBridge()` starts at `previousBeat.p1`, but travel starts before the outgoing shot reaches `u=1`. For example, just before t=5.75 the unadjusted home camera is approximately `(2.602, 2.842, 6.519)`, whereas the first bridge starts at `(1.8, 2.8, 6)` and then adds y=0.8. Other bridges add lateral offsets up to 4.2. At arrival those offsets remain, then disappear when the next beat starts. FOV changes from the shot's 38/40/46 degrees to the bridge's 42–46 degrees without endpoint matching. These are mathematical discontinuities, not a new visual-review verdict. [Sources: bridge start/end, lines 277–291; camera assignment, lines 353–371](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:277).

**Camera position currently determines packet destination.** Bridge `end` is the next shot's camera position, not the equipment handoff point. `pulseAt` clamps to 1 near arrival, so the packet stops there while the camera catches up. Trail positions use a different offset from the packet. Decouple the infrastructure route, packet route, and camera rail while preserving the recognizable `anchors` and handoff hardware. [Source](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:353).

**Some return and secondary motion is history-sensitive.** `globeReturnDots` is created but never animated. `homeReturn` shares the chat texture but has its own cloned light; only original `roomGlow` is animated. GPU fan transforms depend on whether the Data center branch ran first. The travel halo copies `camera.quaternion` before the current travel `lookAt()`. These are small, relevant corrections to make before judging the new camera. [Clones](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:246), [halo](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:359), [updates](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:372).

## 2. Filmmaker's lens treatment

### Editorial intent and lens convention

Direct the film as **human scale → infrastructure scale → microscopic explanation → human scale**. Home gets patient intimacy; street gets lateral parallax; the map gets a steady explanatory hold; subsea gets a precise inspection; the data center gets disciplined axial movement; model internals get readable choreography; home returns to a settled composition. Motion should lead attention toward a new fact, then let that fact be read.

Use focal length to document intent and vertical FOV to implement it. Set `camera.filmGauge=36` if using `setFocalLength()`. With a 36 mm horizontal gate and a 16:9 image, the effective gate height is 20.25 mm:

| Focal length | Vertical FOV at 16:9 | Intended use |
|---|---:|---|
| 24 mm | 45.75° | At most a brief infrastructure transit |
| 28 mm | 39.76° | Street and full map |
| 35 mm | 32.27° | Home, shore, aisle, broad model view |
| 40 mm | 28.41° | Home return and moderate subject isolation |
| 50 mm | 22.90° | Cable inspection or selected rack detail |
| 65 mm | 17.71° | Optional tight cable insert after framing validation |

These are horizontal full-frame-width equivalents cropped to 16:9, not a claim that 38° equals 38 mm. The current 38° vertical FOV is about 28.6 mm using Three's default 35 mm gauge at 16:9. `PerspectiveCamera.setFocalLength()` uses the current film height and updates the projection matrix. Portrait needs its own composition; keeping the same focal length changes the gate behavior. [Verified pinned camera source](https://cdn.jsdelivr.net/npm/three@0.186.1/src/cameras/PerspectiveCamera.js).

### Shot-by-shot proposal

All coordinates below are **set-local**, before `worldOffsets`. These are camera blocking seeds, not measurements from a rendered preview. Keep the beat boundaries and transit windows unchanged. A subject-fit check may move the camera backward before narrowing the lens. Do not blindly replace a wide FOV with a long lens at the old distance.

| Time available for the main shot | Lens / camera blocking | Composition, focus, and direction |
|---|---|---|
| **0–5.75: `home`** | 35 mm. Begin near current `(5,3.3,9)`; move toward `(2.6,2.8,6.2)`. Replace the lateral sine arc with one shallow diagonal dolly. Hold the first 0.5 seconds, then move, settling by 4.8. | Keep the warm room, window, table, and laptop recognizable. Favor the laptop toward center-right below the headline. The screen center is approximately `(-.45,1.98,-.705)` in home coordinates, derived from the `laptop` offset and screen mesh. Focus on it by 1.2 seconds and keep the prompt readable through 4.8. Let aim ease toward `homeRouter` only as the departure begins; never sacrifice the prompt to an early router pan. |
| **7–12.75: `street`** | 28 mm. Start around `(.6,3.3,16)`, end `(-1.3,2.1,6.2)`; aim moves from `(-1.5,1.5,1)` to the cabinet at `(-4.1,1.05,1.1)`. | Lower the current high approach and shorten the long rush down the street. Use the vending machine on camera-right and utility poles on camera-left for foreground parallax. Finish with cabinet, path, and `streetLabel` together. Keep enough depth of field to explain where the packet is going. |
| **14–20.75: `globeSet`** | 28 mm; approximately `(0,1.8,14.5)` to `(.7,1.9,14)`, aimed near `(0,.4,-.8)`. | Keep the whole Pacific map and both endpoint labels visible. Let `globeDots` carry attention from Japan toward the West Coast; reduce or remove `globe.rotation.y` oscillation for this shot. No DOF and no orbit around the flat board. Hold the geographic explanation before travel begins. |
| **22–27.75: `ocean`** | Begin with a 35 mm inspection view near `(13,4.4,5.5)` aimed at `(5.5,.55,1)`. At **24.6**, make one deliberate insert cut to 50 mm near `(11.8,2.5,3.2)` aimed at `(5.45,.1,0)`; creep at most 0.35 units along the inspection direction by 27.0. | The wide view establishes ROV, cable, and cutaway. The insert presents the stepped disks along x=5.15–5.675 as the hero. Align their screen position across the cut. Rack focus from the armor rim to glass core over roughly 0.7 seconds, then hold. Keep `cableLabel` and the armor/core label readable. This is an explicitly authored cut inside a beat, not an accidental bridge snap. |
| **29–34.75: `shore`** | 35 mm. Try `(17,8.5,21)` to `(13,6.5,18)`, aim `(1,1,-2.5)`. | A restrained descending crane reveals the beach trench, fence, building, and transformer yard. The trench leads the eye toward the facility. Do not fly behind the facade or crop away the sea/land relationship. Keep focus deep and `landingLight` nearly steady. |
| **36–41.75: `dc`** | 35 mm. Axial move from `(0,2.8,16)` to `(.35,2.6,9.5)`, aiming down the aisle near `(0,2,-4)`. In the last 1.5 seconds pan gently toward `(3.4,2,-3.2)` without crossing the rack row. | Start with a strong vanishing point and two balanced rack banks. The current endpoint at x=5.4 moves outside the central aisle; keeping the rail inside the aisle maintains spatial clarity. Finish on the chosen serving rack. Moderate focus separation only after both rack banks have read. |
| **43–50.75: `model`** | 35 mm with a wider/backed-away composition: approximately `(0,5.8,16.5)` to `(1,5.3,16)`, aiming near `(.7,2.4,1)`. | Prioritize all four input words, the four hardware labels, and the longest output tile, `THAN WATER`. Use a small lateral slide, then stop while outputs appear. Focus stays deep during token reading. A separate desktop close-up is optional only if it does not reduce reading time. Keep the existing portrait single-token treatment. |
| **52–54.75: `pacificReturn`** | Match the outbound 28 mm map composition; only a tiny reverse lateral move. | Reverse packet direction on the existing illustrative map route. The caption already says real return routes may differ; do not imply a newly verified route. Spend this short shot showing direction, not introducing another camera flourish. |
| **56–60: `homeReturn`** | 40 mm. Begin near `(3.7,2.9,8)`, finish near `(1.7,2.5,6)` aimed at the same local screen center as the opening. | Return to the opening's visual grammar with a tighter frame and visible answer. Decelerate by 58.5 and hold the last 1.5 seconds completely still. The `drawChat(true)` answer, not a packet bob or camera drift, closes the story. |

The single subsea insert cut is optional if the wide-to-detail composition cannot fit both labels cleanly. In that case keep a 35–40 mm continuous inspection shot and omit the cut rather than adding more edits. All eight existing infrastructure rides remain.

### Transition mechanics

Keep the existing travel intervals: **5.75–7, 12.75–14, 20.75–22, 27.75–29, 34.75–36, 41.75–43, 50.75–52, 54.75–56**. Each bridge must connect actual evaluated camera states at those endpoints, including portrait adjustments. [Current span logic](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:340).

Introduce a pure `sampleShotPose(t, viewport)` returning position, quaternion, focal length/FOV, focus point, and optional shake weight. Introduce `sampleBridgePose(index,t,viewport)` with the same output. These are proposed names. Keep `beats` as the story/caption authority; add shot metadata beside it rather than rewriting the DOM timeline.

Use an authored camera rail beside the infrastructure, separate from `bridge.path`. Match endpoint position and velocity using piecewise cubic Hermite curves, or Bézier handles derived from the neighboring shots' evaluated velocities. This gives C1 continuity: matching position and first derivative at a join. Blend the endpoint orientations with quaternion interpolation; use a precomputed stable up/side frame along the middle section so near-vertical tangents cannot flip the camera. Ease FOV from the outgoing shot to the transit lens and then to the incoming shot; remove the unconditional 42–46° pulse. Keep roll zero in explanatory shots and below about 1° during transit.

Start/end side offsets and the y=0.8 lift at zero relative to the matched shot poses. Introduce them only in the bridge interior. Sample the camera rail against simple wall/rack/cabinet bounds with a 0.15–0.25 scene-unit safety radius. The apartment opening at x≈-5.7 must remain navigable. Use this clearance check to author the route; do not put the camera in a bouncing rigid body.

Use physical handoff anchors for packet start/end: home router, street cabinet, map endpoint, exposed fiber, landing duct, gateway, serving rack, model output, and return router. Do not end a packet at `nextBeat.p0`. Express trail spacing in distance along the packet curve, not unrelated normalized offsets. Orient `halo` only after the final camera orientation is applied. A packet changing medium can crossfade between local and bridge representations over 0.1–0.2 seconds, provided there is one apparent lead packet.

For a return bridge through `streetReturn`, decide visibility explicitly: show that intermediate set only in the relevant section of the rail and test occlusion. Its construction alone does not currently make it visible. Do not reveal every set at once.

### Depth of field, framing, and image treatment

Use the existing-version addons: `EffectComposer`, `RenderPass`, `BokehPass`, and `OutputPass`. Render **scene → optional DOF → output conversion**. Keep the current ACES/sRGB/exposure appearance as the reference; `OutputPass` reads the renderer's tone-mapping and output settings. Do not add a second gamma or exposure conversion. [Pinned OutputPass source](https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/postprocessing/OutputPass.js).

`BokehPass` is a screen-space approximation, not a physical f-stop simulator. Its `focus` is distance along the camera view direction. Calculate `-focusPointWorld.applyMatrix4(camera.matrixWorldInverse).z` on a copy after camera matrices update; do not use raw Euclidean distance for off-axis subjects. Setting `camera.focus` alone does not blur anything. Start with aperture around **0.0005–0.0015**, `maxblur` around **0.003**, and tune against the actual scene scale. These are proposed restrained settings. [Pinned BokehPass](https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/postprocessing/BokehPass.js), [pinned shader](https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/shaders/BokehShader.js).

Enable DOF for the home screen hold, subsea insert, selected rack finish, and home return. Disable it for maps, transits, portrait token explanations, and any frame where labels lose legibility. DOM captions remain outside the WebGL postprocess. In-world `sprite()` labels are inside it and use `depthTest:false`; test their blur/depth interaction explicitly. If a label cannot stay legible, disable DOF for that shot first. Do not add a second label render layer unless necessary.

Resize composer pixel ratio and buffers together with the renderer; use `composer.setPixelRatio()` and `composer.setSize()` without multiplying dimensions twice. A normal canvas's `antialias:true` does not by itself guarantee equivalent antialiasing in compositor render targets. Compare thin cable edges before accepting the compositor. Keep direct `renderer.render()` as the quality fallback. [Pinned composer implementation](https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/postprocessing/EffectComposer.js).

Frame against the **actual DOM rectangles** for `.chapter`, `.captionBox`, `.topbar`, and `.footer`, not guessed percentage safe zones. Project key subjects and label bounds into screen space. At desktop sizes keep the main subject around the middle/right of the unobscured region. Below 700 px use dedicated shot distances/aims, preserve the existing model-token substitutions, and keep transit subjects in frame too; `updateTravel()` currently has no separate portrait framing. Recalculate on resize while paused.

Do not add letterbox bars, chromatic aberration, broad bloom, extra film grain, or motion blur in this pass. The CSS grain and current palette already supply an image treatment. Direction should come from composition, lighting, timing, and subject motion.

### Lighting and materials that support the camera

Give each shot a stable lighting origin. Reposition `key` **and its target** together near the active set; explicitly fit its shadow camera to the local visible action. Interpolate the light rig through transits rather than snapping it at the beat boundary. Keep one directional shadow map initially and preserve its 2048 resolution. Do not enlarge its frustum to cover all 198 units of staging at once.

Move `blueLight` and `goldLight` with the active lighting origin if they are intended as cinematic fill; otherwise stop treating their intensity changes at z=110/132 as local rack lighting. Preserve the existing local practical lights and warm-home/cool-infrastructure relationships. Evaluate visible material changes under corrected lighting before altering roughness or metalness. The current finite point ranges and stationary positions explain this recommendation. [Global points](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:99), [intensity assignment](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:379).

Clone only materials whose properties are animated independently, beginning with `rackLights`. Keep shared static `M.steel`, `M.wood`, and `M.black`. Replace the regular `roomGlow` and `landingLight` intensity oscillation with a steady value or at most a very small seeded fluctuation; their current sine waves read as periodic effects rather than motivated illumination. Mount the ROV spotlight to the moving ROV and keep its target in the correct set coordinates. Resolve the return home's light explicitly instead of animating an invisible original.

## 3. Physical interaction: what to simulate and what to keep authored

### Recommended implementation choice

Use a small hybrid: **cannon-es 0.20.0 for the ROV and genuine contacts; a short position-based cable solver for flexible spans; analytic force/drag motion for dust; authored camera and symbolic data paths**. Avoid an engine-wide conversion.

Add the exact optional importmap entry `"cannon-es": "https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js"`. Import it dynamically so a failed optional physics download can retain the current authored motion and complete the film. The published package identifies that file as its ESM module, and the direct CDN file was fetched successfully in this planning pass. It exports `World`, `Body`, `Box`, `Sphere`, `Cylinder`, `ContactMaterial`, constraints, and `Spring`. No React wrapper, bundler, WASM loader, or package install is needed for this choice. [Pinned package metadata](https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/package.json), [pinned browser module](https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js).

A lower-dependency alternative is damped analytic ROV motion plus positional cable constraints only. It gives plausible secondary motion but does not supply general rigid-body contact response. Keep that as a fallback, not as a claim of collision simulation. A larger rigid/soft-body engine is unnecessary for this cast of objects.

### Timeline-safe simulation contract

Preserve `update(t)` as the public sampling operation. Introduce `prepareMotionCaches()` and `samplePhysicalState(t)` as proposed internal helpers. Pre-simulate only short active physical sequences, store positions and quaternions in typed arrays, and interpolate them at `elapsed`. This is real force/contact integration baked for playback, not a live simulation advanced by every call to `update()`.

Use a fixed **1/120-second** step for the small ROV/contact sequence and **1/60-second** for flexible wires. Call `world.step(h)` with a fixed h during preparation; the documented fixed-step mode supports this. Do not call wall-clock-driven `world.fixedStep()` from both seek and render. [World stepping API](https://pmndrs.github.io/cannon-es/docs/classes/World.html#step).

For each simulated set, start from a known pose and seed, pre-roll about two seconds to settle constraints, and cache through its full visible interval, including the incoming bridge. Initialize the ROV cache before t=20.75 because `ocean` can be visible during arrival, not merely from t=22. Keep body positions set-local; add the set transform exactly once when applying them. A street return can sample the same wire-motion definition at its own local time, but must not accidentally share a mutable geometry buffer with the outbound copy.

For the ROV alone, nine seconds at 120 Hz with position plus quaternion is roughly 30 KB in float32 storage. Cable-node caches are also small at the proposed counts. Yield preparation in bounded chunks so the loading screen and controls do not stall. If preparation fails, use the existing authored pose/motion. No first-visit simulation delay when a user seeks directly to t=25.

A frame at t=25 must match after fresh load, normal playback, seeking backward from t=59, or repeated `update(25)`. Do not use per-frame random numbers, `performance.now()` inside simulation effects, or accumulated camera lerps. Cache contact events too so any event response is evaluated by timestamp. Numerical repeatability is required within the same runtime; do not promise cross-browser bit-identical physics.

### ROV: real inertia, buoyancy, and contact

The existing `rov` is a good physical actor: a 1.7×0.7×1.05 hull with four supports, stationed at `(5,1.15,2.7)`. It currently yaws by `.05*sin(t)` without translating, and `rovLamp` is a separate child of `ocean`. [Source](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:195).

Model the hull as one dynamic box with four simple foot shapes if they can contact anything. Begin with mass 12 in the chosen simulation convention, gravity `(0,-9.81,0)`, upward buoyancy approximately balancing weight, drag relative to a weak local current, and angular damping. Treat these as visual-scale parameters: a 3.25-unit-wide laptop elsewhere demonstrates that the whole film is not a single literal meter-scale environment.

Drive an inspection target from `(5,1.15,2.7)` toward `(5.25,1.25,2.45)` over about two seconds, then hold. Apply a capped proportional-and-damping force `Kp*(target-position) - Kd*velocity`, rather than assigning position every frame. Aim for approximately 0.2–0.3 units/second maximum speed and less than 3° pitch/roll. Use a small buoyancy application offset above the center of mass for righting torque. Avoid stacking strong engine damping and strong explicit drag without retuning.

Add static compound bounds only for nearby physical cable housings, seabed, and relevant rocks. Match cylinder axes to the rendered geometry with a debug overlay. Use low restitution, initially 0–0.1, and moderate friction, initially 0.4–0.6. Do not give every ring, LED, and token a collider. A forced low-speed collision in a debug test must stop penetration and dissipate motion; the final inspection flight should normally avoid the cable.

**Resolve the ocean ground reference before enabling contact.** `floor(ocean)` places its top at y=-0.07, while rocks/ridges and repeaters are centered around y=-2.65/-2.45 and cable cylinders are at y=0. The current ocean is a schematic cutaway with vertically inconsistent ground. Capture the ocean floor mesh reference, then test a local-only seabed adjustment near y=-2.8 top if the new camera exposes this. Align collision bounds with the accepted visual floor. Do not put an invisible collider at a different height, globally change `floor()`, or drop the deliberately exploded cable core under gravity. [Floor helper](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:110), [ocean construction](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:187).

Attach `rovLamp` at the equivalent hull-local offset, preserving the original beam at the initial pose; update its target toward the inspection point. Keep decorative front lights attached to the hull. A tether is optional only if both attachment points and a believable service origin are shown. Do not add a cable disappearing into empty space merely to demonstrate rope physics.

### Street cables: tension, sag, and restrained wind

The four street wires have fixed Catmull–Rom control points at z=-14,-4,8,14, whereas the utility poles are at z=-10,-1,8. Their height changes do not describe sagging spans attached to those poles. [Source](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:147).

Create explicit support points at the existing crossarms and include sensible offscreen terminal anchors. For a cheap static first step, compute a hanging curve per span; a small-sag parabola `y=lerp(y0,y1,s)-4*sag*s*(1-s)` is sufficient at these views. Start with 0.15–0.30 units of sag over a nine-unit span, preserving clearance above the street. Keep every support fixed.

Then add a small position-based dynamics solver: 12–16 nodes per span, fixed endpoints, gravity, drag, distance constraints, and a weak bend constraint. Integrate at 60 Hz with 6–8 constraint iterations. Seed a low-frequency wind field; limit lateral sway to roughly 0.03–0.07 units. Pre-roll to a resting sag. Never stretch the wire to follow a camera or packet. Use collision projection only where a span can touch a pole/building.

Keep `tube()` unchanged for static routes because it returns only the curve, not the mesh. Add a separate dynamic-cable helper that owns its nodes, mesh, and buffer. Use 6 radial segments or instanced short cylinders; update existing buffer attributes or instance matrices. Do not allocate a new `TubeGeometry` every frame. Ensure bounds remain valid after deformation.

The three bright lines created by `addTravelCable()` are partly explanatory infrastructure rails. Preserve their handoff geometry; only give genuine unsupported physical spans sag after confirming they still explain the route. `streetPath`, `globePath`, Wi-Fi arcs, attention links, and the shore's buried access trench are not loose ropes.

### Dust, water particles, and waves

There is no airborne or underwater particle system in the file; `starArr` belongs to the map background. Keep those stars distinct. [Star field](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:159).

Add underwater suspended particles first: approximately 120–200 desktop particles, 40–80 on mobile, in a bounded local volume near the ROV spotlight. Use one `THREE.Points` geometry or one instanced draw, seeded birth times/positions, a slow current, drag toward that current, and weak downward settling. Add a small local velocity disturbance near moving ROV thrusters; fade particles through their lifetime and recycle deterministically. The same timestamp must reproduce the same cloud.

Use `depthTest:true`, `depthWrite:false`, low opacity, and no particle shadows. Plain point materials do not acquire a visible volumetric beam just because a spotlight exists: modulate brightness from the light cone/distance in a small shader or CPU attribute update. Avoid uniform additive sparkles and avoid particles inside the hardware. A simple seabed boundary and hull exclusion are enough; individual cannon bodies are unnecessary.

A few home dust motes in `roomGlow` are optional and lower priority. Do not add visible dust clouds inside a clean server aisle. Shore water may gain a tiny analytic wave displacement on the existing six wave-line buffers, but no fluid solver or whole-ocean mesh replacement is justified by this shot.

### Fans, moving symbols, and weight

`fans` currently contains 80 rack fan cylinders and 12 GPU fan cylinders. Their rotational symmetry hides most spinning, and the GPU fans stop updating once the Data center branch ends. Split references into proposed `rackFans` and `modelFans`; add a few visible rotor blades inside each existing housing and keep the outer torus stationary. Spin the rotor around its own shaft with a stored base orientation. Give angular velocity a small deterministic ramp and integrate angle from that velocity; do not use elapsed wall-clock lerps. Moderate apparent rotation avoids aliasing at 30 Hz. [Fan construction](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:222), [model fans](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:227).

`tokenMeshes` use a modulo z-motion that wraps back abruptly. For visible input tiles, replace wrapping with scheduled enter/hold/exit segments or a cached critically damped response. A critically damped response settles without oscillating. `outputTokens` should accelerate gently, settle at their readable positions, and retain the existing word order and portrait selection. Do not add rigid-body gravity or collisions to semantic tiles, `nodes`, `homeDot`, `fiberPulse`, `dcPulse`, or network packets. They represent information, not massive projectiles. [Current symbolic motion](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:372).

Leave the cup, laptop lid, lamp shade, cabinets, racks, and rocks static by default. Nothing in the existing action hits or manipulates them. Adding a falling cup or opening lid would create a new narrative action and new readability risks. There is no existing cloth garment, curtain mesh, or flag to simulate; the shoji/window surfaces are rigid architecture. Cloth is an explicit non-goal for this iteration.

### Camera response to impacts

There are **no authored impacts** in the present film. Packet arrivals must not shake the camera. Normal home, map, shore, and aisle shots stay stable.

If an optional physical contact is visible in a shot whose camera is mounted to the affected structure, derive a timestamped response from contact speed/impulse. `ContactEquation.getImpactVelocityAlongNormal()` can provide relative normal speed; it is not itself an impulse. Gate small/resting contacts, combine simultaneous contacts, and clamp amplitude. [Verified cannon-es module](https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js).

Evaluate a damped oscillation `A*exp(-lambda*tau)*sin(omega*tau)` from event time, with independent small translational/rotational axes. Start with caps of 0.01–0.02 scene units and 0.1–0.2°, decay within roughly 0.3 seconds, and test at the longest lens. A free observation camera does not inherit a remote ROV bump. Keep this response disabled unless the staging provides a physical camera connection. Reduced-motion mode disables it completely.

## 4. Prioritized execution tasks

Each task is a separate reviewable unit. “Depends” means prerequisites, not permission to combine unrelated changes. Preserve the current render path as a fallback until the relevant replacement passes its checks. All proposed helper names below are new; the other names refer to existing code.

1. **P0: Capture the accepted baseline and transition samples.**  
   Touch/read: `beats`, `update()`, `updateTravel()`, `render()`, and all control handlers; no visual change. Record frames at 0, 3, 9, 17, 25, 32, 39, 47, 53, 58.5, and 60 seconds at 1440×900 and 390×844. Sample every transit entry/exit at ±1/60 second and its midpoint. Record console/network errors and frame-time distribution on the LAN device used for acceptance.  
   **Pass:** a reproducible before/after comparison exists; source hash matches the planning baseline or any intervening changes are explicitly reconciled. Depends: none.

2. **P0: Make existing visual state independent of seek history.**  
   Touch: `rackLights`, shared `M`, `fans`, `roomGlow`, `homeReturn`, `globeReturnDots`, `globe`, `drawChat()`, `lastScreenAnswer`, and scene branches in `update()`. Give independently animated LEDs separate materials; explicitly initialize and assign clone visibility/transforms; animate reverse map dots; update fan state by absolute time in both relevant scenes; orient travel `halo` after the camera. Evaluate visible incoming/outgoing sets during bridges, not only `b.set`. Assign the chat answer state consistently from t even when seeking through non-home scenes.  
   **Pass:** visiting 39→3, 59→25, 59→17, fresh→47, and replay→0 gives the same relevant state as direct visits. Repeated updates at one timestamp produce no drift. Preserve current imagery except the identified state corrections. Depends: 1.

3. **P0: Extract the camera sampler without changing shot design.**  
   Touch: `beats`, `v3()`, `smooth()`, camera portions of `update()`/`updateTravel()`, resize handler. Add proposed `sampleShotPose()`/`sampleBridgePose()` and one final camera-application point. Initially reproduce existing shot math and portrait behavior exactly. Keep captions/controls driven by existing `beats`.  
   **Pass:** baseline non-transit frames match; pausing, keyboard seeking, and resizing still work; the sampler can evaluate arbitrary times without rendering or mutation of prior state. Depends: 2.

4. **P0: Repair all eight camera/packet handoffs.**  
   Touch: `makeTravelBridge()`, `anchors`, `travelBridges`, `updateTravel()`, packet/halo/trail, and proposed camera samplers. Separate camera rail from infrastructure and packet routes; match camera position, orientation, FOV, and endpoint velocities; taper interior offsets; use stable frames; validate wall and rack clearance. Keep existing beat boundaries and `travelSpecs` captions. Decide intermediate `streetReturn` visibility explicitly.  
   **Pass:** evaluate each side of every join at epsilon=0.001 seconds; position difference tends to zero as epsilon shrinks, rather than retaining a fixed jump. At the join itself, endpoint position/orientation/FOV agree to numerical tolerance. No side-frame flips, wall crossings, or endpoint packet pileup. Repeat in portrait. Depends: 3.

5. **P0: Implement home, map, and return shot direction.**  
   Touch: relevant `beats` camera metadata, `home`, `homeReturn`, `globeSet`, `pacificReturn`, `globe.rotation`, `homeDot`, and camera sampler. Apply the proposed home dolly, readable map holds, reverse route animation, and final still frame. Fit the subject bounds before accepting the focal lengths.  
   **Pass:** prompt and final answer remain readable; both map endpoints and labels fit; last 1.5 seconds are stable; no transition regresses after camera metadata changes. Depends: 4.

6. **P1: Implement street, shore, and aisle camera direction.**  
   Touch: their shot metadata, `streetLabel`, `dcPulse`, camera sampler, and bridge endpoint sampling. Apply lower street approach, shore reveal, and central aisle dolly with a small finishing pan.  
   **Pass:** cabinet handoff is clear, beach-to-building connection remains visible, camera stays outside rack solids, and existing captions have enough reading time. Depends: 4; can be reviewed separately from 5.

7. **P1: Implement subsea and model shot direction.**  
   Touch: their camera metadata, `cableLabel`, `cutaway`, `modelBanner`, `tokenMeshes`, `outputTokens`, and `nodes`. Add the deliberate subsea detail insert or retain the continuous fallback. Keep all model stages and output words readable. Do not add DOF yet.  
   **Pass:** armor/core detail is visually distinct; the ROV still establishes scale; the longest output tile fits; portrait retains one-token presentation and no new overlap. Depends: 4.

8. **P1: Correct lighting locality and material animation.**  
   Touch: `key`, its target/shadow camera, `blueLight`, `goldLight`, `roomGlow`, the home-return light, `landingLight`, existing local fills, and lighting assignments in `update()`. Fit lighting to each set and transition, steady regular pulsing, preserve ACES exposure and palette. Use shadow/frustum debug helpers only during validation.  
   **Pass:** home/return light consistently; rack and ocean subjects retain detail; shadows are local and do not jump at handoffs; no global material flicker; no added shadow-casting point-light budget. Depends: 2 and 4.

9. **P1: Add optional physics loading and deterministic cache infrastructure.**  
   Touch: importmap, module initialization, `loading`, `update()`, replay/seek integration. Add dynamic cannon import plus proposed `prepareMotionCaches()` and `samplePhysicalState()`. First validate with one temporary debug body and remove/hide it from the delivered scene. Use fixed steps, fixed seeds, interpolation, and bounded preparation chunks.  
   **Pass:** blocked physics URL still leaves a usable authored film; no DOM or animation changes from the new loader alone; equal timestamps sample equal poses; no catch-up burst after tab visibility changes. Depends: 2.

10. **P1: Give the ROV physical motion and aligned colliders.**  
    Touch: `rov`, `rovLamp`, its target, ocean construction and the `Subsea cable` animation branch. Retain mesh references needed for static bounds; reconcile the ocean floor locally if required by the accepted view. Add buoyancy, drag, capped controller forces, and low-restitution contacts; cache the complete incoming/main/outgoing visibility interval.  
    **Pass:** ROV moves with acceleration and settling, lamp follows its hull, collision debug test does not tunnel, final flight does not hit the cable, no collider disagrees with visible ground, and fresh/direct/backward samples at t=23/25/28 agree. Depends: 7 and 9.

11. **P1: Add attached, sagging street wires.**  
    Touch: wire/pole construction in `street`, `streetReturn` cloning, new dynamic-cable helper, and cached scene motion. First land static supported sag; then add constrained wind motion as a separate small change within this task. Keep `tube()`, `streetPath`, and geographic route curves stable.  
    **Pass:** support points never move; visible wire length error remains below approximately 1%; no pole/building penetration or stretching; seek/replay is repeatable; no per-frame geometry allocation or accidental clone coupling. Depends: 2; cached animation follows 9's sampling contract but does not require cannon itself.

12. **P1: Make fans and token motion communicate momentum.**  
    Touch: `rack()`, model fan construction, `fans`, `tokenMeshes`, `outputTokens`, and their `update()` branches. Separate fan groups, add simple blade geometry and shaft-axis rotation, evaluate speed ramps analytically, and replace visible token wraps with enter/hold/exit schedules.  
    **Pass:** GPU fans animate on direct seek and normal playback; housings do not rotate; input/output order and portrait rules remain intact; no token snaps while visible; output words settle long enough to read. Depends: 2 and 7.

13. **P2: Add restrained underwater particles.**  
    Touch: `ocean`, `rovLamp`, ROV sampled state, new particle buffer/shader, and `update()`. Use deterministic current/drag/lifetime behavior, spotlight weighting, and no body-per-particle solver. Home motes and shore wave displacement remain optional after this passes.  
    **Pass:** particles reveal water motion without obscuring the cutaway, disappear behind opaque hardware, avoid the hull, reproduce at equal times, and add at most one draw call for the main cloud. Depends: 8 and 10.

14. **P2: Add selective DOF with a direct-render fallback.**  
    Touch: addon imports, `renderer`, proposed per-shot focus metadata, `render()`, initial render, resize handler, and relevant `sprite()` label handling. Add composer/Bokeh/OutputPass using the pinned existing Three version, keep effect disabled for designated shots and low-quality mode, and verify tone mapping and antialiasing.  
    **Pass:** disabling DOF preserves the accepted image; focus follows the intended subject without hunting; labels and captions remain readable; paused resize works; optional addon failure falls back to direct rendering. Depends: 5–8.

15. **P2, conditional: Add contact-driven camera response only if a shot motivates it.**  
    Touch: cached contact events and the final camera-pose application, never DOM overlays. Use small, time-sampled damped motion and explicit camera attachment semantics. Do not schedule an impact just to exercise the effect.  
    **Pass:** no contact produces no shake; irrelevant remote contact produces no shake; a debug attached-camera impact produces one bounded response; repeated seek samples match; reduced motion disables it. If no final shot has such an impact, omit this feature. Depends: 9–10.

16. **P0 release gate: Run visual, interaction, accessibility, performance, and LAN regression checks.**  
    Touch: the resulting film, optional effect switches, `render()`, resize, visibility, and controls. Re-run task 1's frames and all joins at 1440×900, 1920×1080, 390×844, and 844×390. Exercise pointer capture, arrow keys, pause during a bridge, replay at the end, repeated replay, background/foreground, and direct seek into every scene. Compare normal playback against seek snapshots.  
    **Pass:** no new console/network failures in normal operation, no content or route regression, and no newly obscured UI. Target 60 fps desktop and at least 30 fps on the accepted mobile/LAN device, while also comparing against its measured baseline. If baseline is already slower, require no more than roughly 10% worsening in p95 frame time and report the actual result. Reduce particle counts and disable DOF before reducing scene content. Verify a failed optional dependency still completes the story. Run the LAN preview again and repeat blind review of the changed camera/physics behavior. Depends: all selected tasks.

A reduced-motion implementation belongs in the final gate: the existing media rule only changes `scroll-behavior`, not Three.js motion. Read `matchMedia('(prefers-reduced-motion: reduce)')`; disable shake, extra cable sway, and optional drift, use static readable scene poses or restrained alternatives, and preserve the 60-second captions and controls. Do not claim the current CSS already handles this. [Existing rule](/srv/dusk/home/pc/Downloads/laptop-ai-datacenter-demo.html:64).

## 5. Non-goals and regression boundaries

- **Do not change the story contract.** Keep 60 seconds, nine beat boundaries, caption/headline text, the Japan→Pacific→landing→service→model→return order, and the warning that routes are illustrative. Preserve `geo()`, `pacificLon()`, `routeGeo`, and the Pacific-centered map artwork unless a separately demonstrated bug requires a targeted correction.
- **Do not replace the infrastructure journeys.** Keep the eight `travelKinds` and `travelSpecs`, recognizable relay hardware, physical apartment opening, and medium-specific surroundings. No generic warp tunnel, fade-to-black sequence, or all-scenes-visible flyover.
- **Do not redesign the page.** Preserve branding, colors, typography, grain, loading overlay, timeline, keyboard access, captions, mobile controls, and their usable touch targets. Leave the 700 px presentation breakpoint intact unless a measured framing issue requires a separate decision.
- **Do not change the delivery model.** Keep the single-file CDN/importmap workflow and Three.js 0.186.1. No React, Vite, npm installation, backend, WebGPU rewrite, external model asset pipeline, or required local service beyond the existing static/LAN serving method.
- **Do not simulate information as matter.** Packets do not bounce, weigh down cables, kick racks, or create camera impacts. Token tiles and attention links remain an explanatory diagram, not a literal claim about hardware architecture or particle transport.
- **Do not invent cloth, collisions, or prop business.** No falling cup, desk strike, laptop opening sequence, swinging racks, collapsing cable, flags, clothing, or fabric solver. Rigid-body contact is justified for a moving ROV and validation, not as an excuse to add an accident.
- **Do not rebuild accepted geometry for cosmetic consistency.** Preserve detailed apartment, vending machine, cabinet, landing compound, eight racks, four model modules, and cutaway. The ocean floor/collider alignment is a narrowly scoped exception to validate under the new camera, not permission to re-layout all sets.
- **Do not spread state through shared materials or cloned buffers.** Preserve useful sharing for static assets, but make independent animation ownership explicit. Do not edit `M.cyan` globally to animate one LED or mutate the outbound cable buffer to animate the return street.
- **Do not replace legibility with lens effects.** Keep map labels, subtitles, model words, and final chat readable. DOF, particle density, focal length, and movement must yield to those constraints.
- **Do not introduce soundtrack/autoplay audio, full fluid simulation, volumetric rendering, blanket bloom, motion blur, temporal accumulation, or new export/capture UI.** These do not address the immediate camera and physical-motion opportunities.
- **Do not interpret this plan as a new acceptance result.** The prior reviews and LAN success are user-reported. The executor must validate the modified film, especially the transit joins, mobile composition, deterministic seeking, optional CDN failures, and final answer hold.

## 6. Evidence and implementation references

The local line links above refer to the unchanged 392-line planning baseline. Names are more durable than line numbers after implementation begins.

The following exact-version sources were fetched read-only during this pass on 2026-10-22:

- [Three.js 0.186.1 PerspectiveCamera](https://cdn.jsdelivr.net/npm/three@0.186.1/src/cameras/PerspectiveCamera.js): film gauge, focal length, FOV, and projection behavior.
- [Three.js 0.186.1 BokehPass](https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/postprocessing/BokehPass.js) and [BokehShader](https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/shaders/BokehShader.js): focus coordinate convention, aperture/blur uniforms, depth render, and resizing.
- [Three.js 0.186.1 EffectComposer](https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/postprocessing/EffectComposer.js) and [OutputPass](https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/postprocessing/OutputPass.js): render targets, pixel ratio, sizing, and output conversion.
- [cannon-es 0.20.0 package metadata](https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/package.json) and [ESM distribution](https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.js): pinned browser entry point and available physics APIs.
- [cannon-es World documentation](https://pmndrs.github.io/cannon-es/docs/classes/World.html#step): fixed-step versus elapsed-time stepping. This documentation is unversioned; use the pinned distribution above as authority if signatures differ.

The proposed lenses, shot coordinates, timings inside beats, solver settings, and quality thresholds are design decisions for this file. They are not claims that a library prescribes those artistic choices or that the result has already been rendered and approved.
