Skip to content

AGC: SuperTuxKart (PPSA69421): the referee's point light ignores its discard and blacks out the scene behind it #8364

Closed

Description

@LeandroLP23

What

On main 01e5f8f, SuperTuxKart (PPSA69421 00.001.000) draws the starting-line referee solid black during "Ready! / Set! / Go!". The referee is the Thunderbird (models/thunderbird.spm, texture thunderbird.png), and the traffic-light sign it carries (traffic_light.png, shader="unlit" in materials.xml) is black as well. The black is uniform: the shapes are right and they move with the animation, but there is no texture detail. The rest of the tutorial track is lit and textured. Black surfaces in this title are also mentioned in #7307 (Abyss track, NVIDIA).

Reproduce

Tutorial race, no other input: answer "Yes" to the tutorial prompt through APS5_PAD_INPUT_FILE (left stick right, then cross). The countdown starts about 40 s later. The executable is converted with the catalog's --trap-sdk-syscall-stubs.

History: #5321 fixed the same symptom for the rest of the scene

First-parent bisect on the race start, classifying by the brightness of the sand beside the road:

Since then the referee is the remaining black surface. It stays black on every build I tried:

Ruled out

  • Missing texels in guest memory. STK resizes traffic_light.png (128x384, RGB) to a 128x512 RGBA8 texture with 10 mips. When the countdown starts, that texture is sampled with non-zero texels in guest memory: 504 of 1535 sampled 256-byte strides are non-zero. The bytes are there.
  • The storage-image view of the texture. With APS5_NO_SAMPLED_FROM_STORAGE=1, the texture is read as a snapshot, and the referee is still black.
  • The vertex format. thunderbird.spm has the same SPM header as karts/tux/tux.spm: animated (SPMA), normals only, no vertex colours, no tangents. The Tux kart draws correctly.
  • A different shader. The draw-program line just before each lookup of the 128x512 texture is the same vertex/fragment pair that draws the rest of the race. In that run it was 0x263883000/0x263885000, used from the first race frame onwards (about 3,100 draws).

Observation

The black matches texel (0, 0) of both textures:

Texture Texel (0, 0) Average
thunderbird.png (0, 0, 0, 0) (31, 63, 99, 165)
traffic_light.png (13, 13, 12) (37, 33, 16)

So the referee's draw seems to sample one constant coordinate, or a different texture that reads as zero, rather than its stored texels. I haven't found which of the referee's per-draw inputs is wrong: texture coordinates, its texture descriptor, or per-instance data.

Setup

Windows 11 26300, AMD Radeon 860M (driver 32.0.13058.8001), WinLibs GCC 15.2.0, Release. Screenshots of each stage are available if useful.

AI-assisted: yes (Claude Code)

Activity

  1. LeandroLP23 commented on Oct 20, 2026

    @LeandroLP23
    ContributorAuthor

    Found the trigger: the black is the referee's point light, whose pixel shader's discard is not honoured.

    STK attaches a point light to the referee in src/graphics/referee.cpp: addLight(..., 0.7f, 2.0f, 0.7f, 0, 0, ...), red, recoloured with the traffic light. The light pass, data/shaders/pointlight.vert and pointlight.frag, draws each point light as a screen-space quad from UpdateClipRegion. That quad is the axis-aligned black rectangle. Inside it the fragment shader does:

    float att = energy * 20. / (1. + d * d);
    att *= (radius - d) / radius;
    if (att <= 0.) discard;
    ...
    Diff = vec4(Diffuse * NdotL * light_col * att, 1.);

    I patched only pointlight.frag in the run folder; the package and the emulator are unchanged. Same scene, main 01e5f8f:

    pointlight.frag Referee
    unchanged black rectangle, black bird
    discard; as the first statement correct: blue Thunderbird, sign red → yellow → green
    if (att <= 0.) { Diff = vec4(0.); Spec = vec4(0.); return; } instead of discard correct, with the red light visible on the referee

    So the pixels the shader discards (everything farther than radius from the light, which is nearly the whole quad) still reach the light accumulation targets, and the scene there goes black. Writing zero to them explicitly avoids that.

    The compiled pixel shader is the one used only while the referee is on screen (81–83 draws, from race start to "Go!"). In its IR after translate, block $1 (pc 0x00–0x70) samples depth and normals, computes att, and ends with ACO's discard:

    • s0 = s0 & ~vcc (the exact mask);
    • SCC = s0 != 0;
    • exec = wqm(s0).

    Block $5 (pc 0x260–0x26c) is the early-exit block: SetAttribute of zeros to a null export, then the program ends. The two MRT exports at the end of block $4 use exec = s0. So whether a discarded lane is killed depends on that lane's exec reaching the vm export (pixel_valid_mask_active, then OpKill at the end in SpirvModuleEmitter.cpp), on both the early-exit path and the normal path. I haven't yet found which of the two keeps the discarded lanes alive. I can attach the full IR dump of that shader if useful.

    The black mesh of the bird is the same effect: the bird lies inside the light's quad.

    AI-assisted: yes (Claude Code)

  2. changed the title [-]AGC: SuperTuxKart (PPSA69421) draws the starting-line referee and its sign solid black[/-] [+]AGC: SuperTuxKart (PPSA69421): the referee's point light ignores its discard and blacks out the scene behind it[/+] on Oct 20, 2026
  3. LeandroLP23 commented on Oct 20, 2026

    @LeandroLP23
    ContributorAuthor

    Two corrections to my comment above, plus one more data point.

    The early exit is ruled out. I patched pointlight.frag so that every wave keeps live lanes:

    if (att <= 0.) { if ((int(gl_FragCoord.x) & 7) == 0) { Diff = vec4(0.); Spec = vec4(0.); return; } discard; }

    One column in eight returns with zero output, so the exact mask never becomes empty and ACO's discard early-exit block can't be taken. The rectangle is still uniformly black, on main d50ec76. So the discarded lanes survive on the normal path to the exports, lane by lane, and not only when a whole wave is discarded.

    The IR I described is the wrong shader. Program 0x258605000 discards when a value is below 0.5. That is an alpha test, so it is most likely the referee's own material (thunderbird.png has alpha), not pointlight.frag. Its per-lane mask handling looks correct to me: s0 &= ~vcc, the thread bit becomes old bit && !vcc, and the exports use exec = s0.

    I haven't isolated the point-light program yet. Its pair is used only while the referee is on screen, but with APS5_DUMP_IR=all no dumped program contains the 20.0 literal from energy * 20.. Either the light program is compiled through a path that dump doesn't cover, or the compiler folded the literal. The GLSL A/B above is the reliable reproduction in the meantime.

    AI-assisted: yes (Claude Code)

  4. simonbbby commented on Oct 20, 2026

    @simonbbby
    Contributor

    Linux/NVIDIA reproduction and a capture of the actual point-light fragment program.

    SuperTuxKart PPSA69421 00.001.000, catalog SHA-256 f2240bf0e67cf585ae7c6acc16068e1b8bef5e3c1b5f7e8ae550053d937b712a; Ubuntu 24.04.3, GCC 13.3, RTX 3090 / NVIDIA 595.99.02, subgroup 32.

    Baseline qualification: source is main 56b299c3a9f3484b2db38021964f0117f8701f93 plus only #7897's ApplicationHeap.cpp production hunk. Unmodified Linux main stops earlier: GDB observes strdup("u_fog_color") return null, then Mesa's hash path reaches strlen(nullptr) while linking its first GL program. The allocator hunk lets it reach the race. This is not an unmodified-main Linux rendering result.

    Rebuilt and reran on newer main 44e3389c882c8ee3249e16933aad6479c3e00d08 plus the same allocator hunk: the original shader still produces the rectangle, including after #7954's pass-input barrier fix. The controls and captured request described below belong to 56b299c3, not that newer rerun.

    The tutorial prompt reproduces the black referee/rectangle. For repeatable shader controls I also launched ./app.elf -N -t tutorial --numkarts=1, with a fresh process, ANYPS5_NO_SHADER_CACHE=1, APS5_NO_PIPELINE_JOURNAL=1, and the NVIDIA ICD restricted:

    Control Observed countdown
    Original title shader Black rectangle and black referee
    Only if (att <= 0.) discard; replaced with explicit zero Diff/Spec and return Rectangle gone; blue referee/traffic light visible
    Original title shader, only the isolated fragment's materialized SPIR-V entry replaced temporarily with unconditional OpKill, keeping the draw metadata Rectangle gone; blue referee visible; this suppresses the whole light, not a proposed fix
    Original shader, backend valid-mask initializer changed 1 → 0 Still black; rejected

    Program isolation: the zero-output edit changes exactly one fragment's normalized after resources IR: 0x2553fd000, paired with vertex 0x2553fb000 in these runs. The 0x41a00000 (20.0) constant is in that vertex program's IR, explaining why searching only pixel programs misses it. These addresses are run-specific.

    The fragment is 308 dwords, wave32, pixelKillEnable=1. I captured its request after draw SRT memory materialization; a preparation-stage dump alone cannot replay the runtime descriptor reads. The captured request successfully recompiles standalone (35,016 SPIR-V words); this is a compile replay, not a standalone GPU rendering reproduction. The actual runtime artifact also contains the valid-mask stores and three conditional OpKill paths; a simply missing kill instruction is not established. A small separate wave32 WQM → s_andn2_b32 discard → VM export control passes on this RTX 3090, so that sequence alone does not reproduce the defect.

    This isolates a reproducible title-backed case and identifies the previously unidentified light program; it does not yet establish a production fix. Temporary diagnostics and the rejected initializer were removed. No Khronos validation layer is installed on this runner, and no Windows/performance/full-suite claim is made.

  5. LeandroLP23 commented on Oct 20, 2026

    @LeandroLP23
    ContributorAuthor

    Narrowed further. This now looks less like the shader's kill and more like what happens to killed pixels in a draw whose other pixels write.

    The point-light program and its SPIR-V. The program is 0x255213000 (vertex, the only one with the 20.0 literal and UpdateClipRegion's square roots) with 0x255215000 (fragment). I dumped its SPIR-V locally from Recompiler.cpp, right before validation and optimization, and disassembled it. The kill looks right:

    • the vm export stores pixel_valid_mask_active from the export's exec;
    • that exec is !helper && !(exec && att < FLT_MIN), as a phi over the spotlight branch;
    • at the end, the shader runs OpKill unless the mask is set or gl_HelperInvocation is true;
    • the two early-exit blocks also store the mask and kill.

    The program runs on every race frame, before and after the referee is on screen. I haven't identified which other lights it draws there, or whether their quads cover visible geometry.

    pointlight.frag variants (same scene, main d50ec76, only the shader file in the run folder changed):

    Variant Referee
    unchanged (if (att <= 0.) discard;) black rectangle
    if (!(att > 0.)) discard; (NaN discarded too) black rectangle
    spotlight branch removed (if (sscale != 0.) → if (false)) black rectangle
    one column in eight writes zero instead of discarding black rectangle
    discard; first, so every pixel is discarded correct
    { Diff = vec4(0.); Spec = vec4(0.); return; } instead of discard correct

    So att is not NaN, and the spotlight branch and ACO's early exit don't matter. The rectangle goes black only when one draw has both written and discarded pixels. Then the discarded pixels don't keep what the light-accumulation targets held before the draw.

    My guess is that the problem sits in how the driver handles these targets (Diff/Spec, additively blended) rather than in the shader. For example, the pixels the draw covers could be stored from an image that wasn't loaded with the target's previous contents, which would be fine for written pixels and wrong for killed ones. I haven't confirmed it. Next I want to look at the color-target path for this draw.

    AI-assisted: yes (Claude Code)

  6. simonbbby commented on Oct 20, 2026

    @simonbbby
    Contributor

    I tried a smaller target-preservation control on main 5f7e30233e62ae5fdb7a6e3257698bc2e550bcc2. It does not reproduce the loss yet:

    • Two linear R16G16B16A16_SFLOAT MRTs, wave32, compressed FP16 exports, ONE + ONE additive blending for color and alpha, pixelKillEnable=true.
    • A full GPU draw first seeds both targets. Two subsequent draws retain even columns and discard odd columns using the save-EXEC / WQM / restore-exact-EXEC / vm export sequence.
    • Every channel in every pixel is checked. MRT0 starts at 0.5, so the seeded/discarded columns must remain 1.5 and written columns become 3.5. MRT1 starts at 0.25: expected 1.25 / 3.25.
    • 3/3 fresh processes pass on RTX 3090, NVIDIA 595.99.02; 3/3 pass on gfx1036, RADV Mesa 25.2.8, Ubuntu 24.04. Shader cache disabled. No Khronos validation layer is installed here, so this is not a validation-layer result.

    This only checks linear, uncompressed-metadata targets through Graphics::Draw; it does not reproduce STK's actual attachment layout, aliases, depth state or preceding resource transitions. It leaves the title-specific target load/preservation hypothesis open, while providing a passing control for mixed discard plus GPU-written additive MRT contents.

    The test-only patch against that main is below; no production change is proposed.

    Mixed-discard MRT control (replace the existing PixelValidMask fixture temporarily)
    diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PixelValidMask.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PixelValidMask.cpp
    index 2b8f7b61f..db493903a 100644
    --- a/core/libs/prx/libSceAgcDriver/tests/execution/PixelValidMask.cpp
    +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PixelValidMask.cpp
    @@ -16,8 +16,9 @@ using ShaderRecompiler::ShaderStage;
     
     constexpr std::uint32_t Width = 64;
     constexpr std::uint32_t Height = 32;
    -constexpr std::byte Kept{0x40};
    -alignas(256) std::array<std::byte, Width * Height * 4> Pixels{};
    +constexpr std::uint16_t Kept=0x3800u;
    +alignas(256) std::array<std::uint16_t, Width * Height * 4> Pixels{};
    +alignas(256) std::array<std::uint16_t, Width * Height * 4> Pixels2{};
     
     alignas(256) constexpr std::array<std::uint32_t, 6> VertexCode{
         0xe0382000, 0x80000005, 0xbf8c3f70, 0xf80008cf, 0x03020100, 0xbf810000,
    @@ -36,12 +37,17 @@ std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t st
         return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((address >> 32u) & 0xffffu) | (stride << 16u), count, 0x01016facu};
     }
     
    +alignas(256) constexpr std::array<std::uint32_t, 19> MixedCode{0xbe80037eu,0xbefe097eu,0x7e041100u,0x36060481u,0x7d8a0680u,0x8a006a00u,0xbefe0900u,0xbf880008u,0xbefe0300u,0x7e0e02ffu,0x3c003c00u,0xf800040fu,0x707u,0xf8001c1fu,0x707u,0xbf810000u,0xf8001890u,0x0u,0xbf810000u};
    +
    +alignas(256) constexpr std::array<std::uint32_t, 7> SeedCode{0x7e0e02ffu,0x3c003c00u,0xf800040fu,0x707u,0xf8001c1fu,0x707u,0xbf810000u};
    +
     constexpr std::array<std::array<float, 4>, 3> Triangle{{
         {-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}
     }};
     
     void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, std::span<const std::uint32_t> pixelCode) {
         Pixels.fill(Kept);
    +    Pixels2.fill(0x3400u);
         const auto target = device.Target();
     
         std::vector<std::uint32_t> vertexUserData(4, 0u);
    @@ -63,7 +69,9 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, std::span<con
         pixel.inputAddr = ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionX) | ShaderRecompiler::PixelInputBit(ShaderRecompiler::PixelInput::PositionY);
         pixel.posX = true;
         pixel.posY = true;
    -    pixel.targetOutputMode[0] = 9;
    +    pixel.targetOutputMode[0] = 4;
    +    pixel.targetOutputMode[1] = 4;
    +    pixel.pixelKillEnable = true;
         pixel.targetExportMapping.fill(0xe4u);
         const std::vector<std::uint32_t> pixelUserData(8, 0u);
         const std::array<ShaderRecompiler::MemoryRegion, 1> pixelMemory{{{reinterpret_cast<std::uintptr_t>(pixelCode.data()), std::as_bytes(pixelCode)}}};
    @@ -82,8 +90,13 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, std::span<con
     
         AgcDriver::Graphics::State state{};
         state.stages = {AgcDriver::Graphics::ShaderPath::Vertex, 0u, waveSize, waveSize, std::nullopt, std::nullopt};
    -    state.color = {reinterpret_cast<std::uintptr_t>(Pixels.data()), {Width, Height}, VK_FORMAT_R8G8B8A8_UNORM, Pixels.size(), 0xe4u};
    -    state.colors = {state.color};
    +    state.color = {reinterpret_cast<std::uintptr_t>(Pixels.data()), {Width, Height}, VK_FORMAT_R16G16B16A16_SFLOAT, sizeof(Pixels), 0xe4u};
    +    state.color.elementBytes = 8;
    +    auto second = state.color;
    +    second.address = reinterpret_cast<std::uintptr_t>(Pixels2.data());
    +    second.slot = 1;
    +    second.exportIndex = 1;
    +    state.colors = {state.color, second};
         state.hasColorTarget = true;
         state.renderExtent = {Width, Height};
         state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
    @@ -93,25 +106,44 @@ void Draw(AgcDriver::VulkanDevice& device, std::uint32_t waveSize, std::span<con
         state.cullMode = VK_CULL_MODE_NONE;
         state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
         state.blend.colorWriteMask = 15;
    -    state.blends = {state.blend};
    +    state.blend.blendEnable = true;
    +    state.blend.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
    +    state.blend.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
    +    state.blend.colorBlendOp = VK_BLEND_OP_ADD;
    +    state.blend.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
    +    state.blend.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
    +    state.blend.alphaBlendOp = VK_BLEND_OP_ADD;
    +    state.blends = {state.blend, state.blend};
         state.blendConstants = {};
         const AgcDriver::Pm4::DrawParameters draw{0, static_cast<std::uint32_t>(Triangle.size()), 0, 1, 0, false};
    +    const std::array<ShaderRecompiler::MemoryRegion, 1> seedMemory{{{reinterpret_cast<std::uintptr_t>(SeedCode.data()), std::as_bytes(std::span(SeedCode))}}};
    +    auto seedRequest = fragment;
    +    seedRequest.shader.codeAddress = reinterpret_cast<std::uintptr_t>(SeedCode.data());
    +    seedRequest.shader.code = SeedCode;
    +    seedRequest.context.memory = seedMemory;
    +    const auto seedResult = ShaderRecompiler::Recompile(seedRequest);
    +    const std::array<AgcDriver::Graphics::CompiledShader, 2> seedShaders{{
    +        {ShaderStage::Vertex, &vertexResult, 0},
    +        {ShaderStage::Fragment, &seedResult, PixelPushOffset(vertexPush, target)}
    +    }};
    +    device.Draw(state, draw, seedShaders);
    +    device.Draw(state, draw, shaders);
         device.Draw(state, draw, shaders);
         device.WaitIdle();
     }
     
    -void Check(std::uint32_t waveSize, bool masked) {
    -    const auto what = "pixel valid mask wave" + std::to_string(waveSize) + (masked ? " odd columns cleared" : " full EXEC");
    +void Check() {
         for (std::uint32_t pixel = 0; pixel < Width * Height; ++pixel) {
    -        const bool exported = !masked || (pixel % Width) % 2u == 0u;
    -        const auto expected = exported ? std::byte{255} : Kept;
    +        bool write = (pixel % Width) % 2u == 0u;
             for (std::uint32_t channel = 0; channel < 4; ++channel) {
    -            const auto actual = Pixels[pixel * 4u + channel];
    -            Require(actual == expected, what + ": pixel " + std::to_string(pixel) + " channel " + std::to_string(channel) + " is " + std::to_string(static_cast<unsigned>(actual)) + ", expected " + std::to_string(static_cast<unsigned>(expected)) + (exported ? " (exported)" : " (cleared from EXEC before the vm export)"));
    +            const auto i=pixel*4u+channel;
    +            Require(Pixels[i] == (write ? 0x4300u : 0x3e00u), "MRT0 mismatch at pixel " + std::to_string(pixel) + " actual " + std::to_string(Pixels[i]));
    +            Require(Pixels2[i] == (write ? 0x4280u : 0x3d00u), "MRT1 mismatch at pixel " + std::to_string(pixel) + " actual " + std::to_string(Pixels2[i]));
             }
         }
     }
     
    +
     }
     
     int main() {
    @@ -122,13 +154,9 @@ int main() {
                 std::printf("skipped, subgroup size %u cannot hold a wave32\n", device->Target().subgroupSize);
                 return VulkanTestSkipped;
             }
    -        for (const auto waveSize : {64u, 32u}) {
    -            Draw(*device, waveSize, FullPixelCode);
    -            Check(waveSize, false);
    -            Draw(*device, waveSize, MaskedPixelCode);
    -            Check(waveSize, true);
    -        }
    -        std::puts("pixel valid mask tests passed");
    +        Draw(*device,32u,MixedCode);
    +        Check();
    +        std::puts("mixed-discard two compressed FP16 MRT additive preservation, GPU seed plus two draws passed");
             return 0;
         } catch (const std::exception& error) {
             std::cerr << error.what() << '\n';
    
  7. LeandroLP23 commented on Oct 20, 2026

    @LeandroLP23
    ContributorAuthor

    Root cause found, and it is neither the kill nor the light targets as I guessed above: the point light's flat inputs all read 0.

    pointlight.frag reads its 12 flat inputs through v_interp_mov_f32 P20. That is ps5-opengl's flatshade_first lowering, vertex 2 of a triangle. Under the first provoking vertex, main read each one as vertex 2 minus vertex 0, which is 0 for a flat value. So att = 0 * -inf = NaN, and att <= 0. is false for NaN, so nothing was discarded and the whole quad wrote NaN. My earlier !(att > 0.) variant didn't catch it, because the shader compiler is allowed to fold it back into att <= 0.. A bit test on att does catch it, and then the referee renders. A per-input check showed radius exactly 0.

    Fix: #8538. It reads such a P20 read as the provoking vertex, like #4030 does for P10. With it, the unmodified game draws the referee correctly in 2 of 2 runs.

  8. Betr0x commented on Oct 27, 2026

    @Betr0x
    Contributor

    @LeandroLP23 A question about SuperTuxKart (PPSA69421), since this issue is the most detailed run of it I could find: roughly what frame rate do you see in the tutorial race on your setup, and which OS/GPU is that on? A number from the [present] line or the window title is enough.

    Background: on macOS (Apple M4, x86-64 under Rosetta 2, MoltenVK 1.4.2) the menus run at ~25 FPS but 3D scenes (story-mode overworld) at ~1.7 FPS. Most of that is the macOS write watch (~25,000 page faults per frame at ~18 µs each under Rosetta); with a local prototype that re-arms rewritten pages once per present it reaches ~6 FPS. A Windows/Linux figure would show how much of the remaining gap is macOS-specific.

    AI-assisted: yes

  9. LeandroLP23 commented on Oct 27, 2026

    @LeandroLP23
    ContributorAuthor

    @Betr0x On Windows the tutorial race now runs at about 12 FPS on current main.

    Setup: Windows 11 26300, Radeon 860M iGPU (AMD 32.0.13058.8001), Ryzen AI 7 350, WinLibs GCC 15.2.0, Release, main 3fea2a937b. PPSA69421 00.001.000 is converted with the catalog's settings, and the race is started through APS5_PAD_INPUT_FILE ("Yes" on the tutorial prompt, then cross).

    Scene FPS (window title, 5 s samples)
    Main menu with the tutorial prompt 9.1 to 9.8
    Tutorial race, first 60 s after the countdown 11.7 to 12.3

    The title's own counter agrees: 11.6 to 12.0 fps over 5 s. The slowest frame is 99 to 118 ms, and the main thread is at about 33 to 42 % of one core.

    For comparison, the same race on this machine measured 1.70 and 2.16 FPS on main d34da5d694 (Oct 22), as posted in #6718. Since then #6718 and #7465 have landed. #6718 alone gave 2 to 3 times in that thread, by removing a storage round trip on every pass. So on Windows, most of the old gap was not the write watch.

    AI-assisted: yes (Claude Code)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions