Skip to content

Commit 806a14e

Browse files
DennisSmolekclaude
andcommitted
ci: Grid hypothesis falsified; stall probe instrumentation; honest ciSkips
The ?nogrid experiment disproved the Grid theory: rtt/tsl-halftone stall grid-less and sprites (never had a grid) stalls too, while shadow-contact (custom nodes + useUniforms) passes. Four stalls -> neutral ciSkip; full pass/fail matrix + falsified/open hypotheses recorded in HANDOFF. ReadinessSignal now publishes __frameCount/__loadersActive and the smoke timeout message reports them - the next red run classifies the stall type (dead loop / recompile crawl / loaders never settle) instead of guessing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent e3c1434 commit 806a14e

4 files changed

Lines changed: 42 additions & 15 deletions

File tree

docs/HANDOFF.md

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,24 @@ cheaper as AGENTS.md absorbed each round's lessons (now at v0.4, see its changel
3333
**the research-designed WebGPU path is proven on free runners**.
3434
- `packageManager` pin + vendored fiber tarball (1.3MB, UPSTREAM.md A1) were needed
3535
to make CI installable.
36-
- **SwiftShader stall pattern (open investigation)**: examples combining drei's Grid
37-
WITH a custom node graph (render pipeline or custom outputNode) hang readiness
38-
silently — zero page errors, 2×180s. Grid-only passes; custom-nodes-only passes.
39-
Affected: skinning-instancing, rtt, tsl-halftone → `ciSkip` in the manifest
40-
(exception list per SPEC §10, each with the reason). All three pass on Metal/real
41-
GPUs. Bisect idea: CI matrix job rendering rtt with grid off vs on. Possibly a
42-
drei-Grid-shader trigger (fwidth/discard under SwiftShader) — could merge with
43-
UPSTREAM B6 once bisected.
36+
- **SwiftShader stall (open investigation — Grid hypothesis FALSIFIED)**: four
37+
examples hang readiness silently on SwiftShader (zero page errors, 2×180s); all
38+
pass on Metal. The `?nogrid` experiment disproved the Grid theory (rtt/halftone
39+
still stall grid-less; sprites stalls with no grid at all). Full matrix:
40+
- STALL: skinning-instancing, rtt, tsl-halftone, sprites
41+
- PASS: animation-skinning-blending (9s), hello-webgpu (5s),
42+
postprocessing-bloom-emissive (24s), sky (7s), shadow-contact (7s)
43+
- Not yet separated by: render pipeline (bloom passes, rtt stalls), Grid
44+
(falsified), fiber `useUniforms` (shadow-contact calls it and passes),
45+
animation (anim-blending passes).
46+
- Instrumentation added: readiness timeouts now report `__frameCount` /
47+
`__loadersActive` in the failure message — next red run classifies the stall
48+
(0 frames = dead loop; few = per-frame pipeline recompile crawl; many =
49+
loaders never settle). Bisect from that data.
50+
- Mechanisms: `ciSkip` (skip with reason) and `?nogrid`/`ciNoGrid` (run grid-less)
51+
both exist in the manifest + smoke spec; the four stalls currently use `ciSkip`.
52+
- Smoke job is `continue-on-error` (advisory) until this is resolved — no failure
53+
emails; flip back in ci.yml when stable.
4454

4555
## For Dennis
4656

src/examples.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"tags": ["animation", "skinning", "instancing", "gltf", "tsl", "post-processing", "leva"],
1818
"original": "https://threejs.org/examples/#webgpu_skinning_instancing",
1919
"credits": "Michelle model from the three.js examples",
20-
"ciNoGrid": "SwiftShader stall when Grid + custom node graph coexist (see HANDOFF); grid suppressed via ?nogrid in CI so the example itself still smoke-tests"
20+
"ciSkip": "SwiftShader readiness stall (passes on Metal/real GPUs) — cause under investigation, see HANDOFF stall matrix"
2121
},
2222
{
2323
"slug": "postprocessing-bloom-emissive",
@@ -39,7 +39,7 @@
3939
"tags": ["tsl", "render-to-texture", "post-processing", "pointer"],
4040
"original": "https://threejs.org/examples/#webgpu_rtt",
4141
"credits": "uv_grid_opengl texture from the three.js examples",
42-
"ciNoGrid": "SwiftShader stall when Grid + custom node graph coexist (see HANDOFF); grid suppressed via ?nogrid in CI so the example itself still smoke-tests"
42+
"ciSkip": "SwiftShader readiness stall (passes on Metal/real GPUs) — cause under investigation, see HANDOFF stall matrix"
4343
},
4444
{
4545
"slug": "shadow-contact",
@@ -53,13 +53,14 @@
5353
"tags": ["tsl", "node-material", "gltf", "leva"],
5454
"original": "https://threejs.org/examples/#webgpu_tsl_halftone",
5555
"credits": "Michelle model from the three.js examples",
56-
"ciNoGrid": "SwiftShader stall when Grid + custom node graph coexist (see HANDOFF); grid suppressed via ?nogrid in CI so the example itself still smoke-tests"
56+
"ciSkip": "SwiftShader readiness stall (passes on Metal/real GPUs) — cause under investigation, see HANDOFF stall matrix"
5757
},
5858
{
5959
"slug": "sprites",
6060
"title": "Sprites",
6161
"tags": ["tsl", "node-material", "sprites", "fog", "leva"],
6262
"original": "https://threejs.org/examples/#webgpu_sprites",
63-
"credits": "sprite1.png texture from the three.js examples"
63+
"credits": "sprite1.png texture from the three.js examples",
64+
"ciSkip": "SwiftShader readiness stall (passes on Metal/real GPUs) — cause under investigation, see HANDOFF stall matrix"
6465
}
6566
]

src/utils/ReadinessSignal.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import { useProgress } from '@react-three/drei/core'
1414
declare global {
1515
interface Window {
1616
__exampleReady?: boolean
17+
/** Diagnostics for CI stall triage: total finish-phase frames since mount. */
18+
__frameCount?: number
19+
/** Diagnostics: last-seen loader activity (drei useProgress). */
20+
__loadersActive?: boolean
1721
}
1822
}
1923

@@ -26,12 +30,15 @@ export function ReadinessSignal() {
2630

2731
useEffect(() => {
2832
window.__exampleReady = false
33+
window.__frameCount = 0
2934
return () => {
3035
window.__exampleReady = false
3136
}
3237
}, [])
3338

3439
useFrame(() => {
40+
window.__frameCount = (window.__frameCount ?? 0) + 1
41+
window.__loadersActive = useProgress.getState().active
3542
if (useProgress.getState().active) {
3643
settled.current = 0
3744
window.__exampleReady = false

tests/smoke.spec.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,19 @@ for (const { slug, ...meta } of examples) {
4040
timeout: process.env.CI ? 180_000 : 60_000,
4141
})
4242
} catch (cause) {
43-
// Readiness timeouts are usually a dead render loop, not slowness — surface
44-
// everything the page said so CI logs are diagnosable without an artifact dig.
43+
// Surface everything the page can tell us so CI logs classify the stall:
44+
// frames=0 → render loop never ran; frames tiny → crawl (per-frame pipeline
45+
// recompile?); frames huge → loaders never settled (readiness logic).
46+
const probe = await page
47+
.evaluate(() => ({
48+
frames: window.__frameCount ?? -1,
49+
loadersActive: window.__loadersActive ?? null,
50+
ready: window.__exampleReady ?? null,
51+
}))
52+
.catch(() => null)
4553
throw new Error(
46-
`readiness timeout for ${slug}; page reported ${errors.length} error(s):\n` +
54+
`readiness timeout for ${slug}; probe=${JSON.stringify(probe)}; ` +
55+
`page reported ${errors.length} error(s):\n` +
4756
(errors.join('\n') || '(no console/page errors captured)'),
4857
{ cause },
4958
)

0 commit comments

Comments
 (0)