From 85dacfad2bdd9f0c148462c1cf8a6f02a51be59c Mon Sep 17 00:00:00 2001 From: Sam Valladares Date: Mon, 22 Jun 2026 14:20:37 -0500 Subject: [PATCH] feat(cinema): depth-of-field defocus (immersion step 3b/6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Off-focus particles dim (read as bokeh defocus under the bloom) with a breathing rack-focus. Folded into the single depthFade depth read — NOT sprite scaleNode, which collapsed the sprites to invisible and collides with the upcoming streak. Subtle (0.3) so it adds cinematic depth without darkening the figure. Gate: svelte-check 0/0, 937 tests, verified live (depth grading reads, no recursion). Co-Authored-By: Claude Opus 4.8 --- apps/dashboard/src/lib/graph/cinema/storm.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/dashboard/src/lib/graph/cinema/storm.ts b/apps/dashboard/src/lib/graph/cinema/storm.ts index 9a88bc4..400956e 100644 --- a/apps/dashboard/src/lib/graph/cinema/storm.ts +++ b/apps/dashboard/src/lib/graph/cinema/storm.ts @@ -155,6 +155,12 @@ export class SemanticComputeStorm { // VOLUMETRIC FOG — distant particles dim toward the void with view depth (exp // falloff) for atmospheric depth. Combined with near-fade in one depth read. private uFogDensity = uniform(0.012); + // DEPTH OF FIELD — off-focus particles dim (read as bokeh defocus under the + // bloom). Folded into the single depthFade depth read (no sprite-scale, which + // is finicky + collides with the streak). Focus tracks the dive. + private uFocus = uniform(28.0); + private uFocusRange = uniform(20.0); // wider in-focus band → most of figure crisp + private uDofDim = uniform(0.3); // subtle off-focus fade → depth without darkening // JS-side dream state (not uniforms): which figure is live + how many fired. private dreamCount = 0; @@ -694,7 +700,11 @@ export class SemanticComputeStorm { const d = positionView.z.negate(); // +forward view distance, read ONCE const near = smoothstep(this.uFadeNear, this.uFadeNear.add(this.uFadeBand), d); const fog = clamp(this.uFogDensity.mul(d).negate().exp(), 0.18, 1.0); - return near.mul(fog); + // DOF: dim particles off the focus plane (defocus → bokeh under bloom). + // coc 0 at focus → 1 fully out of focus; brightness 1 → (1-uDofDim). + const coc = clamp(d.sub(this.uFocus).abs().div(this.uFocusRange), 0, 1); + const focusBright = oneMinus(coc.mul(this.uDofDim)); + return near.mul(fog).mul(focusBright); }); // ── THE COLOR BLAST ── the signature detonation chroma. Keyed on the LONG @@ -779,6 +789,10 @@ export class SemanticComputeStorm { // wave clock counts up so the spectral shockwave travels outward over time. this.uBlast.value = Math.max(0, this.uBlast.value - dt * 0.35); this.uBlastTime.value += dt; + // RACK FOCUS — slow breathing pull of the DOF focus plane so the cinematic + // focus is always alive (Step 4 will couple this to the infinite-zoom dive). + const focusTarget = 26 + Math.sin(this.uTime.value * 0.18) * 9; // 17..35 + this.uFocus.value += (focusTarget - this.uFocus.value) * Math.min(1, dt * 2); // Wait for any in-flight compute to finish before queuing the next. if (this.computeInFlight) await this.computeInFlight;