From f67f9ca5dd80ba670367af42ce2dbd20ad880644 Mon Sep 17 00:00:00 2001 From: grom Date: Mon, 26 Oct 2026 20:42:00 -0600 Subject: [PATCH] Fix: the bright pixels along interior seams are a centroid bug under MSAA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit grom's screenshots: a one-pixel-wide dotted line of saturated colour running down a wall seam in Ironforge, still when he is still. The colours are the tell — pure white, pure blue, orange, red against a (26,32,45) background — so this was never a lighting level or a crack letting the outside in. It is the Great Forge's own lighting read off a cliff. It is MSAA, which he has at 8x through the Multisampling row I seated last week. A partially covered pixel is shaded ONCE, at the pixel centre, and at a geometry edge that centre can lie OUTSIDE the triangle — so a centre-interpolated varying is EXTRAPOLATED past its vertices. For `point_lit`, which is a SUM OF POINT LIGHTS accumulated per vertex, that does not nudge a value, it runs the sum away: the fragment comes back the saturated colour of whatever light dominates that vertex. Centroid moves the sample point back inside the covered area, so every varying is interpolated and never extrapolated. Reproduced, bisected and fixed by measurement on `daylight-if-forge`, counting pixels that differ from the 1x render by more than 60: 1x baseline 0 8x, before 6 (255,255,255) (36,34,255) (255,52,40) (190,26,33) ... 8x, post chain off 6 not the post chain 8x, static_gx off 0 the lane is the source 8x, centroid on uv alone 6 not the texture lane 8x, centroid on point_lit and color 0 Verified across four interiors: Great Forge, Ironforge forge, Stormwind cathedral, the Goldshire inn. Every MSAA difference that survives is ordinary edge blending — each value lies inside the range of its own neighbours — and **the 1x render is byte-identical to before this change**, which is what centroid should be when every pixel is fully covered. Two things I got wrong on the way, both worth recording: - I reported MSAA as inert. It is not. `gxMultisample` is a LATCHED row, so `WOW_CVAR=gxMultisample=8` only STAGES it and the registry still reads 1 — my A/B was two 1x runs. Through `config.toml`, the path a player actually uses, it reaches the camera and 15 pipelines compile at `samples=8`. A latched row cannot be A/B'd with `WOW_CVAR`; it needs `WOW_CAPTURE_CVARS`. - On that false reading I added a `publish_msaa_setting` system to force the value in at `CvarLoad`. It was a no-op against a bug that was not there, and it is reverted. `uv` keeps its centroid even though it was not the culprit: it is the same class of error on the same lane, where an extrapolated UV into the shared pool lands on unrelated texels, and it costs nothing. `world_position` and `world_normal` are left alone deliberately — four interiors measure clean without them, and a change with no measurement behind it is how the last two rounds went wrong. Verified: ALL GATES GREEN (315 s); `benilla-app --lib` against the install at the exact 269-failure baseline, set-identical by `comm`. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn --- .../benilla-world/src/shaders/static_gx.wgsl | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/benilla-world/src/shaders/static_gx.wgsl b/crates/benilla-world/src/shaders/static_gx.wgsl index fb968169..859018e9 100644 --- a/crates/benilla-world/src/shaders/static_gx.wgsl +++ b/crates/benilla-world/src/shaders/static_gx.wgsl @@ -1264,10 +1264,28 @@ struct GxVsOut { @builtin(position) position: vec4, @location(0) world_position: vec4, @location(1) world_normal: vec3, - @location(2) uv: vec2, + // CENTROID, and this is load-bearing under MSAA. A partially covered pixel is shaded ONCE, at + // the pixel centre — and at a geometry edge that centre can lie OUTSIDE the triangle, so a + // centre-interpolated varying is EXTRAPOLATED past its vertices. For a UV into this lane's + // shared pool that lands on unrelated texels, and the fragment comes back a saturated colour + // that exists nowhere near it: single bright pixels strung along every seam, which is what + // `gxMultisample 8` used to paint across interior walls. Centroid moves the sample point back + // inside the covered area, so the UV is interpolated and never extrapolated. + // + // Measured on `daylight-if-forge`: six such pixels at 8x — (255,255,255), (37,35,255), + // (204,26,33) against a (26,32,45) background — and none with this. The entity path does not + // show it, which is why the fix is here and not in `wow_model.wgsl`. + @location(2) @interpolate(perspective, centroid) uv: vec2, @location(3) @interpolate(flat) word: u32, - @location(4) point_lit: vec3, - @location(5) color: vec4, + // CENTROID, for the reason the UV above carries it — and this is the one that was actually + // painting the seams. `point_lit` is a SUM OF POINT LIGHTS accumulated per vertex, so + // extrapolating it past the triangle does not shift a texel, it runs the sum away: the + // fragment comes back the saturated colour of whatever light dominates that vertex. The six + // stray pixels measured on `daylight-if-forge` were (255,255,255), (255,133,95), (36,34,255), + // (255,145,51), (255,52,40) and (190,26,33) — white, orange, blue, orange, red and red, which + // is the Great Forge's own lighting read off a cliff, not texture garbage. + @location(4) @interpolate(perspective, centroid) point_lit: vec3, + @location(5) @interpolate(perspective, centroid) color: vec4, // MONKEY (outdoor torch shadows; ext light k8): WHICH ≤`EXT_SEL_K` exterior table entries // `point_lit` was summed from, packed 8 bits each across FOUR u32s (see `EXT_SEL_NONE` / // `ext_sel_get`). FLAT, because it is a choice, not a quantity — interpolating packed indices