#133 Mac (native Metal): per-channel color corruption in the world render, progressive over a session
Status: Awaiting review · Area: Graphics & Art · Rendering & Performance
On native Mac, the 3D world shows strong color tints that worsen over a session while the interface looks normal.
Current behavior
On the native Mac client, the world render loses one or more RGB channels. Affected surfaces render with a strong color cast — magenta, yellow, cyan, green — or with one channel scaled down while the others stay correct. It is present on a fresh load and gets worse over a session. **The UI is not affected.** In the same frame, action bars, nameplates and chat render correct colors while the world does not. In one capture the maximum blue value anywhere in the world render was 34, while UI elements in that same frame reached 240–254. That split is the clearest signature of this bug and the easiest thing to check. **Measured, Duskwood, build 1.60.1.70009** (macOS Digital Color Meter, native display values): | | R | G | B | R/G | |--------------|-----:|-----:|-----:|-----:| | tree bark | 4.9 | 36.9 | 52.0 | 0.13 | | grass | 10.0 | 39.5 | 46.8 | 0.25 | | canopy | 5.7 | 33.2 | 46.1 | 0.17 | | dead tree | 2.2 | 33.3 | 47.4 | 0.07 | Bark rendering with red at a seventh of green is not a moody zone. **A channel goes fully dead, not just dim.** A stone wall in the Dwarven District samples at R 0, G 58, B 0 — red and blue both completely gone, green untouched. Color Meter on an adjacent green surface in the same scene reads R 0, G 66, B 0. **Two distinct failure modes run at once.** Across 483 Color Meter samples, red was at exactly 0 in 84% of them with a median of 0, and its value correlated with surface brightness at 0.04 — that is, it was off regardless of lighting. Blue was at exactly 0 in 15% of samples, median 6, correlating with brightness at 0.48. One channel is being zeroed; another is being attenuated across a continuous range with no quantization gaps. **It progresses over a session with nothing else changing.** Same spot, same camera, about three minutes apart: **It spreads by area, not only by getting deeper.** Channel ratios inside an affected patch stay roughly constant while the patch itself grows. These three are seconds apart from the same vantage point, watching a green region eat into an amber scene: I mention this because I spent several days measuring channel ratios in fixed regions, which by construction cannot see area spread. If you are checking whether a change helped, measure the affected area, not just the depth of the cast. **The affected region has hard edges that follow world geometry, not the screen.** Tracking the visible boundary across camera movement gives 2.7px error against a texture-space model and 12.7px against a screen-space one. **It is not tied to one zone or one palette.** Same fault, four zones, four different casts: **It gets severe enough to be mistaken for geometry rendering black, and that matters.** Two frames of the same Elwynn hillside minutes apart: earlier later whole frame R 75.2 42.2 G 59.0 49.9 B 17.7 11.7 R/G 1.28 0.85 foliage clumps, darkest 10% mean luminance 24.4 3.9 R / G / B 35/31/8 3/4/4 % of frame below luminance 20 1.2% 20.3% Each of these is a quick progression over a few seconds gradually turning each tile affected close to black. Exact-zero pixels in that frame are 0.149%. I am flagging this because it looks identical to a separate black-geometry fault that was fixed in this build, and the only reliable discriminator is exact zero versus merely dark. Measure before concluding either way. **Where it is not.** Ruled out by testing: time of day, screen content, camera position, altitude, zone, indoor vs outdoor, instance vs overworld, faction, character age, texture resolution, projected textures, and every other graphics setting. Clearing `Cache/` and `WTF/` does not affect it. It appears on a brand-new character in a never-visited zone, and in the new-character intro cinematic before the world loads at all — in that cinematic red carries 232–255 distinct levels per frame while green carries about half that and blue less. The 2D loading screen immediately before that cinematic measures completely clean: 158,953 lit pixels, zero exact-zero channels. **It does not reproduce under D3D12 translated to Metal on the same machine.** Running the Windows client under CrossOver (Rosetta x86_64 → D3D12 → Game Porting Toolkit 3.0 → Metal) on the same Mac — same GPU, same Apple driver, same Metal framework, same build, same settings, same character, same coordinates, minutes apart: | | native Metal | D3D12 → Metal | |--------------------|-------------------------|----------------------------| | cliff face | R 66.5 G 5.5 B 27.4 | R 98.8 G 94.6 B 104.7 | | second cliff patch | R 44.6 G 2.8 B 15.3 | R 126.8 G 100.4 B 95.1 | | blue slate roof | R 26.3 G 40.5 B 73.3 | R 68.8 G 73.8 B 82.1 | Green reads 5.5 and 2.8 on native cliff faces. The same surfaces under translation read 94.6 and 100.4, with all 256 levels present in every channel. **To be clear about why I ran that: it is a diagnostic, not a recommendation.** I am not suggesting anyone use CrossOver to play, and I would rather this didn't become the Mac workaround of the week. Holding the hardware constant and swapping only the graphics backend is the cleanest way I have to say where the fault is not. It rules out the hardware, Apple's Metal driver, the GPU, and the lighting math — both backends run the same algorithms from the same source. What differs is that the native path compiles Blizzard's own Metal shaders and builds their Metal render passes, while the translated path runs the same DXIL converted to Metal by a third-party layer. **System:** Mac Studio (2025), M4 Max, 128 GB, macOS 26.7 (25G229), 2 external displays. **Build:** 1.60.1.70009. Also present on 1.60.1.69977. --------------------------------------------------------------------------------
Expected behavior
World geometry should render with all three color channels intact, at the same fidelity the UI render path already achieves in the same frame. Concretely: - A neutral gray surface should measure R ≈ G ≈ B. It currently measures ratios as low as 0.24. - No channel should sit at exactly 0 across large areas of lit, textured geometry. - Rendered color should not change over a session with no change in location, time of day, or settings. - The native Metal backend should produce the same channel values as the D3D12-translated path does on the same hardware for the same scene. This is what correct looks like on this machine, same client, same session:
How to reproduce
1. Launch the Mac client (native, Graphics API: Metal) on Apple Silicon. 2. Create a new character, or log in an existing one. No particular zone is required — this reproduces everywhere I have tested, including indoors and inside instances. 3. If you made a new character, watch the intro cinematic. The corruption is already present there, before the world loads. 4. Enter the world and go to a surface whose correct color is not in question — gray stone in the Stormwind Trade District is a good one, because R, G and B should read close to equal on it. 5. Take a **macOS** screenshot (Cmd+Shift+4), not the in-game screenshot key, so the capture path doesn't recompress anything. 6. Sample the surface with Digital Color Meter (built into macOS, set to "Display native values") and compare it against a UI element in the same frame. 7. Play normally for 20–30 minutes without changing zone or settings, then repeat steps 4–6 at the same spot with the same camera angle. The ratios will have moved further. **Two traps worth knowing about, because both caught me:** - **A zone's own palette can hide this fault or mimic it.** Tirisfal's green-gray hid a green cast from me for an hour; Durotar's orange made red-dominant corruption look like normal lighting. Do not judge by the terrain. Judge on a surface whose color you already know, and prefer a paired control — same spot, same camera, one variable changed. - **Do not hold a surface fixed and wait.** Lighting changes with time of day, so any fixed-surface-over-time protocol produces a false positive. Use a zone with static lighting, or compare against a UI element in the same frame.