@three-blocks/devtools 1.0.0-alpha.176.g8ff8a72e6369 → 1.0.0-alpha.190.g3ffe5c0ecc9c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +27 -2
  2. package/LICENSE +1 -2
  3. package/NOTICE +19 -27
  4. package/README.md +89 -144
  5. package/dist/browser/shader-capture.js +2 -2
  6. package/dist/{browser-smoke-76626IZQ.js → browser-smoke-HB7ZXQGR.js} +1 -1
  7. package/dist/chunk-3HTNEAVD.cjs +1 -0
  8. package/dist/chunk-3TQVLKUH.js +14 -0
  9. package/dist/chunk-54BHFG2E.js +76 -0
  10. package/dist/{chunk-ZKK6E42X.js → chunk-77HY5X2T.js} +1 -1
  11. package/dist/chunk-NEWSG5RX.js +3 -0
  12. package/dist/chunk-UQOPSJR6.js +6 -0
  13. package/dist/chunk-WNNN3UJI.js +93 -0
  14. package/dist/chunk-ZKXUYNSG.js +18 -0
  15. package/dist/cli.js +21 -21
  16. package/dist/index.d.ts +14 -10
  17. package/dist/index.js +21 -21
  18. package/dist/shader-browser-6TM4RNUL.js +2 -0
  19. package/dist/shader-browser-ZHG2JISC.js +1 -0
  20. package/dist/shader-capture-server.d.ts +6 -8
  21. package/dist/shader-capture-server.js +15 -15
  22. package/dist/{shader-config-4EXO5DKK.js → shader-config-BYBJUL3C.js} +1 -1
  23. package/dist/{shader-transform-CUkZn5Oa.d.ts → shader-transform-DSFaVrux.d.ts} +2 -0
  24. package/dist/{shader-watch-QHTNTSZE.js → shader-watch-Z3VGP4QB.js} +1 -1
  25. package/dist/shader-workflow-SO7WCR4Y.js +2 -0
  26. package/dist/{status-DV2BYZHR.js → status-CFJSWBDI.js} +1 -1
  27. package/dist/transform.cjs +1 -1
  28. package/dist/transform.d.ts +1 -1
  29. package/dist/transform.js +1 -1
  30. package/dist/webpack-loader.cjs +1 -1
  31. package/dist/webpack-loader.d.ts +1 -1
  32. package/dist/webpack-loader.js +1 -1
  33. package/package.json +3 -3
  34. package/dist/chunk-26FK4CRS.js +0 -6
  35. package/dist/chunk-4EHPYBKA.cjs +0 -1
  36. package/dist/chunk-HRYYNBVC.js +0 -3
  37. package/dist/chunk-INDAJN42.js +0 -94
  38. package/dist/chunk-ME3X2KFY.js +0 -14
  39. package/dist/chunk-SCJNCGMF.js +0 -17
  40. package/dist/chunk-VGJ3V4QQ.js +0 -78
  41. package/dist/shader-browser-65V22LLL.js +0 -1
  42. package/dist/shader-browser-JII6ZFCW.js +0 -2
  43. package/dist/shader-workflow-U4G565EB.js +0 -2
package/dist/cli.js CHANGED
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import {c,b as b$1,h,e,a as a$2,f,d}from'./chunk-76WRV5MZ.js';import {a as a$1}from'./chunk-ZT6T2W4B.js';import {a}from'./chunk-HOZDD6PW.js';import u from'process';var n=u.argv.slice(2),ee=n.indexOf("--reporter"),F=n.find(e=>e.startsWith("--reporter="))?.slice(11)??(ee===-1?void 0:n[ee+1]),se=ee!==-1&&(F===void 0||F.startsWith("--"))?'--reporter requires "plain" or "json".':F!==void 0&&F!=="json"&&F!=="plain"?'--reporter must be "plain" or "json".':void 0,w=n.includes("--json")||F==="json",de=new Set(["--reporter","--root","--route","--build-mode","--expect-shaders","--width","--height","--timeout","--settle","--min-luminance","--hmr","--size","--output","--driver","--serve","--url","--runs","--debounce","--config","--mode"]),te=[];for(let e=0;e<n.length;e++){let t=n[e];if(t!==void 0){if(t.startsWith("--")){de.has(t)&&e++;continue}te.push(t);}}var y=te[0]??"status",M=te[1];function b(e){let t=n.find(c=>c.startsWith(`${e}=`));if(t!==void 0){let c=t.slice(e.length+1);if(c.length===0)throw new Error(`${e} requires a value.`);return c}let i=n.indexOf(e);if(i===-1)return;let r=n[i+1];if(r===void 0||r.startsWith("--"))throw new Error(`${e} requires a value.`);return r}a(b,"optionValue");function ue(e){let t=[];for(let i=0;i<n.length;i++){let r=n[i];if(r!==void 0){if(r.startsWith(`${e}=`)){let c=r.slice(e.length+1);if(c.length===0)throw new Error(`${e} requires a value.`);t.push(c);}else if(r===e){let c=n[i+1];if(c===void 0||c.startsWith("--"))throw new Error(`${e} requires a value.`);t.push(c);}}}return t}a(ue,"optionValues");function $(e){let t=b(e);if(t===void 0)return;let i=Number(t);if(!Number.isFinite(i))throw new Error(`${e} must be a finite number.`);return i}a($,"numberOption");async function ce(e){let t=`Shader provider adapter patch:
2
+ import {c,b as b$1,h,e,a as a$3,f,d}from'./chunk-76WRV5MZ.js';import {a as a$2}from'./chunk-ZT6T2W4B.js';import {a as a$1}from'./chunk-HOZDD6PW.js';import a from'process';var n=a.argv.slice(2),ee=n.indexOf("--reporter"),G=n.find(e=>e.startsWith("--reporter="))?.slice(11)??(ee===-1?void 0:n[ee+1]),se=ee!==-1&&(G===void 0||G.startsWith("--"))?'--reporter requires "plain" or "json".':G!==void 0&&G!=="json"&&G!=="plain"?'--reporter must be "plain" or "json".':void 0,w=n.includes("--json")||G==="json",de=new Set(["--reporter","--root","--route","--build-mode","--expect-shaders","--width","--height","--timeout","--settle","--min-luminance","--hmr","--size","--output","--driver","--serve","--url","--runs","--debounce","--config","--mode"]),te=[];for(let e=0;e<n.length;e++){let t=n[e];if(t!==void 0){if(t.startsWith("--")){de.has(t)&&e++;continue}te.push(t);}}var y=te[0]??"status",E=te[1];function b(e){let t=n.find(c=>c.startsWith(`${e}=`));if(t!==void 0){let c=t.slice(e.length+1);if(c.length===0)throw new Error(`${e} requires a value.`);return c}let i=n.indexOf(e);if(i===-1)return;let r=n[i+1];if(r===void 0||r.startsWith("--"))throw new Error(`${e} requires a value.`);return r}a$1(b,"optionValue");function ue(e){let t=[];for(let i=0;i<n.length;i++){let r=n[i];if(r!==void 0){if(r.startsWith(`${e}=`)){let c=r.slice(e.length+1);if(c.length===0)throw new Error(`${e} requires a value.`);t.push(c);}else if(r===e){let c=n[i+1];if(c===void 0||c.startsWith("--"))throw new Error(`${e} requires a value.`);t.push(c);}}}return t}a$1(ue,"optionValues");function $(e){let t=b(e);if(t===void 0)return;let i=Number(t);if(!Number.isFinite(i))throw new Error(`${e} must be a finite number.`);return i}a$1($,"numberOption");async function ce(e){let t=`Shader provider adapter patch:
3
3
  ${e}
4
- `;if(u.stderr.write(w?`${JSON.stringify({type:"shader-adapter-diff",diff:e})}
5
- `:t),n.includes("--yes"))return true;if(!u.stdin.isTTY||!u.stderr.isTTY)throw new Error("Adapter wiring needs consent. Review the diff above, then rerun with --yes; no files were changed.");let{createInterface:i}=await import('readline/promises'),r=i({input:u.stdin,output:u.stderr});try{let c=await r.question("Apply this patch? (y/N) ");return /^y(?:es)?$/iu.test(c.trim())}finally{r.close();}}a(ce,"approveAdapterPatch");function C(e){return w?{update(){},stop(){}}:h(e)}a(C,"wait");function le(){let e$1=a(l=>b$1.accent(l),"c"),t=a(l=>b$1.dim(l),"d"),i=c.mark?`${c.mark} `:"",r=[["Inspect",[[`${e$1("status")}`,`project report: shaders, text, browser evidence ${t("\u2014 no browser, no GPU")}`],[`${e$1("shaders")}`,"verify shader freshness and exit 1 with the exact recovery command"],[`${e$1("shaders status")} ${t("[--check]")}`,`per-scene shader freshness ${t("(--check exits 1 when not fresh)")}`],[`${e$1("text status")}`,"text atlas coverage"]]],["Produce",[[`${e$1("shaders capture")} ${t("[--if-stale] [--driver static --serve dist]")}`,"batch/CI fallback capture; during development use the overlay Capture button"],[`${e$1("shaders watch")} ${t("[--debounce <ms>]")}`,"recapture automatically while you work"],[`${e$1("text generate")} ${t("[--config <path>]")}`,"regenerate MSDF text atlases"],[`${e$1("environment bake")} ${t("[--size 256]")}`,"bake the PMREM room environment to KTX2"]]],["Verify",[[`${e$1("shaders test")}`,"median timing + zero-build + compute + pixel parity gate"],[`${e$1("shaders test --parity-only")}`,"one correctness pair per scene; no timing or published-page soak"],[`${e$1("browser smoke")} ${t("[--route /] [--hmr <module>]")}`,"boot the dev app, verify worker lifecycle + HMR"],[`${e$1("browser preview")} ${t("[--route /]...")}`,"build + serve production, verify precompiled hydration"]]]],c$1=["",`${i}${b$1.bold("three-blocks devtools")} ${b$1.dim(`v${a$1}`)}`,b$1.dim("WebGPU shader precompilation and verification for Three Blocks projects."),"",`${b$1.bold("Usage")}`,` ${e$1("three-blocks-devtools")} ${t("[command] [options] [--json]")}`];for(let[l,o]of r)c$1.push("",b$1.bold(l),...e(o,{indent:2,maxWidth:a$2.width-2}));c$1.push("",b$1.bold("Options"),...e([[t("--json"),"stable machine-readable report on stdout"],[t("--reporter <plain|json>"),"select the stable terminal or CI reporter"],[t("--root <path>"),"project root (defaults to the nearest three-blocks project)"],[t("--headed"),"show the automation browser window"],[t("--runs <n>"),"shader timing captures after warmup (default 5)"],[t("--parity-only"),"skip timing warmup/repeats and the published-page soak"],[t("--commit-timing"),"also write shareable hardware-labelled build timing"],[t("--skip-browser"),"check shader schema and freshness without browser parity"],[t("--driver <vite|static>"),"capture through Vite or an already-built static app"],[t("--serve <path>"),"built application directory for --driver static"],[t("--url <url>"),"attach to an already-running transformed application"],[t("--yes"),"apply a previewed framework adapter patch non-interactively"]],{indent:2,maxWidth:a$2.width-2}),"",b$1.dim("During development, use the in-page overlay Capture button for one-click recapture."),b$1.dim("`shaders capture` remains the explicit isolated-browser fallback for batch and CI."),""),f(c$1.join(`
6
- `));}a(le,"help");function pe(e){return e?`${e.render} render \xB7 ${e.compute} compute`:"no coverage"}a(pe,"coverageText");function _(e){return e<100?e.toFixed(1):Math.round(e).toString()}a(_,"milliseconds");function A(e,t,i){return `${e} ${d(t,12)} ${i}`}a(A,"row");function oe(e$1){if(e$1.skipped)return A(c.tick,"shaders",`fresh ${c.dot} capture skipped ${b$1.dim(`(--if-stale) \xB7 ${e$1.reason}`)}`);let t=Math.max(0,Math.round((e$1.durationMs??0)/1e3)),i=e$1.scenes.length,r=[A(c.tick,"shaders",`captured in ${t} s ${c.dot} wrote ${i} shader scene${i===1?"":"s"}`)];e$1.configuration?.created===true&&r.push(b$1.dim(` created ${e$1.configuration.file} with the default "/" capture route`));for(let l of e$1.scenes)r.push(A(c.tick,l.scene,`${pe(l.coverage)} ${c.dot} ${b$1.dim(`${l.attempts} attempt${l.attempts===1?"":"s"}`)}`));let c$1=e$1.scenes.flatMap(l=>{let o=l.timing;if(o?.launch===void 0||o.firstRequest===void 0||o.readiness===void 0||o.lastBuild===void 0||o.settled===void 0||o.posted===void 0)return [];let p=a((S,f)=>`${_(Math.max(0,f-S))} ms`,"elapsed");return [[l.scene,`launch\u2192request ${p(o.launch,o.firstRequest)} ${c.dot} ready ${p(o.firstRequest,o.readiness)} ${c.dot} last build ${p(o.readiness,o.lastBuild)} ${c.dot} settle ${p(o.lastBuild,o.settled)} ${c.dot} post ${p(o.settled,o.posted)}`]]});if(c$1.length>0&&r.push("",b$1.bold("Capture stages"),...e(c$1,{indent:2,maxWidth:a$2.width-2})),e$1.measurement!==void 0&&r.push(` TSL builds at startup: ${e$1.measurement.beforeBuilds} \u2192 ${e$1.measurement.afterBuilds} ${c.dot} first frame \u2212${_(Math.abs(e$1.measurement.firstFrameDeltaMs))} ms (measured)`),e$1.timing===void 0)r.push(b$1.dim(" Build timing not recorded for this capture \xB7 run three-blocks shaders test"));else {let l=Object.values(e$1.timing.scenes).reduce((p,S)=>p+S.avoidedBuildMs,0),o=e$1.timing.source==="latest-local-capture"?"Latest local capture":`Committed capture \xB7 recorded on ${e$1.timing.adapter}`;r.push(b$1.dim(` \u2248${_(l)} ms TSL build time saved \xB7 ${o}`));}return r.push(b$1.dim(" a running dev server reloads on its own; otherwise refresh the page")),r.join(`
7
- `)}a(oe,"formatShaderCapture");function fe(e){let t=[A(c.tick,"shaders",`no-GPU ${e.noGpu.state} ${c.dot} browser ${e.browser}`)];for(let i of e.scenes){let r=e.timing?.scenes[i.scene],c$1=r===void 0?`${i.coverage.render+i.coverage.compute} TSL build${i.coverage.render+i.coverage.compute===1?"":"s"} skipped`:`${r.builds} TSL build${r.builds===1?"":"s"} skipped ${c.dot} \u2248${_(r.avoidedBuildMs)} ms \xB1 ${_(r.spreadMs)} TSL build time saved ${c.dot} median of ${r.runs} ${c.dot} Latest local capture`,l=r!==void 0&&r.spreadMs>r.avoidedBuildMs*.4?` ${c.dot} ${b$1.warn("noisy measurement \u2014 close other GPU apps and re-run")}`:"";t.push(A(c.tick,i.scene,`${c$1} ${c.dot} ${i.coverage.compute} compute ${c.dot} ${(i.parity.diffRatio*100).toFixed(3)}% pixel diff${l}`));}return t.join(`
8
- `)}a(fe,"formatShaderTest");async function he(){if(se!==void 0)throw new Error(se);if(y==="help"||y==="--help"||n.includes("--help"))return le();if(y==="version"||n.includes("--version")){f(w?JSON.stringify({version:a$1}):a$1);return}if(y==="browser"){let{formatBrowserPreviewReport:o,formatBrowserSmokeReport:p,runBrowserPreview:S,runBrowserSmoke:f}=await import('./browser-smoke-76626IZQ.js'),{findProjectRoot:x}=await import('./status-DV2BYZHR.js');if(M==="preview"){let I=b("--root")??await x(),T=ue("--route"),U=b("--build-mode"),W=b("--expect-shaders")??"precompiled";if(W!=="precompiled"&&W!=="live")throw new Error('--expect-shaders must be "precompiled" or "live".');let j=$("--width"),P=$("--height"),J=$("--timeout"),L=$("--settle"),R=$("--min-luminance"),z=C("building and verifying the production bundle"),d;try{d=await S({root:I,...T.length===0?{}:{routes:T},...U===void 0?{}:{buildMode:U},expectShaders:W,text:n.includes("--text"),...J===void 0?{}:{timeoutMs:J},...L===void 0?{}:{settleMs:L},...R===void 0?{}:{minimumLuminance:R},...j===void 0&&P===void 0?{}:{viewport:{width:j??1024,height:P??640}},headed:n.includes("--headed")});}finally{z.stop();}u.stdout.write(`${w?JSON.stringify(d,null,2):o(d)}
9
- `),d.passed||(u.exitCode=1);return}if(M!=="smoke")throw new Error(`Unsupported browser command "${M??""}".`);let k=b("--root")??await x(),m=$("--width"),E=$("--height"),O=b("--route"),K=$("--timeout"),q=$("--settle"),G=$("--min-luminance"),g=b("--hmr");if(n.includes("--webgl")&&n.includes("--unsupported-worker"))throw new Error("--webgl and --unsupported-worker are mutually exclusive.");let B=C(`smoking ${O??"/"} in a real browser`),v;try{v=await f({root:k,...O===void 0?{}:{route:O},...K===void 0?{}:{timeoutMs:K},...q===void 0?{}:{settleMs:q},...G===void 0?{}:{minimumLuminance:G},...g===void 0?{}:{hmrModule:g},forceWebgl:n.includes("--webgl"),unsupportedWorker:n.includes("--unsupported-worker"),expectTextGenerationFailure:n.includes("--expect-text-generation-failure"),...m===void 0&&E===void 0?{}:{viewport:{width:m??1024,height:E??640}},headed:n.includes("--headed")});}finally{B.stop();}u.stdout.write(`${w?JSON.stringify(v,null,2):p(v)}
10
- `),v.passed||(u.exitCode=1);return}if(y==="environment"){if(M!=="bake")throw new Error(`Unsupported environment command "${M??""}".`);let{bakeRoomEnvironment:o,formatEnvironmentBakeReport:p}=await import('./environment-bake-5P4IAL4O.js'),{findProjectRoot:S}=await import('./status-DV2BYZHR.js'),f=b("--root")??await S(),x=$("--size"),k=b("--output"),m=$("--timeout"),E=C("baking the room environment on the GPU"),O;try{O=await o({root:f,...x===void 0?{}:{size:x},...k===void 0?{}:{output:k},...m===void 0?{}:{timeoutMs:m},compression:n.includes("--no-zstd")?"none":"zstd",headed:n.includes("--headed")});}finally{E.stop();}u.stdout.write(`${w?JSON.stringify(O,null,2):p(O)}
11
- `);return}if(y==="shaders"){let{findShaderProjectRoot:o}=await import('./shader-config-4EXO5DKK.js'),p=M??"status";if(!["status","capture","test","watch"].includes(p))throw new Error(`Unsupported shader command "${p}".`);let S=b("--root"),f$1=await o(S),x=b("--driver");if(x!==void 0&&x!=="vite"&&x!=="static")throw new Error('--driver must be "vite" or "static".');let k=x,m=b("--serve"),E=b("--url");if(p==="status"){let{inspectShaderMatrix:g}=await import('./shader-workflow-U4G565EB.js'),{formatShaderMatrixStatus:B}=await import('./status-DV2BYZHR.js'),v=await g(f$1);u.stdout.write(`${w?JSON.stringify(v,null,2):B(v)}
12
- `),(n.includes("--check")||M===void 0)&&v.state!=="fresh"&&(u.exitCode=1);return}if(p==="capture"){let{captureShaders:g}=await import('./shader-workflow-U4G565EB.js'),{applyShaderAutoWire:B,hasRegisterDevtoolsCall:v,inspectShaderAutoWire:I}=await import('./shader-autowire-DCODBFMX.js'),{buildShaderCaptureProject:T,startNextShaderCaptureServer:U}=await import('./shader-project-runner-GUR3LTYS.js'),{inferShaderCaptureTarget:W}=await import('./shader-config-4EXO5DKK.js'),j=k,P=m,J=E,L,R,z;try{if(j===void 0&&P===void 0&&J===void 0){let d=await I(f$1);if(!d.configured&&d.plan!==void 0){if(!await ce(d.plan.diff))throw new Error("Adapter wiring was declined; no files were changed.");await B(d.plan);}else if(!d.configured&&d.bundler!=="vite"&&d.bundler!=="unknown")throw new Error(`${d.reason} No files were changed.`);if(d.bundler==="next")R=C("starting the project-local Next.js capture server"),L=await U(f$1),J=L.url,R.update("capturing shader scenes");else if(d.bundler==="webpack"||d.bundler==="esbuild"||d.bundler==="rollup"){R=C(`building the ${d.bundler} capture target`),await T(f$1);let N=await W(f$1);if(N.driverKind!=="static"||N.serve===void 0)throw new Error(`${d.bundler} build completed but no dist/, build/, or out/index.html was found; pass --driver static --serve <directory>.`);j="static",P=N.serve,R.update("capturing shader scenes");}}R??=C("capturing shader scenes"),z=await g({root:f$1,headed:n.includes("--headed"),ifStale:n.includes("--if-stale"),...j===void 0?{}:{driverKind:j},...P===void 0?{}:{serve:P},...J===void 0?{}:{url:J},onSceneStart:a((d,N,H)=>R?.update(`capturing ${b$1.accent(d)} ${b$1.dim(`(${N+1}/${H})`)}`),"onSceneStart"),onSceneRetry:a((d,N,H,re,V)=>{let ae=V instanceof Error&&/timed out/iu.test(V.message),ne=`capture attempt ${N}/${H} for "${d}" after ${ae?`timeout (${Math.round(re/1e3)} s burned)`:`failure (${V instanceof Error?V.message:String(V)})`}`;u.stderr.write(w?`${JSON.stringify({type:"shader-capture-retry",scene:d,attempt:N,total:H,timeoutMs:re,message:ne})}
4
+ `;if(a.stderr.write(w?`${JSON.stringify({type:"shader-adapter-diff",diff:e})}
5
+ `:t),n.includes("--yes"))return true;if(!a.stdin.isTTY||!a.stderr.isTTY)throw new Error("Adapter wiring needs consent. Review the diff above, then rerun with --yes; no files were changed.");let{createInterface:i}=await import('readline/promises'),r=i({input:a.stdin,output:a.stderr});try{let c=await r.question("Apply this patch? (y/N) ");return /^y(?:es)?$/iu.test(c.trim())}finally{r.close();}}a$1(ce,"approveAdapterPatch");function C(e){return w?{update(){},stop(){}}:h(e)}a$1(C,"wait");function le(){let e$1=a$1(f=>b$1.accent(f),"c"),t=a$1(f=>b$1.dim(f),"d"),i=c.mark?`${c.mark} `:"",r=[["Inspect",[[`${e$1("status")}`,`project report: shaders, text, browser evidence ${t("\u2014 no browser, no GPU")}`],[`${e$1("shaders")}`,"verify shader freshness and exit 1 with the exact recovery command"],[`${e$1("shaders status")} ${t("[--check]")}`,`per-scene shader freshness ${t("(--check exits 1 when not fresh)")}`],[`${e$1("text status")}`,"text atlas coverage"]]],["Produce",[[`${e$1("shaders capture")} ${t("[--if-stale] [--commit-timing] [--driver static --serve dist]")}`,"capture shaders plus a local timing estimate; during development use the overlay Capture button"],[`${e$1("shaders watch")} ${t("[--debounce <ms>]")}`,"recapture automatically while you work"],[`${e$1("text generate")} ${t("[--config <path>]")}`,"regenerate MSDF text atlases"],[`${e$1("environment bake")} ${t("[--size 256]")}`,"bake the PMREM room environment to KTX2"]]],["Verify",[[`${e$1("shaders test")}`,"median timing + zero-build + compute + pixel parity gate"],[`${e$1("shaders test --parity-only")}`,"one correctness pair per scene; no timing or published-page soak"],[`${e$1("browser smoke")} ${t("[--route /] [--hmr <module>]")}`,"boot the dev app, verify worker lifecycle + HMR"],[`${e$1("browser preview")} ${t("[--route /]...")}`,"build + serve production, verify precompiled hydration"]]]],c$1=["",`${i}${b$1.bold("three-blocks devtools")} ${b$1.dim(`v${a$2}`)}`,b$1.dim("WebGPU shader precompilation and verification for Three Blocks projects."),"",`${b$1.bold("Usage")}`,` ${e$1("three-blocks-devtools")} ${t("[command] [options] [--json]")}`];for(let[f,d]of r)c$1.push("",b$1.bold(f),...e(d,{indent:2,maxWidth:a$3.width-2}));c$1.push("",b$1.bold("Options"),...e([[t("--json"),"stable machine-readable report on stdout"],[t("--reporter <plain|json>"),"select the stable terminal or CI reporter"],[t("--root <path>"),"project root (defaults to the nearest three-blocks project)"],[t("--headed"),"show the automation browser window"],[t("--runs <n>"),"shader timing captures after warmup (default 5)"],[t("--parity-only"),"skip timing warmup/repeats and the published-page soak"],[t("--commit-timing"),"also write shareable hardware-labelled build timing"],[t("--skip-browser"),"check shader schema and freshness without browser parity"],[t("--driver <vite|static>"),"capture through Vite or an already-built static app"],[t("--serve <path>"),"built application directory for --driver static"],[t("--url <url>"),"attach to an already-running transformed application"],[t("--yes"),"apply a previewed framework adapter patch non-interactively"]],{indent:2,maxWidth:a$3.width-2}),"",b$1.dim("During development, use the in-page overlay Capture button for one-click recapture."),b$1.dim("`shaders capture` remains the explicit isolated-browser fallback for batch and CI."),""),f(c$1.join(`
6
+ `));}a$1(le,"help");function pe(e){return e?`${e.render} render \xB7 ${e.compute} compute`:"no coverage"}a$1(pe,"coverageText");function Q(e){return e<100?e.toFixed(1):Math.round(e).toString()}a$1(Q,"milliseconds");function A(e,t,i){return `${e} ${d(t,12)} ${i}`}a$1(A,"row");function oe(e$1){if(e$1.skipped)return A(c.tick,"shaders",`fresh ${c.dot} capture skipped ${b$1.dim(`(--if-stale) \xB7 ${e$1.reason}`)}`);let t=Math.max(0,Math.round((e$1.durationMs??0)/1e3)),i=e$1.scenes.length,r=[A(c.tick,"shaders",`captured in ${t} s ${c.dot} wrote ${i} shader scene${i===1?"":"s"}`)];e$1.configuration?.created===true&&r.push(b$1.dim(` created ${e$1.configuration.file} with the default "/" capture route`));for(let f of e$1.scenes)r.push(A(c.tick,f.scene,`${pe(f.coverage)} ${c.dot} ${b$1.dim(`${f.attempts} attempt${f.attempts===1?"":"s"}`)}`));let c$1=e$1.scenes.flatMap(f=>{let d=f.timing;if(d?.launch===void 0||d.firstRequest===void 0||d.readiness===void 0||d.lastBuild===void 0||d.settled===void 0||d.posted===void 0)return [];let m=a$1((M,l)=>`${Q(Math.max(0,l-M))} ms`,"elapsed");return [[f.scene,`launch\u2192request ${m(d.launch,d.firstRequest)} ${c.dot} ready ${m(d.firstRequest,d.readiness)} ${c.dot} last build ${m(d.readiness,d.lastBuild)} ${c.dot} settle ${m(d.lastBuild,d.settled)} ${c.dot} post ${m(d.settled,d.posted)}`]]});return c$1.length>0&&r.push("",b$1.bold("Capture stages"),...e(c$1,{indent:2,maxWidth:a$3.width-2})),e$1.measurement!==void 0&&r.push(` TSL builds at startup: ${e$1.measurement.beforeBuilds} \u2192 ${e$1.measurement.afterBuilds} ${c.dot} \u2248${Q(Math.abs(e$1.measurement.firstFrameDeltaMs))} ms TSL work avoided (single-capture estimate)`),r.push(b$1.dim(" a running dev server reloads on its own; otherwise refresh the page")),r.join(`
7
+ `)}a$1(oe,"formatShaderCapture");function fe(e){let t=[A(c.tick,"shaders",`no-GPU ${e.noGpu.state} ${c.dot} browser ${e.browser}`)];for(let i of e.scenes){let r=e.timing?.scenes[i.scene],c$1=r===void 0?`${i.coverage.render+i.coverage.compute} TSL build${i.coverage.render+i.coverage.compute===1?"":"s"} skipped`:`${r.builds} TSL build${r.builds===1?"":"s"} skipped ${c.dot} \u2248${Q(r.avoidedBuildMs)} ms \xB1 ${Q(r.spreadMs)} TSL work avoided ${c.dot} median of ${r.runs} ${c.dot} Latest local capture`,f=r!==void 0&&r.spreadMs>r.avoidedBuildMs*.4?` ${c.dot} ${b$1.warn("noisy measurement \u2014 close other GPU apps and re-run")}`:"";t.push(A(c.tick,i.scene,`${c$1} ${c.dot} ${i.coverage.compute} compute ${c.dot} ${(i.parity.diffRatio*100).toFixed(3)}% pixel diff${f}`));}return t.join(`
8
+ `)}a$1(fe,"formatShaderTest");async function he(){if(se!==void 0)throw new Error(se);if(y==="help"||y==="--help"||n.includes("--help"))return le();if(y==="version"||n.includes("--version")){f(w?JSON.stringify({version:a$2}):a$2);return}if(y==="browser"){let{formatBrowserPreviewReport:d,formatBrowserSmokeReport:m,runBrowserPreview:M,runBrowserSmoke:l}=await import('./browser-smoke-HB7ZXQGR.js'),{findProjectRoot:S}=await import('./status-CFJSWBDI.js');if(E==="preview"){let U=b("--root")??await S(),j=ue("--route"),I=b("--build-mode"),P=b("--expect-shaders")??"precompiled";if(P!=="precompiled"&&P!=="live")throw new Error('--expect-shaders must be "precompiled" or "live".');let T=$("--width"),B=$("--height"),J=$("--timeout"),D=$("--settle"),O=$("--min-luminance"),z=C("building and verifying the production bundle"),o;try{o=await M({root:U,...j.length===0?{}:{routes:j},...I===void 0?{}:{buildMode:I},expectShaders:P,text:n.includes("--text"),...J===void 0?{}:{timeoutMs:J},...D===void 0?{}:{settleMs:D},...O===void 0?{}:{minimumLuminance:O},...T===void 0&&B===void 0?{}:{viewport:{width:T??1024,height:B??640}},headed:n.includes("--headed")});}finally{z.stop();}a.stdout.write(`${w?JSON.stringify(o,null,2):d(o)}
9
+ `),o.passed||(a.exitCode=1);return}if(E!=="smoke")throw new Error(`Unsupported browser command "${E??""}".`);let x=b("--root")??await S(),h=$("--width"),k=$("--height"),R=b("--route"),_=$("--timeout"),q=$("--settle"),L=$("--min-luminance"),g=b("--hmr");if(n.includes("--webgl")&&n.includes("--unsupported-worker"))throw new Error("--webgl and --unsupported-worker are mutually exclusive.");let W=C(`smoking ${R??"/"} in a real browser`),v;try{v=await l({root:x,...R===void 0?{}:{route:R},..._===void 0?{}:{timeoutMs:_},...q===void 0?{}:{settleMs:q},...L===void 0?{}:{minimumLuminance:L},...g===void 0?{}:{hmrModule:g},forceWebgl:n.includes("--webgl"),unsupportedWorker:n.includes("--unsupported-worker"),expectTextGenerationFailure:n.includes("--expect-text-generation-failure"),...h===void 0&&k===void 0?{}:{viewport:{width:h??1024,height:k??640}},headed:n.includes("--headed")});}finally{W.stop();}a.stdout.write(`${w?JSON.stringify(v,null,2):m(v)}
10
+ `),v.passed||(a.exitCode=1);return}if(y==="environment"){if(E!=="bake")throw new Error(`Unsupported environment command "${E??""}".`);let{bakeRoomEnvironment:d,formatEnvironmentBakeReport:m}=await import('./environment-bake-5P4IAL4O.js'),{findProjectRoot:M}=await import('./status-CFJSWBDI.js'),l=b("--root")??await M(),S=$("--size"),x=b("--output"),h=$("--timeout"),k=C("baking the room environment on the GPU"),R;try{R=await d({root:l,...S===void 0?{}:{size:S},...x===void 0?{}:{output:x},...h===void 0?{}:{timeoutMs:h},compression:n.includes("--no-zstd")?"none":"zstd",headed:n.includes("--headed")});}finally{k.stop();}a.stdout.write(`${w?JSON.stringify(R,null,2):m(R)}
11
+ `);return}if(y==="shaders"){let{findShaderProjectRoot:d}=await import('./shader-config-BYBJUL3C.js'),m=E??"status";if(!["status","capture","test","watch"].includes(m))throw new Error(`Unsupported shader command "${m}".`);let M=b("--root"),l=await d(M),S=b("--driver");if(S!==void 0&&S!=="vite"&&S!=="static")throw new Error('--driver must be "vite" or "static".');let x=S,h=b("--serve"),k=b("--url");if(m==="status"){let{inspectShaderMatrix:g}=await import('./shader-workflow-SO7WCR4Y.js'),{formatShaderMatrixStatus:W}=await import('./status-CFJSWBDI.js'),v=await g(l);a.stdout.write(`${w?JSON.stringify(v,null,2):W(v)}
12
+ `),(n.includes("--check")||E===void 0)&&v.state!=="fresh"&&(a.exitCode=1);return}if(m==="capture"){let{captureShaders:g}=await import('./shader-workflow-SO7WCR4Y.js'),{applyShaderAutoWire:W,hasRegisterDevtoolsCall:v,inspectShaderAutoWire:U}=await import('./shader-autowire-DCODBFMX.js'),{buildShaderCaptureProject:j,startNextShaderCaptureServer:I}=await import('./shader-project-runner-GUR3LTYS.js'),{inferShaderCaptureTarget:P}=await import('./shader-config-BYBJUL3C.js'),T=x,B=h,J=k,D,O,z;try{if(T===void 0&&B===void 0&&J===void 0){let o=await U(l);if(!o.configured&&o.plan!==void 0){if(!await ce(o.plan.diff))throw new Error("Adapter wiring was declined; no files were changed.");await W(o.plan);}else if(!o.configured&&o.bundler!=="vite"&&o.bundler!=="unknown")throw new Error(`${o.reason} No files were changed.`);if(o.bundler==="next")O=C("starting the project-local Next.js capture server"),D=await I(l),J=D.url,O.update("capturing shader scenes");else if(o.bundler==="webpack"||o.bundler==="esbuild"||o.bundler==="rollup"){O=C(`building the ${o.bundler} capture target`),await j(l);let N=await P(l);if(N.driverKind!=="static"||N.serve===void 0)throw new Error(`${o.bundler} build completed but no dist/, build/, or out/index.html was found; pass --driver static --serve <directory>.`);T="static",B=N.serve,O.update("capturing shader scenes");}}O??=C("capturing shader scenes"),z=await g({root:l,headed:n.includes("--headed"),ifStale:n.includes("--if-stale"),commitTiming:n.includes("--commit-timing"),...T===void 0?{}:{driverKind:T},...B===void 0?{}:{serve:B},...J===void 0?{}:{url:J},onSceneStart:a$1((o,N,K)=>O?.update(`capturing ${b$1.accent(o)} ${b$1.dim(`(${N+1}/${K})`)}`),"onSceneStart"),onSceneRetry:a$1((o,N,K,re,V)=>{let ae=V instanceof Error&&/timed out/iu.test(V.message),ne=`capture attempt ${N}/${K} for "${o}" after ${ae?`timeout (${Math.round(re/1e3)} s burned)`:`failure (${V instanceof Error?V.message:String(V)})`}`;a.stderr.write(w?`${JSON.stringify({type:"shader-capture-retry",scene:o,attempt:N,total:K,timeoutMs:re,message:ne})}
13
13
  `:`${ne}
14
- `);},"onSceneRetry")});}finally{R?.stop(),await L?.close();}if(u.stdout.write(`${w?JSON.stringify(z,null,2):oe(z)}
15
- `),!await v(f$1)){let d="Add registerDevtools({ renderer }) from three-blocks/devtools so committed shader artifacts hydrate in development and production.";u.stderr.write(w?`${JSON.stringify({type:"shader-runtime-registration",state:"missing",message:d})}
16
- `:`${c.warn} ${d}
17
- `);}return}if(p==="test"){let g=b("--mode");if(g!==void 0&&g!=="strict")throw new Error('--mode currently supports only "strict".');let{testShaders:B}=await import('./shader-workflow-U4G565EB.js'),v=C("verifying precompiled shaders against the live baseline"),I=$("--runs"),T;try{T=await B({root:f$1,headed:n.includes("--headed"),skipBrowser:n.includes("--skip-browser"),...k===void 0?{}:{driverKind:k},...m===void 0?{}:{serve:m},...E===void 0?{}:{url:E},...I===void 0?{}:{runs:I},parityOnly:n.includes("--parity-only"),commitTiming:n.includes("--commit-timing"),onSceneStart:a((U,W,j)=>v.update(`verifying ${b$1.accent(U)} ${b$1.dim(`(${W+1}/${j})`)}`),"onSceneStart")});}finally{v.stop();}u.stdout.write(`${w?JSON.stringify(T,null,2):fe(T)}
18
- `);return}let O=$("--debounce"),{watchShaders:K}=await import('./shader-watch-QHTNTSZE.js'),q=new AbortController;u.once("SIGINT",()=>q.abort()),u.once("SIGTERM",()=>q.abort());let G=await K({root:f$1,headed:n.includes("--headed"),...k===void 0?{}:{driverKind:k},...m===void 0?{}:{serve:m},...E===void 0?{}:{url:E},...O===void 0?{}:{debounceMs:O},signal:q.signal,onResult(g){u.stdout.write(`${w?JSON.stringify(g):oe(g)}
19
- `);},onError(g){u.stderr.write(`${w?JSON.stringify({error:g.message}):`${c.cross} ${b$1.danger(g.message)}`}
20
- `);}});w||f(A(c.ring,"watching",b$1.dim(G.root))),await G.done;return}if(y==="text"&&M&&M!=="status"){if(M!=="generate")throw new Error(`Unsupported text command "${M}".`);let{generateTextAtlases:o}=await import('./text-atlas-BHPSDU7C.js'),{findProjectRoot:p}=await import('./status-DV2BYZHR.js'),S=n.indexOf("--config"),f$1=S===-1?"three-blocks.text.ts":n[S+1];if(f$1===void 0||f$1.startsWith("--"))throw new Error("--config requires a project-relative path.");let x=await p(),k=C("generating MSDF text atlases"),m;try{m=await o(x,f$1);}finally{k.stop();}if(w)u.stdout.write(`${JSON.stringify(m,null,2)}
21
- `);else {f(A(c.tick,"text",`${m.glyphs} glyphs ${c.dot} ${m.atlases.length} atlas${m.atlases.length===1?"":"es"} regenerated`));for(let E of m.updatedFiles)f(` ${d(b$1.dim("updated"),12)} ${b$1.dim(E)}`);}return}if(!["status","text"].includes(y))throw new Error(`Unknown command "${y}".`);let{formatThreeBlocksStatus:e,inspectThreeBlocksStatus:t,threeBlocksStatusIsFresh:i}=await import('./status-DV2BYZHR.js'),r=await t(),c$1=(r.text);if(y==="text"&&!c$1)throw new Error("This project does not enable the text feature.");let l=y==="text"?c$1:r;u.stdout.write(`${w?JSON.stringify(l,null,2):y==="status"?e(r):JSON.stringify(l,null,2)}
22
- `),y==="status"&&n.includes("--check")&&!i(r)&&(u.exitCode=1);}a(he,"main");he().catch(e=>{let t=e instanceof Error?e.message:String(e);w?u.stderr.write(`${JSON.stringify({error:t})}
23
- `):u.stderr.write(`${c.cross} ${b$1.danger(t)}
24
- `),u.exitCode=1;});
14
+ `);},"onSceneRetry")});}finally{O?.stop(),await D?.close();}if(a.stdout.write(`${w?JSON.stringify(z,null,2):oe(z)}
15
+ `),!await v(l)){let o="Add registerDevtools({ renderer }) from three-blocks/devtools so committed shader artifacts hydrate in development and production.";a.stderr.write(w?`${JSON.stringify({type:"shader-runtime-registration",state:"missing",message:o})}
16
+ `:`${c.warn} ${o}
17
+ `);}return}if(m==="test"){let g=b("--mode");if(g!==void 0&&g!=="strict")throw new Error('--mode currently supports only "strict".');let{testShaders:W}=await import('./shader-workflow-SO7WCR4Y.js'),v=C("verifying precompiled shaders against the live baseline"),U=$("--runs"),j;try{j=await W({root:l,headed:n.includes("--headed"),skipBrowser:n.includes("--skip-browser"),...x===void 0?{}:{driverKind:x},...h===void 0?{}:{serve:h},...k===void 0?{}:{url:k},...U===void 0?{}:{runs:U},parityOnly:n.includes("--parity-only"),commitTiming:n.includes("--commit-timing"),onSceneStart:a$1((I,P,T)=>v.update(`verifying ${b$1.accent(I)} ${b$1.dim(`(${P+1}/${T})`)}`),"onSceneStart")});}finally{v.stop();}a.stdout.write(`${w?JSON.stringify(j,null,2):fe(j)}
18
+ `);return}let R=$("--debounce"),{watchShaders:_}=await import('./shader-watch-Z3VGP4QB.js'),q=new AbortController;a.once("SIGINT",()=>q.abort()),a.once("SIGTERM",()=>q.abort());let L=await _({root:l,headed:n.includes("--headed"),...x===void 0?{}:{driverKind:x},...h===void 0?{}:{serve:h},...k===void 0?{}:{url:k},...R===void 0?{}:{debounceMs:R},signal:q.signal,onResult(g){a.stdout.write(`${w?JSON.stringify(g):oe(g)}
19
+ `);},onError(g){a.stderr.write(`${w?JSON.stringify({error:g.message}):`${c.cross} ${b$1.danger(g.message)}`}
20
+ `);}});w||f(A(c.ring,"watching",b$1.dim(L.root))),await L.done;return}if(y==="text"&&E&&E!=="status"){if(E!=="generate")throw new Error(`Unsupported text command "${E}".`);let{generateTextAtlases:d$1}=await import('./text-atlas-BHPSDU7C.js'),{findProjectRoot:m}=await import('./status-CFJSWBDI.js'),M=n.indexOf("--config"),l=M===-1?"three-blocks.text.ts":n[M+1];if(l===void 0||l.startsWith("--"))throw new Error("--config requires a project-relative path.");let S=await m(),x=C("generating MSDF text atlases"),h;try{h=await d$1(S,l);}finally{x.stop();}if(w)a.stdout.write(`${JSON.stringify(h,null,2)}
21
+ `);else {f(A(c.tick,"text",`${h.glyphs} glyphs ${c.dot} ${h.atlases.length} atlas${h.atlases.length===1?"":"es"} regenerated`));for(let k of h.updatedFiles)f(` ${d(b$1.dim("updated"),12)} ${b$1.dim(k)}`);}return}if(!["status","text"].includes(y))throw new Error(`Unknown command "${y}".`);let{formatThreeBlocksStatus:e,inspectThreeBlocksStatus:t,threeBlocksStatusIsFresh:i}=await import('./status-CFJSWBDI.js'),r=await t(),c$1=(r.text);if(y==="text"&&!c$1)throw new Error("This project does not enable the text feature.");let f$1=y==="text"?c$1:r;a.stdout.write(`${w?JSON.stringify(f$1,null,2):y==="status"?e(r):JSON.stringify(f$1,null,2)}
22
+ `),y==="status"&&n.includes("--check")&&!i(r)&&(a.exitCode=1);}a$1(he,"main");he().catch(e=>{let t=e instanceof Error?e.message:String(e);w?a.stderr.write(`${JSON.stringify({error:t})}
23
+ `):a.stderr.write(`${c.cross} ${b$1.danger(t)}
24
+ `),a.exitCode=1;});
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { PrecompiledManifest } from 'three-blocks/shaders';
2
- export { A as AppliedShaderProviderTransform, a as ApplyShaderProviderTransformOptions, S as SHADER_CAPTURE_CLIENT_ID, b as ShaderCaptureTransformOptions, c as ShaderProviderTransform, d as ShaderProviderTransformOptions, e as ShaderProviderTransformStatus, f as applyShaderProviderTransform, g as assertShaderProviderTransform, h as createShaderProviderTransform } from './shader-transform-CUkZn5Oa.js';
2
+ export { A as AppliedShaderProviderTransform, a as ApplyShaderProviderTransformOptions, S as SHADER_CAPTURE_CLIENT_ID, b as ShaderCaptureTransformOptions, c as ShaderProviderTransform, d as ShaderProviderTransformOptions, e as ShaderProviderTransformStatus, f as applyShaderProviderTransform, g as assertShaderProviderTransform, h as createShaderProviderTransform } from './shader-transform-DSFaVrux.js';
3
3
  import 'vite';
4
4
 
5
5
  type ArtifactState = 'fresh' | 'pending';
@@ -182,8 +182,6 @@ interface ShaderSceneConfig {
182
182
  readonly parityFrames?: number;
183
183
  /** Optional scene-specific pixel-difference budget for intrinsically nondeterministic GPU work. */
184
184
  readonly maxDiffRatio?: number;
185
- /** Permit Three's backend pipeline cache to reuse an earlier injected pipeline with identical WGSL. */
186
- readonly allowPipelineReuse?: boolean;
187
185
  /** Require the normal published page's primary canvas to keep changing after startup. */
188
186
  readonly runtimeMotion?: boolean;
189
187
  }
@@ -249,22 +247,22 @@ declare function ensureShaderToolConfig(root: string): Promise<EnsuredShaderTool
249
247
  /** Published devtools protocol/package version. Kept dependency-free for instant CLI startup. */
250
248
  declare const SHADER_DEVTOOLS_VERSION: "0.1.0";
251
249
 
252
- declare const SHADER_PROVIDER_TRANSFORM_VERSION: 3;
250
+ declare const SHADER_PROVIDER_TRANSFORM_VERSION: 50;
253
251
  declare const SHADER_LOCAL_TIMING_FILE: "timing.local.json";
254
252
  declare const SHADER_COMMITTED_TIMING_FILE: "timing.json";
255
253
  declare const SHADER_PARITY_EVIDENCE_FILE: "parity.json";
256
254
  interface ShaderSceneTiming {
257
255
  /** Number of registered render/compute NodeBuilder builds covered by the manifest. */
258
256
  readonly builds: number;
259
- /** Cumulative NodeBuilder time in the live half of the capture. */
257
+ /** Cumulative NodeBuilder time observed while shaders build live. */
260
258
  readonly liveBuildMs: number;
261
- /** Cumulative NodeBuilder time in the precompiled half of the same capture. */
259
+ /** Observed precompiled time, or zero for the single-capture hydration estimate. */
262
260
  readonly precompiledBuildMs: number;
263
261
  /** Non-negative live minus precompiled NodeBuilder time. */
264
262
  readonly avoidedBuildMs: number;
265
- /** Number of measured live/precompiled pairs represented by the medians. */
263
+ /** One for a capture estimate; greater than one for warmed live/precompiled medians. */
266
264
  readonly runs: number;
267
- /** Maximum absolute per-run saved-time deviation from the median. */
265
+ /** Maximum absolute per-run deviation, or zero for a capture estimate. */
268
266
  readonly spreadMs: number;
269
267
  }
270
268
  interface ShaderLocalTiming {
@@ -508,12 +506,14 @@ interface ShaderMatrixStatus {
508
506
  readonly timing?: ShaderTiming;
509
507
  }
510
508
  /** Inspect all configured manifests without launching Vite, a browser, or a GPU. */
511
- declare function inspectShaderMatrix(start?: string): Promise<ShaderMatrixStatus>;
509
+ declare function inspectShaderMatrix(start?: string, runtimeOverride?: InstalledShaderRuntime): Promise<ShaderMatrixStatus>;
512
510
 
513
511
  interface CaptureShadersOptions {
514
512
  readonly start?: string;
515
513
  readonly root?: string;
516
514
  readonly config?: ShaderToolConfig;
515
+ /** Exact runtime identity for an attached or checkout-built application. */
516
+ readonly runtime?: InstalledShaderRuntime;
517
517
  readonly driver?: ShaderBrowserDriver;
518
518
  /** Built-output capture bypasses Vite in the target project. */
519
519
  readonly driverKind?: 'vite' | 'static';
@@ -524,6 +524,8 @@ interface CaptureShadersOptions {
524
524
  readonly headed?: boolean;
525
525
  readonly ifStale?: boolean;
526
526
  readonly attempts?: number;
527
+ /** Also write a shareable, hardware-labeled single-capture timing estimate. */
528
+ readonly commitTiming?: boolean;
527
529
  /** Progress hook fired before each scene capture begins. */
528
530
  readonly onSceneStart?: (scene: string, index: number, total: number) => void;
529
531
  /** Progress hook fired before a failed scene is retried. */
@@ -543,7 +545,7 @@ interface CaptureSceneResult {
543
545
  interface ShaderStartupMeasurement {
544
546
  readonly beforeBuilds: number;
545
547
  readonly afterBuilds: 0;
546
- /** Negative measured NodeBuilder time removed from the first-frame critical path. */
548
+ /** Negative cumulative NodeBuilder work observed during the capture build phase. */
547
549
  readonly firstFrameDeltaMs: number;
548
550
  readonly source: 'capture-build-phase';
549
551
  }
@@ -568,6 +570,8 @@ interface TestShadersOptions {
568
570
  readonly start?: string;
569
571
  readonly root?: string;
570
572
  readonly config?: ShaderToolConfig;
573
+ /** Exact runtime identity for an attached or checkout-built application. */
574
+ readonly runtime?: InstalledShaderRuntime;
571
575
  readonly driver?: ShaderBrowserDriver;
572
576
  readonly driverKind?: 'vite' | 'static';
573
577
  readonly serve?: string;