Fix: the bright pixels along interior seams are a centroid bug under MSAA

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-26 20:42:00 -06:00
co-authored by Claude Opus 5
parent 77fce9210e
commit f67f9ca5dd
@@ -1264,10 +1264,28 @@ struct GxVsOut {
@builtin(position) position: vec4<f32>,
@location(0) world_position: vec4<f32>,
@location(1) world_normal: vec3<f32>,
@location(2) uv: vec2<f32>,
// 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<f32>,
@location(3) @interpolate(flat) word: u32,
@location(4) point_lit: vec3<f32>,
@location(5) color: vec4<f32>,
// 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<f32>,
@location(5) @interpolate(perspective, centroid) color: vec4<f32>,
// 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