fix(cinema): tighten storm framing so the bloom halo never clips

The rainbow storm looked next-dimensional but still clipped the edges — the
additive bloom halo extends each particle's glow well past its geometric radius,
so the visible cloud was bigger than the contain sphere.

- spawn radius 15 -> 8 (particles start inside the shell, no asymmetric inward yank)
- sandbox fitRadius margin 0.55 -> 0.40 (leaves room for the bloom halo)
- camera band tightened + pushed farther (30-44) so the contained cloud sits
  small + centered; director standoff clamped into that band in centerOnOrigin
  mode so the camera never fights the per-frame clamp (the off-center jump).

937 tests + build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam Valladares 2026-06-22 01:18:53 -05:00
parent 65c801bc2f
commit 782399adb1
3 changed files with 19 additions and 6 deletions

View file

@ -168,6 +168,11 @@ export class CinemaDirector {
else if (shot.move === 'pull_back') standoff *= 1.5;
else if (shot.move === 'crane') standoff *= 1.8;
}
// In centered (WebGPU storm) mode the subject is pinned to the origin and
// the sandbox clamps the camera to a far band. Keep the directed standoff
// INSIDE that band so the camera never fights the clamp (which read as an
// off-center jump) — variety here comes from angle + orbit, not distance.
if (this.opts.centerOnOrigin) standoff = Math.max(31, Math.min(43, standoff));
return out.copy(nodePos).addScaledVector(_tmpDir, standoff);
}

View file

@ -16,8 +16,11 @@ import type { SemanticRole, SemanticComputeStorm } from './storm';
// The storm lives at the world origin, permanently. The camera always looks here
// and is clamped to a safe distance band so the subject can never leave frame.
const ORIGIN = new THREE.Vector3(0, 0, 0);
const MIN_CAM_DIST = 18;
const MAX_CAM_DIST = 46;
// Keep the camera in a narrow, fairly FAR band so the contained storm always
// sits comfortably small and centered in frame (a closer camera makes the cloud
// fill — and spill past — the edges once the bloom halo is added).
const MIN_CAM_DIST = 30;
const MAX_CAM_DIST = 44;
export function isWebGPUSupported(): boolean {
return typeof navigator !== 'undefined' && 'gpu' in navigator;
@ -185,11 +188,14 @@ export class CinemaSandbox {
}
this.camera.lookAt(ORIGIN);
// Size the containment sphere to the camera's FOV at the origin so the
// storm always fully fits the frame with margin.
// Size the containment sphere to the camera's VERTICAL FOV at the origin
// (the limiting dimension on a landscape frame). The 0.40 factor leaves
// generous room for the additive BLOOM HALO — each particle's glow spreads
// well beyond its geometric position, so the visible cloud is much larger
// than the radius; an aggressive margin is what actually stops the clip.
const dist = this.camera.position.length();
const vfov = (this.camera.fov * Math.PI) / 180;
const fitRadius = Math.tan(vfov / 2) * dist * 0.55;
const fitRadius = Math.tan(vfov / 2) * dist * 0.4;
this.storm.setContainRadius(fitRadius);
await this.storm.update(deltaSeconds);

View file

@ -110,7 +110,9 @@ export class SemanticComputeStorm {
this.renderer = renderer;
this.scene = scene;
this.count = opts.count ?? 150_000;
const spawn = opts.spawnRadius ?? 15;
// Spawn inside the contained zone so particles don't start outside the
// shell and get yanked inward asymmetrically (which read as off-center).
const spawn = opts.spawnRadius ?? 8;
const positions = new Float32Array(this.count * 3);
const velocities = new Float32Array(this.count * 3);