From d9fb791af6d4e5616aa4f186282c49f00454a0b5 Mon Sep 17 00:00:00 2001 From: Sam Valladares Date: Mon, 22 Jun 2026 13:53:52 -0500 Subject: [PATCH] refactor(cinema): canonical sprite center (positionNode = instancePos) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SpriteNodeMaterial.setupPositionView already rebuilds the billboard quad from positionGeometry — the prior .add(positionLocal) double-counted it (harmless at 0.1 size). Bare center is required for the upcoming velocity-stretch streak (scaleNode/rotationNode will drive the quad). Verified renders identically. Co-Authored-By: Claude Opus 4.8 --- apps/dashboard/src/lib/graph/cinema/storm.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/dashboard/src/lib/graph/cinema/storm.ts b/apps/dashboard/src/lib/graph/cinema/storm.ts index 607790f..6bc65ad 100644 --- a/apps/dashboard/src/lib/graph/cinema/storm.ts +++ b/apps/dashboard/src/lib/graph/cinema/storm.ts @@ -43,7 +43,6 @@ import { fract, abs, floor, - positionLocal, smoothstep, oneMinus, cross, @@ -525,15 +524,15 @@ export class SemanticComputeStorm { emissiveNode: unknown; }; - // CRITICAL: particle world position = per-instance GPU compute output - // (storage buffer, indexed by instanceIndex) PLUS the sprite's local quad - // vertex (positionLocal) so each billboard keeps its size while being - // translated to its computed position. Assigning the bare storage element - // to positionNode (without positionLocal) collapses every quad to a point - // at its instance origin — the bug the audit caught. + // CANONICAL SPRITE CENTER: positionNode is the sprite's CENTER only. + // SpriteNodeMaterial.setupPositionView already builds the billboard quad + // from positionGeometry.xy (scaled by scaleNode, rotated by rotationNode), + // so the previous `.add(positionLocal)` double-counted the quad (harmless at + // the 0.1 size, sub-pixel). Using the bare center is required for the + // velocity-stretch streak (scaleNode/rotationNode now drive the quad shape). const phaseStore = storage(bufferPhase, 'float', this.count); const instancePos = storage(bufferPos, 'vec3', this.count).element(instanceIndex); - mat.positionNode = instancePos.add(positionLocal); + mat.positionNode = instancePos; // ── SHARED RAINBOW COLOR ── // One Fn produces the pure iridescent color for a particle; we feed it to