Skip to content

Commit e3c1434

Browse files
DennisSmolekclaude
andcommitted
ci: ?nogrid escape hatch replaces ciSkip for the Grid-stall trio; UPSTREAM B12
DemoHelpers honors a test-only ?nogrid query param; CI navigates the three Grid+node-graph stall examples with it instead of skipping them - restores smoke coverage AND doubles as the bisection experiment (green = Grid-shader hypothesis confirmed; still stalling = falsified, revert to skips). B12: useUniforms scope/name strings flow unvalidated into WGSL identifiers (kebab-case scope = runtime shader compile error; suggest sanitize or throw). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 683cda8 commit e3c1434

5 files changed

Lines changed: 31 additions & 5 deletions

File tree

docs/UPSTREAM.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ commit** (AGENTS.md points agents at this file).
135135
- **Suggested fix**: let `Fn`'s type accept a tuple of node-typed params (generic
136136
parameter per arg, or a `Fn<[Node<'float'>, Node<'vec3'>]>` signature).
137137

138+
### B12 · fiber: `useUniforms` scope/name strings flow unvalidated into WGSL identifiers
139+
140+
- **What**: the debug name fiber generates for a uniform (`${scope}_${name}`) ends up
141+
as a WGSL struct member identifier. WGSL forbids hyphens (and other JS-string-legal
142+
characters), so a kebab-case scope name (`useUniforms('halftone-purple', …)`)
143+
produces a **runtime fragment-shader compile error** — tsc and the build both pass;
144+
nothing fails until the shader compiles in the browser.
145+
- **Evidence**: hit porting `webgpu_tsl_halftone`; caught only by our smoke suite's
146+
console-error assertion. Renaming the scope to camelCase fixed it.
147+
- **Suggested fix**: sanitize the generated identifier (replace non-`[A-Za-z0-9_]`
148+
chars) or throw early from `useUniforms` with a clear message naming the offending
149+
scope/key. Silent pass-through into codegen is the worst of the options.
150+
138151
### B11 · @types/three: `Scene.fogNode` missing
139152

140153
- **What**: `Scene.fogNode` is a real webgpu runtime property (read by

src/app/manifest.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export interface ExampleMeta {
1212
credits?: string
1313
/** CI smoke-tier exception (SPEC §10): reason this example can't run on SwiftShader. */
1414
ciSkip?: string
15+
/** CI runs this example with ?nogrid (DemoHelpers grid suppressed) — SwiftShader
16+
* Grid+node-graph stall workaround that keeps smoke coverage. Value = reason. */
17+
ciNoGrid?: string
1518
}
1619

1720
export const exampleMeta = examples as ExampleMeta[]

src/examples.json

Lines changed: 3 additions & 3 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-
"ciSkip": "SwiftShader readiness stall — fits the Grid+custom-node-graph pattern (see HANDOFF: grid-only passes, nodes-only passes, both hang pipeline compile silently)"
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"
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-
"ciSkip": "SwiftShader readiness stall, zero page errors (2x180s); passes on Metal. Fits the Grid+custom-node-graph pattern (see HANDOFF)"
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"
4343
},
4444
{
4545
"slug": "shadow-contact",
@@ -53,7 +53,7 @@
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-
"ciSkip": "SwiftShader readiness stall, zero page errors (2x180s); passes on Metal. Fits the Grid+custom-node-graph pattern (see HANDOFF)"
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"
5757
},
5858
{
5959
"slug": "sprites",

src/utils/DemoHelpers.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ export interface DemoHelpersProps {
2121
pan?: boolean
2222
}
2323

24+
// Test-only escape hatch: `?nogrid` suppresses the grid regardless of props. CI uses
25+
// it for examples hitting the SwiftShader stall (Grid + custom node graph hangs
26+
// pipeline compile on software Vulkan — see docs/HANDOFF.md); also the bisection
27+
// probe for that bug. Read once at module load; not part of the component API.
28+
const NOGRID_OVERRIDE =
29+
typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('nogrid')
30+
2431
export function DemoHelpers({
2532
grid = true,
2633
controls = true,
@@ -31,7 +38,7 @@ export function DemoHelpers({
3138
}: DemoHelpersProps) {
3239
return (
3340
<>
34-
{grid && (
41+
{grid && !NOGRID_OVERRIDE && (
3542
<Grid
3643
position={[0, 0.002, 0]}
3744
infiniteGrid

tests/smoke.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ const IGNORED_CONSOLE = [
1313

1414
for (const { slug, ...meta } of examples) {
1515
const ciSkip = 'ciSkip' in meta ? String(meta.ciSkip) : undefined
16+
// ciNoGrid: run the example in CI with DemoHelpers' grid suppressed (?nogrid) —
17+
// works around the SwiftShader Grid+node-graph stall WITHOUT losing smoke coverage.
18+
const ciNoGrid = 'ciNoGrid' in meta ? String(meta.ciNoGrid) : undefined
1619
test(`${slug}: WebGPU context, readiness signal, non-black canvas`, async ({ page }) => {
1720
// Exception list (SPEC §10, three.js-CI prior art): examples too heavy for
1821
// SwiftShader declare ciSkip WITH A REASON in the manifest. They still run locally.
@@ -26,7 +29,7 @@ for (const { slug, ...meta } of examples) {
2629
errors.push(`console.error: ${text}`)
2730
})
2831

29-
await page.goto(`/examples/${slug}`)
32+
await page.goto(`/examples/${slug}${process.env.CI && ciNoGrid ? '?nogrid' : ''}`)
3033

3134
// Readiness = loaders settled + clean frames (window.__exampleReady, set by
3235
// <ReadinessSignal> inside DemoHelpers). Poll instead of sleeping. CI gets a

0 commit comments

Comments
 (0)