mirror of
https://github.com/samvallad33/vestige.git
synced 2026-07-24 23:41:01 +02:00
fix(dashboard): correct WebGPU shader coordinate + frame-rate bugs (MiniMax M3 audit)
MiniMax M3 (max-depth audit, fresh eyes on the shader MATH that Claude+Kimi's lifecycle-focused passes missed) found 4 pre-existing criticals, all verified: - C1 aspect-ratio inversion: vertex shader divided particle X by aspect, which SQUASHED the whole field into the center ~56% of a 16:9 screen (~22% empty bars each side). Fix: position now uses the full [-1,1] clip box (no divide); roundness comes from a proper px2ndc = 2/viewport corner offset so quads are sizePx x sizePx device px on any aspect ratio (also kills the 0.01 magic number). - C3 firewall flare landed at the wrong X (distance measured in unit-box space while the particle rendered in divided space). Fixed-for-free by C1; the crimson distance is now aspect-corrected so the halo is circular, centered on the catch. - C4 frame-rate-dependent flow absorption: the velocity lerp used a fixed 0.06 per-frame factor (not dt-scaled), so the field was 2x snappier at 120Hz ProMotion vs 60Hz. Fix: absorb = 1 - exp(-dt * 3.7) — identical feel at 60fps, correct everywhere else. VERIFIED: svelte-check 0 errors / 967 files; zero WGSL validation errors in the browser (a broken shader struct/syntax would throw at pipeline creation — none). NOTE: the on-screen full-width widening is not yet visually captured this session — the preview backend returns API 500 so the (app) layout error-states and the field doesn't mount; pending eyeball on a wide window against a live backend. Memory Cinema / Graph3D untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
85650f02fd
commit
deb3b82134
1 changed files with 20 additions and 5 deletions
|
|
@ -113,7 +113,12 @@ fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|||
let rm = u.viewport.w;
|
||||
let speed = mix(1.0, 0.18, rm) * u.params.z;
|
||||
let flow = curlFlow(pr.pos.xyz * 1.1);
|
||||
pr.vel = vec4f(mix(pr.vel.xyz, flow * 0.6, 0.06), pr.vel.w);
|
||||
// Frame-rate-independent absorption toward the flow. 0.06/frame at 60fps =>
|
||||
// rate 3.7/s; exp form keeps the feel identical at 60fps but correct on
|
||||
// 120Hz ProMotion / 30fps throttle (was a fixed per-frame lerp -> 2x snappier
|
||||
// at 120Hz). u.params.y is dt.
|
||||
let absorb = 1.0 - exp(-u.params.y * 3.7);
|
||||
pr.vel = vec4f(mix(pr.vel.xyz, flow * 0.6, absorb), pr.vel.w);
|
||||
|
||||
// --- reactive event impulse (scoped to the event origin) -----------------
|
||||
let ecode = u.event.x;
|
||||
|
|
@ -169,12 +174,17 @@ fn vs(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VsOut
|
|||
);
|
||||
let c = corners[vi];
|
||||
let p = parts[ii];
|
||||
let aspect = u.viewport.x / max(u.viewport.y, 1.0);
|
||||
// gentle parallax depth so the cloud reads volumetric
|
||||
let depth = 1.7 + p.pos.z * 0.5;
|
||||
let sizePx = (2.6 + p.pos.w * 3.4) / depth;
|
||||
var ndc = vec2f(p.pos.x / aspect, p.pos.y);
|
||||
ndc = ndc + c * vec2f(sizePx * 0.01 / aspect, sizePx * 0.01);
|
||||
// Position fills the FULL clip box [-1,1]^2 — do NOT divide x by aspect
|
||||
// (that squashed the field into the center ~56% on 16:9). Roundness comes
|
||||
// from the corner offset instead: convert sizePx (device px) to NDC per
|
||||
// axis via px2ndc = 2/viewport, so the quad is sizePx x sizePx px and round
|
||||
// on any aspect ratio.
|
||||
var ndc = vec2f(p.pos.x, p.pos.y);
|
||||
let px2ndc = vec2f(2.0 / max(u.viewport.x, 1.0), 2.0 / max(u.viewport.y, 1.0));
|
||||
ndc = ndc + c * (px2ndc * sizePx);
|
||||
let speed = length(p.vel.xyz);
|
||||
var out: VsOut;
|
||||
out.clip = vec4f(ndc, 0.0, 1.0);
|
||||
|
|
@ -185,7 +195,12 @@ fn vs(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VsOut
|
|||
// "angry" block. Crimson is reserved ONLY for this; nothing else turns red.
|
||||
var crimson = 0.0;
|
||||
if (u.event.x > 0.5 && u.event.x < 1.5) {
|
||||
let d = length(p.pos.xy - vec2f(u.event.z, u.event.w));
|
||||
// p.pos.xy and the event origin (u.event.zw) are now in the SAME clip
|
||||
// space (no aspect divide on position), so the flare lands exactly on
|
||||
// the catch origin. Aspect-correct the distance so the halo is circular
|
||||
// on wide screens rather than an x-stretched ellipse.
|
||||
let aspect = u.viewport.x / max(u.viewport.y, 1.0);
|
||||
let d = length((p.pos.xy - vec2f(u.event.z, u.event.w)) * vec2f(aspect, 1.0));
|
||||
crimson = exp(-d * 1.6) * u.event.y;
|
||||
}
|
||||
out.crimson = clamp(crimson, 0.0, 1.0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue