Skip to content

Commit e75224c

Browse files
DennisSmolekclaude
andcommitted
fix: corpus-wide B17 repair — 14 more frozen examples un-frozen
Full-corpus pixel-diff + frameCount audit after flames find: every ungated suspending hook froze its example (loop alive, pixels dead on the old root). Explicit Suspense gates added to rtt, skinning-instancing, tsl-halftone, instance-mesh, lights-phong, lights-spotlight, materials-basic, materials-envmaps, tonemapping, and 5 loader-gltf-* ports; all probe-verified animating. All four SwiftShader CI stall examples were B17 cases — the stall matrix is plausibly this bug (watch next advisory CI run; ciSkips may be removable). B17 brief expanded, AGENTS v0.13, HANDOFF audit section. 59/59 smoke green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d6fc92f commit e75224c

18 files changed

Lines changed: 168 additions & 79 deletions

AGENTS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,16 @@ override lands with an UPSTREAM.md entry in the same commit.** Highlights:
332332

333333
## Changelog
334334

335+
- 2026-07-27 — v0.13: corpus-wide B17 audit + repair (wave-8 interlude). A
336+
full-corpus pixel-diff + frameCount sweep found 14 more B17-frozen examples
337+
beyond flames' original three — every ungated suspending hook in the corpus,
338+
including all four SwiftShader-stall examples (that CI mystery is plausibly
339+
THIS bug; try dropping ciSkips). All repaired with explicit Suspense gates and
340+
probe-verified. Fingerprint for the future: `__frameCount` advances while
341+
pixels freeze = dual-root, check for the createRoot warning. Probe windows
342+
must exceed stop-go animation periods (postprocessing-pixel false-alarmed at
343+
1.5s; clean at 4s). Statics-by-design confirmed: morphtargets, depth-texture,
344+
tonemapping, postprocessing-ao, compute-texture family.
335345
- 2026-07-27 — v0.12 amendments from wave-7 pair 4 (tsl-vfx-flames +
336346
tsl-vfx-tornado — wave 7 closes at 57 examples): **the explicit-Suspense rule**
337347
(Canvas-boundary suspension re-runs createRoot on alpha.3 and freezes all TSL

docs/HANDOFF.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
# Session Handoff — 2026-07-27 (overnight, continued: repo live + M2 waves 1–2)
22

3+
## Wave 8 interlude: corpus-wide B17 audit (same night)
4+
5+
Flames' B17 find (wave 7) triggered a full-corpus animation audit: two-frame
6+
pixel-diff + `__frameCount` probes over all examples. Result: **14 more frozen
7+
examples repaired** (every ungated suspending hook in the corpus — rtt,
8+
skinning-instancing, tsl-halftone, instance-mesh, lights-phong, lights-spotlight,
9+
materials-basic, materials-envmaps, tonemapping, five loader-gltf-* ports), all
10+
verified animating post-fix. Fingerprint: loop alive (`__frameCount` advances) +
11+
pixels frozen + `R3F.createRoot should only be called once!` warning.
12+
13+
**Big thread: all four SwiftShader CI stall examples were B17 cases.** The stall
14+
mystery is plausibly this bug — next CI pass, watch the advisory smoke job; if
15+
the four now pass on SwiftShader, drop their `ciSkip`s and consider flipping
16+
smoke back to blocking.
17+
18+
Statics-by-design confirmed (0px, clean console, full-rate loop): morphtargets,
19+
depth-texture, tonemapping, geometry-loft*(see B17 open anomaly: warning with no
20+
suspending hook), postprocessing-ao, loader-gltf-sheen/-compressed.
21+
Follow-up still queued: the pixel-diff "animates" smoke assertion with a
22+
`static: true` manifest flag (probe windows must exceed stop-go periods — 4s).
23+
324
## Wave 7 (same night)
425

526
8 ports, 4 pairs — postprocessing cluster (6) + TSL VFX pair (2). **57 examples

docs/UPSTREAM.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,23 @@ commit** (AGENTS.md points agents at this file).
254254
createRoot warning, all three had suspending `useTexture` with no explicit
255255
boundary. Adding `<Suspense fallback={null}>` inside Canvas fixes all of them
256256
(post-fix: 12k–75k px/s changing, zero warnings).
257+
- **Full-corpus audit (wave 8)**: a systematic sweep found 14 MORE affected
258+
examples (every ungated suspending hook in the corpus): rtt,
259+
skinning-instancing, tsl-halftone, instance-mesh, lights-phong,
260+
lights-spotlight, materials-basic, materials-envmaps, tonemapping, and the five
261+
loader-gltf-* single-model ports. All repaired the same way and verified
262+
animating (or legitimately static with clean consoles + full-rate loops) by
263+
pixel-diff + `__frameCount` probes. Mechanism note: the frame loop KEEPS
264+
RUNNING (`__frameCount` advances) while the displayed canvas stays on the dead
265+
root's last frame — "loop alive, pixels frozen" is the fingerprint.
266+
- **Likely explains the SwiftShader CI stall matrix**: all four stall examples
267+
(skinning-instancing, rtt, tsl-halftone, sprites) were B17 cases — on
268+
software raster the dual-root race plausibly lands so the readiness signal
269+
never fires at all. Try removing the `ciSkip`s after this repair lands.
270+
- **Open anomaly**: `geometry-loft` logs the same createRoot warning with NO
271+
suspending hook anywhere in the example (17 heavy LoftGeometry exhibits, slow
272+
first render) — still animates, but the loop degraded to ~5fps under probe
273+
conditions. Different trigger for the same re-entry?
257274
- **Workaround in repo**: Layer 1 rule — every suspending subtree inside Canvas
258275
gets an explicit Suspense boundary.
259276
- **Suggested fix**: guard the root-creation path against the re-entry that

src/examples/instance-mesh.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
* - DemoHelpers baseline (grid + camera controls) added; original had a fixed camera
3030
* with no user interaction.
3131
*/
32-
import { useEffect, useMemo, useRef } from 'react'
32+
import { Suspense, useEffect, useMemo, useRef } from 'react'
3333
import { Canvas, useFrame, useLoader } from '@react-three/fiber/webgpu'
3434
import { useControls } from 'leva'
3535
import { mix, normalWorld, oscSine, range, time } from 'three/tsl'
@@ -134,7 +134,11 @@ export default function InstanceMesh() {
134134
background="#000000"
135135
camera={{ position: [amount * 0.9, amount * 0.9, amount * 0.9], fov: 60, near: 0.1, far: 100 }}
136136
>
137-
<InstancedSuzanne key={amount} amount={amount} visible={Math.min(visible, count)} />
137+
{/* B17 gate: ungated suspension reaching Canvas's boundary re-runs createRoot
138+
and freezes the displayed scene (AGENTS.md; corpus-wide repair, wave 8). */}
139+
<Suspense fallback={null}>
140+
<InstancedSuzanne key={amount} amount={amount} visible={Math.min(visible, count)} />
141+
</Suspense>
138142
<DemoHelpers grid={false} />
139143
</Canvas>
140144
)

src/examples/lights-phong.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
* declare `fogNode` even though the WebGPU renderer's `NodeManager` reads it directly
4242
* off the live scene instance (same documented gap as `sprites.tsx`, AGENTS.md B11)
4343
*/
44-
import { useEffect, useMemo } from 'react'
44+
import { Suspense, useEffect, useMemo } from 'react'
4545
import { Canvas, useFrame, useThree } from '@react-three/fiber/webgpu'
4646
import { useTexture } from '@react-three/drei/webgpu'
4747
import { folder, useControls } from 'leva'
@@ -214,12 +214,16 @@ export default function LightsPhong() {
214214
return (
215215
<Canvas renderer background="#000000" camera={{ position: [0, 0, 7], fov: 50, near: 0.01, far: 100 }}>
216216
<SceneFog fogColor={fogColor} near={fogNear} far={fogFar} />
217-
<Teapots
218-
leftLightKey={leftLightKey as LightKey}
219-
rightLightKey={rightLightKey as LightKey}
220-
centerShininess={centerShininess}
221-
rightShininess={rightShininess}
222-
/>
217+
{/* B17 gate: ungated suspension reaching Canvas's boundary re-runs createRoot
218+
and freezes the displayed scene (AGENTS.md; corpus-wide repair, wave 8). */}
219+
<Suspense fallback={null}>
220+
<Teapots
221+
leftLightKey={leftLightKey as LightKey}
222+
rightLightKey={rightLightKey as LightKey}
223+
centerShininess={centerShininess}
224+
rightShininess={rightShininess}
225+
/>
226+
</Suspense>
223227
<DemoHelpers grid={false} minDistance={3} maxDistance={25} />
224228
</Canvas>
225229
)

src/examples/lights-spotlight.tsx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
* IS the shadow receiver this example is about; an infinite grid at y=0.002 would
4444
* render through/under it, competing with the shadow for visual attention
4545
*/
46-
import { useEffect, useMemo, useRef } from 'react'
46+
import { Suspense, useEffect, useMemo, useRef } from 'react'
4747
import { Canvas, useFrame, useLoader } from '@react-three/fiber/webgpu'
4848
import { useTexture } from '@react-three/drei/webgpu'
4949
import { folder, useControls } from 'leva'
@@ -224,20 +224,24 @@ export default function LightsSpotlight() {
224224
return (
225225
<Canvas renderer shadows background="#000000" camera={{ position: [7, 4, 1], fov: 40, near: 0.1, far: 100 }}>
226226
<hemisphereLight color="#ffffff" groundColor="#8d8d8d" intensity={0.25} />
227-
<SpotlightRig
228-
mapKey={map as MapKey}
229-
color={color}
230-
intensity={intensity}
231-
distance={distance}
232-
angle={angle}
233-
penumbra={penumbra}
234-
decay={decay}
235-
focus={focus}
236-
shadowIntensity={shadowIntensity}
237-
helpers={helpers}
238-
/>
239-
<Floor />
240-
<Lucy />
227+
{/* B17 gate: ungated suspension reaching Canvas's boundary re-runs createRoot
228+
and freezes the displayed scene (AGENTS.md; corpus-wide repair, wave 8). */}
229+
<Suspense fallback={null}>
230+
<SpotlightRig
231+
mapKey={map as MapKey}
232+
color={color}
233+
intensity={intensity}
234+
distance={distance}
235+
angle={angle}
236+
penumbra={penumbra}
237+
decay={decay}
238+
focus={focus}
239+
shadowIntensity={shadowIntensity}
240+
helpers={helpers}
241+
/>
242+
<Floor />
243+
<Lucy />
244+
</Suspense>
241245
<DemoHelpers
242246
grid={false}
243247
target={[0, 1, 0]}

src/examples/loader-gltf-anisotropy.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* y = -0.08) so the world-origin grid plane would slice straight through the subject
4141
* of a macro shot (same call as the other loader-gltf-* ports).
4242
*/
43+
import { Suspense } from 'react'
4344
import { Canvas } from '@react-three/fiber/webgpu'
4445
import { Environment, useGLTF } from '@react-three/drei/webgpu'
4546
import { useControls } from 'leva'
@@ -70,8 +71,12 @@ export default function LoaderGltfAnisotropy() {
7071
renderer={{ toneMapping: ACESFilmicToneMapping, toneMappingExposure: 1.35 }}
7172
camera={{ position: [-0.35, -0.2, 0.35], fov: 40, near: 0.01, far: 10 }}
7273
>
73-
<Environment files={HDR_URL} background backgroundBlurriness={blurriness} />
74-
<BarnLamp />
74+
{/* B17 gate: ungated suspension reaching Canvas's boundary re-runs createRoot
75+
and freezes the displayed scene (AGENTS.md; corpus-wide repair, wave 8). */}
76+
<Suspense fallback={null}>
77+
<Environment files={HDR_URL} background backgroundBlurriness={blurriness} />
78+
<BarnLamp />
79+
</Suspense>
7580
<DemoHelpers
7681
grid={false}
7782
target={[0, -0.08, 0.11]}

src/examples/loader-gltf-compressed.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
* backdrop, and the mat's base sits at y = -0.8, so a ground grid at y ≈ 0 would
3333
* slice through the middle of the model
3434
*/
35-
import { useRef } from 'react'
35+
import { Suspense, useRef } from 'react'
3636
import { Canvas, useFrame, useThree } from '@react-three/fiber/webgpu'
3737
import { useGLTF } from '@react-three/drei/webgpu'
3838
import { useControls } from 'leva'
@@ -78,7 +78,11 @@ export default function LoaderGltfCompressed() {
7878
background="#eeeeee"
7979
camera={{ position: [2, 2, 2], fov: 50, near: 1, far: 20 }}
8080
>
81-
<CoffeeMat />
81+
{/* B17 gate: ungated suspension reaching Canvas's boundary re-runs createRoot
82+
and freezes the displayed scene (AGENTS.md; corpus-wide repair, wave 8). */}
83+
<Suspense fallback={null}>
84+
<CoffeeMat />
85+
</Suspense>
8286
<Headlight power={power} />
8387
<DemoHelpers grid={false} minDistance={3} maxDistance={6} />
8488
</Canvas>

src/examples/loader-gltf-iridescence.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
* ground grid at the default 0.5 unit cell size (same call as
3636
* `loader-gltf-transmission`)
3737
*/
38+
import { Suspense } from 'react'
3839
import { Canvas } from '@react-three/fiber/webgpu'
3940
import { Environment, useGLTF } from '@react-three/drei/webgpu'
4041
import { useControls } from 'leva'
@@ -65,8 +66,12 @@ export default function LoaderGltfIridescence() {
6566
renderer={{ toneMapping: ACESFilmicToneMapping }}
6667
camera={{ position: [0.35, 0.05, 0.35], fov: 50, near: 0.05, far: 20 }}
6768
>
68-
<Environment files={HDR_URL} background backgroundBlurriness={blurriness} />
69-
<IridescenceLamp />
69+
{/* B17 gate: ungated suspension reaching Canvas's boundary re-runs createRoot
70+
and freezes the displayed scene (AGENTS.md; corpus-wide repair, wave 8). */}
71+
<Suspense fallback={null}>
72+
<Environment files={HDR_URL} background backgroundBlurriness={blurriness} />
73+
<IridescenceLamp />
74+
</Suspense>
7075
<DemoHelpers
7176
grid={false}
7277
target={[0, 0.2, 0]}

src/examples/loader-gltf-sheen.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* plane, and the infinite grid moiré-aliases across it, competing with the fabric study
3434
* (same call as the other two loader-gltf-* ports).
3535
*/
36-
import { useEffect } from 'react'
36+
import { Suspense, useEffect } from 'react'
3737
import { Canvas } from '@react-three/fiber/webgpu'
3838
import { Environment, useGLTF } from '@react-three/drei/webgpu'
3939
import { useControls } from 'leva'
@@ -72,8 +72,12 @@ export default function LoaderGltfSheen() {
7272
renderer={{ toneMapping: ACESFilmicToneMapping }}
7373
camera={{ position: [-0.75, 0.7, 1.25], fov: 45, near: 0.1, far: 20 }}
7474
>
75-
<Environment files={HDR_URL} background />
76-
<SheenChair sheen={sheen} />
75+
{/* B17 gate: ungated suspension reaching Canvas's boundary re-runs createRoot
76+
and freezes the displayed scene (AGENTS.md; corpus-wide repair, wave 8). */}
77+
<Suspense fallback={null}>
78+
<Environment files={HDR_URL} background />
79+
<SheenChair sheen={sheen} />
80+
</Suspense>
7781
<DemoHelpers grid={false} target={[0, 0.35, 0]} minDistance={1} maxDistance={10} />
7882
</Canvas>
7983
)

0 commit comments

Comments
 (0)