@driftengine/core 3.61.0

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 (955) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +9 -0
  4. package/dist/behavior/behaviorTree.d.ts +140 -0
  5. package/dist/behavior/behaviorTree.js +289 -0
  6. package/dist/build/shaderComments.d.ts +64 -0
  7. package/dist/build/shaderComments.js +0 -0
  8. package/dist/cinematic/player.d.ts +80 -0
  9. package/dist/cinematic/player.js +184 -0
  10. package/dist/cinematic/script.d.ts +63 -0
  11. package/dist/cinematic/script.js +141 -0
  12. package/dist/core/bootGate.d.ts +37 -0
  13. package/dist/core/bootGate.js +47 -0
  14. package/dist/core/loadTracker.d.ts +78 -0
  15. package/dist/core/loadTracker.js +106 -0
  16. package/dist/core/loop.d.ts +118 -0
  17. package/dist/core/loop.js +84 -0
  18. package/dist/core/messageQueue.d.ts +61 -0
  19. package/dist/core/messageQueue.js +104 -0
  20. package/dist/core/onceSet.d.ts +27 -0
  21. package/dist/core/onceSet.js +62 -0
  22. package/dist/core/preferences.d.ts +47 -0
  23. package/dist/core/preferences.js +140 -0
  24. package/dist/core/remoteSave.d.ts +154 -0
  25. package/dist/core/remoteSave.js +249 -0
  26. package/dist/core/rng.d.ts +64 -0
  27. package/dist/core/rng.js +72 -0
  28. package/dist/core/stepBudget.d.ts +91 -0
  29. package/dist/core/stepBudget.js +105 -0
  30. package/dist/core/storage.d.ts +52 -0
  31. package/dist/core/storage.js +85 -0
  32. package/dist/core/tickTrace.d.ts +72 -0
  33. package/dist/core/tickTrace.js +102 -0
  34. package/dist/core/timeCurve.d.ts +83 -0
  35. package/dist/core/timeCurve.js +107 -0
  36. package/dist/core/withDefaults.d.ts +21 -0
  37. package/dist/core/withDefaults.js +29 -0
  38. package/dist/dev/contactProbe.d.ts +41 -0
  39. package/dist/dev/contactProbe.js +58 -0
  40. package/dist/dev/fpsMeter.d.ts +45 -0
  41. package/dist/dev/fpsMeter.js +86 -0
  42. package/dist/environment/celestialClock.d.ts +141 -0
  43. package/dist/environment/celestialClock.js +262 -0
  44. package/dist/geometry/decal.d.ts +61 -0
  45. package/dist/geometry/decal.js +178 -0
  46. package/dist/geometry/filmPatch.d.ts +35 -0
  47. package/dist/geometry/filmPatch.js +112 -0
  48. package/dist/geometry/growable.d.ts +70 -0
  49. package/dist/geometry/growable.js +167 -0
  50. package/dist/geometry/lightVolume.d.ts +76 -0
  51. package/dist/geometry/lightVolume.js +65 -0
  52. package/dist/geometry/meshBuilder.d.ts +426 -0
  53. package/dist/geometry/meshBuilder.js +1184 -0
  54. package/dist/geometry/pixelFont.d.ts +59 -0
  55. package/dist/geometry/pixelFont.js +179 -0
  56. package/dist/geometry/ribbon.d.ts +101 -0
  57. package/dist/geometry/ribbon.js +546 -0
  58. package/dist/geometry/shellFill.d.ts +101 -0
  59. package/dist/geometry/shellFill.js +203 -0
  60. package/dist/geometry/spline.d.ts +162 -0
  61. package/dist/geometry/spline.js +527 -0
  62. package/dist/geometry/tangents.d.ts +30 -0
  63. package/dist/geometry/tangents.js +142 -0
  64. package/dist/geometry/textMesh.d.ts +51 -0
  65. package/dist/geometry/textMesh.js +57 -0
  66. package/dist/geometry/treeBuilder.d.ts +37 -0
  67. package/dist/geometry/treeBuilder.js +56 -0
  68. package/dist/host/display.d.ts +135 -0
  69. package/dist/host/display.js +114 -0
  70. package/dist/host/files.d.ts +25 -0
  71. package/dist/host/files.js +44 -0
  72. package/dist/host/lifecycle.d.ts +45 -0
  73. package/dist/host/lifecycle.js +28 -0
  74. package/dist/host/platformServices.d.ts +23 -0
  75. package/dist/host/platformServices.js +1 -0
  76. package/dist/host/screen.d.ts +49 -0
  77. package/dist/host/screen.js +90 -0
  78. package/dist/index.d.ts +289 -0
  79. package/dist/index.js +320 -0
  80. package/dist/input/actionMap.d.ts +181 -0
  81. package/dist/input/actionMap.js +314 -0
  82. package/dist/input/activeDevice.d.ts +55 -0
  83. package/dist/input/activeDevice.js +83 -0
  84. package/dist/input/gamepadIdentity.d.ts +43 -0
  85. package/dist/input/gamepadIdentity.js +108 -0
  86. package/dist/input/gamepadMapping.d.ts +56 -0
  87. package/dist/input/gamepadMapping.js +91 -0
  88. package/dist/input/input.d.ts +281 -0
  89. package/dist/input/input.js +703 -0
  90. package/dist/input/inputHarness.d.ts +42 -0
  91. package/dist/input/inputHarness.js +193 -0
  92. package/dist/input/rightGesture.d.ts +57 -0
  93. package/dist/input/rightGesture.js +49 -0
  94. package/dist/input/rumble.d.ts +71 -0
  95. package/dist/input/rumble.js +119 -0
  96. package/dist/input/touchControls.d.ts +138 -0
  97. package/dist/input/touchControls.js +352 -0
  98. package/dist/input/typingTarget.d.ts +6 -0
  99. package/dist/input/typingTarget.js +27 -0
  100. package/dist/math/bounds.d.ts +61 -0
  101. package/dist/math/bounds.js +121 -0
  102. package/dist/math/color.d.ts +8 -0
  103. package/dist/math/color.js +35 -0
  104. package/dist/math/exact.d.ts +66 -0
  105. package/dist/math/exact.js +262 -0
  106. package/dist/math/frustum.d.ts +44 -0
  107. package/dist/math/frustum.js +90 -0
  108. package/dist/math/intersect.d.ts +76 -0
  109. package/dist/math/intersect.js +185 -0
  110. package/dist/math/scalar.d.ts +51 -0
  111. package/dist/math/scalar.js +99 -0
  112. package/dist/math/spring.d.ts +59 -0
  113. package/dist/math/spring.js +88 -0
  114. package/dist/nav/navGraph.d.ts +71 -0
  115. package/dist/nav/navGraph.js +111 -0
  116. package/dist/nav/navPath.d.ts +93 -0
  117. package/dist/nav/navPath.js +162 -0
  118. package/dist/nav/navSearch.d.ts +74 -0
  119. package/dist/nav/navSearch.js +198 -0
  120. package/dist/physics/boxSurface.d.ts +48 -0
  121. package/dist/physics/boxSurface.js +73 -0
  122. package/dist/physics/colliderSurface.d.ts +46 -0
  123. package/dist/physics/colliderSurface.js +146 -0
  124. package/dist/physics/compositeSurface.d.ts +33 -0
  125. package/dist/physics/compositeSurface.js +108 -0
  126. package/dist/physics/heightSurface.d.ts +103 -0
  127. package/dist/physics/heightSurface.js +153 -0
  128. package/dist/physics/ribbonSurface.d.ts +239 -0
  129. package/dist/physics/ribbonSurface.js +522 -0
  130. package/dist/render/ambientOcclusionPass.d.ts +53 -0
  131. package/dist/render/ambientOcclusionPass.js +179 -0
  132. package/dist/render/areaLights.d.ts +116 -0
  133. package/dist/render/areaLights.js +91 -0
  134. package/dist/render/areaShadowSet.d.ts +184 -0
  135. package/dist/render/areaShadowSet.js +277 -0
  136. package/dist/render/atmosphere.d.ts +94 -0
  137. package/dist/render/atmosphere.js +91 -0
  138. package/dist/render/backend/api.d.ts +496 -0
  139. package/dist/render/backend/api.js +1 -0
  140. package/dist/render/backend/budget.d.ts +82 -0
  141. package/dist/render/backend/budget.js +99 -0
  142. package/dist/render/backend/createRenderer.d.ts +135 -0
  143. package/dist/render/backend/createRenderer.js +201 -0
  144. package/dist/render/backend/deadline.d.ts +24 -0
  145. package/dist/render/backend/deadline.js +38 -0
  146. package/dist/render/backend/probe.d.ts +30 -0
  147. package/dist/render/backend/probe.js +129 -0
  148. package/dist/render/backend/select.d.ts +70 -0
  149. package/dist/render/backend/select.js +249 -0
  150. package/dist/render/backend/timer.d.ts +50 -0
  151. package/dist/render/backend/timer.js +1 -0
  152. package/dist/render/backend/webgl2/instanced.d.ts +21 -0
  153. package/dist/render/backend/webgl2/instanced.js +42 -0
  154. package/dist/render/backend/webgl2/morphTexture.d.ts +21 -0
  155. package/dist/render/backend/webgl2/morphTexture.js +55 -0
  156. package/dist/render/backend/webgl2/renderer.d.ts +3027 -0
  157. package/dist/render/backend/webgl2/renderer.js +6041 -0
  158. package/dist/render/backend/webgl2/skinPaletteTexture.d.ts +32 -0
  159. package/dist/render/backend/webgl2/skinPaletteTexture.js +68 -0
  160. package/dist/render/backend/webgpu/boltPass.d.ts +42 -0
  161. package/dist/render/backend/webgpu/boltPass.js +160 -0
  162. package/dist/render/backend/webgpu/buffers.d.ts +159 -0
  163. package/dist/render/backend/webgpu/buffers.js +402 -0
  164. package/dist/render/backend/webgpu/causticsPass.d.ts +36 -0
  165. package/dist/render/backend/webgpu/causticsPass.js +138 -0
  166. package/dist/render/backend/webgpu/clusterBinner.d.ts +51 -0
  167. package/dist/render/backend/webgpu/clusterBinner.js +206 -0
  168. package/dist/render/backend/webgpu/depthPass.d.ts +114 -0
  169. package/dist/render/backend/webgpu/depthPass.js +228 -0
  170. package/dist/render/backend/webgpu/device.d.ts +52 -0
  171. package/dist/render/backend/webgpu/device.js +121 -0
  172. package/dist/render/backend/webgpu/drawCommand.d.ts +43 -0
  173. package/dist/render/backend/webgpu/drawCommand.js +54 -0
  174. package/dist/render/backend/webgpu/environmentTexels.d.ts +28 -0
  175. package/dist/render/backend/webgpu/environmentTexels.js +79 -0
  176. package/dist/render/backend/webgpu/filmPass.d.ts +29 -0
  177. package/dist/render/backend/webgpu/filmPass.js +97 -0
  178. package/dist/render/backend/webgpu/flatPass.d.ts +213 -0
  179. package/dist/render/backend/webgpu/flatPass.js +482 -0
  180. package/dist/render/backend/webgpu/flockPass.d.ts +34 -0
  181. package/dist/render/backend/webgpu/flockPass.js +129 -0
  182. package/dist/render/backend/webgpu/gpuTimestamps.d.ts +94 -0
  183. package/dist/render/backend/webgpu/gpuTimestamps.js +213 -0
  184. package/dist/render/backend/webgpu/insetPass.d.ts +19 -0
  185. package/dist/render/backend/webgpu/insetPass.js +108 -0
  186. package/dist/render/backend/webgpu/instanced.d.ts +27 -0
  187. package/dist/render/backend/webgpu/instanced.js +49 -0
  188. package/dist/render/backend/webgpu/lightVolumePass.d.ts +75 -0
  189. package/dist/render/backend/webgpu/lightVolumePass.js +154 -0
  190. package/dist/render/backend/webgpu/linePass.d.ts +90 -0
  191. package/dist/render/backend/webgpu/linePass.js +220 -0
  192. package/dist/render/backend/webgpu/morphTexture.d.ts +18 -0
  193. package/dist/render/backend/webgpu/morphTexture.js +51 -0
  194. package/dist/render/backend/webgpu/panelPass.d.ts +37 -0
  195. package/dist/render/backend/webgpu/panelPass.js +114 -0
  196. package/dist/render/backend/webgpu/particlePass.d.ts +89 -0
  197. package/dist/render/backend/webgpu/particlePass.js +280 -0
  198. package/dist/render/backend/webgpu/pipelineCache.d.ts +93 -0
  199. package/dist/render/backend/webgpu/pipelineCache.js +140 -0
  200. package/dist/render/backend/webgpu/plumePass.d.ts +69 -0
  201. package/dist/render/backend/webgpu/plumePass.js +213 -0
  202. package/dist/render/backend/webgpu/pointShadowPass.d.ts +88 -0
  203. package/dist/render/backend/webgpu/pointShadowPass.js +280 -0
  204. package/dist/render/backend/webgpu/postPass.d.ts +342 -0
  205. package/dist/render/backend/webgpu/postPass.js +366 -0
  206. package/dist/render/backend/webgpu/probePass.d.ts +108 -0
  207. package/dist/render/backend/webgpu/probePass.js +188 -0
  208. package/dist/render/backend/webgpu/reflectionPass.d.ts +29 -0
  209. package/dist/render/backend/webgpu/reflectionPass.js +87 -0
  210. package/dist/render/backend/webgpu/renderer.d.ts +3082 -0
  211. package/dist/render/backend/webgpu/renderer.js +10911 -0
  212. package/dist/render/backend/webgpu/scatterPass.d.ts +95 -0
  213. package/dist/render/backend/webgpu/scatterPass.js +266 -0
  214. package/dist/render/backend/webgpu/sdfTextPass.d.ts +86 -0
  215. package/dist/render/backend/webgpu/sdfTextPass.js +190 -0
  216. package/dist/render/backend/webgpu/shaderModules.d.ts +9 -0
  217. package/dist/render/backend/webgpu/shaderModules.js +45 -0
  218. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +23 -0
  219. package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +208 -0
  220. package/dist/render/backend/webgpu/skinPaletteRing.d.ts +67 -0
  221. package/dist/render/backend/webgpu/skinPaletteRing.js +97 -0
  222. package/dist/render/backend/webgpu/skinPaletteTexture.d.ts +17 -0
  223. package/dist/render/backend/webgpu/skinPaletteTexture.js +68 -0
  224. package/dist/render/backend/webgpu/skyPass.d.ts +102 -0
  225. package/dist/render/backend/webgpu/skyPass.js +70 -0
  226. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +41 -0
  227. package/dist/render/backend/webgpu/surfaceTexturePass.js +228 -0
  228. package/dist/render/backend/webgpu/textPass.d.ts +73 -0
  229. package/dist/render/backend/webgpu/textPass.js +190 -0
  230. package/dist/render/backend/webgpu/uniformRing.d.ts +133 -0
  231. package/dist/render/backend/webgpu/uniformRing.js +208 -0
  232. package/dist/render/backend/webgpu/waterPass.d.ts +73 -0
  233. package/dist/render/backend/webgpu/waterPass.js +160 -0
  234. package/dist/render/backend/webgpu/windStreakPass.d.ts +46 -0
  235. package/dist/render/backend/webgpu/windStreakPass.js +153 -0
  236. package/dist/render/billboard.d.ts +27 -0
  237. package/dist/render/billboard.js +79 -0
  238. package/dist/render/bloomChain.d.ts +66 -0
  239. package/dist/render/bloomChain.js +83 -0
  240. package/dist/render/bloomPass.d.ts +54 -0
  241. package/dist/render/bloomPass.js +219 -0
  242. package/dist/render/boltBatch.d.ts +39 -0
  243. package/dist/render/boltBatch.js +121 -0
  244. package/dist/render/boltPool.d.ts +135 -0
  245. package/dist/render/boltPool.js +259 -0
  246. package/dist/render/boom.d.ts +46 -0
  247. package/dist/render/boom.js +81 -0
  248. package/dist/render/camera.d.ts +119 -0
  249. package/dist/render/camera.js +304 -0
  250. package/dist/render/causticsRenderer.d.ts +63 -0
  251. package/dist/render/causticsRenderer.js +119 -0
  252. package/dist/render/cinematicCamera.d.ts +102 -0
  253. package/dist/render/cinematicCamera.js +227 -0
  254. package/dist/render/clusteredLights.d.ts +260 -0
  255. package/dist/render/clusteredLights.js +479 -0
  256. package/dist/render/colourGrade.d.ts +62 -0
  257. package/dist/render/colourGrade.js +91 -0
  258. package/dist/render/compute.d.ts +74 -0
  259. package/dist/render/compute.js +8 -0
  260. package/dist/render/contextLoss.d.ts +32 -0
  261. package/dist/render/contextLoss.js +46 -0
  262. package/dist/render/debugLines.d.ts +75 -0
  263. package/dist/render/debugLines.js +281 -0
  264. package/dist/render/decalPass.d.ts +34 -0
  265. package/dist/render/decalPass.js +103 -0
  266. package/dist/render/decalProjector.d.ts +140 -0
  267. package/dist/render/decalProjector.js +282 -0
  268. package/dist/render/decalQueue.d.ts +53 -0
  269. package/dist/render/decalQueue.js +87 -0
  270. package/dist/render/depthConvention.d.ts +314 -0
  271. package/dist/render/depthConvention.js +346 -0
  272. package/dist/render/drawingBuffer.d.ts +52 -0
  273. package/dist/render/drawingBuffer.js +67 -0
  274. package/dist/render/emptyTexture.d.ts +60 -0
  275. package/dist/render/emptyTexture.js +103 -0
  276. package/dist/render/envProbeArray.d.ts +85 -0
  277. package/dist/render/envProbeArray.js +164 -0
  278. package/dist/render/equirectToCube.d.ts +54 -0
  279. package/dist/render/equirectToCube.js +123 -0
  280. package/dist/render/flockGeometry.d.ts +23 -0
  281. package/dist/render/flockGeometry.js +43 -0
  282. package/dist/render/flockRenderer.d.ts +37 -0
  283. package/dist/render/flockRenderer.js +70 -0
  284. package/dist/render/frame/arena.d.ts +32 -0
  285. package/dist/render/frame/arena.js +65 -0
  286. package/dist/render/frame/index.d.ts +18 -0
  287. package/dist/render/frame/index.js +4 -0
  288. package/dist/render/frame/replay.d.ts +34 -0
  289. package/dist/render/frame/replay.js +48 -0
  290. package/dist/render/frame/resources.d.ts +25 -0
  291. package/dist/render/frame/resources.js +55 -0
  292. package/dist/render/frame/schedule.d.ts +37 -0
  293. package/dist/render/frame/schedule.js +61 -0
  294. package/dist/render/frameView.d.ts +19 -0
  295. package/dist/render/frameView.js +1 -0
  296. package/dist/render/gizmo.d.ts +236 -0
  297. package/dist/render/gizmo.js +727 -0
  298. package/dist/render/globalMedium.d.ts +124 -0
  299. package/dist/render/globalMedium.js +100 -0
  300. package/dist/render/globalMediumPass.d.ts +101 -0
  301. package/dist/render/globalMediumPass.js +250 -0
  302. package/dist/render/gpuCapability.d.ts +111 -0
  303. package/dist/render/gpuCapability.js +194 -0
  304. package/dist/render/gpuTimer.d.ts +87 -0
  305. package/dist/render/gpuTimer.js +145 -0
  306. package/dist/render/halfFloat.d.ts +4 -0
  307. package/dist/render/halfFloat.js +38 -0
  308. package/dist/render/handleRegistry.d.ts +54 -0
  309. package/dist/render/handleRegistry.js +84 -0
  310. package/dist/render/iesProfile.d.ts +64 -0
  311. package/dist/render/iesProfile.js +179 -0
  312. package/dist/render/imageTexels.d.ts +50 -0
  313. package/dist/render/imageTexels.js +91 -0
  314. package/dist/render/instancedMesh.d.ts +48 -0
  315. package/dist/render/instancedMesh.js +127 -0
  316. package/dist/render/instances.d.ts +69 -0
  317. package/dist/render/instances.js +52 -0
  318. package/dist/render/lightBudget.d.ts +397 -0
  319. package/dist/render/lightBudget.js +524 -0
  320. package/dist/render/lightMatrix.d.ts +3 -0
  321. package/dist/render/lightMatrix.js +45 -0
  322. package/dist/render/lightVolumeDraw.d.ts +132 -0
  323. package/dist/render/lightVolumeDraw.js +181 -0
  324. package/dist/render/lineBatch.d.ts +60 -0
  325. package/dist/render/lineBatch.js +160 -0
  326. package/dist/render/linePoints.d.ts +33 -0
  327. package/dist/render/linePoints.js +46 -0
  328. package/dist/render/livePointShadowSet.d.ts +72 -0
  329. package/dist/render/livePointShadowSet.js +258 -0
  330. package/dist/render/lod.d.ts +35 -0
  331. package/dist/render/lod.js +65 -0
  332. package/dist/render/mesh.d.ts +177 -0
  333. package/dist/render/mesh.js +466 -0
  334. package/dist/render/occlusion.d.ts +81 -0
  335. package/dist/render/occlusion.js +378 -0
  336. package/dist/render/oitPass.d.ts +67 -0
  337. package/dist/render/oitPass.js +213 -0
  338. package/dist/render/orderIndependent.d.ts +92 -0
  339. package/dist/render/orderIndependent.js +108 -0
  340. package/dist/render/particleBatch.d.ts +140 -0
  341. package/dist/render/particleBatch.js +186 -0
  342. package/dist/render/particleMaterial.d.ts +54 -0
  343. package/dist/render/particleMaterial.js +42 -0
  344. package/dist/render/particlePool.d.ts +124 -0
  345. package/dist/render/particlePool.js +182 -0
  346. package/dist/render/pass.d.ts +195 -0
  347. package/dist/render/pass.js +9 -0
  348. package/dist/render/passTarget.d.ts +86 -0
  349. package/dist/render/passTarget.js +121 -0
  350. package/dist/render/pickable.d.ts +31 -0
  351. package/dist/render/pickable.js +172 -0
  352. package/dist/render/planarReflection.d.ts +59 -0
  353. package/dist/render/planarReflection.js +194 -0
  354. package/dist/render/planarReflectionDraw.d.ts +31 -0
  355. package/dist/render/planarReflectionDraw.js +65 -0
  356. package/dist/render/plumeGeometry.d.ts +25 -0
  357. package/dist/render/plumeGeometry.js +61 -0
  358. package/dist/render/plumeMaterial.d.ts +31 -0
  359. package/dist/render/plumeMaterial.js +64 -0
  360. package/dist/render/plumeRenderer.d.ts +96 -0
  361. package/dist/render/plumeRenderer.js +185 -0
  362. package/dist/render/pointLightSelection.d.ts +175 -0
  363. package/dist/render/pointLightSelection.js +301 -0
  364. package/dist/render/pointShadowArray.d.ts +95 -0
  365. package/dist/render/pointShadowArray.js +219 -0
  366. package/dist/render/pointShadowBudget.d.ts +118 -0
  367. package/dist/render/pointShadowBudget.js +221 -0
  368. package/dist/render/pointShadowImage.d.ts +265 -0
  369. package/dist/render/pointShadowImage.js +339 -0
  370. package/dist/render/pointShadowMap.d.ts +93 -0
  371. package/dist/render/pointShadowMap.js +167 -0
  372. package/dist/render/pointShadowSystem.d.ts +195 -0
  373. package/dist/render/pointShadowSystem.js +336 -0
  374. package/dist/render/prefilterEnvMap.d.ts +124 -0
  375. package/dist/render/prefilterEnvMap.js +146 -0
  376. package/dist/render/prefilterPass.d.ts +44 -0
  377. package/dist/render/prefilterPass.js +98 -0
  378. package/dist/render/probeGrid.d.ts +122 -0
  379. package/dist/render/probeGrid.js +174 -0
  380. package/dist/render/rainField.d.ts +99 -0
  381. package/dist/render/rainField.js +168 -0
  382. package/dist/render/reflectionProbe.d.ts +188 -0
  383. package/dist/render/reflectionProbe.js +437 -0
  384. package/dist/render/renderQuality.d.ts +699 -0
  385. package/dist/render/renderQuality.js +293 -0
  386. package/dist/render/rendererHarness.d.ts +39 -0
  387. package/dist/render/rendererHarness.js +185 -0
  388. package/dist/render/resolutionGovernor.d.ts +107 -0
  389. package/dist/render/resolutionGovernor.js +144 -0
  390. package/dist/render/scatterDeform.d.ts +17 -0
  391. package/dist/render/scatterDeform.js +46 -0
  392. package/dist/render/sceneTarget.d.ts +288 -0
  393. package/dist/render/sceneTarget.js +856 -0
  394. package/dist/render/screenSpaceReflection.d.ts +233 -0
  395. package/dist/render/screenSpaceReflection.js +415 -0
  396. package/dist/render/sdfFont.d.ts +77 -0
  397. package/dist/render/sdfFont.js +86 -0
  398. package/dist/render/sdfTextLayout.d.ts +46 -0
  399. package/dist/render/sdfTextLayout.js +145 -0
  400. package/dist/render/sdfTextRenderer.d.ts +75 -0
  401. package/dist/render/sdfTextRenderer.js +194 -0
  402. package/dist/render/seaState.d.ts +25 -0
  403. package/dist/render/seaState.js +34 -0
  404. package/dist/render/segmentQuads.d.ts +29 -0
  405. package/dist/render/segmentQuads.js +75 -0
  406. package/dist/render/shader.d.ts +5 -0
  407. package/dist/render/shader.js +172 -0
  408. package/dist/render/shaders/ambientOcclusion.d.ts +45 -0
  409. package/dist/render/shaders/ambientOcclusion.js +352 -0
  410. package/dist/render/shaders/arcane.d.ts +19 -0
  411. package/dist/render/shaders/arcane.js +102 -0
  412. package/dist/render/shaders/bloom.d.ts +70 -0
  413. package/dist/render/shaders/bloom.js +227 -0
  414. package/dist/render/shaders/bolt.d.ts +17 -0
  415. package/dist/render/shaders/bolt.js +161 -0
  416. package/dist/render/shaders/caustics.d.ts +38 -0
  417. package/dist/render/shaders/caustics.js +167 -0
  418. package/dist/render/shaders/conditionals.d.ts +21 -0
  419. package/dist/render/shaders/conditionals.js +55 -0
  420. package/dist/render/shaders/decalProject.d.ts +25 -0
  421. package/dist/render/shaders/decalProject.js +125 -0
  422. package/dist/render/shaders/depth.d.ts +14 -0
  423. package/dist/render/shaders/depth.js +114 -0
  424. package/dist/render/shaders/depthOfField.d.ts +41 -0
  425. package/dist/render/shaders/depthOfField.js +139 -0
  426. package/dist/render/shaders/film.d.ts +15 -0
  427. package/dist/render/shaders/film.js +283 -0
  428. package/dist/render/shaders/fire.d.ts +12 -0
  429. package/dist/render/shaders/fire.js +178 -0
  430. package/dist/render/shaders/flat/directionalShadow.d.ts +1 -0
  431. package/dist/render/shaders/flat/directionalShadow.js +195 -0
  432. package/dist/render/shaders/flat/index.d.ts +135 -0
  433. package/dist/render/shaders/flat/index.js +387 -0
  434. package/dist/render/shaders/flat/lobes.d.ts +1 -0
  435. package/dist/render/shaders/flat/lobes.js +398 -0
  436. package/dist/render/shaders/flat/main.d.ts +2 -0
  437. package/dist/render/shaders/flat/main.js +1630 -0
  438. package/dist/render/shaders/flat/pointShadow.d.ts +1 -0
  439. package/dist/render/shaders/flat/pointShadow.js +322 -0
  440. package/dist/render/shaders/flat/preamble.d.ts +15 -0
  441. package/dist/render/shaders/flat/preamble.js +864 -0
  442. package/dist/render/shaders/flat/probeGrid.d.ts +21 -0
  443. package/dist/render/shaders/flat/probeGrid.js +151 -0
  444. package/dist/render/shaders/flat/surface.d.ts +2 -0
  445. package/dist/render/shaders/flat/surface.js +40 -0
  446. package/dist/render/shaders/flock.d.ts +11 -0
  447. package/dist/render/shaders/flock.js +102 -0
  448. package/dist/render/shaders/fog.d.ts +37 -0
  449. package/dist/render/shaders/fog.js +98 -0
  450. package/dist/render/shaders/fullscreen.d.ts +16 -0
  451. package/dist/render/shaders/fullscreen.js +27 -0
  452. package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +65 -0
  453. package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +71 -0
  454. package/dist/render/shaders/generated/arcane.wgsl.d.ts +90 -0
  455. package/dist/render/shaders/generated/arcane.wgsl.js +96 -0
  456. package/dist/render/shaders/generated/bloom.wgsl.d.ts +69 -0
  457. package/dist/render/shaders/generated/bloom.wgsl.js +75 -0
  458. package/dist/render/shaders/generated/bolt.wgsl.d.ts +118 -0
  459. package/dist/render/shaders/generated/bolt.wgsl.js +124 -0
  460. package/dist/render/shaders/generated/caustics.wgsl.d.ts +118 -0
  461. package/dist/render/shaders/generated/caustics.wgsl.js +124 -0
  462. package/dist/render/shaders/generated/decalProject.wgsl.d.ts +61 -0
  463. package/dist/render/shaders/generated/decalProject.wgsl.js +67 -0
  464. package/dist/render/shaders/generated/depth.wgsl.d.ts +131 -0
  465. package/dist/render/shaders/generated/depth.wgsl.js +137 -0
  466. package/dist/render/shaders/generated/film.wgsl.d.ts +134 -0
  467. package/dist/render/shaders/generated/film.wgsl.js +140 -0
  468. package/dist/render/shaders/generated/fire.wgsl.d.ts +128 -0
  469. package/dist/render/shaders/generated/fire.wgsl.js +134 -0
  470. package/dist/render/shaders/generated/flat.wgsl.d.ts +9163 -0
  471. package/dist/render/shaders/generated/flat.wgsl.js +9192 -0
  472. package/dist/render/shaders/generated/flock.wgsl.d.ts +73 -0
  473. package/dist/render/shaders/generated/flock.wgsl.js +79 -0
  474. package/dist/render/shaders/generated/fullscreen.wgsl.d.ts +12 -0
  475. package/dist/render/shaders/generated/fullscreen.wgsl.js +18 -0
  476. package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +130 -0
  477. package/dist/render/shaders/generated/globalMedium.wgsl.js +136 -0
  478. package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +230 -0
  479. package/dist/render/shaders/generated/lightVolume.wgsl.js +239 -0
  480. package/dist/render/shaders/generated/line.wgsl.d.ts +128 -0
  481. package/dist/render/shaders/generated/line.wgsl.js +134 -0
  482. package/dist/render/shaders/generated/octahedralResolve.wgsl.d.ts +51 -0
  483. package/dist/render/shaders/generated/octahedralResolve.wgsl.js +57 -0
  484. package/dist/render/shaders/generated/oitResolve.wgsl.d.ts +23 -0
  485. package/dist/render/shaders/generated/oitResolve.wgsl.js +29 -0
  486. package/dist/render/shaders/generated/panel.wgsl.d.ts +48 -0
  487. package/dist/render/shaders/generated/panel.wgsl.js +54 -0
  488. package/dist/render/shaders/generated/particle.wgsl.d.ts +332 -0
  489. package/dist/render/shaders/generated/particle.wgsl.js +338 -0
  490. package/dist/render/shaders/generated/prefilter.wgsl.d.ts +61 -0
  491. package/dist/render/shaders/generated/prefilter.wgsl.js +67 -0
  492. package/dist/render/shaders/generated/rush.wgsl.d.ts +131 -0
  493. package/dist/render/shaders/generated/rush.wgsl.js +137 -0
  494. package/dist/render/shaders/generated/scatter.wgsl.d.ts +195 -0
  495. package/dist/render/shaders/generated/scatter.wgsl.js +201 -0
  496. package/dist/render/shaders/generated/sdfText.wgsl.d.ts +69 -0
  497. package/dist/render/shaders/generated/sdfText.wgsl.js +75 -0
  498. package/dist/render/shaders/generated/sky.wgsl.d.ts +100 -0
  499. package/dist/render/shaders/generated/sky.wgsl.js +106 -0
  500. package/dist/render/shaders/generated/smoke.wgsl.d.ts +85 -0
  501. package/dist/render/shaders/generated/smoke.wgsl.js +91 -0
  502. package/dist/render/shaders/generated/ssrResolve.wgsl.d.ts +18 -0
  503. package/dist/render/shaders/generated/ssrResolve.wgsl.js +24 -0
  504. package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +86 -0
  505. package/dist/render/shaders/generated/ssrTrace.wgsl.js +92 -0
  506. package/dist/render/shaders/generated/temporalResolve.wgsl.d.ts +46 -0
  507. package/dist/render/shaders/generated/temporalResolve.wgsl.js +52 -0
  508. package/dist/render/shaders/generated/text.wgsl.d.ts +98 -0
  509. package/dist/render/shaders/generated/text.wgsl.js +104 -0
  510. package/dist/render/shaders/generated/water.wgsl.d.ts +247 -0
  511. package/dist/render/shaders/generated/water.wgsl.js +253 -0
  512. package/dist/render/shaders/generated/windStreaks.wgsl.d.ts +73 -0
  513. package/dist/render/shaders/generated/windStreaks.wgsl.js +79 -0
  514. package/dist/render/shaders/gerstner.d.ts +21 -0
  515. package/dist/render/shaders/gerstner.js +110 -0
  516. package/dist/render/shaders/globalMedium.d.ts +56 -0
  517. package/dist/render/shaders/globalMedium.js +393 -0
  518. package/dist/render/shaders/lightVolume.d.ts +61 -0
  519. package/dist/render/shaders/lightVolume.js +527 -0
  520. package/dist/render/shaders/line.d.ts +18 -0
  521. package/dist/render/shaders/line.js +122 -0
  522. package/dist/render/shaders/morph.d.ts +26 -0
  523. package/dist/render/shaders/morph.js +61 -0
  524. package/dist/render/shaders/octahedral.d.ts +76 -0
  525. package/dist/render/shaders/octahedral.js +161 -0
  526. package/dist/render/shaders/octahedralResolve.d.ts +25 -0
  527. package/dist/render/shaders/octahedralResolve.js +81 -0
  528. package/dist/render/shaders/oitResolve.d.ts +17 -0
  529. package/dist/render/shaders/oitResolve.js +42 -0
  530. package/dist/render/shaders/outputTransform.d.ts +37 -0
  531. package/dist/render/shaders/outputTransform.js +106 -0
  532. package/dist/render/shaders/panel.d.ts +17 -0
  533. package/dist/render/shaders/panel.js +50 -0
  534. package/dist/render/shaders/particle.d.ts +82 -0
  535. package/dist/render/shaders/particle.js +431 -0
  536. package/dist/render/shaders/prefilter.d.ts +37 -0
  537. package/dist/render/shaders/prefilter.js +276 -0
  538. package/dist/render/shaders/rush.d.ts +21 -0
  539. package/dist/render/shaders/rush.js +355 -0
  540. package/dist/render/shaders/scatter.d.ts +14 -0
  541. package/dist/render/shaders/scatter.js +206 -0
  542. package/dist/render/shaders/sdfText.d.ts +9 -0
  543. package/dist/render/shaders/sdfText.js +65 -0
  544. package/dist/render/shaders/skinning.d.ts +17 -0
  545. package/dist/render/shaders/skinning.js +59 -0
  546. package/dist/render/shaders/sky.d.ts +10 -0
  547. package/dist/render/shaders/sky.js +242 -0
  548. package/dist/render/shaders/smoke.d.ts +8 -0
  549. package/dist/render/shaders/smoke.js +88 -0
  550. package/dist/render/shaders/ssrResolve.d.ts +15 -0
  551. package/dist/render/shaders/ssrResolve.js +28 -0
  552. package/dist/render/shaders/ssrTrace.d.ts +22 -0
  553. package/dist/render/shaders/ssrTrace.js +230 -0
  554. package/dist/render/shaders/tangentFrame.d.ts +17 -0
  555. package/dist/render/shaders/tangentFrame.js +70 -0
  556. package/dist/render/shaders/temporalResolve.d.ts +1 -0
  557. package/dist/render/shaders/temporalResolve.js +150 -0
  558. package/dist/render/shaders/text.d.ts +17 -0
  559. package/dist/render/shaders/text.js +155 -0
  560. package/dist/render/shaders/vertexChannel.d.ts +39 -0
  561. package/dist/render/shaders/vertexChannel.js +69 -0
  562. package/dist/render/shaders/water.d.ts +18 -0
  563. package/dist/render/shaders/water.js +354 -0
  564. package/dist/render/shaders/windStreaks.d.ts +14 -0
  565. package/dist/render/shaders/windStreaks.js +129 -0
  566. package/dist/render/shadowCasters.d.ts +107 -0
  567. package/dist/render/shadowCasters.js +1 -0
  568. package/dist/render/shadowMap.d.ts +10 -0
  569. package/dist/render/shadowMap.js +80 -0
  570. package/dist/render/shotPlacement.d.ts +80 -0
  571. package/dist/render/shotPlacement.js +183 -0
  572. package/dist/render/skinPalette.d.ts +90 -0
  573. package/dist/render/skinPalette.js +136 -0
  574. package/dist/render/ssrPass.d.ts +59 -0
  575. package/dist/render/ssrPass.js +196 -0
  576. package/dist/render/surfaceSheet.d.ts +52 -0
  577. package/dist/render/surfaceSheet.js +99 -0
  578. package/dist/render/surfaceTexture.d.ts +197 -0
  579. package/dist/render/surfaceTexture.js +116 -0
  580. package/dist/render/temporalAa.d.ts +104 -0
  581. package/dist/render/temporalAa.js +194 -0
  582. package/dist/render/temporalPass.d.ts +54 -0
  583. package/dist/render/temporalPass.js +135 -0
  584. package/dist/render/textLayout.d.ts +88 -0
  585. package/dist/render/textLayout.js +201 -0
  586. package/dist/render/textRenderer.d.ts +32 -0
  587. package/dist/render/textRenderer.js +176 -0
  588. package/dist/render/thirdPersonCamera.d.ts +272 -0
  589. package/dist/render/thirdPersonCamera.js +479 -0
  590. package/dist/render/trample.d.ts +57 -0
  591. package/dist/render/trample.js +134 -0
  592. package/dist/render/translucentQueue.d.ts +76 -0
  593. package/dist/render/translucentQueue.js +90 -0
  594. package/dist/render/uniformVectorBudget.d.ts +105 -0
  595. package/dist/render/uniformVectorBudget.js +201 -0
  596. package/dist/render/uploadStep.d.ts +14 -0
  597. package/dist/render/uploadStep.js +14 -0
  598. package/dist/render/vertexDefaults.d.ts +48 -0
  599. package/dist/render/vertexDefaults.js +111 -0
  600. package/dist/render/visibility.d.ts +22 -0
  601. package/dist/render/visibility.js +47 -0
  602. package/dist/render/waterDraw.d.ts +57 -0
  603. package/dist/render/waterDraw.js +102 -0
  604. package/dist/render/waterGrid.d.ts +62 -0
  605. package/dist/render/waterGrid.js +151 -0
  606. package/dist/render/waterRenderer.d.ts +163 -0
  607. package/dist/render/waterRenderer.js +165 -0
  608. package/dist/render/wind.d.ts +45 -0
  609. package/dist/render/wind.js +32 -0
  610. package/dist/render/windField.d.ts +34 -0
  611. package/dist/render/windField.js +13 -0
  612. package/dist/render/windStreakDraw.d.ts +73 -0
  613. package/dist/render/windStreakDraw.js +86 -0
  614. package/dist/render/windStreakRenderer.d.ts +37 -0
  615. package/dist/render/windStreakRenderer.js +86 -0
  616. package/dist/scene/node.d.ts +128 -0
  617. package/dist/scene/node.js +319 -0
  618. package/dist/scene/visit.d.ts +38 -0
  619. package/dist/scene/visit.js +43 -0
  620. package/dist/ui/clipMime.d.ts +46 -0
  621. package/dist/ui/clipMime.js +88 -0
  622. package/dist/ui/exportTarget.d.ts +305 -0
  623. package/dist/ui/exportTarget.js +368 -0
  624. package/dist/ui/frameOverlay.d.ts +59 -0
  625. package/dist/ui/frameOverlay.js +76 -0
  626. package/dist/ui/framePacer.d.ts +142 -0
  627. package/dist/ui/framePacer.js +284 -0
  628. package/dist/ui/frameRecorder.d.ts +188 -0
  629. package/dist/ui/frameRecorder.js +221 -0
  630. package/dist/ui/fullscreen.d.ts +72 -0
  631. package/dist/ui/fullscreen.js +92 -0
  632. package/dist/ui/pixelCursor.d.ts +28 -0
  633. package/dist/ui/pixelCursor.js +66 -0
  634. package/dist/ui/splash.d.ts +86 -0
  635. package/dist/ui/splash.js +300 -0
  636. package/dist/ui/stillFrame.d.ts +22 -0
  637. package/dist/ui/stillFrame.js +40 -0
  638. package/package.json +60 -0
  639. package/src/behavior/behaviorTree.ts +346 -0
  640. package/src/build/shaderComments.ts +0 -0
  641. package/src/cinematic/player.ts +209 -0
  642. package/src/cinematic/script.ts +231 -0
  643. package/src/core/bootGate.ts +49 -0
  644. package/src/core/loadTracker.ts +144 -0
  645. package/src/core/loop.ts +211 -0
  646. package/src/core/messageQueue.ts +145 -0
  647. package/src/core/onceSet.ts +65 -0
  648. package/src/core/preferences.ts +177 -0
  649. package/src/core/remoteSave.ts +347 -0
  650. package/src/core/rng.ts +101 -0
  651. package/src/core/stepBudget.ts +136 -0
  652. package/src/core/storage.ts +114 -0
  653. package/src/core/tickTrace.ts +129 -0
  654. package/src/core/timeCurve.ts +175 -0
  655. package/src/core/withDefaults.ts +28 -0
  656. package/src/dev/contactProbe.ts +115 -0
  657. package/src/dev/fpsMeter.ts +101 -0
  658. package/src/environment/celestialClock.ts +385 -0
  659. package/src/geometry/decal.ts +260 -0
  660. package/src/geometry/filmPatch.ts +158 -0
  661. package/src/geometry/growable.ts +165 -0
  662. package/src/geometry/lightVolume.ts +161 -0
  663. package/src/geometry/meshBuilder.ts +1397 -0
  664. package/src/geometry/pixelFont.ts +189 -0
  665. package/src/geometry/ribbon.ts +790 -0
  666. package/src/geometry/shellFill.ts +265 -0
  667. package/src/geometry/spline.ts +659 -0
  668. package/src/geometry/tangents.ts +173 -0
  669. package/src/geometry/textMesh.ts +96 -0
  670. package/src/geometry/treeBuilder.ts +114 -0
  671. package/src/host/display.ts +180 -0
  672. package/src/host/files.ts +77 -0
  673. package/src/host/lifecycle.ts +63 -0
  674. package/src/host/platformServices.ts +23 -0
  675. package/src/host/screen.ts +134 -0
  676. package/src/index.ts +730 -0
  677. package/src/input/actionMap.ts +377 -0
  678. package/src/input/activeDevice.ts +86 -0
  679. package/src/input/gamepadIdentity.ts +131 -0
  680. package/src/input/gamepadMapping.ts +121 -0
  681. package/src/input/input.ts +872 -0
  682. package/src/input/inputHarness.ts +226 -0
  683. package/src/input/rightGesture.ts +76 -0
  684. package/src/input/rumble.ts +138 -0
  685. package/src/input/touchControls.ts +451 -0
  686. package/src/input/typingTarget.ts +26 -0
  687. package/src/math/bounds.ts +153 -0
  688. package/src/math/color.ts +37 -0
  689. package/src/math/exact.ts +274 -0
  690. package/src/math/frustum.ts +115 -0
  691. package/src/math/intersect.ts +228 -0
  692. package/src/math/scalar.ts +109 -0
  693. package/src/math/spring.ts +106 -0
  694. package/src/nav/navGraph.ts +168 -0
  695. package/src/nav/navPath.ts +217 -0
  696. package/src/nav/navSearch.ts +200 -0
  697. package/src/physics/boxSurface.ts +96 -0
  698. package/src/physics/colliderSurface.ts +213 -0
  699. package/src/physics/compositeSurface.ts +111 -0
  700. package/src/physics/heightSurface.ts +234 -0
  701. package/src/physics/ribbonSurface.ts +642 -0
  702. package/src/render/ambientOcclusionPass.ts +200 -0
  703. package/src/render/areaLights.ts +194 -0
  704. package/src/render/areaShadowSet.ts +405 -0
  705. package/src/render/atmosphere.ts +186 -0
  706. package/src/render/backend/api.ts +802 -0
  707. package/src/render/backend/budget.ts +139 -0
  708. package/src/render/backend/createRenderer.ts +343 -0
  709. package/src/render/backend/deadline.ts +37 -0
  710. package/src/render/backend/probe.ts +152 -0
  711. package/src/render/backend/select.ts +288 -0
  712. package/src/render/backend/timer.ts +59 -0
  713. package/src/render/backend/webgl2/instanced.ts +46 -0
  714. package/src/render/backend/webgl2/morphTexture.ts +58 -0
  715. package/src/render/backend/webgl2/renderer.ts +7484 -0
  716. package/src/render/backend/webgl2/skinPaletteTexture.ts +70 -0
  717. package/src/render/backend/webgpu/boltPass.ts +205 -0
  718. package/src/render/backend/webgpu/buffers.ts +553 -0
  719. package/src/render/backend/webgpu/causticsPass.ts +174 -0
  720. package/src/render/backend/webgpu/clusterBinner.ts +259 -0
  721. package/src/render/backend/webgpu/depthPass.ts +261 -0
  722. package/src/render/backend/webgpu/device.ts +148 -0
  723. package/src/render/backend/webgpu/drawCommand.ts +79 -0
  724. package/src/render/backend/webgpu/environmentTexels.ts +96 -0
  725. package/src/render/backend/webgpu/filmPass.ts +123 -0
  726. package/src/render/backend/webgpu/flatPass.ts +635 -0
  727. package/src/render/backend/webgpu/flockPass.ts +163 -0
  728. package/src/render/backend/webgpu/gpuTimestamps.ts +255 -0
  729. package/src/render/backend/webgpu/insetPass.ts +123 -0
  730. package/src/render/backend/webgpu/instanced.ts +55 -0
  731. package/src/render/backend/webgpu/lightVolumePass.ts +203 -0
  732. package/src/render/backend/webgpu/linePass.ts +270 -0
  733. package/src/render/backend/webgpu/morphTexture.ts +61 -0
  734. package/src/render/backend/webgpu/panelPass.ts +138 -0
  735. package/src/render/backend/webgpu/particlePass.ts +370 -0
  736. package/src/render/backend/webgpu/pipelineCache.ts +152 -0
  737. package/src/render/backend/webgpu/plumePass.ts +292 -0
  738. package/src/render/backend/webgpu/pointShadowPass.ts +368 -0
  739. package/src/render/backend/webgpu/postPass.ts +470 -0
  740. package/src/render/backend/webgpu/probePass.ts +218 -0
  741. package/src/render/backend/webgpu/reflectionPass.ts +137 -0
  742. package/src/render/backend/webgpu/renderer.ts +13004 -0
  743. package/src/render/backend/webgpu/scatterPass.ts +354 -0
  744. package/src/render/backend/webgpu/sdfTextPass.ts +220 -0
  745. package/src/render/backend/webgpu/shaderModules.ts +49 -0
  746. package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +219 -0
  747. package/src/render/backend/webgpu/skinPaletteRing.ts +105 -0
  748. package/src/render/backend/webgpu/skinPaletteTexture.ts +79 -0
  749. package/src/render/backend/webgpu/skyPass.ts +85 -0
  750. package/src/render/backend/webgpu/surfaceTexturePass.ts +257 -0
  751. package/src/render/backend/webgpu/textPass.ts +224 -0
  752. package/src/render/backend/webgpu/uniformRing.ts +228 -0
  753. package/src/render/backend/webgpu/waterPass.ts +211 -0
  754. package/src/render/backend/webgpu/windStreakPass.ts +196 -0
  755. package/src/render/billboard.ts +108 -0
  756. package/src/render/bloomChain.ts +101 -0
  757. package/src/render/bloomPass.ts +256 -0
  758. package/src/render/boltBatch.ts +141 -0
  759. package/src/render/boltPool.ts +360 -0
  760. package/src/render/boom.ts +123 -0
  761. package/src/render/camera.ts +348 -0
  762. package/src/render/causticsRenderer.ts +171 -0
  763. package/src/render/cinematicCamera.ts +278 -0
  764. package/src/render/clusteredLights.ts +598 -0
  765. package/src/render/colourGrade.ts +109 -0
  766. package/src/render/compute.ts +83 -0
  767. package/src/render/contextLoss.ts +50 -0
  768. package/src/render/debugLines.ts +411 -0
  769. package/src/render/decalPass.ts +124 -0
  770. package/src/render/decalProjector.ts +390 -0
  771. package/src/render/decalQueue.ts +110 -0
  772. package/src/render/depthConvention.ts +383 -0
  773. package/src/render/drawingBuffer.ts +89 -0
  774. package/src/render/emptyTexture.ts +130 -0
  775. package/src/render/envProbeArray.ts +201 -0
  776. package/src/render/equirectToCube.ts +160 -0
  777. package/src/render/flockGeometry.ts +53 -0
  778. package/src/render/flockRenderer.ts +111 -0
  779. package/src/render/frame/arena.ts +93 -0
  780. package/src/render/frame/index.ts +29 -0
  781. package/src/render/frame/replay.ts +50 -0
  782. package/src/render/frame/resources.ts +61 -0
  783. package/src/render/frame/schedule.ts +85 -0
  784. package/src/render/frameView.ts +20 -0
  785. package/src/render/gizmo.ts +923 -0
  786. package/src/render/globalMedium.ts +154 -0
  787. package/src/render/globalMediumPass.ts +323 -0
  788. package/src/render/gpuCapability.ts +244 -0
  789. package/src/render/gpuTimer.ts +182 -0
  790. package/src/render/halfFloat.ts +39 -0
  791. package/src/render/handleRegistry.ts +96 -0
  792. package/src/render/iesProfile.ts +215 -0
  793. package/src/render/imageTexels.ts +109 -0
  794. package/src/render/instancedMesh.ts +187 -0
  795. package/src/render/instances.ts +92 -0
  796. package/src/render/lightBudget.ts +655 -0
  797. package/src/render/lightMatrix.ts +69 -0
  798. package/src/render/lightVolumeDraw.ts +216 -0
  799. package/src/render/lineBatch.ts +180 -0
  800. package/src/render/linePoints.ts +61 -0
  801. package/src/render/livePointShadowSet.ts +285 -0
  802. package/src/render/lod.ts +86 -0
  803. package/src/render/mesh.ts +536 -0
  804. package/src/render/occlusion.ts +406 -0
  805. package/src/render/oitPass.ts +230 -0
  806. package/src/render/orderIndependent.ts +136 -0
  807. package/src/render/particleBatch.ts +299 -0
  808. package/src/render/particleMaterial.ts +84 -0
  809. package/src/render/particlePool.ts +291 -0
  810. package/src/render/pass.ts +203 -0
  811. package/src/render/passTarget.ts +225 -0
  812. package/src/render/pickable.ts +222 -0
  813. package/src/render/planarReflection.ts +233 -0
  814. package/src/render/planarReflectionDraw.ts +87 -0
  815. package/src/render/plumeGeometry.ts +75 -0
  816. package/src/render/plumeMaterial.ts +99 -0
  817. package/src/render/plumeRenderer.ts +272 -0
  818. package/src/render/pointLightSelection.ts +447 -0
  819. package/src/render/pointShadowArray.ts +276 -0
  820. package/src/render/pointShadowBudget.ts +305 -0
  821. package/src/render/pointShadowImage.ts +470 -0
  822. package/src/render/pointShadowMap.ts +228 -0
  823. package/src/render/pointShadowSystem.ts +408 -0
  824. package/src/render/prefilterEnvMap.ts +162 -0
  825. package/src/render/prefilterPass.ts +115 -0
  826. package/src/render/probeGrid.ts +248 -0
  827. package/src/render/rainField.ts +240 -0
  828. package/src/render/reflectionProbe.ts +530 -0
  829. package/src/render/renderQuality.ts +1054 -0
  830. package/src/render/rendererHarness.ts +214 -0
  831. package/src/render/resolutionGovernor.ts +189 -0
  832. package/src/render/scatterDeform.ts +72 -0
  833. package/src/render/sceneTarget.ts +1032 -0
  834. package/src/render/screenSpaceReflection.ts +524 -0
  835. package/src/render/sdfFont.ts +157 -0
  836. package/src/render/sdfTextLayout.ts +206 -0
  837. package/src/render/sdfTextRenderer.ts +233 -0
  838. package/src/render/seaState.ts +55 -0
  839. package/src/render/segmentQuads.ts +92 -0
  840. package/src/render/shader.ts +190 -0
  841. package/src/render/shaders/ambientOcclusion.ts +354 -0
  842. package/src/render/shaders/arcane.ts +103 -0
  843. package/src/render/shaders/bloom.ts +232 -0
  844. package/src/render/shaders/bolt.ts +164 -0
  845. package/src/render/shaders/caustics.ts +169 -0
  846. package/src/render/shaders/conditionals.ts +58 -0
  847. package/src/render/shaders/decalProject.ts +127 -0
  848. package/src/render/shaders/depth.ts +131 -0
  849. package/src/render/shaders/depthOfField.ts +141 -0
  850. package/src/render/shaders/film.ts +285 -0
  851. package/src/render/shaders/fire.ts +180 -0
  852. package/src/render/shaders/flat/directionalShadow.ts +196 -0
  853. package/src/render/shaders/flat/index.ts +502 -0
  854. package/src/render/shaders/flat/lobes.ts +399 -0
  855. package/src/render/shaders/flat/main.ts +1631 -0
  856. package/src/render/shaders/flat/pointShadow.ts +323 -0
  857. package/src/render/shaders/flat/preamble.ts +875 -0
  858. package/src/render/shaders/flat/probeGrid.ts +152 -0
  859. package/src/render/shaders/flat/surface.ts +41 -0
  860. package/src/render/shaders/flock.ts +103 -0
  861. package/src/render/shaders/fog.ts +99 -0
  862. package/src/render/shaders/fullscreen.ts +28 -0
  863. package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +74 -0
  864. package/src/render/shaders/generated/arcane.wgsl.ts +98 -0
  865. package/src/render/shaders/generated/bloom.wgsl.ts +79 -0
  866. package/src/render/shaders/generated/bolt.wgsl.ts +127 -0
  867. package/src/render/shaders/generated/caustics.wgsl.ts +127 -0
  868. package/src/render/shaders/generated/decalProject.wgsl.ts +69 -0
  869. package/src/render/shaders/generated/depth.wgsl.ts +142 -0
  870. package/src/render/shaders/generated/film.wgsl.ts +143 -0
  871. package/src/render/shaders/generated/fire.wgsl.ts +137 -0
  872. package/src/render/shaders/generated/flat.wgsl.ts +9195 -0
  873. package/src/render/shaders/generated/flock.wgsl.ts +82 -0
  874. package/src/render/shaders/generated/fullscreen.wgsl.ts +20 -0
  875. package/src/render/shaders/generated/globalMedium.wgsl.ts +139 -0
  876. package/src/render/shaders/generated/lightVolume.wgsl.ts +242 -0
  877. package/src/render/shaders/generated/line.wgsl.ts +137 -0
  878. package/src/render/shaders/generated/octahedralResolve.wgsl.ts +60 -0
  879. package/src/render/shaders/generated/oitResolve.wgsl.ts +31 -0
  880. package/src/render/shaders/generated/panel.wgsl.ts +57 -0
  881. package/src/render/shaders/generated/particle.wgsl.ts +343 -0
  882. package/src/render/shaders/generated/prefilter.wgsl.ts +69 -0
  883. package/src/render/shaders/generated/rush.wgsl.ts +139 -0
  884. package/src/render/shaders/generated/scatter.wgsl.ts +205 -0
  885. package/src/render/shaders/generated/sdfText.wgsl.ts +78 -0
  886. package/src/render/shaders/generated/sky.wgsl.ts +109 -0
  887. package/src/render/shaders/generated/smoke.wgsl.ts +93 -0
  888. package/src/render/shaders/generated/ssrResolve.wgsl.ts +26 -0
  889. package/src/render/shaders/generated/ssrTrace.wgsl.ts +94 -0
  890. package/src/render/shaders/generated/temporalResolve.wgsl.ts +54 -0
  891. package/src/render/shaders/generated/text.wgsl.ts +107 -0
  892. package/src/render/shaders/generated/water.wgsl.ts +256 -0
  893. package/src/render/shaders/generated/windStreaks.wgsl.ts +82 -0
  894. package/src/render/shaders/gerstner.ts +110 -0
  895. package/src/render/shaders/globalMedium.ts +396 -0
  896. package/src/render/shaders/lightVolume.ts +546 -0
  897. package/src/render/shaders/line.ts +125 -0
  898. package/src/render/shaders/morph.ts +62 -0
  899. package/src/render/shaders/octahedral.ts +170 -0
  900. package/src/render/shaders/octahedralResolve.ts +83 -0
  901. package/src/render/shaders/oitResolve.ts +42 -0
  902. package/src/render/shaders/outputTransform.ts +107 -0
  903. package/src/render/shaders/panel.ts +51 -0
  904. package/src/render/shaders/particle.ts +438 -0
  905. package/src/render/shaders/prefilter.ts +278 -0
  906. package/src/render/shaders/rush.ts +357 -0
  907. package/src/render/shaders/scatter.ts +211 -0
  908. package/src/render/shaders/sdfText.ts +68 -0
  909. package/src/render/shaders/skinning.ts +59 -0
  910. package/src/render/shaders/sky.ts +243 -0
  911. package/src/render/shaders/smoke.ts +89 -0
  912. package/src/render/shaders/ssrResolve.ts +28 -0
  913. package/src/render/shaders/ssrTrace.ts +233 -0
  914. package/src/render/shaders/tangentFrame.ts +70 -0
  915. package/src/render/shaders/temporalResolve.ts +151 -0
  916. package/src/render/shaders/text.ts +156 -0
  917. package/src/render/shaders/vertexChannel.ts +71 -0
  918. package/src/render/shaders/water.ts +356 -0
  919. package/src/render/shaders/windStreaks.ts +130 -0
  920. package/src/render/shadowCasters.ts +124 -0
  921. package/src/render/shadowMap.ts +89 -0
  922. package/src/render/shotPlacement.ts +266 -0
  923. package/src/render/skinPalette.ts +157 -0
  924. package/src/render/ssrPass.ts +241 -0
  925. package/src/render/surfaceSheet.ts +158 -0
  926. package/src/render/surfaceTexture.ts +310 -0
  927. package/src/render/temporalAa.ts +221 -0
  928. package/src/render/temporalPass.ts +153 -0
  929. package/src/render/textLayout.ts +239 -0
  930. package/src/render/textRenderer.ts +201 -0
  931. package/src/render/thirdPersonCamera.ts +578 -0
  932. package/src/render/trample.ts +140 -0
  933. package/src/render/translucentQueue.ts +161 -0
  934. package/src/render/uniformVectorBudget.ts +246 -0
  935. package/src/render/uploadStep.ts +14 -0
  936. package/src/render/vertexDefaults.ts +115 -0
  937. package/src/render/visibility.ts +56 -0
  938. package/src/render/waterDraw.ts +167 -0
  939. package/src/render/waterGrid.ts +180 -0
  940. package/src/render/waterRenderer.ts +352 -0
  941. package/src/render/wind.ts +79 -0
  942. package/src/render/windField.ts +50 -0
  943. package/src/render/windStreakDraw.ts +131 -0
  944. package/src/render/windStreakRenderer.ts +130 -0
  945. package/src/scene/node.ts +336 -0
  946. package/src/scene/visit.ts +82 -0
  947. package/src/ui/clipMime.ts +109 -0
  948. package/src/ui/exportTarget.ts +491 -0
  949. package/src/ui/frameOverlay.ts +159 -0
  950. package/src/ui/framePacer.ts +351 -0
  951. package/src/ui/frameRecorder.ts +333 -0
  952. package/src/ui/fullscreen.ts +113 -0
  953. package/src/ui/pixelCursor.ts +99 -0
  954. package/src/ui/splash.ts +347 -0
  955. package/src/ui/stillFrame.ts +44 -0
@@ -0,0 +1,3082 @@
1
+ import type { ProbeBakeOptions } from '../../reflectionProbe.ts';
2
+ import type { ReadonlyMat4 } from 'gl-matrix';
3
+ import type { Vec3 } from '../../../math/color.ts';
4
+ import { Camera } from '../../camera.ts';
5
+ import type { PickHit, PickableSource } from '../../pickable.ts';
6
+ import type { RendererApi } from '../api.ts';
7
+ import { GpuTimestamps } from './gpuTimestamps.ts';
8
+ import type { Environment, FilmOptions, InsetRect, LightVolumeDrawOptions, SkyColors, TranslucentMeshOptions } from '../webgl2/renderer.ts';
9
+ import type { IncrementalMeshHandle, MeshOptions } from '../api.ts';
10
+ import type { WaterBody } from '../../waterRenderer.ts';
11
+ import type { WindStreakOptions } from '../../windStreakRenderer.ts';
12
+ import type { WindField } from '../../windField.ts';
13
+ import type { FlockParams } from '../../flockRenderer.ts';
14
+ import type { ProbeGridOptions } from '../../probeGrid.ts';
15
+ import type { PhotometricProfile } from '../../iesProfile.ts';
16
+ import { type AreaLightSource } from '../../areaLights.ts';
17
+ import { type ShadowLight } from '../../pointShadowSystem.ts';
18
+ import { GpuText } from './textPass.ts';
19
+ import type { TextStyle } from '../../textLayout.ts';
20
+ import { GpuSurfaceTexture } from './surfaceTexturePass.ts';
21
+ import { GpuSdfText } from './sdfTextPass.ts';
22
+ import type { SdfFont } from '../../sdfFont.ts';
23
+ import type { SdfTextStyle } from '../../sdfTextLayout.ts';
24
+ import type { SurfaceMaterial, SurfaceTextureOptions } from '../../surfaceTexture.ts';
25
+ import type { RenderBackend } from '../api.ts';
26
+ import { type ColourGradeLut } from '../../colourGrade.ts';
27
+ import type { CausticSheet } from '../../causticsRenderer.ts';
28
+ import { type GpuCaustics } from './causticsPass.ts';
29
+ import type { BoltSegments } from '../../boltPool.ts';
30
+ import type { LineSegments } from '../../linePoints.ts';
31
+ import type { ParticleBatchOptions } from '../../particleBatch.ts';
32
+ import type { ParticleInstances } from '../../particlePool.ts';
33
+ import type { PlumeOptions, PlumePlacement } from '../../plumeRenderer.ts';
34
+ import type { InstanceData } from '../../instancedMesh.ts';
35
+ import { type RenderQuality } from '../../renderQuality.ts';
36
+ import type { MeshData } from '../../mesh.ts';
37
+ import type { ShadowCasters } from '../../shadowCasters.ts';
38
+ import { type GpuMesh } from './buffers.ts';
39
+ import type { GpuSurface } from './device.ts';
40
+ import { type GpuScatter } from './scatterPass.ts';
41
+ import { type GpuWater } from './waterPass.ts';
42
+ import { type GpuPlumes } from './plumePass.ts';
43
+ import { type GpuParticles } from './particlePass.ts';
44
+ import { type GpuWindStreaks } from './windStreakPass.ts';
45
+ import { type GpuFlock } from './flockPass.ts';
46
+ import { type GpuBolts } from './boltPass.ts';
47
+ import { type GpuLines } from './linePass.ts';
48
+ import { FrameBudget } from '../budget.ts';
49
+ import type { ReflectiveSurface } from '../../screenSpaceReflection.ts';
50
+ import type { DecalProjector } from '../../decalProjector.ts';
51
+ import type { Bounds } from '../../../math/bounds.ts';
52
+ import { type ComputeDefinition, type ComputeHandle } from '../../compute.ts';
53
+ import type { PassDefinition, PassHandle } from '../../pass.ts';
54
+ import { GpuInstancedBatch } from './instanced.ts';
55
+ import type { MeshInstances } from '../../instances.ts';
56
+ /** Which directional shadow map a pass is filling, matching `renderer.ts`. */
57
+ type ShadowLayer = 'static' | 'static-peel' | 'dynamic';
58
+ /**
59
+ * The WebGPU backend.
60
+ *
61
+ * **`RendererApi`, in full, with no `Partial` and no cast.** It implemented
62
+ * `Partial<RendererApi>` for as long as its passes were being written, and the `Partial` was
63
+ * the schedule: every member still missing was a row of work, and `createRenderer` needed a
64
+ * cast to hand an incomplete backend back behind the complete surface.
65
+ *
66
+ * That is over. The derived type now does the job it exists for — add a method to `Renderer`
67
+ * and this class stops compiling until it has one too, which is the compile-time form of the
68
+ * failure the 2026-08-02 design spec named as the real risk: a fallback that rots because only
69
+ * the developer's own backend is ever exercised.
70
+ *
71
+ * **Present is not the same as honoured, and the reverse is a trap of its own.** `setBloom`,
72
+ * `setSpeedRush` and `setCameraMotionBlur` warned that the composite did not exist here, which
73
+ * was true when they were written and stopped being true when it landed — so the backend spent
74
+ * a while telling consumers that working effects did nothing. All three are read by
75
+ * `composite()` now, and each has been compared against WebGL2 on `demo/dev/probe.html`.
76
+ */
77
+ export declare class WebGPURenderer implements RendererApi {
78
+ /**
79
+ * Real timestamp queries, where the adapter offers them.
80
+ *
81
+ * Typed as the concrete class rather than as `FrameTimer` because this file needs the two
82
+ * methods the interface has no room for: `writesFor`, which every `beginRenderPass` below
83
+ * passes into its descriptor, and `resolve`, which rides the frame's last encoder. A
84
+ * consumer still sees only `FrameTimer` through `RendererApi`.
85
+ */
86
+ readonly gpuTimer: GpuTimestamps;
87
+ /** What drew the frame, for the meter and for bug reports. */
88
+ readonly rendererName: string;
89
+ /**
90
+ * See `RendererApi.reversedDepth`. Always the engine's own setting on this backend: WebGPU's
91
+ * clip space is `[0, 1]` already, so reversing costs an extension nothing and cannot be refused.
92
+ */
93
+ readonly reversedDepth = true;
94
+ /** Which backend is drawing. See `Renderer.backend` for why this is asked rather than guessed. */
95
+ readonly backend: RenderBackend;
96
+ /**
97
+ * The profile this renderer was built against, resolved once.
98
+ *
99
+ * **Resolved by the caller and held, rather than defaulted per use.** Every shadow term
100
+ * below reads it, and the constants they replaced were `DEFAULT_RENDER_QUALITY` copies —
101
+ * which measured wrong the moment a scene overrode one. `demo/dayClock` resolves
102
+ * `directionalShadowMaxSlope` to 9 where the default is 3.
103
+ */
104
+ readonly quality: Readonly<RenderQuality>;
105
+ /** Whether `endFrame` put an image on the canvas. Read by the recorder and the harness. */
106
+ framePresented: boolean;
107
+ /**
108
+ * How many frames this renderer has actually presented, monotonic for its whole lifetime.
109
+ *
110
+ * **Incremented in `swapView`, at the one line that calls `getCurrentTexture`** — the real
111
+ * acquisition, guarded there to fire at most once per frame no matter how many callers ask
112
+ * for the view. Deliberately not `beginFrame` or `endFrame`: a frame that dies before
113
+ * drawing anything never reaches `swapView` at all — see the comment on `frameSwapView` in
114
+ * `beginFrame` — so it correctly never counts, while a frame drawn through the eager
115
+ * `deferFramePass: false` path counts the moment it takes the swap chain rather than
116
+ * waiting for `endFrame` to say so after the fact. A consumer compositing an offline export
117
+ * reads this after `endFrame` and compares it with what it read last, which is how it
118
+ * tells a frame that drew something from one that repeated the canvas it already had —
119
+ * see `ExportTarget`.
120
+ */
121
+ get presentedFrames(): number;
122
+ /**
123
+ * Resolve once every pipeline asked for so far has finished compiling.
124
+ *
125
+ * **Await this before the first frame.** A pipeline is not ready when `createRenderPipeline`
126
+ * returns: the driver compiles the shader at the first draw that uses it, inside the frame,
127
+ * with the queue stalled behind. Measured on an S23 Ultra, that was a 5.2 second frame with
128
+ * an idle main thread, no allocation and nothing on any CPU-side timer — the queue simply
129
+ * did not drain. Compiling asynchronously and waiting here moves that work in front of the
130
+ * first frame, where it can be waited on honestly instead of appearing as a hang.
131
+ *
132
+ * Safe to call at any point and safe to call more than once: it waits for whatever is in
133
+ * flight at the time, so a consumer that builds more meshes later can wait again.
134
+ */
135
+ ready(): Promise<void>;
136
+ private presentedFrameCount;
137
+ /**
138
+ * The joint palettes this frame has set, one slot each. The WebGL2 field of this name is a
139
+ * single texture, and `SkinPaletteRing` carries why the two differ.
140
+ *
141
+ * Held rather than passed, like every other per-draw state here: `setSkinPalette` chooses and
142
+ * the following draws use it, which is the shape `setMaterial` already has.
143
+ */
144
+ private readonly skinPalettes;
145
+ private readonly skinnedBindGroupLayout;
146
+ private readonly morphedBindGroupLayout;
147
+ private readonly bothBindGroupLayout;
148
+ /** The morph weights the following draws use, or null for none. */
149
+ private morphWeights;
150
+ /**
151
+ * One cached bind group per palette slot, at `slot + 1`, with index 0 for a morphed draw that
152
+ * is not skinned.
153
+ *
154
+ * **An array and not a field, because the palette is a ring now.** A single cached group was
155
+ * correct while every skinned draw in a frame bound the same texture; with a slot per character
156
+ * the view changes between them, so one field would miss on every character and build a bind
157
+ * group per character per frame — an allocation in the frame loop, which `AGENTS.md` forbids.
158
+ * Keyed by slot instead, the groups are built once and reused for the life of the renderer.
159
+ */
160
+ private readonly skinnedGroups;
161
+ /** The unskinned group each was built beside, for identity-based invalidation. */
162
+ private readonly skinnedGroupsBeside;
163
+ private readonly skinnedGroupsPalette;
164
+ private readonly skinnedGroupsDeltas;
165
+ /**
166
+ * Which palette slot the following draws skin by, or -1 for none.
167
+ *
168
+ * The slot rather than a flag: two characters in one frame hold two slots at once, and a draw
169
+ * has to name the one that was uploaded for it. Cleared by `beginFrame`, so a frame that draws a
170
+ * rigged mesh without setting a palette takes the plain pipeline — the same defined fallback a
171
+ * caller who passed null gets — rather than reading a slot this frame has already re-let.
172
+ */
173
+ private skinPaletteSlot;
174
+ private readonly surface;
175
+ /** The encoder for the frame in flight, or null between frames. */
176
+ private encoder;
177
+ /** Which optional attributes each mesh key supplied. See the fallback in `submitMesh`. */
178
+ private readonly meshPresent;
179
+ private pass;
180
+ private readonly pipelines;
181
+ /**
182
+ * The same pipelines again, built for the canvas, for anything drawn after `endFrame`.
183
+ *
184
+ * **A second cache rather than a second key in the first one**, because `PipelineCache` carries
185
+ * the format and the sample count as the *authority* for every pipeline it holds — that is the
186
+ * 2026-08-14 rule, and it is what stops a pass disagreeing with the attachment it is about to
187
+ * be used on. A cache holding pipelines that disagree with its own `format` would give that up
188
+ * and put the check back in each caller.
189
+ *
190
+ * So there are two target states and two caches: the world's, wherever the world lands, and the
191
+ * overlay's, which is the canvas at one sample. `ensurePass` reopens on the canvas because the
192
+ * composite has already run — see `overlayDepth` — and a pipeline built against the scene target
193
+ * is rejected there. Measured on `overlay.html?after=1&samples=4`: the whole overlay was dropped,
194
+ * with *"Attachment state of [RenderPipeline "inset|color"] is not compatible with
195
+ * [RenderPassEncoder "overlay"]"* the only evidence anywhere.
196
+ *
197
+ * **The same object as `pipelines` when the two states already agree**, which is the common case
198
+ * of a profile with no composite at one sample — nothing is built twice for a target that is the
199
+ * same target. `pipelineTargets` is what `createMesh` walks so it cannot build one and forget
200
+ * the other.
201
+ */
202
+ private readonly overlayPipelines;
203
+ /** Every distinct cache above, so a mesh's pipelines are built for each. One entry when aliased. */
204
+ private readonly pipelineTargets;
205
+ private readonly bindGroupLayout;
206
+ /** One slot per draw, uploaded once a frame. See `UniformRing`. */
207
+ private readonly perDraw;
208
+ /** The lights, fog and camera, settled once per frame by `bindMeshPass`. */
209
+ private readonly perFrame;
210
+ /**
211
+ * The material block as the CPU holds it, patched by the setters and copied into a slot.
212
+ *
213
+ * Authoritative between draws: a setter changes one field here and marks the block dirty, and
214
+ * the next `drawMesh` takes a slot and copies the whole thing in. That keeps a setter free of
215
+ * any knowledge of slots, which is what lets it be called anywhere a scene wants.
216
+ */
217
+ private readonly perFrameStaging;
218
+ private readonly perFrameFloats;
219
+ private readonly perFrameInts;
220
+ /** The slot the open material occupies, or -1 when the next draw must take a new one. */
221
+ private materialSlot;
222
+ /**
223
+ * What this frame asked for against the ceilings above, reported rather than only warned about.
224
+ *
225
+ * **Declared here, before the rings, because a ring takes its line at construction.** Every
226
+ * ceiling in this file already wrote one `console.warn` per renderer lifetime and nothing else,
227
+ * which cannot be asserted on, cannot be read from a headless check and is gone by the time
228
+ * anybody looks. The order below is the order a report reads in, so the two that lose world
229
+ * geometry come first.
230
+ */
231
+ private readonly budget;
232
+ private readonly drawBudget;
233
+ private readonly materialBudget;
234
+ /**
235
+ * Native bind groups built this frame, which is the one cost a consumer could not see.
236
+ *
237
+ * **No ceiling, because the honest number is nought.** Every other line here counts something
238
+ * rationed; this counts something cached, so a steady scene settles at zero and any standing
239
+ * figure is a cache missing. The measurement that found the albedo-keyed cache had to be taken
240
+ * from outside, by reading a consumer's baked models and replaying this state machine over them
241
+ * in draw order, precisely because the frame reported draws and material changes and never this.
242
+ */
243
+ private readonly bindGroupBudget;
244
+ private readonly shadowDrawBudget;
245
+ private readonly scatterDepthBudget;
246
+ private readonly waterBudget;
247
+ private readonly lightVolumeBudget;
248
+ private readonly windStreakBudget;
249
+ private readonly flockBudget;
250
+ private readonly boltBudget;
251
+ private readonly causticsBudget;
252
+ private readonly textBudget;
253
+ private readonly sdfTextBudget;
254
+ private readonly lineBudget;
255
+ /**
256
+ * What the frame just drawn asked for, and what it was refused.
257
+ *
258
+ * The same object every frame, updated in place — see `budget.ts` for why a number rather than
259
+ * a log, and why `dropped` is the one field a consumer's own check should fail on.
260
+ */
261
+ get frameBudget(): FrameBudget;
262
+ private readonly arena;
263
+ /**
264
+ * Node indices the last schedule kept.
265
+ *
266
+ * **Sized from the arena at replay, which this used to only claim.** It was allocated at
267
+ * `MAX_DRAWS_PER_FRAME` and never grown, while the arena beside it reallocates at twice its
268
+ * capacity — so a frame recording more nodes than this holds had the tail of its passes
269
+ * dropped by `keptNodes`, silently. A node is one per *verb*, not per draw, so overlays,
270
+ * scatter, plumes and text reached it long before the draw ring was full.
271
+ */
272
+ private replayScratch;
273
+ private readonly scheduledPasses;
274
+ private readonly commands;
275
+ /** Guards `ensurePass` against re-entering the flush that is already running. */
276
+ private flushing;
277
+ /** Starts near the verb count; reaching zero is what says the migration is done. */
278
+ private flushesThisFrame;
279
+ private verbFlushesThisFrame;
280
+ /** Passes the last flush scheduled. Zero when nothing has flushed this frame. */
281
+ private passesLastFlush;
282
+ /** Every resource the last flush derived a discard for, as one mask. */
283
+ private discardsLastFlush;
284
+ /** Every resource the last flush derived a clear for, as one mask. */
285
+ private clearsLastFlush;
286
+ /**
287
+ * What still owes a clear this frame.
288
+ *
289
+ * **Owed by attachment rather than by frame**, which `frameNeedsClear` could not express. The
290
+ * rule it carried is that a scene opening the mirror before it draws anything still owes the
291
+ * frame its clear afterwards, and a scene that drew first does not — a rule that lived inside
292
+ * one branch and could not be asked about. Bits are removed by the open that performs them.
293
+ */
294
+ private pendingClearMask;
295
+ /**
296
+ * What the next frame pass to open may discard.
297
+ *
298
+ * Held rather than passed, because the open happens inside `openPass`, which the direct path
299
+ * uses too — threading a graph concept through its signature would put the graph in the one
300
+ * method that exists to have nothing to do with it. Cleared on use, so a pass opened outside a
301
+ * flush gets zero, which is the conservative answer and the right one.
302
+ */
303
+ private pendingDiscard;
304
+ /**
305
+ * Every flush this frame, of either kind.
306
+ *
307
+ * Exposed because the plan gates narrowing `liveOut` on a flush count, and a private counter
308
+ * no caller can read is a claim rather than a measurement. Reading it costs a field access,
309
+ * so a scene can print it every frame without paying for the privilege.
310
+ */
311
+ get graphFlushes(): number;
312
+ /**
313
+ * Flushes caused by a verb about to draw directly, which is what must reach zero.
314
+ *
315
+ * **The plan asks for `flushesThisFrame` to reach zero and that number cannot.** A boundary
316
+ * flush is inherent: the mirror gets a different encoder and its own submit, the overlay is
317
+ * closed on a microtask, the depth snapshot ends the pass to copy the attachment out, and an
318
+ * inset changes the viewport. The design says as much about the reopens — all three are
319
+ * inherent and removing them was never the point — so a frame that reflects will always
320
+ * flush at least twice however complete the migration is.
321
+ *
322
+ * What the migration is actually measured by is the *other* kind: a verb reaching
323
+ * `ensurePass` because it is about to issue commands itself. That is the number that starts
324
+ * near the verb count and goes to zero, and it is the one the gate belongs on.
325
+ */
326
+ get graphVerbFlushes(): number;
327
+ /**
328
+ * Passes the last flush scheduled, which is how a write-set declaration becomes observable.
329
+ *
330
+ * A node declaring a target it does not write is invisible in the picture — the executor
331
+ * replays into whatever pass is open regardless — and shows up here as a pass count that does
332
+ * not match the boundaries the frame actually has.
333
+ */
334
+ get graphPasses(): number;
335
+ /**
336
+ * Everything the last flush derived a discard for, as one mask.
337
+ *
338
+ * **Derived, and not yet acted on.** The executor replays every node into the pass `openPass`
339
+ * gives it, so this changes no bytes on any device. It is the derivation `resolvedStoreOp`
340
+ * takes from a person today, in a form a test can read — which is the whole difference the
341
+ * design claimed for itself, and the only part of it verifiable without a tiler.
342
+ */
343
+ get graphDiscards(): number;
344
+ /**
345
+ * Everything the last flush derived a clear for.
346
+ *
347
+ * The scheduler and `pendingClearMask` compute the same thing, and the mask is what the open
348
+ * acts on — see `openPass` for why it has to be, since a pass can be opened where no schedule
349
+ * describes it. This is the scheduler's answer, exposed so the two can be held against each
350
+ * other rather than assumed to agree.
351
+ */
352
+ get graphClears(): number;
353
+ /**
354
+ * What the last flush's discards are worth, in attachment bytes a frame.
355
+ *
356
+ * The figure the design is arguing about, computed from the schedule and the sizes this
357
+ * renderer allocated rather than estimated from a viewport somebody quoted. Only the two
358
+ * depth attachments can appear in it, because they are the only resources the derivation
359
+ * finds dead — see `liveOutMidFrame`.
360
+ *
361
+ * **A byte here is a byte a tiler would not write.** An immediate-mode GPU has nowhere else
362
+ * to put the data and writes it regardless, which is exactly why this number cannot be
363
+ * confirmed on the machines available here however carefully it is computed.
364
+ *
365
+ * `depth24plus` is counted at four bytes a sample. The format is implementation-defined and
366
+ * a driver may use more; four is the floor, so this number is a floor too.
367
+ */
368
+ get graphDiscardBytes(): number;
369
+ private bindGroup;
370
+ /** Kept so the flat bind group can be rebuilt when a cubemap changes hands. */
371
+ private readonly flatTextures;
372
+ /**
373
+ * Which permutation of the fragment shader this renderer draws with.
374
+ *
375
+ * **Chosen from the resolved profile, which is what `renderer.ts` does — and it does it
376
+ * once, not per material.** `flatFrag` is compiled at construction there from the same four
377
+ * flags, so a world with shadows off compiles none of the shadow path on either backend.
378
+ * This was pinned to `directionalShadows` while the point-shadow samplers had nowhere to
379
+ * come from, which meant the two backends could be running different programs for one
380
+ * profile and nothing said so.
381
+ *
382
+ * `environmentProbe` is false because `bakeReflectionProbe` is not on this class at all, so
383
+ * a scene that wants one gets a named `TypeError` rather than a shader compiled around a
384
+ * probe that will never be filled. That is the loud failure the 2026-08-13 rule asks for.
385
+ *
386
+ * It was `none` for a day, because turning it on drew the scene uniformly black. That was
387
+ * not the shadow path: `shadowMapSize` was missing from this class, so the light matrix
388
+ * every scene builds from it was NaN. See the accessor for what that cost and how it hid.
389
+ */
390
+ private readonly variant;
391
+ /** Whether this profile compiled the point-shadow path, and so has sentinels to write. */
392
+ private readonly pointShadowsCompiled;
393
+ /** What the generator recorded about that permutation: sizes, offsets and bindings. */
394
+ private readonly fragment;
395
+ private depth;
396
+ private depthView;
397
+ /**
398
+ * The pass a draw arriving *after* `endFrame` goes into, and the depth it needs.
399
+ *
400
+ * **An application is allowed to draw its interface after the frame resolves, and one does.**
401
+ * One consumer puts its announcements, its portraits and its item cases after `endFrame`
402
+ * deliberately, so the interface is not inside the off-screen target and is not blurred by the
403
+ * screen-space effects that sample it. WebGL2 allows that for free: its commands run eagerly
404
+ * against the canvas, so a draw after the resolve simply lands on top of it.
405
+ *
406
+ * This backend records into an encoder that `endFrame` has already submitted, so every one of
407
+ * those calls found `this.pass === null` and returned — no text, and a portrait box holding
408
+ * whatever the canvas had, which through a transparent canvas is nothing at all. It reported
409
+ * none of it, which is the failure this whole backend keeps producing.
410
+ *
411
+ * So the pass is reopened on demand, on the canvas itself rather than on the scene target: the
412
+ * composite has already run and this content is meant to sit over its output, untouched by it.
413
+ * Its depth is its own and single-sampled, because the frame's depth carries the scene's sample
414
+ * count and WebGPU requires every attachment in a pass to agree.
415
+ */
416
+ private overlayDepth;
417
+ private overlayDepthView;
418
+ /** Whether the open pass is one reopened after the frame was submitted. */
419
+ private overlayActive;
420
+ /** Whether a flush is already queued, so a hundred overlay draws queue one. */
421
+ private overlayFlushQueued;
422
+ /**
423
+ * The multisampled colour target, when the profile asked for one.
424
+ *
425
+ * Null at one sample, where the swap chain texture is drawn into directly and there is
426
+ * nothing to resolve. Above one it is the *render* target and the swap chain becomes the
427
+ * `resolveTarget`, which is where the averaging happens — the whole reason this exists.
428
+ */
429
+ private colorMsaa;
430
+ private colorMsaaView;
431
+ /** This frame's swap-chain view, taken once so the mirror pass can reopen onto the same one. */
432
+ private frameSwapView;
433
+ /**
434
+ * Where the frame lands when there is a composite to resolve it.
435
+ *
436
+ * Null where `screenEffects` is off, and then every pass writes straight to the swap chain
437
+ * exactly as it did before this existed — which is also what makes `bindMeshPass` grade in
438
+ * the mesh pass in that case, because that pass is then the last one.
439
+ */
440
+ private sceneColor;
441
+ /**
442
+ * The colour the frame had already drawn when the first refracting draw arrived.
443
+ *
444
+ * **A copy, because no shader can read what the pass is writing.** `post.sceneColorMsaa` is
445
+ * created `RENDER_ATTACHMENT` with no `TEXTURE_BINDING` and `post.sceneColor` is the pass's own
446
+ * resolve target, so the only readable thing is a texture nothing is currently attached to.
447
+ * `takeRefractSnapshot` ends the pass, which resolves, copies, and reopens.
448
+ */
449
+ private refractSnapshot;
450
+ private refractSnapshotView;
451
+ /** Whether this frame has taken it. One snapshot serves every refracting draw. */
452
+ private refractSnapshotTaken;
453
+ private sceneColorView;
454
+ /** The multisampled twin, when the profile asks for samples. Resolved into `sceneColor`. */
455
+ private sceneColorMsaa;
456
+ private sceneColorMsaaView;
457
+ /** Sample zero of the depth, as something the generated shaders can read. See `postPass.ts`. */
458
+ private resolvedDepth;
459
+ private resolvedDepthView;
460
+ private aoTarget;
461
+ private aoTargetView;
462
+ /**
463
+ * Where the blur's first axis lands, so the second can read it.
464
+ *
465
+ * **The blur is not a polish step on this estimate, it is half of the algorithm.**
466
+ * `ambientOcclusion.ts` says so at the top: the estimate turns its twelve taps by a rotation
467
+ * that repeats over a 4×4 tile, and the four-wide blur is what averages all sixteen rotations
468
+ * back into one answer. Twelve taps are only enough *because* the blur completes the kernel.
469
+ *
470
+ * Without it the estimate is what that file calls salt and pepper over every surface, and that
471
+ * is exactly what shipped here: `AO_BLUR_FRAG_WGSL` was imported into this file and never
472
+ * called, while `runOcclusion`'s own comment said "then a blur across it". Reported from both
473
+ * consumers as a fine grain — white on one's pillars, dark on the ground under
474
+ * the other's character — and measured on `gilded-chamber` under the first's grade at 41,106
475
+ * speckled pixels against WebGL2's 28,107.
476
+ *
477
+ * Separable, so it is two passes over a half-resolution target rather than one wide one:
478
+ * across into here, then down and back into `aoTarget`, which is what the composite samples.
479
+ */
480
+ private aoScratch;
481
+ private aoScratchView;
482
+ /**
483
+ * The bloom pyramid, level 0 at half the frame and each one after it half again.
484
+ *
485
+ * A list rather than one target, because bloom *is* the pyramid: the frame is thresholded into
486
+ * level 0, halved down the chain, and added back up so level 0 holds every octave at once.
487
+ * This backend had the first of those eleven draws and nothing else, which drew a thresholded
488
+ * image with no blur in it — see `bloomChain.ts` for what that measured.
489
+ */
490
+ private bloomLevels;
491
+ /**
492
+ * One bind group per distinct *source*: the scene first, then each level.
493
+ *
494
+ * Built on resize rather than per stage, because `AGENTS.md` forbids allocating in the frame
495
+ * loop and eleven `createBindGroup` calls a frame is exactly that. What differs between stages
496
+ * beyond the source is the uniform block, and that rides on a dynamic offset instead, which is
497
+ * what the layout already declares.
498
+ */
499
+ private bloomGroups;
500
+ private frameWidth;
501
+ private frameHeight;
502
+ /** The view-projection this frame was drawn with, for the reprojection motion blur needs. */
503
+ private readonly previousViewProj;
504
+ private readonly reprojection;
505
+ private hasPreviousView;
506
+ /** The projection, for occlusion. Null until a mesh pass has settled one. */
507
+ private frameProjection;
508
+ private readonly aoInvProjection;
509
+ private readonly aoProjScale;
510
+ /**
511
+ * Whether this frame has already taken the depth snapshot a light volume clamps against.
512
+ *
513
+ * One per frame, not one per volume: every volume in a frame reads the same opaque scene, and
514
+ * a second snapshot would cost a pass break and a full-screen resolve to produce identical
515
+ * texels. Reset by `beginFrame`.
516
+ */
517
+ private volumeDepthTaken;
518
+ /** Scratch for `uDepthToLocal`, built per volume draw and never allocated in the frame loop. */
519
+ private readonly volumeDepthToLocal;
520
+ private readonly volumeDepthScratch;
521
+ private readonly pickables;
522
+ private readonly pickOrigin;
523
+ private readonly pickDirection;
524
+ private readonly depthResolveLayout;
525
+ private readonly rushLayout;
526
+ private readonly rushUniforms;
527
+ private readonly rushStaging;
528
+ private readonly rushFloats;
529
+ private readonly rushInts;
530
+ private rushBindGroup;
531
+ /**
532
+ * The colour grade, as a 3D texture, and the table it was built from.
533
+ *
534
+ * The source is kept to compare identities rather than to read: a consumer setting a grade
535
+ * every frame would otherwise re-upload 131 kilobytes sixty times a second for bytes that did
536
+ * not change. See `SceneTarget.setColourGrade`, which keeps the same pair for the same reason.
537
+ */
538
+ private gradeTexture;
539
+ private gradeView;
540
+ private gradeSource;
541
+ private gradeSize;
542
+ private gradeStrength;
543
+ /** So the refusal in `setColourGrade` is once per renderer rather than once per frame. */
544
+ private warnedGradeWithoutComposite;
545
+ private readonly aoLayout;
546
+ private readonly aoUniforms;
547
+ /**
548
+ * The global medium: one half-res march and one composite, each with a block of its own.
549
+ *
550
+ * **Two layouts rather than one, because they read different things.** The march reads the
551
+ * depth and the three shadow maps; the composite reads the depth and what the march wrote.
552
+ * The march's target is `rgba16float` — scattered light has no reason to sit in 0 to 1 against
553
+ * an HDR scene, and transmittance wants more than eight bits near 1, where a thin fog lives and
554
+ * where banding would therefore show first.
555
+ */
556
+ private readonly mediumLayout;
557
+ private readonly mediumUniforms;
558
+ private readonly mediumStaging;
559
+ private readonly mediumFloats;
560
+ private readonly mediumInts;
561
+ private mediumTarget;
562
+ private mediumTargetView;
563
+ private mediumBindGroup;
564
+ private readonly mediumUpsampleLayout;
565
+ private readonly mediumUpsampleUniforms;
566
+ private readonly mediumUpsampleStaging;
567
+ private readonly mediumUpsampleFloats;
568
+ private mediumUpsampleGroup;
569
+ /** The inverse projection the composite linearises a depth with, built once per frame. */
570
+ private readonly mediumInvProjection;
571
+ /** This frame's medium. Off until a caller says otherwise. See `setGlobalMedium`. */
572
+ private mediumOptions;
573
+ /**
574
+ * The temporal resolve: its block, its ping-pong pair, and the jitter state.
575
+ *
576
+ * **`correctedViewProj` stays unjittered and `viewProj` is what draws.** The reprojection is
577
+ * built from the former, because the history holds a resolved picture standing for the
578
+ * *unjittered* scene — reprojecting through a jittered matrix would look every sample up half a
579
+ * pixel from where it is and cancel the accumulation.
580
+ */
581
+ private readonly taaStaging;
582
+ private readonly taaFloats;
583
+ private taaLayout;
584
+ private taaUniforms;
585
+ private taaTextures;
586
+ private taaViews;
587
+ /** Group `i` reads history `i` and the pass writes into the other one. */
588
+ private taaBindGroups;
589
+ private taaWrite;
590
+ private readonly temporalHistory;
591
+ private readonly jitteredViewProj;
592
+ private temporalJitterX;
593
+ private temporalJitterY;
594
+ private temporalJittering;
595
+ private temporalHistoryUsable;
596
+ private readonly aoStaging;
597
+ private readonly aoFloats;
598
+ private aoBindGroup;
599
+ private readonly aoBlurLayout;
600
+ /**
601
+ * Both blur axes' blocks in one buffer, a slot each, reached by dynamic offset.
602
+ *
603
+ * The same shape the bloom chain uses and for the same reason: the two passes differ only in
604
+ * `uStep`, and a buffer holding one block would give the second pass the first one's axis —
605
+ * which is a blur run twice across and never down, and looks like a blur that half worked.
606
+ */
607
+ private readonly aoBlurUniforms;
608
+ private readonly aoBlurStaging;
609
+ private readonly aoBlurFloats;
610
+ /** One group per source: the estimate for the across pass, the scratch for the down pass. */
611
+ private aoBlurAcrossGroup;
612
+ private aoBlurDownGroup;
613
+ private readonly bloomLayout;
614
+ private readonly bloomUniforms;
615
+ /**
616
+ * Every stage's block at once, one aligned slot each, written on resize and not per frame.
617
+ *
618
+ * **Nothing in these blocks changes between frames.** A texel size is a function of the
619
+ * target, the threshold is construction-time on the profile, and the tent radius is a
620
+ * constant — so a per-frame upload would rewrite the same bytes sixty times a second. Written
621
+ * together also sidesteps the hazard this file has met twice: `queue.writeBuffer` does not
622
+ * interleave with recorded commands, so one buffer rewritten between two passes gives both
623
+ * passes the last write.
624
+ */
625
+ private readonly bloomStaging;
626
+ private readonly bloomFloats;
627
+ private readonly postSampler;
628
+ private readonly postDepthSampler;
629
+ /** Whether the profile asked for a composite at all. Everything below is null when it did not. */
630
+ private get hasComposite();
631
+ /** What a frame pass writes into: the scene target where there is one, the swap chain else. */
632
+ /**
633
+ * This frame's swap-chain view, taken on first use rather than at `beginFrame`.
634
+ *
635
+ * `getCurrentTexture` is an acquisition and not a read: the texture it hands back is what the
636
+ * browser presents when the task ends, whether or not anything drew into it. Taking it only
637
+ * when something is about to draw is what keeps a frame that fails from blanking the screen.
638
+ */
639
+ private swapView;
640
+ private compositeTarget;
641
+ /** Its multisampled twin, or null at one sample. */
642
+ private compositeMsaa;
643
+ /** Samples per pixel, settled at construction. See `supportedSampleCount`. */
644
+ private readonly samples;
645
+ /** The camera the frame is being drawn with, copied into every draw's slot. */
646
+ private viewProj;
647
+ /**
648
+ * The frame's camera as the caller built it, uncorrected and unjittered.
649
+ *
650
+ * **`viewProj` is not this**, and a decal wants this one. That matrix carries `CLIP_CORRECTION`
651
+ * and the frame's sub-pixel jitter; the generated vertex stages then negate Y a second time, so
652
+ * the Y the rasteriser actually used is *this* matrix's. `DEPTH_01_TO_CLIP_Y_DOWN` carries the
653
+ * derivation and what one missing negation costs.
654
+ */
655
+ private readonly frameRawViewProj;
656
+ private frameCameraSeen;
657
+ /** Where the frame's camera stands, which gives a reconstructed normal its sign. */
658
+ private readonly frameEye;
659
+ /** The environment the frame was drawn with. See `bindMeshPass`. */
660
+ private frameEnv;
661
+ /** That matrix with the clip-space correction folded in, rebuilt once a frame. */
662
+ private readonly correctedViewProj;
663
+ private readonly skyLayout;
664
+ private readonly skyUniforms;
665
+ private readonly skyStaging;
666
+ private readonly skyFloats;
667
+ private readonly skyBindGroup;
668
+ /** The camera's inverse with the clip correction undone, rebuilt once a frame. */
669
+ private readonly skyInvViewProj;
670
+ /**
671
+ * The reflection target, or null where the profile has no use for one.
672
+ *
673
+ * Allocated on the same condition `renderer.ts` allocates its own: water with reflections
674
+ * on, or `planarReflections` asked for outright by a scene whose mirror is not water.
675
+ */
676
+ /**
677
+ * Whether this profile has any use for a mirror, on the same condition `renderer.ts` uses:
678
+ * water with reflections on, or a scene that asked for one outright because its mirror is
679
+ * not water. A profile with neither never pays for the target.
680
+ */
681
+ private readonly wantsReflection;
682
+ private reflection;
683
+ /** Refilled on resize rather than allocated; see `reflectionTargetSize`. */
684
+ private readonly reflectionSize;
685
+ /**
686
+ * Order-independent transparency: the recorded set, the two buffers, and which one is open.
687
+ *
688
+ * **Recorded rather than drawn**, because the accumulation and the revealage are two blend
689
+ * states over the same geometry — and on this backend a blend state is baked into a pipeline, so
690
+ * they are two pipelines as well. `TranslucentQueue` is what lets the set be submitted twice.
691
+ */
692
+ private readonly translucentQueue;
693
+ private oitActive;
694
+ /** Said once for the whole renderer. See `refuseOitMultisampled`. */
695
+ private oitMultisampleRefused;
696
+ private oitReplaying;
697
+ /** Which buffer the replay is filling, which the pipeline key must carry. */
698
+ private oitMode;
699
+ /**
700
+ * The pass the replay draws into.
701
+ *
702
+ * **While this is set, `openPass` hands it back and `recordDraw` refuses.** The frame graph
703
+ * records draws to replay them into the frame's own passes; these draws belong to a pass of
704
+ * their own, opened here and closed before anything else runs.
705
+ */
706
+ private oitEncoder;
707
+ private oitAccum;
708
+ private oitAccumView;
709
+ private oitReveal;
710
+ private oitRevealView;
711
+ /**
712
+ * The frame's drawn decals, their block per mark, and the group that reads the resolved depth.
713
+ *
714
+ * Recorded rather than drawn where the call is made: a projected mark is decided from the depth
715
+ * the frame has drawn, so it cannot run until the opaque world is finished. See `DecalQueue`.
716
+ */
717
+ private readonly decalQueue;
718
+ private decalLayout;
719
+ private decalUniforms;
720
+ private readonly decalStaging;
721
+ private readonly decalFloats;
722
+ private decalBindGroup;
723
+ /** Said once where a mark cannot be drawn at all, rather than dropped in silence. */
724
+ private decalsRefused;
725
+ /**
726
+ * The frame's reflective surfaces, their block per surface, and the buffer the trace writes into.
727
+ *
728
+ * Recorded rather than traced where the call is made, because a march reads the *finished*
729
+ * picture: what a ray finds is a pixel of the scene the frame has already drawn.
730
+ */
731
+ private readonly reflectionQueue;
732
+ private ssrLayout;
733
+ private ssrResolveLayout;
734
+ private ssrUniforms;
735
+ private readonly ssrStaging;
736
+ private readonly ssrFloats;
737
+ private ssrReflection;
738
+ private ssrReflectionView;
739
+ private ssrBindGroup;
740
+ private ssrResolveGroup;
741
+ /** `inverse(viewProjection) * DEPTH_01_TO_CLIP_Y_DOWN`, rebuilt once a frame. */
742
+ private readonly ssrDepthToWorld;
743
+ /** `CLIP_Y_FLIP * viewProjection`, the matrix the trace projects its samples back through. */
744
+ private readonly ssrViewProj;
745
+ private readonly ssrScissor;
746
+ /** Said once where a reflection cannot be traced at all. */
747
+ private reflectionsRefused;
748
+ /** `inverse(viewProjection) * DEPTH_01_TO_CLIP_Y_DOWN`, rebuilt once a frame and not per mark. */
749
+ private readonly decalDepthToWorld;
750
+ private readonly decalScissorRect;
751
+ private oitResolveLayout;
752
+ private oitResolveGroup;
753
+ /** Reused, because the frame loop may not allocate. Mirrors the WebGL2 backend's own. */
754
+ private readonly oitReplayOptions;
755
+ private reflectionPassActive;
756
+ /** Said once: a mirror asked for outside a frame is a call-order fault, not a device limit. */
757
+ private saidMirrorOutsideFrame;
758
+ /**
759
+ * Whether the target holds this frame's mirror yet.
760
+ *
761
+ * Cleared when the pass opens and set when it closes, so water drawn *before* the mirror
762
+ * finished samples nothing rather than last frame's. `PlanarReflection.isReadyFor` is the
763
+ * same guard on the other backend, with the plane's height in it as well.
764
+ */
765
+ private reflectionReady;
766
+ /** The plane the ready target was drawn for, so water at a different height does not use it. */
767
+ private reflectionPlaneY;
768
+ /**
769
+ * The height the medium is resolved at while the mirror is being drawn.
770
+ *
771
+ * **The source camera's, not the mirrored one's.** Fog is a property of where the *viewer*
772
+ * stands, and a mirrored camera stands as far below the water as the viewer stands above it —
773
+ * so resolving the medium from it puts the reflection underwater whenever the viewer is not.
774
+ * `renderer.ts` keeps the same value for the same reason.
775
+ */
776
+ private reflectionAtmosphereY;
777
+ /**
778
+ * Open the mirror pass: the world about to be drawn from behind the water.
779
+ *
780
+ * **The frame's pass is ended and reopened, which is the shape WebGPU makes explicit.** A
781
+ * caller calls this *after* `beginFrame`, so there is a pass open on the swap chain; WebGL2
782
+ * answers by rebinding a framebuffer, and here the only way to draw somewhere else is a
783
+ * second render pass. `endPlanarReflection` reopens the frame's with `load`, so the clear and
784
+ * anything drawn before the mirror survive it.
785
+ *
786
+ * **Declining is a supported state rather than a stub**, and is what a device that refuses
787
+ * the allocation gets: every caller already branches on `null` — `demo/nightCourt` draws the
788
+ * world once instead of twice — and `WaterRenderer` has a complete path for water with
789
+ * nothing to sample.
790
+ */
791
+ beginPlanarReflection(source: Camera, planeY: number, clearColor: Vec3): Camera | null;
792
+ /** The mirrored viewProjection under the clip correction, rebuilt per water draw. */
793
+ private readonly correctedReflectionViewProj;
794
+ /**
795
+ * Push every ring's slots up before the commands that read them are submitted.
796
+ *
797
+ * That ordering is the whole thing a ring depends on — writes land on the queue timeline
798
+ * ahead of the pass that reads them — and it has to happen once per *submission* rather than
799
+ * once per frame, because a frame with a mirror in it submits three times. Re-flushing a
800
+ * prefix that has already been uploaded costs one write of unchanged bytes and cannot be
801
+ * wrong: a ring only ever appends within a frame.
802
+ */
803
+ private flushRings;
804
+ private readonly filmLayout;
805
+ private readonly filmVerts;
806
+ private readonly filmFrags;
807
+ private filmBindGroup;
808
+ /** The film's group with the blank in the mirror's slot; see where it is built. */
809
+ private readonly filmBindGroupBlank;
810
+ /**
811
+ * A thin wet layer over the world: an oil slick, a puddle, a wet patch.
812
+ *
813
+ * `renderer.ts` carries the argument about what `sheen` is and is not, and about why the
814
+ * caller does not restate the plane the mirror was rendered for. The one thing this backend
815
+ * adds is that the mirror's matrix is corrected, for the reason `drawWater` gives: the
816
+ * reflection texture holds a frame in WebGPU's clip space, so a lookup projecting a world
817
+ * point has to be in that same space or the film samples its own mirror image.
818
+ */
819
+ drawFilm(mesh: GpuMesh, camera: Camera, timeSeconds: number, env: Environment, sheen: number, options?: FilmOptions): void;
820
+ /** Scratch for the film's fragment block, filled then copied into a slot. */
821
+ private readonly filmFragStaging;
822
+ private readonly filmFragFloats;
823
+ private readonly filmFragInts;
824
+ /**
825
+ * The image the following draws sample, and the bind group that carries it.
826
+ *
827
+ * **A bind group per texture, cached, rather than one rebuilt per material.** `createBindGroup`
828
+ * is an allocation, and a scene like `night-street` switches texture once per textured part of
829
+ * a loaded model — so rebuilding on every switch is exactly the per-frame allocation the house
830
+ * rules forbid, and the same trap `rebindPointShadowsIfMoved` avoids by dirty-checking. The
831
+ * map is keyed by the texture object, so a material switched away from and back to costs a
832
+ * lookup.
833
+ */
834
+ private albedo;
835
+ /**
836
+ * The material's normal map, or null for the stand-in.
837
+ *
838
+ * Not cached per texture the way albedo is: a material changes its albedo far more often than
839
+ * its normal map, and a second cache keyed on a second texture is a product of the two.
840
+ */
841
+ private normalMap;
842
+ private ormMap;
843
+ /** The caller's emissive map, or null for the stand-in. See `setMaterial`. */
844
+ private emissiveMap;
845
+ /**
846
+ * The flat group for a material, keyed on **all four** of its maps.
847
+ *
848
+ * **It used to be keyed on the albedo alone, and that is why it was emptied so often.** Every
849
+ * group it holds carries the normal, ORM and emissive views that were current when it was built,
850
+ * so a cache keyed on one of the four could only stay correct by being thrown away whenever any
851
+ * of the other three moved. `setMaterial` did that once per changed map, then missed the cache it
852
+ * had just cleared, so a material differing from the one before it in a single map cost up to
853
+ * four `createBindGroup` calls and destroyed every entry a later material would have hit.
854
+ *
855
+ * A consumer measured what that cost over the materials its cars merge to: 476 groups a pass for
856
+ * seven models with 247 distinct signatures between them, drawn a second time for the water's
857
+ * mirror, so about 950 a frame that should have been nought after the first. It scaled with
858
+ * distinct models rather than with cars, because identical cars share a batch, which is why it
859
+ * read from the outside as the frame rate falling off when a second kind of car appeared.
860
+ *
861
+ * **Nested rather than one map under a composite key**, because a key built per lookup is an
862
+ * allocation on a path that runs per material change, and the number of material changes in a
863
+ * frame is exactly the number this is trying to stop being expensive. Four `Map.get` calls
864
+ * allocate nothing. What a miss costs is up to three empty `Map`s, which is bounded by the
865
+ * distinct signatures a scene has and is zero once it has been seen.
866
+ *
867
+ * The four texture invalidations that remain are in `rebuildFlatBindGroup`, and they are the
868
+ * ones that move something *every* group holds: the shadow cubemaps, the probe fence, the cookie
869
+ * atlas and the IES rows. None of those is a frame path.
870
+ */
871
+ private readonly flatBindGroups;
872
+ /** The group holding the one-pixel stand-in, for every draw with no texture set. */
873
+ private blankAlbedoBindGroup;
874
+ /** Repeats across the mesh's own UV range. Per draw, because `uUvScale` is a vertex uniform. */
875
+ private readonly uvScale;
876
+ createSurfaceTexture(source: TexImageSource, options?: SurfaceTextureOptions): GpuSurfaceTexture;
877
+ updateSurfaceTexture(texture: GpuSurfaceTexture, source: TexImageSource): void;
878
+ disposeSurfaceTexture(texture: GpuSurfaceTexture): void;
879
+ /**
880
+ * Choose the surface texture the following `drawMesh` calls sample, or null for none.
881
+ *
882
+ * State rather than an argument to `drawMesh`, for the two reasons `renderer.ts` gives: a
883
+ * texture belongs to a material and a material covers many draws, and `drawMesh` already
884
+ * carries a model, a layer and a tint. `bindMeshPass` resets this to none, so a pass cannot
885
+ * inherit a material from the one before it.
886
+ */
887
+ /**
888
+ * Choose the joint palette the following `drawMesh` calls skin by, or null for none.
889
+ *
890
+ * The twin of the WebGL2 method, answering the same way — a value-typed member of the shared
891
+ * surface must be answered by every backend, and answered honestly, per the 2026-08-13 rule.
892
+ */
893
+ setSkinPalette(palette: Float32Array | null): void;
894
+ /**
895
+ * Choose the morph weights the following `drawMesh` calls deform by, or null for none.
896
+ *
897
+ * The twin of the WebGL2 method: the weights are per draw and the deltas are per mesh, so two
898
+ * characters sharing one head mesh wear different expressions without a second copy of it.
899
+ */
900
+ setMorphWeights(weights: Float32Array | null): void;
901
+ setMaterial(material: SurfaceMaterial<GpuSurfaceTexture> | null): void;
902
+ /**
903
+ * @deprecated Use `setMaterial`. See `renderer.ts` for why this stays: 56 call sites across four
904
+ * repositories, three of which deploy on push, so removing it is a major version rather than a
905
+ * side effect. Both renderers implement the same public surface, and a divergence here is a
906
+ * consumer that works on one backend and not the other.
907
+ */
908
+ setSurfaceTexture(texture: GpuSurfaceTexture | null, uScale?: number, vScale?: number, cutout?: number): void;
909
+ /**
910
+ * The group for this exact set of four maps, built on the first ask and kept.
911
+ *
912
+ * **All four null is the stand-in's group**, which stays a field of its own rather than an entry
913
+ * here: it is what a pass starts from and what `disposeSurfaceTexture` and the ring rebuild have
914
+ * to refresh directly, and giving it two homes would be two things to keep in step.
915
+ *
916
+ * `buildFlatBindGroup` reads the live material fields, so every caller assigns all four before
917
+ * asking. Passing them in as well is deliberate: it is what makes the key and the group agree by
918
+ * construction rather than by the caller having remembered.
919
+ */
920
+ private flatGroupForMaps;
921
+ /**
922
+ * Patch one field of the open material and make the next draw take a new slot.
923
+ *
924
+ * The block is the CPU's until a draw copies it, so a setter is free to be called between
925
+ * draws, several times, or never — which is what "pass state" means on the other backend too.
926
+ */
927
+ private material;
928
+ private materialField;
929
+ /**
930
+ * Kept as a no-op that says so once. See `renderer.ts` for the whole argument; in one line, it
931
+ * was the stand-in for a term that now exists, and it is deprecated rather than removed because
932
+ * it is on the public surface and removing it would break four repositories to save a method.
933
+ */
934
+ setEnvironmentAmbient(_amount: number): void;
935
+ /** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
936
+ setSurfaceReflectivity(amount: number): void;
937
+ /** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
938
+ setEnvironmentGain(gain: number): void;
939
+ setSurfaceGrain(amount: number): void;
940
+ /**
941
+ * How strong the microscopic relief on the surfaces drawn next is, and how coarse.
942
+ *
943
+ * The 60 that `renderer.ts` defaults to is repeated there and here; the two must move
944
+ * together, and that file says why the number is what it is.
945
+ */
946
+ setSurfaceRelief(amount: number, cyclesPerMetre?: number): void;
947
+ /**
948
+ * How hard the bound surface texture's own luminance turns the shading normal.
949
+ *
950
+ * See `Renderer.setSurfaceTextureRelief` for what this is for, why the colour image serves as
951
+ * the height field rather than a second sampler being added, and why this one is not clamped
952
+ * to 0..1 the way its neighbours are.
953
+ */
954
+ setSurfaceTextureRelief(scale: number): void;
955
+ /** Scale the emissive of the following draws. See `Renderer.setEmissiveGain` for why. */
956
+ setEmissiveGain(gain: number): void;
957
+ /**
958
+ * The plain surface an overlay is read against. See `Renderer.fillPanel` for the argument
959
+ * about why neither an inset nor a plate of glyph cubes can stand in for it.
960
+ *
961
+ * **Reopens the pass, like the rest of this section.** A panel is the backdrop a caption or a
962
+ * plate is read against, so it is drawn wherever they are — and they are drawn after `endFrame`
963
+ * by the consumer that has one, to keep the interface out of the screen-space chain. Held to
964
+ * `this.pass`, it was the only member of `-- Overlays --` that still went silently nowhere in
965
+ * that order, which is the shape of failure the 2026-08-13 rule is about.
966
+ */
967
+ fillPanel(rect: InsetRect, color: Vec3, alpha: number): void;
968
+ /** A 3D text object. See `textPass.ts`; the layout it holds is shared with WebGL2. */
969
+ createText(): GpuText;
970
+ setText(text: GpuText, content: string): void;
971
+ setPlate(text: GpuText, widthCells: number, heightCells: number, bottomCell: number): void;
972
+ textWidth(text: GpuText, cellSize: number): number;
973
+ disposeText(text: GpuText): void;
974
+ /** A text label from an SDF font. See `sdfTextPass.ts`; the layout it holds is shared with WebGL2. */
975
+ createSdfText(): GpuSdfText;
976
+ setSdfText(text: GpuSdfText, font: SdfFont, atlas: GpuSurfaceTexture, content: string, style: SdfTextStyle): void;
977
+ disposeSdfText(text: GpuSdfText): void;
978
+ /** The bind group for this atlas, built on the first ask and kept. See `sdfTextBindGroups`. */
979
+ private sdfTextBindGroupFor;
980
+ /**
981
+ * Draw a laid-out SDF string into the scene, at `model`.
982
+ *
983
+ * **Inside whatever pass is already open, against `this.viewProj` — not a pass of its own.**
984
+ * `drawText` calls `openTextPass` because the pixel font's cubes need their own cleared depth
985
+ * buffer to sort against themselves; SDF text is a flat, blended quad with depth *testing*
986
+ * left at the pass's own ambient state, the same assumption `drawTranslucentMesh` makes, so
987
+ * reopening the pass here would do nothing but discard whatever the frame had drawn before
988
+ * it — see `openTextPass`'s own comment for the failure that shape caused once already.
989
+ * `this.viewProj` is `bindMeshPass`'s corrected matrix, the same one `submitMesh` reads; a
990
+ * no-op before the first `bindMeshPass` of a session, same as every other pass that reads it.
991
+ */
992
+ drawSdfText(text: GpuSdfText, model: Float32Array, color: Vec3, opacity: number): void;
993
+ /** Said once rather than every frame, for the reason `warnedFull` gives. */
994
+ private warnedSdfTextFull;
995
+ private warnedTextFull;
996
+ /**
997
+ * Draw a laid-out string over the scene.
998
+ *
999
+ * **A pass of its own, because the depth buffer has to be cleared and WebGPU cannot clear one
1000
+ * in the middle of a pass.** `TextRenderer` calls `gl.clear(DEPTH_BUFFER_BIT)` here and the
1001
+ * reason is in its comment: the cubes are solid, so text drawn with no depth test renders as
1002
+ * one flat facet, and text drawn against the scene's depth disappears into it. Reopening with
1003
+ * `depthLoadOp: 'clear'` and `loadOp: 'load'` on colour is the same statement in this API.
1004
+ *
1005
+ * One pass per string rather than one per frame, which is what WebGL2 does by clearing on
1006
+ * every `draw` — two strings do not sort against each other on either backend.
1007
+ */
1008
+ drawText(text: GpuText, viewportWidth: number, viewportHeight: number, originX: number, originY: number, style: TextStyle, timeSec: number): void;
1009
+ /**
1010
+ * End the frame's pass and reopen it with the depth buffer cleared.
1011
+ *
1012
+ * The colour attachment loads, so everything drawn so far survives; only depth is thrown
1013
+ * away. `this.pass` is replaced, so whatever a scene draws after its text carries on into
1014
+ * the reopened pass and against the cleared depth — which is exactly what happens on WebGL2
1015
+ * after its `gl.clear`.
1016
+ */
1017
+ private openTextPass;
1018
+ /**
1019
+ * The mirror to sample for water resting at `planeY`, or null.
1020
+ *
1021
+ * **Three conditions, not one.** There has to be a target; this frame has to have drawn into
1022
+ * it, or the pool shows the frame before; and it has to have been drawn for *this* plane, or
1023
+ * a scene with two bodies of water at different heights reflects one into the other.
1024
+ * `PlanarReflection.isReadyFor` is the same test on the other backend.
1025
+ */
1026
+ private reflectionFor;
1027
+ /** Close the mirror and put the frame's own pass back, keeping what it already held. */
1028
+ endPlanarReflection(): void;
1029
+ /**
1030
+ * The frame's own render pass, opened to clear or to resume.
1031
+ *
1032
+ * One place, because `beginFrame` and `endPlanarReflection` have to agree about which view is
1033
+ * the attachment and which is the resolve target — and at four samples those are two
1034
+ * different textures, so a second copy of the rule is a mirror pass that quietly redirects
1035
+ * the rest of the frame to the wrong one.
1036
+ */
1037
+ /**
1038
+ * The mirror's pass: the world about to be drawn from behind the water.
1039
+ *
1040
+ * `beginPlanarReflection`'s descriptor, moved here so the mirror is opened through the same
1041
+ * door as everything else and takes its load and store from the same two masks.
1042
+ */
1043
+ private openMirrorPass;
1044
+ private openFramePass;
1045
+ /**
1046
+ * Build a particle pool: the shared cross, instance storage sized to its capacity, and this
1047
+ * batch's own uniform buffers and bind group — see the doc comment on `createGpuParticles`
1048
+ * for why those may not be shared with another batch of the same material. The bind group
1049
+ * *layout* is the one thing still shared per material, resolved here and handed down: it is
1050
+ * structural (what the shader binds, not what value is in it), so sharing it carries none of
1051
+ * the same risk.
1052
+ */
1053
+ createParticles(capacity: number, options: ParticleBatchOptions): GpuParticles;
1054
+ disposeParticles(batch: GpuParticles): void;
1055
+ /**
1056
+ * Draw a pool's live particles.
1057
+ *
1058
+ * **The instance streams are interleaved here rather than uploaded separately**, because
1059
+ * ten attributes will not fit in eight vertex-buffer slots. `packInstances` writes into an
1060
+ * array allocated with the batch, so the frame loop still allocates nothing.
1061
+ */
1062
+ /**
1063
+ * The output transform a forward pass should apply, and the exposure it should apply it at.
1064
+ *
1065
+ * The WebGPU half of `Renderer.gradeCode`, and it has to agree with it draw for draw: this is
1066
+ * one verb with two implementations, and a frame that grades on one backend and not the other
1067
+ * is the silent kind of disagreement. See that method for what the gate is protecting.
1068
+ */
1069
+ private gradeCode;
1070
+ private gradeExposure;
1071
+ drawParticles(batch: GpuParticles, data: ParticleInstances, camera: Camera, env: Environment, timeSeconds: number): void;
1072
+ /**
1073
+ * The bind group layout for one particle material, built on first use.
1074
+ *
1075
+ * Per material rather than shared across all three, because the fragment blocks are
1076
+ * different sizes (`'spark'`'s is 560 bytes, `'mote'`'s is 80) and a bind group whose
1077
+ * `minBindingSize` came from another material's block is rejected at bind time. Safe to
1078
+ * share *within* a material — unlike the uniform buffers and bind group `createGpuParticles`
1079
+ * now builds per batch — because a layout describes what a shader binds, never a value, so
1080
+ * nothing about handing the same one to two batches can go stale mid-frame.
1081
+ */
1082
+ private particleLayoutFor;
1083
+ /** Build a plume batch: crossed quads per placement, and the material's own pipeline. */
1084
+ createPlumes(plumes: readonly PlumePlacement[], options: PlumeOptions): GpuPlumes;
1085
+ disposePlumes(plumes: GpuPlumes): void;
1086
+ /**
1087
+ * Draw a plume batch after the opaque scene.
1088
+ *
1089
+ * **`uClipEnabled` is zero and stays zero until planar reflections land.** `renderer.ts`
1090
+ * passes the reflection's clip plane here so a plume is cut at the water line while the
1091
+ * mirror pass draws it; with no reflection pass there is nothing to clip against, which is
1092
+ * the same state a world with reflections switched off is in.
1093
+ */
1094
+ drawPlumes(plumes: GpuPlumes, camera: Camera, timeSeconds: number, env: Environment, windX?: number, windZ?: number, originX?: number, originY?: number, originZ?: number): void;
1095
+ /**
1096
+ * Build a body of water: the ocean's two sheets and a unit patch for bounded bodies.
1097
+ *
1098
+ * The grid's own numbers — cell size and the two extents — travel with the handle, because
1099
+ * `resolveWater` needs them and they belong to this body rather than to the renderer.
1100
+ */
1101
+ createWater(resolution?: number, nearExtent?: number, farHalfExtent?: number): GpuWater;
1102
+ /** Release a body of water. */
1103
+ disposeWater(water: GpuWater): void;
1104
+ /**
1105
+ * Draw a body of water, after the opaque scene.
1106
+ *
1107
+ * **A mirror is used only if this frame drew one for this water plane.** `reflectionFor`
1108
+ * answers that; where it says no, the binding takes the one-pixel stand-in and
1109
+ * `uReflectionEnabled` is zero, which is the shader's own "no reflection" path and exactly
1110
+ * what `renderer.ts` binds for a world with reflections switched off.
1111
+ */
1112
+ drawWater(water: GpuWater, camera: Camera, timeSeconds: number, settings: WaterBody, env: Environment, windX?: number, windZ?: number): void;
1113
+ /**
1114
+ * Chosen from the profile rather than pinned, because there are two of it and both are
1115
+ * generated. `renderer.ts` compiles `lightVolumeFrag({ directionalShadows })` from the same
1116
+ * field, so a profile with the sun's shadows off gets a shader with no map to read on either
1117
+ * backend. The flat pass is still pinned to one of its sixteen; that is its own row.
1118
+ */
1119
+ private readonly lightVolumeVariant;
1120
+ private readonly lightVolumeFragment;
1121
+ private readonly lightVolumeLayout;
1122
+ private readonly lightVolumeVertices;
1123
+ private readonly lightVolumeFragments;
1124
+ /**
1125
+ * Not `readonly`, because one of the views it holds is allocated with the drawing buffer.
1126
+ *
1127
+ * A bind group holds views, and the depth snapshot a volume clamps against is created and
1128
+ * destroyed by `ensureComposite` — so this is rebuilt there for the same reason `rushBindGroup`
1129
+ * and the water's are. Built once at construction it would hold the stand-in for ever, which
1130
+ * is a beam that still draws and never clamps.
1131
+ */
1132
+ private lightVolumeBindGroup;
1133
+ /** The two things `buildLightVolumeBindGroup` needs that are settled in the constructor. */
1134
+ private readonly volumeShadowSampler;
1135
+ private readonly volumeBlank;
1136
+ /** Where the camera stands in the volume, filled per draw into an object this class owns. */
1137
+ private readonly volume;
1138
+ /**
1139
+ * Draw a volume of light: a closed hull, and the air inside it integrated along the view ray.
1140
+ *
1141
+ * **Two things are decided on the CPU and both are `resolveLightVolume`'s**: the camera in the
1142
+ * volume's own space, which is the frame the march runs in, and whether the camera is inside
1143
+ * the cone, which decides which half of the hull to keep. Neither is a binding, so neither is
1144
+ * written twice — see that module.
1145
+ *
1146
+ * Everything else here is one draw's uniforms, and **every field is written every draw**. A
1147
+ * ring slot holds whatever the volume that used it last frame put there, so a field left out
1148
+ * on the grounds that the shader will not reach it is a stale value waiting for the frame
1149
+ * where it does.
1150
+ */
1151
+ drawLightVolume(mesh: GpuMesh, model: ReadonlyMat4, camera: Camera, strength: number, length: number, spread: number, options?: LightVolumeDrawOptions): void;
1152
+ /** Said once rather than every frame, for the reason `warnedFull` gives. */
1153
+ private warnedVolumesFull;
1154
+ private readonly windStreakLayout;
1155
+ /** A slot per batch, for the reason `windStreakPass.ts`'s layout gives. */
1156
+ private readonly windStreakVerts;
1157
+ private readonly windStreakFrags;
1158
+ private readonly windStreakVertStaging;
1159
+ private readonly windStreakVertFloats;
1160
+ /** The same bytes as an `i32` view, which is what a ring slot is copied through. */
1161
+ private readonly windStreakVertBlock;
1162
+ private readonly windStreakFragStaging;
1163
+ private readonly windStreakFragFloats;
1164
+ private readonly windStreakFragBlock;
1165
+ private readonly windStreakBindGroup;
1166
+ /** Said once rather than every frame, for the reason `warnedFull` gives. */
1167
+ private warnedStreaksFull;
1168
+ /** Refilled per call rather than allocated; see `createResolvedWindStreaks`. */
1169
+ private readonly streaks;
1170
+ /**
1171
+ * Build a lattice of wind-borne debris.
1172
+ *
1173
+ * The thresholds are resolved here rather than left on the handle, because
1174
+ * `windStreakDraw.ts` owns the defaults and a second copy of an onset speed is debris that
1175
+ * appears at a different wind on one backend.
1176
+ */
1177
+ createWindStreaks(options?: WindStreakOptions): GpuWindStreaks;
1178
+ disposeWindStreaks(streaks: GpuWindStreaks): void;
1179
+ /**
1180
+ * Draw the wind, after the opaque scene.
1181
+ *
1182
+ * **Whether the camera is under water is the engine's question, not the caller's**, exactly as
1183
+ * `renderer.ts` argues: every pass already knows where the waterline is, and leaving it to
1184
+ * each call site is how one of them ends up raining dust into the sea.
1185
+ */
1186
+ drawWindStreaks(streaks: GpuWindStreaks, camera: Camera, wind: WindField, timeSeconds: number, tint: Vec3, env: Environment): void;
1187
+ private readonly flockLayout;
1188
+ /** A slot per batch, for the reason `flockPass.ts`'s layout gives. */
1189
+ private readonly flockVerts;
1190
+ private readonly flockFrags;
1191
+ private readonly flockVertStaging;
1192
+ private readonly flockVertFloats;
1193
+ /** The same bytes as an `i32` view, which is what a ring slot is copied through. */
1194
+ private readonly flockVertBlock;
1195
+ private readonly flockFragStaging;
1196
+ private readonly flockFragFloats;
1197
+ private readonly flockFragBlock;
1198
+ private readonly flockBindGroup;
1199
+ /** Said once rather than every frame, for the reason `warnedFull` gives. */
1200
+ private warnedFlockFull;
1201
+ createFlock(count: number): GpuFlock;
1202
+ disposeFlock(flock: GpuFlock): void;
1203
+ /**
1204
+ * Draw a flock.
1205
+ *
1206
+ * **No `uCameraPos`, because the shader no longer declares one.** It fed a banking term that
1207
+ * turned a wing plane toward the viewer, reverted with the rest of the flock work when the
1208
+ * shape being reported turned out to be a lighthouse beam; see `AGENTS.md` 2026-08-10 and the
1209
+ * comment in `flockRenderer.ts`. Binding one here would be reintroducing it on one backend.
1210
+ */
1211
+ drawFlock(flock: GpuFlock, camera: Camera, timeSeconds: number, params: FlockParams, tint: Vec3, windX?: number, windZ?: number): void;
1212
+ private readonly boltLayout;
1213
+ /** A slot per batch, for the reason `boltPass.ts`'s layout gives. */
1214
+ private readonly boltVerts;
1215
+ private readonly boltFrags;
1216
+ private readonly boltVertStaging;
1217
+ private readonly boltVertFloats;
1218
+ /** The same bytes as an `i32` view, which is what a ring slot is copied through. */
1219
+ private readonly boltVertBlock;
1220
+ private readonly boltFragStaging;
1221
+ private readonly boltFragFloats;
1222
+ private readonly boltFragInts;
1223
+ private readonly boltBindGroup;
1224
+ /** Said once rather than every frame, for the reason `warnedFull` gives. */
1225
+ private warnedBoltsFull;
1226
+ createBolts(segmentCapacity: number, label?: string): GpuBolts;
1227
+ disposeBolts(batch: GpuBolts): void;
1228
+ /**
1229
+ * Draw a pool of arcs.
1230
+ *
1231
+ * The per-frame streams are expanded by `expandBoltSegments` and written whole: three writes
1232
+ * of the live prefix rather than one per segment, into staging allocated with the batch.
1233
+ */
1234
+ drawBolts(batch: GpuBolts, data: BoltSegments, camera: Camera, env: Environment, timeSeconds: number, core: Vec3, edge: Vec3, widthM: number, coreGain: number, minWidthPerMetre?: number): void;
1235
+ private readonly lineLayout;
1236
+ /** One slot per draw, not one block like `boltBindGroup` — see `linePass.ts`'s own comment. */
1237
+ private readonly lineVerts;
1238
+ private readonly lineFrags;
1239
+ private readonly lineBindGroup;
1240
+ /** Said once rather than every frame, for the reason `warnedFull` gives. */
1241
+ private warnedLinesFull;
1242
+ /** Build a batch for a polyline. `capacity` is in segments, not points. See `boltPass.ts`'s
1243
+ * `createBolts` for the shape; a line's batch carries two dynamic streams instead of three. */
1244
+ createLines(segmentCapacity: number, label?: string): GpuLines;
1245
+ disposeLines(lines: GpuLines): void;
1246
+ /**
1247
+ * Draw a polyline.
1248
+ *
1249
+ * The per-frame streams are expanded by `expandLineSegments` and written whole, the same
1250
+ * shape `drawBolts` uses for its own three. `uModel` is the one field `LINE_VERT` carries
1251
+ * that `BOLT_VERT` does not — see `linePass.ts`'s own comment for why: an arc has no model
1252
+ * transform and a line, usually attached to something that moves, does.
1253
+ *
1254
+ * Takes a slot from `lineVerts`/`lineFrags` rather than writing a single shared block —
1255
+ * `MAX_OVERLAYS` draws a frame, the same ceiling `drawSdfText` uses, because a caller drawing
1256
+ * more than one polyline a frame (several trails, a waveform split into pieces) is the normal
1257
+ * case here rather than the exception a single bolt pool is.
1258
+ *
1259
+ * **The uniforms are ringed; `lines.fromBuffer`/`.toBuffer` are not.** See `createLines`'s own
1260
+ * comment for the contract that protects them instead: this handle's geometry must not change
1261
+ * between two draws of it inside one frame, because these two buffers get exactly one write
1262
+ * each here, and a second write before the frame's command buffer submits silently replaces
1263
+ * the first for every draw already recorded against it.
1264
+ */
1265
+ drawLines(lines: GpuLines, data: LineSegments, model: ReadonlyMat4, camera: Camera, env: Environment, color: Vec3, widthM: number, opacity: number, softness?: number, minWidthPerMetre?: number, additive?: boolean): void;
1266
+ /** Built on first use per material, because the three fragment blocks are different sizes. */
1267
+ private readonly particleLayoutByMaterial;
1268
+ private readonly plumeLayout;
1269
+ /** One slot per batch, because `sizePulse` and `windResponse` belong to the batch. */
1270
+ private readonly plumeVerts;
1271
+ private readonly plumeFrags;
1272
+ private readonly plumeBindGroup;
1273
+ private readonly waterLayout;
1274
+ /** The one-pixel stand-in, kept so the water bind group can be rebuilt without one. */
1275
+ private readonly blankView;
1276
+ private readonly blankSampler;
1277
+ /**
1278
+ * A slot per body, not one buffer rewritten before each draw.
1279
+ *
1280
+ * **A frame draws more than one body of water.** A ditch at its own level, a basin in a
1281
+ * courtyard and the sea behind them are three draws off one pass, and from a single buffer the
1282
+ * last write won all three: every body then wore the last one's extents, level, colours and
1283
+ * direction. `UniformRing`'s header describes the hazard in general and `AGENTS.md` records it
1284
+ * on 2026-08-27 as a hard rule; water was the pass still holding it, and it was invisible for
1285
+ * as long as nothing drew two — the same reason a single character hid the joint palette's
1286
+ * copy of this for eight releases.
1287
+ */
1288
+ private readonly waterVerts;
1289
+ private readonly waterFrags;
1290
+ private readonly waterVertStaging;
1291
+ private readonly waterVertFloats;
1292
+ private readonly waterVertBlock;
1293
+ private readonly waterFragStaging;
1294
+ private readonly waterFragFloats;
1295
+ private readonly waterFragInts;
1296
+ /** Said once when a frame asks for more bodies than the ring holds. */
1297
+ private warnedWaterFull;
1298
+ /**
1299
+ * Rebuilt whenever the reflection target is, because a bind group holds the *view* and a
1300
+ * resized target is a new texture. Not readonly for that reason alone.
1301
+ */
1302
+ private waterBindGroup;
1303
+ /** Refilled per draw rather than allocated; see `createResolvedWater`. */
1304
+ private readonly resolvedWater;
1305
+ private readonly scatterLayout;
1306
+ /** One slot per batch: the gust and the trample field belong to the batch, not the frame. */
1307
+ private readonly scatterVerts;
1308
+ private readonly scatterFragUniforms;
1309
+ private readonly scatterFragStaging;
1310
+ private readonly scatterFragFloats;
1311
+ private readonly scatterFragInts;
1312
+ private readonly scatterBindGroup;
1313
+ private readonly scatterDepthLayout;
1314
+ /**
1315
+ * A slot per scatter draw, not one buffer rewritten before each of them.
1316
+ *
1317
+ * **The point light's bake is what makes this load-bearing.** Six cube faces are recorded on
1318
+ * one encoder and submitted once, so a `queue.writeBuffer` between two of them lands ahead of
1319
+ * all six — every scatter batch in every face then draws under the *last* face's
1320
+ * `uLightViewProj`. Five sixths of a field of grass therefore went into the octahedral map at
1321
+ * directions the grass does not occupy, and what the shader read back out of them is an
1322
+ * occluder standing beside a lamp with nothing above it. The reported symptom was a
1323
+ * hard-edged patch of shadow near a brazier, on this backend only, gone the moment lamp
1324
+ * shadows were switched off — the other backend sets a uniform and draws, in that order,
1325
+ * so it never had it.
1326
+ *
1327
+ * `UniformRing`'s own header describes this failure in general and `scatterVerts` already
1328
+ * carried the fix for the *visible* scatter pass; only the depth half was left behind.
1329
+ */
1330
+ private readonly scatterDepthDraws;
1331
+ private readonly scatterDepthStaging;
1332
+ private readonly scatterDepthFloats;
1333
+ /** The same bytes as an integer view, so a whole block copies into a slot bit for bit. */
1334
+ private readonly scatterDepthBlock;
1335
+ private readonly scatterDepthBindGroup;
1336
+ /** Refilled per draw rather than allocated; see `createScatterDeform`. */
1337
+ private readonly deform;
1338
+ /**
1339
+ * The frame's wind, as the mesh and depth programs read it.
1340
+ *
1341
+ * A second target from `deform` above and one conversion between them, for the reason the
1342
+ * WebGL2 backend gives at the same field: the scatter batch takes its wind as arguments to its
1343
+ * own draw call and cannot share a target without that signature changing, but both go through
1344
+ * `resolveScatterDeform`, which is where every number is actually decided.
1345
+ */
1346
+ private readonly frameWind;
1347
+ /**
1348
+ * The frame's wind, sampled once by the caller and handed down.
1349
+ *
1350
+ * Every mesh carrying a per-vertex channel bends to this, in the colour pass and in the depth
1351
+ * pass, so a canopy and its own shadow move together. A caller that never calls it gets a still
1352
+ * world, which is what every scene drew before the channel existed.
1353
+ */
1354
+ setWind(windX: number, windZ: number, windGust: number, timeSeconds: number): void;
1355
+ /**
1356
+ * The five wind fields of one shadow-draw slot.
1357
+ *
1358
+ * The rigid and skinned depth variants declare these; the instanced one does not, and nothing
1359
+ * writes this for it. A canopy that bends in the picture and stands still in the shadow map
1360
+ * slides its shade off the ground, which is the defect scatter.ts records having fixed between
1361
+ * its own two programs.
1362
+ */
1363
+ private writeShadowWind;
1364
+ /**
1365
+ * The five wind fields of one per-draw slot.
1366
+ *
1367
+ * **Never called for an instanced draw.** That variant declares none of these, and
1368
+ * `flatPass.ts` records what writing a field a variant does not declare costs: the block is
1369
+ * shorter, so the write lands on whatever occupies that offset instead.
1370
+ */
1371
+ private writeWind;
1372
+ /** Refilled per frame rather than allocated; see `resolvePointLights`. */
1373
+ private readonly lights;
1374
+ /** Refilled per frame rather than allocated; see `resolveAtmosphere`. */
1375
+ private readonly medium;
1376
+ private readonly panelLayout;
1377
+ private readonly panelCorners;
1378
+ private readonly panelVerts;
1379
+ private readonly panelFrags;
1380
+ private readonly panelBindGroup;
1381
+ private readonly textLayout;
1382
+ private readonly textCube;
1383
+ private readonly textVerts;
1384
+ private readonly textFrags;
1385
+ private readonly textBindGroup;
1386
+ /**
1387
+ * SDF text, world geometry rather than an overlay — see `drawSdfText`.
1388
+ *
1389
+ * **A bind group per atlas, cached, matching `flatBindGroups`' own reasoning.** A scene
1390
+ * reuses one font's atlas across every label built from it, so building a fresh bind group
1391
+ * per draw would be exactly the per-frame allocation `AGENTS.md` forbids for the common case
1392
+ * of two labels sharing a font.
1393
+ */
1394
+ private readonly sdfTextLayout;
1395
+ private readonly sdfTextVerts;
1396
+ private readonly sdfTextFrags;
1397
+ private readonly sdfTextBindGroups;
1398
+ private readonly shadowMap;
1399
+ private readonly shadowView;
1400
+ private readonly shadowLayout;
1401
+ /** The same layout plus the joint palette. See `createDepthBindGroupLayout`. */
1402
+ private readonly shadowSkinnedLayout;
1403
+ /**
1404
+ * The skinned shadow bind groups, one per palette slot, cached against what can invalidate them.
1405
+ *
1406
+ * The same argument `flatBindGroupFor` makes, and per slot for the same reason: the group it was
1407
+ * built beside, and the palette view, which now differs between two characters casting into one
1408
+ * bake. Comparing object identity makes a missed invalidation impossible rather than unlikely.
1409
+ */
1410
+ private readonly shadowSkinnedGroups;
1411
+ private readonly shadowSkinnedGroupsBeside;
1412
+ private readonly shadowSkinnedGroupsPalette;
1413
+ /** Held from construction so the skinned group can be rebuilt without them being threaded. */
1414
+ private shadowBlankView;
1415
+ private shadowBlankSampler;
1416
+ private shadowPassUniforms;
1417
+ private readonly shadowBindGroup;
1418
+ /**
1419
+ * The second static layer, or null where the profile asks for one depth layer.
1420
+ *
1421
+ * Null on the same condition `renderer.ts` builds its `peeledShadowMap` on — one decision,
1422
+ * read twice — so a profile that switches peeling off gets no map and no pass on either
1423
+ * backend rather than a map nothing writes.
1424
+ */
1425
+ private readonly peelMap;
1426
+ private readonly peelView;
1427
+ /**
1428
+ * The movers' own layer: the character, the rope it swings on, and anything else that walks.
1429
+ *
1430
+ * **Its own map rather than more geometry in the static one**, which is the decision
1431
+ * `renderer.ts` made and states: where a moving and a static caster overlap, two independent
1432
+ * layers keep each one's own distance fade and multiply their transmissions, where a single
1433
+ * depth buffer would keep whichever is nearer and throw the other away. It is also what lets
1434
+ * the static layer be drawn from geometry that does not change while this one is redrawn per
1435
+ * frame.
1436
+ *
1437
+ * Null on the same condition `renderer.ts` builds its `dynamicShadowMap` on — directional
1438
+ * shadows at all — so one decision is read twice rather than restated.
1439
+ *
1440
+ * **This backend had no such map and `beginShadowPass('dynamic')` returned false**, so every
1441
+ * mover's shadow was silently absent while every static one was drawn. Reported from a consumer
1442
+ * exactly that way: *"sun shadow works for other than the character itself"*. It degraded
1443
+ * honestly — `uDynamicShadowMap` resolved to the white stand-in, which reads as the far plane
1444
+ * and therefore as lit — so nothing looked broken, only empty, which is why it survived the
1445
+ * port and every gate in it.
1446
+ */
1447
+ private readonly dynamicMap;
1448
+ private readonly dynamicView;
1449
+ /** The peel's own bind groups: `uPeelShadowLayer` at 1, and the static map to peel against. */
1450
+ private readonly shadowPeelBindGroup;
1451
+ private readonly scatterDepthPeelBindGroup;
1452
+ /** Which layer the open pass is writing, so the sink picks the bindings that match it. */
1453
+ private shadowLayerIsPeel;
1454
+ /**
1455
+ * Whether a peel pass has filled the map, which is what the frame may sample it on.
1456
+ *
1457
+ * **Not whether the map exists.** `renderer.ts` can ask that question because a GL texture is
1458
+ * born cleared; a WebGPU texture no pass has written holds undefined contents, so a scene
1459
+ * that builds a peel map and never opens the layer would sample garbage. Cleared when the
1460
+ * static layer opens and set when a peel pass ends, which is per frame because both demo
1461
+ * scenes that peel do so every frame.
1462
+ */
1463
+ private peelFilled;
1464
+ /** One slot per caster, the same trick `perDraw` uses and for the same reason. */
1465
+ private readonly shadowDraws;
1466
+ private shadowEncoder;
1467
+ private shadowPass;
1468
+ /**
1469
+ * Which faces the depth pass culls, because the two shadow passes want different answers.
1470
+ *
1471
+ * The directional map culls `back`, matching the frame. A cubemap face culls nothing, and
1472
+ * `pointShadowMap.ts` gives the reason: back faces only stores the far side of every caster,
1473
+ * and for a character's limbs the two sides are centimetres apart, so the caster effectively
1474
+ * vanishes from the map. It is on the key as well as on the pipeline, or the second pass
1475
+ * would get the first one's.
1476
+ */
1477
+ private depthCullMode;
1478
+ /**
1479
+ * The light's matrix under `SHADOW_CLIP_CORRECTION`, once per pass rather than once per
1480
+ * caster. Depth remapped to [0, 1]; **Y deliberately not flipped** — see that constant for
1481
+ * the texel-for-texel comparison that settled it.
1482
+ */
1483
+ private readonly correctedLightViewProj;
1484
+ /**
1485
+ * The same matrix *uncorrected*, which is what the flat shader must project with.
1486
+ *
1487
+ * **The two differ on purpose and getting it wrong mirrors every shadow**, which is not a
1488
+ * figure of speech: it happened, and the two maps were measured as exact mirrors. The lookup
1489
+ * in `flat.ts` is `p = p * 0.5 + 0.5` on all three axes, the OpenGL convention, and it
1490
+ * expects `vLightPos` in GL clip space.
1491
+ *
1492
+ * The map is rendered with the depth half of the correction and not the Y half, and the
1493
+ * depth halves are what make the pair agree: the render stores 0.5·z + 0.5 and the lookup
1494
+ * computes 0.5·z + 0.5, so a receiver and its occluder are on one scale.
1495
+ */
1496
+ private readonly lightViewProj;
1497
+ /**
1498
+ * The bindings for the layer currently open — the peel's where one is, the static pass's
1499
+ * otherwise. A lookup rather than a rebind, because a `createBindGroup` per draw is the
1500
+ * per-frame allocation the house rules forbid; both groups are built once at init.
1501
+ */
1502
+ private shadowGroup;
1503
+ /**
1504
+ * The skinned shadow bind group for the current layer, rebuilt only when it must be.
1505
+ *
1506
+ * **The peel layer is not served here and casts rigidly**, deliberately: the peel exists so a
1507
+ * second independently fading occluder can be recorded, and a character is not one — it is the
1508
+ * thing in front. Serving it would mean a second cached group for a case nothing produces.
1509
+ */
1510
+ private shadowSkinnedGroup;
1511
+ private scatterDepthGroup;
1512
+ /**
1513
+ * Where a caster is drawn from, given to `drawShadowCasters` as the engine's own sink.
1514
+ *
1515
+ * Built once and held, rather than made per call: it closes over `this` and a fresh one
1516
+ * every frame would be an allocation in a per-frame path.
1517
+ */
1518
+ private readonly casterSink;
1519
+ /** Said once rather than every frame, because a full ring is a flood otherwise. */
1520
+ private warnedFull;
1521
+ /** The same, for the shadow round's scatter ring. See `scatterDepthDraws`. */
1522
+ private warnedScatterDepthFull;
1523
+ /** The density ceiling a governor has asked for, applied on the next resize. */
1524
+ private scale;
1525
+ /** The density ceiling in effect, matching the WebGL2 renderer's accessor. */
1526
+ get resolutionScale(): number;
1527
+ /**
1528
+ * Side of the directional shadow map, in texels.
1529
+ *
1530
+ * **A scene divides by this, so its absence is not an absence — it is a NaN.** Every caller
1531
+ * reaches `computeLightMatrix`, which computes `texelWorldSize = radius * 2 / shadowMapSize`
1532
+ * to snap the light frustum to the texel grid. `undefined` there makes the snap NaN, and the
1533
+ * NaN reaches all sixteen entries of the light matrix through `mat4.ortho`.
1534
+ *
1535
+ * That is what "the whole scene comes out uniformly occluded" was. Both halves of the shadow
1536
+ * path were poisoned by it and neither of them was wrong: the depth pass drew a caster whose
1537
+ * every vertex was NaN, so nothing rasterised and the map stayed at its clear value — read
1538
+ * back on hardware as 4,194,304 texels of exactly 1.0 — while the flat pass projected the
1539
+ * receiver with the same NaN, and `shadowFactor` compares with `<=`, which is false against
1540
+ * a NaN, so every fragment fell past the four early returns into the filter loop and out of
1541
+ * it as NaN.
1542
+ *
1543
+ * **It was diagnosed as the shadow *sample* returning a constant, and that was wrong.** The
1544
+ * evidence was that substituting the clip-corrected light matrix for the uncorrected one
1545
+ * changed not one pixel, which looked like proof the lookup ignored its projection. Both
1546
+ * matrices were NaN; a correction applied to a NaN is a NaN, so the test could not have
1547
+ * distinguished them.
1548
+ *
1549
+ * **The general hazard, which is worth more than this accessor.** A missing *method* on this
1550
+ * class throws — `renderer.createMesh is not a function` is how the backend announced itself
1551
+ * reaching a browser at all. A missing *property* returns `undefined`, and `undefined` in
1552
+ * arithmetic is a picture rather than an error. The rest of that group is below, and they
1553
+ * are grouped deliberately: the hazard is the class, not this one member of it.
1554
+ */
1555
+ get shadowMapSize(): number;
1556
+ /** Viewport in CSS pixels, which is what a screen-space overlay lays out against. */
1557
+ get cssWidth(): number;
1558
+ get cssHeight(): number;
1559
+ /** Drawing-buffer aspect, guarded against a zero-height canvas exactly as WebGL2 guards it. */
1560
+ get aspect(): number;
1561
+ /** The canvas being drawn into, for a consumer that needs to measure or place against it. */
1562
+ get canvas(): HTMLCanvasElement;
1563
+ /**
1564
+ * Whether the device has gone.
1565
+ *
1566
+ * Named for the WebGL concept because that is what the shared surface calls it, and the two
1567
+ * failures are the same event to a consumer: `device.lost` here, `webglcontextlost` there.
1568
+ * `device.ts` already absorbed the difference in shape.
1569
+ */
1570
+ get contextLost(): boolean;
1571
+ /**
1572
+ * Whether a part this backend recognises as weak had its pixel terms lowered at construction.
1573
+ *
1574
+ * **This used to be a hardcoded `false`**, because `rendererName` was the literal string
1575
+ * `WebGPU` and `isWeakGpuFamily` had nothing to match — so the clamp that rescued an
1576
+ * Adreno 619 on WebGL2 was dead on the backend that replaced it, and `capabilityClamp: true`
1577
+ * from a consumer silently did nothing. `select.ts` now builds the name from `adapter.info`,
1578
+ * so the same predicate over the same table answers on both backends.
1579
+ *
1580
+ * Only the pixel terms move, exactly as in `renderer.ts`: the shadow and water switches are
1581
+ * what a preset *means*, and drawing a different scene than the settings screen describes is
1582
+ * worse than drawing the right one at fewer pixels.
1583
+ */
1584
+ readonly capabilityClamped: boolean;
1585
+ /** Zero until row 3 ports the point-shadow cube maps, and zero is what WebGL2 reports then too. */
1586
+ get sampledShadowLights(): number;
1587
+ /**
1588
+ * The full budget, always, and that is a fact about the backend rather than a stub.
1589
+ *
1590
+ * WebGL2 sizes these arrays to what the part's fragment uniform grid will hold — see
1591
+ * `uniformVectorBudget.ts` — because a shader declaring more rows than the device offers does
1592
+ * not link. WebGPU has no such ceiling to be short of: the lit block is about 7 KB against a
1593
+ * guaranteed 64 KB binding size, and the generated WGSL is built and committed at the full
1594
+ * budget either way. So a consumer who lowered `maxLights` for a phone's WebGL2 path and lands
1595
+ * on WebGPU gets all sixteen, which is the better picture and one this device can afford.
1596
+ */
1597
+ get shadedLights(): number;
1598
+ /** All four, for the reason `shadedLights` gives. */
1599
+ get shadedAreaLights(): number;
1600
+ /**
1601
+ * **True from the first presented frame, which is weaker than what WebGL2 means by it.**
1602
+ *
1603
+ * There it is a fence: `clientWaitSync` on the first frame's commands, so it turns true when
1604
+ * the driver has actually finished the first frame's compiles and uploads, which is what a
1605
+ * loading screen wants to wait for. The equivalent here is `onSubmittedWorkDone`, and wiring
1606
+ * it is row 24's business alongside the timestamp queries.
1607
+ *
1608
+ * Reported as "a frame has been presented" until then. A consumer polling this gets a true
1609
+ * that is early rather than one that never arrives, and early is the failure that shows a
1610
+ * loading screen dismissed a little too soon rather than one that hangs forever.
1611
+ */
1612
+ get firstFrameSettled(): boolean;
1613
+ private presentedAnyFrame;
1614
+ /** Whether the loss below has already been said. Said once, never once per frame. */
1615
+ private reportedLost;
1616
+ /**
1617
+ * Say, exactly once, that this renderer is drawing nothing and why.
1618
+ *
1619
+ * **A lost device is silent here, and that silence is the defect.** Every entry point guards
1620
+ * on `surface.lost` and returns, so a frame loop goes on calling forty of them at full rate
1621
+ * and produces a black canvas with no error, no exception and no warning. A host that is not
1622
+ * already listening to `onContextLost` has no way to tell that apart from a rendering bug —
1623
+ * which is what happened: a demos page showed a black frame indefinitely and reported it as a
1624
+ * broken renderer.
1625
+ *
1626
+ * WebGPU cannot restore a device, only replace one, so the recovery is a consumer's to
1627
+ * perform. This makes sure the consumer at least knows there is something to recover from,
1628
+ * and names the call that does it.
1629
+ */
1630
+ private reportLostOnce;
1631
+ /**
1632
+ * The colour this frame clears to, held until the frame's pass is actually opened.
1633
+ *
1634
+ * A preallocated tuple rather than the caller's, because `beginFrame` runs every frame and
1635
+ * keeping a reference to a consumer's array would let them mutate it under us.
1636
+ */
1637
+ private readonly pendingClearColor;
1638
+ /** What `beginPlanarReflection` was asked to clear the mirror to, held until it opens. */
1639
+ private readonly mirrorClearColor;
1640
+ /** Whether the frame's pass still owes its clear. False from the first open onwards. */
1641
+ constructor(surface: GpuSurface, quality?: Readonly<RenderQuality>, rendererName?: string);
1642
+ /**
1643
+ * The height the medium is resolved at, which is the *viewer's* and not the camera's.
1644
+ *
1645
+ * They are the same except inside the mirror pass, where the camera stands as far below the
1646
+ * water as the viewer stands above it. Resolving fog from that puts the whole reflection
1647
+ * underwater whenever the viewer is not, and the result is a pool that reads as filled with
1648
+ * the wrong medium rather than as a camera in the wrong place. `renderer.ts` keeps
1649
+ * `reflectionAtmosphereY` for exactly this and passes it to four binders.
1650
+ */
1651
+ private atmosphereHeight;
1652
+ /**
1653
+ * One array of the block, at the stride the generator declared.
1654
+ *
1655
+ * Element by element rather than `set`, for the reason `scatterInto` gives: an array element
1656
+ * in one of these blocks has its own slot, and a straight fill would lay ten answers across
1657
+ * the first two and a half.
1658
+ */
1659
+ private writeShadowParams;
1660
+ /** The index and the weight of one selection array, which are always written together. */
1661
+ private writeShadowSelection;
1662
+ /**
1663
+ * Say that no light in one selection array owns a cubemap.
1664
+ *
1665
+ * Written per element at the block's declared stride rather than filled, for the reason
1666
+ * `scatterInto` gives: an array element in one of these blocks has its own sixteen-byte slot
1667
+ * and a straight fill would lay ten answers across the first two and a half.
1668
+ */
1669
+ private clearShadowSelection;
1670
+ /** Which optional attributes a mesh supplied, recovered from its buffers for the depth pass. */
1671
+ private presentOf;
1672
+ /**
1673
+ * Open the frame and clear to the sky.
1674
+ *
1675
+ * **Every entry point returns early on a lost device rather than throwing.** A consumer's
1676
+ * `requestAnimationFrame` loop keeps calling after a driver reset, and `AGENTS.md`'s rule
1677
+ * that the frame loop never throws is what stops that turning one GPU fault into sixty
1678
+ * uncaught exceptions a second in somebody's error reporting.
1679
+ */
1680
+ beginFrame(clearColor: Vec3): void;
1681
+ /**
1682
+ * The depth buffer, rebuilt only when the drawing buffer changes size.
1683
+ *
1684
+ * WebGPU has no depth attachment of its own the way a default framebuffer does, so one is
1685
+ * owned here. Rebuilding it every frame would allocate in the frame loop; rebuilding it
1686
+ * never would mean a resized canvas draws against a depth buffer of the old size, which is
1687
+ * a validation error rather than a wrong picture.
1688
+ */
1689
+ private ensureDepth;
1690
+ /**
1691
+ * Upload the frame's geometry, build one mesh's buffers, and hand back a handle.
1692
+ *
1693
+ * Construction time, never per frame: `MeshData` arrays are the caller's and are uploaded
1694
+ * once. The pipeline is built here too, for the same reason — which attributes a mesh
1695
+ * supplied decides the vertex layout, so it is known now and never recomputed.
1696
+ */
1697
+ /**
1698
+ * Compile every pipeline this mesh can be drawn with, and say which key it draws under.
1699
+ *
1700
+ * Shared by the two `createMesh` entry points, because warming is decided by the *data* and
1701
+ * neither the pipeline set nor the key depends on whether the geometry lands in one call or
1702
+ * over six frames.
1703
+ */
1704
+ private warmFlatPipelines;
1705
+ /**
1706
+ * The pipeline key stapled to a mesh, on the mesh itself.
1707
+ *
1708
+ * **Not a spread**, which is what this was. `{ ...mesh, key }` copies the value a getter held
1709
+ * at that instant, so `complete` would have been frozen at whatever it was when the mesh was
1710
+ * made — false, for every incremental mesh, for ever, and nothing spread would ever have been
1711
+ * drawn. It also handed the caller a different object from the one the upload was filling.
1712
+ */
1713
+ private keyed;
1714
+ createMesh(data: MeshData, options?: MeshOptions): GpuMesh;
1715
+ /**
1716
+ * Geometry in, handle out — but the geometry lands over as many frames as the caller gives it.
1717
+ *
1718
+ * See `RendererApi.createMeshIncremental` for what this is for and what a caller may do with a
1719
+ * mesh that has not finished arriving. The iterator here adds the one thing the buffer code
1720
+ * cannot know about: **a lost device ends the upload.** An upload that spans frames can
1721
+ * straddle one, and going on writing to a dead device would raise validation errors a consumer
1722
+ * can do nothing about. Done with `complete` still false is the signal that it was abandoned.
1723
+ */
1724
+ createMeshIncremental(data: MeshData, options?: MeshOptions): IncrementalMeshHandle;
1725
+ /**
1726
+ * Rewrite a mesh's positions, and its normals where the caller has them.
1727
+ *
1728
+ * Refused in words on a mesh that did not declare itself dynamic, because the thing that makes
1729
+ * an update possible on this backend — the interleaved array the vertex data was built from —
1730
+ * is only kept when a consumer asked for it. See `GpuMesh.update`.
1731
+ */
1732
+ updateMesh(mesh: GpuMesh, positions: Float32Array, normals?: Float32Array): void;
1733
+ /**
1734
+ * Settle everything the frame shares: the camera, the lights and the fog.
1735
+ *
1736
+ * Written into the fragment block at the offsets the generator computed, which is the one
1737
+ * place these numbers are stated. The vertex block's `uViewProj` is copied into every
1738
+ * draw's slot instead, because a dynamic-offset binding cannot mix per-frame and per-draw
1739
+ * data in one buffer.
1740
+ */
1741
+ bindMeshPass(camera: Camera, env: Environment): void;
1742
+ /**
1743
+ * The slot the next draw's material lives in, taking a new one when something dirtied it.
1744
+ *
1745
+ * **A copy per material change, not per draw.** A scene drawing a hundred meshes of one
1746
+ * material spends one slot and one copy; a scene switching material every draw spends one
1747
+ * each, which is what it asked for.
1748
+ */
1749
+ private materialSlotForDraw;
1750
+ /** Said once rather than every frame, for the reason `warnedFull` gives. */
1751
+ private warnedMaterialsFull;
1752
+ /**
1753
+ * Draw one mesh.
1754
+ *
1755
+ * Takes a slot from the ring for its own model matrix and tint, so that draws do not
1756
+ * overwrite each other's uniforms — see `UniformRing` for why a single buffer cannot work.
1757
+ */
1758
+ /**
1759
+ * `depthLayer` reaches a pipeline here, where it used to be accepted and dropped.
1760
+ *
1761
+ * It was `_depthLayer` for as long as this backend has existed: the parameter was on the
1762
+ * interface, the other backend honoured it, and a consumer that declared which of two fused
1763
+ * surfaces should win got the answer on WebGL2 and a per-pixel coin toss here — with nothing
1764
+ * failing and nothing in the parity ledger saying so.
1765
+ */
1766
+ drawMesh(mesh: GpuMesh, model: ArrayLike<number>, depthLayer?: number, tint?: Vec3 | null): void;
1767
+ /**
1768
+ * Draw a mesh that lets the world through, at `opacity`.
1769
+ *
1770
+ * Its own pipeline rather than a blend toggle, because WebGPU bakes blend state into one —
1771
+ * see `flatPipeline`. `uOpacity` is material state like every other term the shader reads per
1772
+ * draw, so it takes a material slot and is put back afterwards; `renderer.ts` restores it to
1773
+ * 1 for the same reason.
1774
+ *
1775
+ * `options` turns lighting and fog off independently, without a second pipeline: see
1776
+ * `TranslucentMeshOptions` and `submitMesh` for how each rides the same material slot
1777
+ * `uOpacity` already uses. Both default to `true`, so this reads identically for every call
1778
+ * site that predates the parameter.
1779
+ */
1780
+ /**
1781
+ * Mark whatever the depth buffer holds inside a projector's box.
1782
+ *
1783
+ * The WebGL2 renderer's own `drawDecal` carries what this is for and what it costs; the two
1784
+ * record the same thing and draw it in the same place in the frame. Silently nothing where the
1785
+ * profile has no screen effects, since there is then no resolved depth to read.
1786
+ */
1787
+ drawDecal(projector: DecalProjector): void;
1788
+ /**
1789
+ * Reflect what the frame drew, in the surfaces inside a box.
1790
+ *
1791
+ * The WebGL2 renderer's own `drawReflection` carries what this is for and what it cannot do; the
1792
+ * two record the same thing and trace it in the same place in the frame.
1793
+ */
1794
+ drawReflection(surface: ReflectiveSurface): void;
1795
+ /** Said once, not per frame, where this renderer cannot read the picture a march samples. */
1796
+ private refuseReflections;
1797
+ /**
1798
+ * Said once, not per frame, where this renderer cannot read the depth a mark is decided from.
1799
+ *
1800
+ * **Called from `endFrame` as well as from the pass, and that is not belt and braces.** A
1801
+ * profile with no screen effects never opens a composite at all, so `runDecals` is never reached
1802
+ * and the marks would be dropped in silence — while the other backend, whose decal block sits in
1803
+ * its own `endFrame`, says so. One backend explaining itself and the other not is the
1804
+ * disagreement the parity rule exists to prevent, and it is what the first run of
1805
+ * `scripts/decal-check.mjs` caught here.
1806
+ */
1807
+ private refuseDecals;
1808
+ /**
1809
+ * Said once, not per frame, where multisampling excludes the effect a profile asked for.
1810
+ *
1811
+ * **The comment above the decision has claimed since the effect shipped that it "says so once",
1812
+ * and for two releases nothing did.** Both are switches on a graphics screen, so a player
1813
+ * turning antialiasing on turns this off — and a quality setting that is enabled and does
1814
+ * nothing is the fault this engine has now shipped under three other names. `refuseDecals`
1815
+ * carries the argument for why the *other* backend saying it and this one not is worse than
1816
+ * neither saying it; the words are `OIT_MULTISAMPLE_REFUSAL`, which both backends read rather
1817
+ * than each keeping a copy.
1818
+ */
1819
+ private refuseOitMultisampled;
1820
+ drawTranslucentMesh(mesh: GpuMesh, model: ReadonlyMat4, opacity: number, options?: TranslucentMeshOptions): void;
1821
+ /**
1822
+ * @param blend Which of the two entry points above this came through, and **not** something to
1823
+ * infer from `opacity`.
1824
+ *
1825
+ * `outColor.a` is `uOpacity * coverage`, and `coverage` is the bound texture's own alpha. So a
1826
+ * caller passing an opacity of 1 to `drawTranslucentMesh` is not saying *opaque*, it is saying
1827
+ * *the shape is in the image*: a caption, a decal, a painted shadow, a glow. Reading that as
1828
+ * opaque takes the unblended pipeline, and then every texel the cutout kept is written at full
1829
+ * strength — the soft edge of a blurred shadow lands as a solid slab bounded by the cutout's
1830
+ * iso-contour. One consumer's cover visualiser drew its caption that way: a hard black outline
1831
+ * around every letter, and none in the same frame on `?backend=webgl2`.
1832
+ *
1833
+ * The other backend has no way to make this mistake, and that is the parity being kept rather
1834
+ * than a new rule: `renderer.ts` enables `BLEND` on the way into `drawTranslucentMesh` and
1835
+ * disables it on the way out, without ever consulting the number.
1836
+ *
1837
+ * @param options `drawMesh` passes only `depthLayer` and takes the `bindMeshPass` default of
1838
+ * lit and fogged for the rest. `uLightingEnabled`/`uFogEnabled` are material
1839
+ * state exactly like `uOpacity` above them — dirtied while they differ from the default and
1840
+ * restored unconditionally afterward, whether or not a material slot was available for this
1841
+ * particular draw to use them. Never a pipeline permutation.
1842
+ */
1843
+ private submitMesh;
1844
+ /** Release geometry. */
1845
+ disposeMesh(mesh: GpuMesh): void;
1846
+ /**
1847
+ * Attach per-instance placement to a mesh already on the device.
1848
+ *
1849
+ * One batch per mesh is not enforced here — this backend could hold several, since the
1850
+ * instance buffer is bound per draw rather than baked into a vertex array — but it is refused
1851
+ * anyway, because WebGL2 *cannot* and a capability one backend has and the other does not is
1852
+ * the shape this repository spent a week removing.
1853
+ */
1854
+ createInstanced(mesh: GpuMesh, capacity: number): GpuInstancedBatch;
1855
+ /** Which meshes already carry a batch, so a second is refused rather than silently shared. */
1856
+ private readonly instanced;
1857
+ /** Push placement and colour. Only the live prefix; see `GpuInstancedBatch.upload`. */
1858
+ uploadInstanced(batch: GpuInstancedBatch, data: MeshInstances): void;
1859
+ /** Draw every live instance, opaque. */
1860
+ drawInstanced(batch: GpuInstancedBatch, data: MeshInstances): void;
1861
+ /** Draw every live instance, blended. See `drawTranslucentMesh` for what the options mean. */
1862
+ drawTranslucentInstanced(batch: GpuInstancedBatch, data: MeshInstances, opacity: number, options?: TranslucentMeshOptions): void;
1863
+ /** Release the placement. The mesh is the caller's and is not released. */
1864
+ disposeInstanced(batch: GpuInstancedBatch): void;
1865
+ /**
1866
+ * One draw, one material slot, one uniform slot, however many instances.
1867
+ *
1868
+ * **The uniform slot carries only what the instanced block declares.** That variant has no
1869
+ * `uModel` and no `uTint` — they arrive as attributes — so this writes the four fields the two
1870
+ * variants share and nothing else. Their offsets are identical by construction and
1871
+ * `flatPass.ts` asserts it; writing `uModel`'s plain offset here would land in a part of the
1872
+ * slot the shader does not read, which is harmless and still wrong to do.
1873
+ *
1874
+ * **What it gives up** against `drawMesh` is skinning and morphing, refused where the variant
1875
+ * is built, and a per-instance opacity: `opacity` here dims the whole batch.
1876
+ */
1877
+ private submitInstanced;
1878
+ /** Upload a base mesh and size its per-instance buffers. See `createGpuScatter`. */
1879
+ createScatter(base: MeshData, data: InstanceData): GpuScatter;
1880
+ /**
1881
+ * Push placement to the device.
1882
+ *
1883
+ * Returns the batch so `createScatter` can upload in one expression. Only the live prefix
1884
+ * is written — `data.count` instances of a buffer sized for `capacity` — because the rest
1885
+ * is not drawn and uploading it would be the per-frame waste `uploadScatter` exists to
1886
+ * make visible.
1887
+ */
1888
+ uploadScatter(scatter: GpuScatter, data: InstanceData): GpuScatter;
1889
+ /** Release a batch. */
1890
+ disposeScatter(scatter: GpuScatter): void;
1891
+ /**
1892
+ * Draw every live instance of a batch, in one call.
1893
+ *
1894
+ * **Its own uniform buffers rather than the flat pass's**, because it is a different
1895
+ * program with a different block: the vertex stage here carries the wind and the trample
1896
+ * ring, and the fragment stage carries lighting and the medium and nothing else.
1897
+ */
1898
+ drawScatter(scatter: GpuScatter, data: InstanceData, camera: Camera, env: Environment, windX: number, windZ: number, windGust: number, timeSeconds: number, trample?: Float32Array | null): void;
1899
+ /**
1900
+ * Move the drawing-buffer density ceiling, as a governor does to hold a frame budget.
1901
+ *
1902
+ * Stored and applied on the next `resize`, which is where the WebGL2 path applies it too.
1903
+ */
1904
+ applyResolutionScale(scale: number): void;
1905
+ /**
1906
+ * Open the shadow pass: the world about to be drawn from the light's point of view.
1907
+ *
1908
+ * **Its own encoder, because a scene calls this before `beginFrame`.** The ordering is the
1909
+ * scene's — shadows are baked, then the frame is drawn against them — and this backend
1910
+ * follows it rather than reorganising it.
1911
+ *
1912
+ * All three layers, which is what the shader multiplies together. The `dynamic` one used to
1913
+ * return false here — see `dynamicMap` for what that cost and why nothing caught it.
1914
+ */
1915
+ beginShadowPass(lightViewProj: ReadonlyMat4, layer?: ShadowLayer): boolean;
1916
+ /** Submit everything that casts into the layer `beginShadowPass` opened. */
1917
+ /**
1918
+ * The same enumeration, replayed into the colour pass that is open.
1919
+ *
1920
+ * See `SceneCasters` in `shadowCasters.ts` for why this exists and why scatter is declined. The
1921
+ * sink is built once and held, because a caster enumeration runs per pass and a sink allocated
1922
+ * per call would allocate per pass.
1923
+ */
1924
+ private readonly sceneCasterSink;
1925
+ /**
1926
+ * The material the open replay has bound, or `undefined` before its first entry.
1927
+ *
1928
+ * `ShadowCasterSink`'s material is optional and omitting it means *no material* rather than
1929
+ * *unchanged*, so a run of entries sharing one material has no shorter spelling and the sink
1930
+ * bound once per entry. On this backend that is a cliff rather than a slope: `setMaterial`
1931
+ * dirties `materialSlot`, so a bind per entry is a **slot per draw** out of
1932
+ * `MAX_MATERIALS_PER_FRAME` — and past the ring `materialSlotForDraw` returns null and the draw
1933
+ * is skipped, which is the world that appears and disappears that constant's own comment
1934
+ * describes. A consumer measured 82 materials a model over two to four draws each, and a
1935
+ * reflection replaying the frame shares the ring with the pass that recorded it.
1936
+ *
1937
+ * **Reference identity, not a deep compare.** A caster list holds the material objects its draws
1938
+ * bound, so a run from one surface is literally one object; two structurally equal materials from
1939
+ * different surfaces are rare and cost exactly what they cost today.
1940
+ *
1941
+ * `undefined` rather than `null` for "nothing bound yet", because `null` is a material an entry
1942
+ * can legitimately ask for — it is what an entry carrying no material means.
1943
+ */
1944
+ private sceneCasterMaterial;
1945
+ /** Bind one replay entry's material, unless it is the one already standing. */
1946
+ private bindSceneCasterMaterial;
1947
+ /**
1948
+ * Draw everything a caster enumeration contains into the open colour pass, with its materials.
1949
+ *
1950
+ * The colour counterpart of `drawShadowCasters`, so one closure answers "what is in this frame"
1951
+ * for the shadow cascade, every cubemap face, and a second viewpoint such as a mirror or a probe
1952
+ * face. What it replaces is a consumer re-entering its whole draw path with a second camera.
1953
+ */
1954
+ drawSceneCasters(casters: ShadowCasters): void;
1955
+ drawShadowCasters(casters: ShadowCasters): void;
1956
+ /** Close the shadow pass and submit it, so the frame can sample what it wrote. */
1957
+ endShadowPass(): void;
1958
+ /**
1959
+ * The pool, or null where the profile switched cube shadows off.
1960
+ *
1961
+ * `PointShadowSystem` over `GpuPointShadowMap`: the pooling, the staleness and the crossfade
1962
+ * are the same code the WebGL2 path runs, and only the cubemap is this backend's. See
1963
+ * `PointShadowSource`.
1964
+ */
1965
+ private readonly pointShadows;
1966
+ /** A world's casting rectangles, on layers above the pool in the same array. See `renderer.ts`. */
1967
+ private readonly areaShadows;
1968
+ private readonly resolvedAreaShadows;
1969
+ private readonly areaBakeScratch;
1970
+ private warnedAreaShadowRange;
1971
+ private warnedAreaShadowsOff;
1972
+ private warnedAreaShadowLayers;
1973
+ /** How many point lights the array was last sized for; see the other backend's copy. */
1974
+ private preparedPointLightCount;
1975
+ /** See `renderer.ts`: built when a world reports its lights, and rebuilt only to grow. */
1976
+ private pointShadowArray;
1977
+ private readonly bakeScratch;
1978
+ /** Said once per renderer; see `updatePointShadows`. */
1979
+ private warnedNoPointShadowArray;
1980
+ /** Said once, for the retired ambient dial. */
1981
+ private warnedEnvironmentAmbient;
1982
+ private readonly castingWorldIndices;
1983
+ private readonly resolvedPointShadows;
1984
+ /** One face's view-projection, rebuilt per face rather than allocated. */
1985
+ private readonly faceViewProj;
1986
+ /** The corrected copy the vertex stage is handed. See `SHADOW_CLIP_CORRECTION`. */
1987
+ private readonly correctedFaceViewProj;
1988
+ /** Size the pool once the world reports its light count. Nothing is baked here. */
1989
+ prepareStaticPointShadows(lights: readonly ShadowLight[],
1990
+ /** The world's rectangles, so the casting ones get layers. See `renderer.ts`. */
1991
+ areaLights?: readonly AreaLightSource[]): void;
1992
+ /** Say once that a rectangle asked to cast and named no range. See `renderer.ts`. */
1993
+ private warnAreaShadowRange;
1994
+ /**
1995
+ * Say once that a rectangle wants layers the array was not sized for.
1996
+ *
1997
+ * **This is the area-light shape of the failure `updatePointShadows` warns about above**, and it
1998
+ * is just as quiet: `prepareStaticPointShadows` sizes the array — `texStorage3D` and
1999
+ * `createTexture` are both immutable — so a consumer that passes its rectangles here and not
2000
+ * there gets an array with no room above the pool. Every declaration is then honoured except the
2001
+ * storage, the set holds no maps, every layer publishes -1, and the frame is indistinguishable
2002
+ * from one whose rectangles declined to cast.
2003
+ */
2004
+ private warnAreaShadowLayers;
2005
+ /** Say once that a rectangle asked to cast while the array it would read is off. */
2006
+ private warnAreaShadowsOff;
2007
+ /**
2008
+ * Refresh the cubemaps this frame's budget allows, and settle what the shader will sample.
2009
+ *
2010
+ * Every decision here is shared: `selectCastingLights` keeps a light that declined to cast
2011
+ * out of the pool, `sync` hands out slots, `updateLiveSelection` runs the crossfade, and
2012
+ * `runPointShadowBakes` spends the face budget in the order a measured frame chose. What is
2013
+ * this backend's is one callback — a render pass per face — and the bind group.
2014
+ */
2015
+ updatePointShadows(lights: readonly ShadowLight[], activeWorldIndices: Int32Array, activeCount: number, liveX: number, liveY: number, liveZ: number, frameDt: number, staticCasters: ShadowCasters, dynamicCasters: ShadowCasters, warmWorldIndices?: Int32Array, warmCount?: number,
2016
+ /** The rectangles this frame shades, in `selectAreaLights` order. See `renderer.ts`. */
2017
+ areaLights?: readonly AreaLightSource[]): void;
2018
+ /**
2019
+ * The rectangles' round, inside the shadow encoder this backend already opened.
2020
+ *
2021
+ * Every decision — which layers, when they re-bake, what the shader is told — is
2022
+ * `AreaShadowSet`'s and is shared with the other backend. What is this one's is the callback:
2023
+ * a render pass per face and a resolve into the layer.
2024
+ */
2025
+ private updateAreaShadows;
2026
+ /**
2027
+ * One cubemap face: a depth pass, the casters, done.
2028
+ *
2029
+ * **Corrected by `SHADOW_CLIP_CORRECTION`, not `CLIP_CORRECTION`.** This map is sampled and
2030
+ * never presented, so it wants the depth remap without the Y flip, exactly as the directional
2031
+ * map does — see that constant for the texel-for-texel comparison that settled it. The
2032
+ * lookup is `flat.ts`'s `pointShadow`, which normalises a direction and reads the cube; a
2033
+ * flipped face would mirror every shadow about its own axis and still look like shadows.
2034
+ */
2035
+ private bakeFace;
2036
+ /**
2037
+ * One rendered face into its region of the light's octahedral layer.
2038
+ *
2039
+ * On the bake's own encoder, between two face passes, which is what lets one scratch texture
2040
+ * serve every light: the face is consumed before the next one is drawn. See
2041
+ * `pointShadowArray.ts` for why the regions are disjoint.
2042
+ */
2043
+ private resolveFace;
2044
+ private buildFlatBindGroup;
2045
+ /**
2046
+ * The bind group a draw uses, skinned or not.
2047
+ *
2048
+ * **The skinned twin is cached against the two things that can invalidate it**, rather than
2049
+ * against a flag somebody has to remember to clear at six call sites: the unskinned group it was
2050
+ * built beside — which is rebuilt whenever the material or a shadow slot moves — and the palette
2051
+ * view, which changes only when the texture is reallocated. Comparing object identity means a
2052
+ * missed invalidation is not possible rather than merely unlikely, and it is why
2053
+ * `SkinPaletteTexture.view()` caches: a fresh view every call never compares equal and would
2054
+ * rebuild this every draw.
2055
+ */
2056
+ private flatBindGroupFor;
2057
+ /** The layout matching a pair of vertex flags. A pipeline and its bind group must agree. */
2058
+ private flatLayoutFor;
2059
+ /**
2060
+ * Give a ring that ran out last frame the room to draw this one whole.
2061
+ *
2062
+ * **Here and nowhere else.** `flushOverlay` above has submitted everything outstanding and the
2063
+ * frame's encoder has not been replaced yet, so no command references the buffer about to be
2064
+ * destroyed and nothing has been written into the staging that discarding it would lose.
2065
+ * `UniformRing.allocate` still refuses to grow where it runs out, for the reason it gives: that
2066
+ * would stall the frame to make room.
2067
+ *
2068
+ * **Only the two rings that scale with the scene.** A frame that switches material 1,267 times
2069
+ * has 1,267 materials' worth of world in it and the honest answer is to draw them; a frame
2070
+ * asking for a ninth simultaneous flock has hit a ceiling that means something else, and those
2071
+ * keep skipping and reporting. The two here are also the two whose staleness has an answer
2072
+ * already written — every flat bind group is reachable from `flatBindGroups`, the blank, and
2073
+ * the skinned twins, and nothing else holds either buffer.
2074
+ *
2075
+ * **What it costs is one frame.** A scene that grows past a ceiling loses geometry for the frame
2076
+ * that discovers it and is whole from the next, where before it lost that geometry for as long
2077
+ * as the scene stayed big. What would make it wrong is a consumer whose frame legitimately
2078
+ * oscillates across a ceiling: the ring keeps the larger size, so that settles rather than
2079
+ * thrashing.
2080
+ */
2081
+ private growRings;
2082
+ /**
2083
+ * Every flat bind group, rebuilt against buffers that have just been replaced.
2084
+ *
2085
+ * **The blank is built with the maps forced to null rather than with whatever the last frame
2086
+ * left set**, which is the one thing here that is not obvious. `blankAlbedoBindGroup` is what a
2087
+ * pass starts from and it means *no maps at all*; `buildFlatBindGroup` reads the live material
2088
+ * fields through `flatTextures`, so building it at frame start with a normal map still held from
2089
+ * the last frame would produce a "blank" group that quietly carries one. The fields are put back
2090
+ * immediately, because they are the material a caller set and this is not a material change.
2091
+ *
2092
+ * The skinned twins invalidate themselves off `bindGroup`'s identity and are cleared anyway:
2093
+ * they hold `perDraw.buffer` directly, and a cache that is only *probably* invalid is the kind
2094
+ * that is wrong once.
2095
+ */
2096
+ private rebuildFlatGroupsForNewRings;
2097
+ /**
2098
+ * Rebuild the group for the open material, and drop every cached one.
2099
+ *
2100
+ * **The cache is keyed by albedo and every entry also holds the shadow cubemaps**, so when a
2101
+ * cubemap moves slot the entries are all stale in a way nothing about the albedo reveals. A
2102
+ * scene that switched back to an earlier texture would get a group pointing at the pool's
2103
+ * previous occupant — a shadow from another light, on the right surface, which reads as a
2104
+ * lighting bug rather than as a stale binding.
2105
+ */
2106
+ private rebuildFlatBindGroup;
2107
+ /**
2108
+ * The sky, as one triangle covering the screen with the world reconstructed per fragment.
2109
+ *
2110
+ * `sky.sunDir` and the rest go in at the offsets the generator computed, and **so does the
2111
+ * medium the camera is standing in**, which for a fortnight they did not.
2112
+ *
2113
+ * The block declares `uUnderwaterColor` and `uUnderwaterFactor` and this method never wrote
2114
+ * either, so both held the zero a `Float32Array` is born with for the life of the session.
2115
+ * Zero factor means "not submerged", so the shader's last line —
2116
+ * `col = mix(col, uUnderwaterColor * waterLight, uUnderwaterFactor)` — was a mix that never
2117
+ * moved: **a camera under the sea drew the sunset it would have seen from above it**, sun,
2118
+ * stars, clouds and all, while every other pass in the frame correctly went green. The
2119
+ * comment that stood here called the atmosphere uniforms a later task's, which is how a
2120
+ * missing bind reads as a scheduled one.
2121
+ *
2122
+ * `resolveAtmosphere` through `atmosphereHeight`, matching `renderer.ts`'s `bindAtmosphere`
2123
+ * call in its own `drawSky` exactly — including the mirror's eye height, or a reflection
2124
+ * renders its sky from the camera's side of the surface.
2125
+ */
2126
+ drawSky(camera: Camera, sky: SkyColors, env: Environment): void;
2127
+ /** Close the pass and present. A no-op when no frame was begun. */
2128
+ /**
2129
+ * The scene from its off-screen target onto the canvas, with everything that happens on the way.
2130
+ *
2131
+ * One pass does the lot, because every effect here reads the same resolved image: the speed
2132
+ * blur, the camera motion blur, the occlusion it multiplies in, the bloom it adds and the tone
2133
+ * curve it ends on. The stages before it exist only to produce the two textures that pass
2134
+ * consumes.
2135
+ *
2136
+ * `sceneTarget.ts` is the same order on the other backend, including *why* the grade lands
2137
+ * here rather than in the mesh pass: with a target, the resolve is the last pass, and grading
2138
+ * twice is a curve applied to its own output.
2139
+ */
2140
+ private composite;
2141
+ /**
2142
+ * The composite's bind group, rebuilt from whatever views exist now.
2143
+ *
2144
+ * **Extracted from `ensureComposite` because a colour grade also invalidates it.** A bind group
2145
+ * holds the *views* it was built with, so a resized target and a replaced lookup table are the
2146
+ * same problem — and having two places build one group is how the two would come to disagree
2147
+ * about which bindings it carries.
2148
+ *
2149
+ * A null scene view means the composite has not been sized yet, and `ensureComposite` will call
2150
+ * this when it is. Doing nothing here is correct rather than a deferral: there is nothing to
2151
+ * bind and no frame in flight to break.
2152
+ */
2153
+ private rebuildRushBindGroup;
2154
+ /** The composite's group over one scene source. See `rebuildRushBindGroup`. */
2155
+ private buildRushBindGroup;
2156
+ /**
2157
+ * A field's offset in a post-chain block, or a throw.
2158
+ *
2159
+ * **Loud, like the flat pass's own, and for a reason this file proved the hard way.** These
2160
+ * blocks are small and their names are easy to guess wrong: the occlusion block has no
2161
+ * `uTexelSize` and the bloom block calls it `uTexel` and has no `uStrength` at all. A lookup
2162
+ * returning −1 writes to index −1 of a typed array, which is *silently discarded* — so a
2163
+ * mistyped name is an effect that runs with an unset uniform and draws a plausible picture.
2164
+ * Both of those were written here and neither raised anything.
2165
+ */
2166
+ private postField;
2167
+ /** Sample zero of the depth, into something the generated shaders can sample. */
2168
+ private resolveDepth;
2169
+ /**
2170
+ * Occlusion from the depth the resolve produced, then a blur across it.
2171
+ *
2172
+ * The projection is what turns a radius in metres into a radius in UV, and it is null until a
2173
+ * mesh pass has settled one — the honest guard `renderer.ts` uses, because occlusion built on
2174
+ * an identity projection samples the depth of a scene nobody drew.
2175
+ */
2176
+ /**
2177
+ * This frame resolved against the history, into the half of the pair it did not read.
2178
+ *
2179
+ * **A blend of zero still runs the pass.** The first frame, the frame after a resize and the
2180
+ * frame after a cut have nothing to sample and the shader exits early on that — but the pass
2181
+ * must still write, or the history it leaves for the next frame is whatever was in the texture.
2182
+ */
2183
+ private temporalResolve;
2184
+ /**
2185
+ * The frame's reflective surfaces, marched against the picture and composited over it.
2186
+ *
2187
+ * **Two passes, and the first cannot write where it reads.** The trace samples the scene colour
2188
+ * to find out what a ray hit, so it writes into a buffer of its own; the resolve then hands those
2189
+ * premultiplied texels to an `over` blend. Either half writing into the texture the other reads
2190
+ * would be a validation failure here and a feedback loop on the other backend, which is the same
2191
+ * argument `runOit` makes about its own resolve.
2192
+ *
2193
+ * **Excluded while multisampling**, which is not timidity: the other backend cannot sample a
2194
+ * multisampled colour attachment at all, and a reflection that appears on one backend and not the
2195
+ * other is the disagreement the parity rule exists to prevent. Said once rather than dropped.
2196
+ */
2197
+ private runReflections;
2198
+ /**
2199
+ * The frame's marks, multiplied into the finished scene.
2200
+ *
2201
+ * **One scissored triangle each and no geometry at all.** The mark is decided per pixel from the
2202
+ * resolved depth, so the rasteriser is only asked for coverage of the part of the screen the
2203
+ * projector box can reach — `decalScissor` computes that from the box's eight corners, and
2204
+ * argues there why a rectangle stands in for drawing the box as a mesh.
2205
+ *
2206
+ * **Every block is written before the pass opens.** `queue.writeBuffer` is ordered on the queue
2207
+ * timeline and does not interleave with recorded commands, so writing one block between two
2208
+ * draws would give both draws the last write — every mark wearing the last projector's pose.
2209
+ * That is the hazard `bloomStaging` records, met by a pass that runs many times in one frame.
2210
+ */
2211
+ private runDecals;
2212
+ /**
2213
+ * The recorded translucent set, accumulated and revealed, then composited over the scene.
2214
+ *
2215
+ * **Two passes over the same geometry**, because the two buffers are two blend states and a
2216
+ * blend state is baked into a pipeline here. The accumulation sums weighted colour; the revealage
2217
+ * multiplies what each layer let through. Both commute, which is the whole property — the frame
2218
+ * stops depending on which surface was submitted first.
2219
+ *
2220
+ * **Depth is attached read-only.** They test against the opaque world so a pane behind a wall is
2221
+ * rejected at the wall, and neither writes, so nothing hides anything else and there is something
2222
+ * left to blend.
2223
+ */
2224
+ private runOit;
2225
+ /**
2226
+ * The global medium: a half-res march, then one composite blended over the scene.
2227
+ *
2228
+ * **After the glass and before the post chain**, which is where the other backend puts it and
2229
+ * for the same reasons: it is the air *in front of* the finished picture, so fog over a
2230
+ * reflective floor dims the reflection; and bloom should see a lit fog bank as brightness.
2231
+ *
2232
+ * Nothing here runs at density 0 — the caller's guard is `mediumActive`, which both backends
2233
+ * share so that "identical to the pass being absent" cannot become true on one of them only.
2234
+ */
2235
+ private runMedium;
2236
+ /** `inverse(viewProj) * DEPTH_01_TO_CLIP_Y_DOWN`, rebuilt once a frame rather than per pass. */
2237
+ private readonly mediumDepthToWorld;
2238
+ /** Sixteen real numbers for a frame that never set a light matrix. */
2239
+ private readonly mediumIdentity;
2240
+ private runOcclusion;
2241
+ /**
2242
+ * The other half of the estimate: across, then down.
2243
+ *
2244
+ * **Not a smoothing pass over a finished answer.** `ambientOcclusion.ts` designs the two
2245
+ * together — twelve taps turned by a rotation that repeats over a 4×4 tile, and a four-wide
2246
+ * blur that averages all sixteen turns back into one number — so the estimate on its own is
2247
+ * not a rougher occlusion, it is a quarter of one, and it reads as salt and pepper. Skipped
2248
+ * entirely here until now; see `aoScratch`.
2249
+ *
2250
+ * Depth-aware, which is what keeps it a blur rather than a smear: an ordinary gaussian over
2251
+ * this estimate draws a dark halo around every silhouette, and that is more obviously wrong
2252
+ * than the noise it removes. The shader weights each tap by how close its view-space depth is
2253
+ * to the centre's, which is why both passes bind the depth as well as the estimate.
2254
+ */
2255
+ private blurOcclusion;
2256
+ /**
2257
+ * The bright part of the frame, blurred, for the composite to add back.
2258
+ *
2259
+ * **The pyramid, which is what bloom is.** Threshold the frame into level 0, halve it down the
2260
+ * chain, then add each level back into the one above it, so level 0 ends up holding every
2261
+ * octave at once and the falloff is their sum rather than one kernel's. `bloomPass.ts` runs
2262
+ * exactly this on the other backend, and `bloomChain.ts` holds the three numbers that decide
2263
+ * the pyramid's shape so neither side can build a different one.
2264
+ *
2265
+ * **This was the prefilter alone**, and how that survived is the part worth keeping: it
2266
+ * compiled, it validated, and it drew a picture. A thresholded frame at half resolution read
2267
+ * bilinearly is entirely plausible-looking and has no glare in it at all, and no scene turns
2268
+ * bloom on — so nothing compared it against anything until the format defect above made the
2269
+ * effect measurable. Six of this port's bugs are that shape.
2270
+ *
2271
+ * Nothing is uploaded here. Every block these stages read was written on resize; see
2272
+ * `bloomStaging` for why that is not merely an optimisation.
2273
+ */
2274
+ private runBloom;
2275
+ /**
2276
+ * Snapshot the opaque scene's depth, so a volume can clamp its march to it.
2277
+ *
2278
+ * **Neither backend may sample the depth it is testing against**, so this is a copy and not
2279
+ * the attachment: WebGL2 calls that undefined and WebGPU rejects the pass outright. The copy
2280
+ * already exists here — `resolveDepth` writes sample zero into a single-sample float target
2281
+ * for occlusion and motion blur — so what this adds is taking it *earlier*, at the moment a
2282
+ * volume first needs it, rather than in the composite.
2283
+ *
2284
+ * The pass has to be broken to do it, because the depth is an attachment of the open one. That
2285
+ * is the same end-and-reopen `openTextPass` performs, with one difference that decides whether
2286
+ * the frame survives: **both attachments load**. Text reopens with the depth *cleared*, which
2287
+ * is what puts a message in front of the scene; a volume is in the scene and still has to sort
2288
+ * against it, so clearing here would let every beam draw straight through the world.
2289
+ *
2290
+ * Returns false where there is no copy to take — a profile with no composite has no target for
2291
+ * one — and the caller then leaves the clamp switched off rather than sampling something else.
2292
+ * WebGL2 reaches the same answer for the same profile, which is the parity that matters: the
2293
+ * two backends agree about *when* a beam is clamped, not merely about how.
2294
+ */
2295
+ /**
2296
+ * Everything a volume samples: the three shadow layers, and the frame's own depth.
2297
+ *
2298
+ * All three layers because `lightVolume.ts` samples all three — a shaft is interrupted by
2299
+ * whatever stands in it, and a mover standing in it is the case the dynamic layer exists for.
2300
+ * Bound here as well as in the mesh group, matching `renderer.ts`, which binds its
2301
+ * `dynamicShadowMap` to both.
2302
+ *
2303
+ * A method rather than a constructor expression because the depth snapshot is allocated with
2304
+ * the drawing buffer: `ensureComposite` calls this again whenever it rebuilds that target.
2305
+ */
2306
+ private buildLightVolumeBindGroup;
2307
+ /**
2308
+ * Copy the colour the frame has drawn so far, so a refracting draw can read it.
2309
+ *
2310
+ * **Neither backend can read what it is writing, and this one cannot even resolve on demand.**
2311
+ * `post.sceneColorMsaa` is `RENDER_ATTACHMENT` with no `TEXTURE_BINDING`, so the only readable
2312
+ * copy of the frame is what ending the pass resolves into `post.sceneColor` — and that is the
2313
+ * pass's own attachment, which a bind group may not sample while it is attached. So: end the
2314
+ * pass, copy the resolved texture aside, reopen with `loadOp: 'load'`.
2315
+ *
2316
+ * **A fourth instance of a boundary this backend already runs three times** — the mirror, the
2317
+ * light volume's depth snapshot and text — rather than a new mechanism. It is modelled on
2318
+ * `takeVolumeDepth` line for line, including its two refusals, which hold here for the same
2319
+ * reasons: ending the mirror's pass would throw away what the mirror had drawn and send the rest
2320
+ * of the scene into the frame instead, invisibly on an immediate-mode GPU and not at all on a
2321
+ * tile-based one.
2322
+ *
2323
+ * Latched, so one copy serves every refracting draw in a frame: glass does not refract other
2324
+ * glass, and a frame that refracts nothing breaks no pass at all.
2325
+ */
2326
+ private takeRefractSnapshot;
2327
+ private takeVolumeDepth;
2328
+ /**
2329
+ * The pass to record into, reopening one on the canvas if the frame has already gone.
2330
+ *
2331
+ * Returns null only where there is genuinely nowhere to draw: a lost surface, or a call before
2332
+ * any frame has begun. See `overlayDepth` for why this exists at all.
2333
+ */
2334
+ /**
2335
+ * Schedule and replay whatever the graph has recorded, then empty it.
2336
+ *
2337
+ * The schedule is computed and its pass count asserted in development, but **it does not yet
2338
+ * drive pass creation**: every node recorded so far is a mesh draw writing the scene target,
2339
+ * so the schedule is one pass and `ensurePass` already opens exactly that. Letting the
2340
+ * schedule open passes is the next plan's work, and doing it here would change pass
2341
+ * boundaries in the same commit that introduces recording — two things to bisect instead of
2342
+ * one.
2343
+ */
2344
+ private flushGraph;
2345
+ /**
2346
+ * What must survive a flush that is not the frame's last.
2347
+ *
2348
+ * **The plan said this becomes `maskOf('canvas')` once every verb records, and it does not.**
2349
+ * Tried, and a frame of three scene draws schedules zero passes: the composite reads the
2350
+ * scene's colour and the composite is not a graph node, so the scheduler sees a write nothing
2351
+ * reads and drops the whole frame. Nothing else in this programme could have found that — the
2352
+ * executor replays every node regardless of the schedule, so the picture is identical either
2353
+ * way — which is exactly the failure mode the plan warned a wrong `reads` would have.
2354
+ *
2355
+ * The honest set is therefore everything the graph cannot see a reader for, minus what is
2356
+ * provably dead. Expressed as a subtraction rather than a list on purpose: a resource added
2357
+ * to the table later defaults to surviving, which is the safe direction to be wrong in.
2358
+ *
2359
+ * **Only one thing is dead here.** Nothing samples the mirror's depth and nothing loads it
2360
+ * back — it sorts the mirror against itself and is never looked at again. The frame's own
2361
+ * depth is not dead mid-frame, because `openFramePass` resumes with `depthLoadOp: 'load'` so
2362
+ * that geometry drawn after the mirror depth-tests against geometry drawn before it, and a
2363
+ * load is a read.
2364
+ */
2365
+ private liveOutMidFrame;
2366
+ /** What a registered pass has claimed it samples, which the frame therefore keeps. */
2367
+ private liveOutForPasses;
2368
+ /**
2369
+ * What must survive the frame's last flush, which is where the depth prize is.
2370
+ *
2371
+ * **Nothing loads the frame's depth after this point**, so it is stored every frame for a
2372
+ * reader that does not exist — the case the design named as the one that pays. The exception
2373
+ * is a composite that resolves it, for camera motion blur or ambient occlusion, and the test
2374
+ * here is the same expression `composite` applies a few lines later so the two cannot drift
2375
+ * apart without both being wrong.
2376
+ *
2377
+ * Conservative on the first frame of a run: `hasPreviousView` is false then and the composite
2378
+ * blurs nothing, but this keeps depth live anyway rather than encode a second copy of that
2379
+ * rule. One stored attachment on one frame is not worth a second place for it to be wrong.
2380
+ */
2381
+ private liveOutFinalFlush;
2382
+ /**
2383
+ * Whether the composite is going to resolve the frame's depth out.
2384
+ *
2385
+ * **One expression, called from both places, because the two copies drifted the first time
2386
+ * anybody added a third reader.** The comment above used to say "the test here is the same
2387
+ * expression `composite` applies a few lines later so the two cannot drift apart without both
2388
+ * being wrong", and depth of field was added to `composite`'s copy and not to this one — so the
2389
+ * effect ran against a depth attachment the scheduler had discarded, read zero everywhere, and
2390
+ * produced a *plausible* picture: every pixel at the near plane, so every pixel at full blur,
2391
+ * blurred correctly and focused on nothing. It responded to the blur radius and not to the focus
2392
+ * distance, which is what made it findable. Same expression now means the same call.
2393
+ *
2394
+ * Conservative where `composite` could be exact: the motion term is the raw ceiling and dial,
2395
+ * where the composite also requires a previous view to reproject through. That costs one
2396
+ * unused depth resolve on the first frame of a run and buys the property this comment is about.
2397
+ */
2398
+ private compositeWantsDepth;
2399
+ /**
2400
+ * What the pass `ensurePass` hands back actually writes.
2401
+ *
2402
+ * A verb can draw either side of `endFrame` — a consumer puts its interface after it on
2403
+ * purpose, so the interface escapes the post chain — and after that the target is the canvas
2404
+ * rather than the scene. Declaring one or the other unconditionally would be wrong half the
2405
+ * time, and wrong in a way nothing notices until `liveOut` narrows.
2406
+ */
2407
+ private currentTarget;
2408
+ /**
2409
+ * Take a command to record this draw into, or `null` to draw directly.
2410
+ *
2411
+ * **Returns the command rather than taking a callback**, which the plan first proposed: a
2412
+ * closure per draw is an allocation, and this is the hottest path in the renderer. A call
2413
+ * site reads as one `if` either way.
2414
+ */
2415
+ private recordDraw;
2416
+ /** Replay one recorded draw into an open pass. */
2417
+ private replayNode;
2418
+ private ensurePass;
2419
+ /**
2420
+ * The pass a verb will draw into, without flushing what is already recorded.
2421
+ *
2422
+ * For a verb that records: its draw goes into the arena *behind* everything already there, so
2423
+ * there is nothing to get out of the way first. It still opens the pass, because the caller's
2424
+ * guard needs to know there is somewhere to draw at all, and because the replay needs one.
2425
+ *
2426
+ * With the switch off nothing ever reaches the arena, so a recording verb's direct path is
2427
+ * reached with the arena empty and the two entry points do the same thing.
2428
+ */
2429
+ /**
2430
+ * Whether there is somewhere to draw, without opening it.
2431
+ *
2432
+ * `openPass` answers the same question by doing the thing, which is what a verb needed while it
2433
+ * drew directly and is exactly wrong while it records: **the schedule cannot describe a pass
2434
+ * that is already open.** Every recording verb asks this instead, and the pass itself is
2435
+ * obtained only where it is used, which is the direct branch.
2436
+ *
2437
+ * The conditions are `openPass`'s own with the opening removed, in the same order.
2438
+ */
2439
+ private canDraw;
2440
+ private openPass;
2441
+ /**
2442
+ * The cache whose pipelines match the pass this draw will land in.
2443
+ *
2444
+ * Read at the draw rather than settled once, because the same call draws into either: `drawText`
2445
+ * is an announcement over the canvas in one frame and a label inside the world in the next. The
2446
+ * lookup is a field read and a branch — `PipelineCache` exists so the pipeline itself is a `Map`
2447
+ * hit — so this stays inside the per-frame budget.
2448
+ *
2449
+ * **`framePresented` is read beside `overlayActive`, and that is the whole of a bug that kept
2450
+ * `frameGraph` off by default.** The question is which pass the draw *ends up in*, and
2451
+ * `overlayActive` only answers which one is open *now*. Those differ whenever a verb resolves its
2452
+ * pipeline before the pass exists, which is every recorded draw: recording defers the draw to a
2453
+ * flush, and the flush is what calls `openPass` and sets `overlayActive`. So a mesh drawn after
2454
+ * `endFrame` took a scene pipeline at 4 samples into an overlay pass at 1, and WebGPU reported it
2455
+ * at `finish` — dropping the whole command buffer and the frame with it.
2456
+ *
2457
+ * These two conditions are `openPass`'s own, in its order: past `framePresented` it builds the
2458
+ * overlay. Reading the same field here is what makes the pipeline and the pass agree by
2459
+ * construction rather than by timing.
2460
+ *
2461
+ * **Two things do legitimately want the scene's pipeline after the frame is presented**, and this
2462
+ * clause once said nothing did — which was wrong and was caught by a device rather than by
2463
+ * reading. A probe bake and a planar reflection both draw into targets of their own, built at the
2464
+ * scene's sample count and format, and a scene is entitled to bake either one after `endFrame`.
2465
+ * The showroom does exactly that, and routing its bake to the overlay cache put a one-sample
2466
+ * pipeline into a four-sample face — the same fault as the original, inverted.
2467
+ *
2468
+ * So the order here is `openPass`'s order: its own two early branches first, then the overlay.
2469
+ * That is not a coincidence to be tidied away — the two functions answer the same question, and
2470
+ * any future branch added to one belongs in the other.
2471
+ */
2472
+ private targetPipelines;
2473
+ /** End and submit a reopened pass. Safe to call when there is none. */
2474
+ private flushOverlay;
2475
+ endFrame(): void;
2476
+ /**
2477
+ * Match the drawing buffer to the canvas, taking no arguments.
2478
+ *
2479
+ * **The signature is the WebGL2 renderer's, and the derived surface is what enforced it.**
2480
+ * This was written taking a width and a height, which is the natural shape for
2481
+ * `GPUCanvasContext.configure`, and `tsc` rejected it against `RendererApi` before it
2482
+ * could reach a consumer. `Renderer.resize()` reads the canvas and the device pixel ratio
2483
+ * itself, so a caller never passes a size and must not have to learn which backend it
2484
+ * holds in order to know that.
2485
+ *
2486
+ * **The sizing policy is `drawingBufferSize`'s, not this file's.** Locked dimensions, the
2487
+ * pixel ceiling and the density a governor moves are one set of rules, and a second copy of
2488
+ * them here would be a frame that is a different size on each backend for reasons nobody
2489
+ * could see. The budget is deliberately not applied to a *locked* buffer, for the reason
2490
+ * `renderer.ts` gives: a lock is a caller naming an exact size for a reason no quality
2491
+ * setting knows about, and quietly returning a different one writes a file that is not the
2492
+ * size it claims.
2493
+ */
2494
+ resize(): void;
2495
+ /**
2496
+ * Move the drawing-buffer area cap, in pixels. Zero is uncapped.
2497
+ *
2498
+ * Live, unlike every other quality lever, and `renderer.ts` explains why that is the feature:
2499
+ * everything sized *from* the drawing buffer re-fits itself, so this changes how many pixels
2500
+ * the next frame covers and nothing else. A player hunting their own frame rate can move it
2501
+ * and see the answer.
2502
+ */
2503
+ setMaxDrawingBufferPixels(pixels: number): void;
2504
+ /**
2505
+ * Told when the device is lost, immediately if it already has been.
2506
+ *
2507
+ * **The lateness is the interesting half and `device.ts` owns it.** WebGL fires
2508
+ * `webglcontextlost` once and a listener that arrives afterwards hears nothing; a WebGPU
2509
+ * device's loss is a promise that stays resolved, so a renderer built after a driver reset
2510
+ * can still be told. `GpuSurface.onLost` latches it and this hands the listener straight
2511
+ * through, which is why there is no list of listeners here to keep.
2512
+ */
2513
+ onContextLost(listener: () => void): void;
2514
+ /** Rebuilt once per `bindMeshPass`, never per draw. */
2515
+ private readonly frustum;
2516
+ /**
2517
+ * Whether a mesh placed by this matrix is anywhere in the frame.
2518
+ *
2519
+ * Valid between `bindMeshPass` and `endFrame`, because that is when a camera has been given.
2520
+ * **A consumer is better placed to use this than `cullDraws` is**: it can skip the model
2521
+ * matrix, the animation update and the material switch as well, none of which the renderer can
2522
+ * avoid once `drawMesh` has been called.
2523
+ */
2524
+ visible(bounds: Bounds, model: ReadonlyMat4): boolean;
2525
+ /**
2526
+ * The occlusion buffer, or null where the profile did not ask for one.
2527
+ *
2528
+ * **Built at the first `bindMeshPass` rather than at construction**, because a field initialiser
2529
+ * runs before the constructor body and `quality` is not resolved yet there. Resized when the
2530
+ * drawing buffer's aspect changes and cleared at every pass — which is also where the
2531
+ * view-projection it projects through comes from.
2532
+ */
2533
+ private occlusion;
2534
+ /**
2535
+ * Declare a box that things behind it may safely be hidden by.
2536
+ *
2537
+ * **Between `bindMeshPass` and the first `drawMesh`**, because the pass is what supplies the
2538
+ * camera and the first test is what freezes the pyramid. A no-op when the profile asked for no
2539
+ * occlusion buffer, so a consumer may call it unconditionally.
2540
+ *
2541
+ * The box is in the model matrix's own space, and it is the box you may be *hidden by* rather
2542
+ * than the one you occupy — an occluder larger than the solid it stands for culls things that are
2543
+ * visible, which is a hole in the world. `OcclusionBuffer` carries the rest of the reasoning.
2544
+ */
2545
+ addOccluder(min: ArrayLike<number>, max: ArrayLike<number>, model: ReadonlyMat4): void;
2546
+ /**
2547
+ * Whether a mesh is entirely behind the occluders declared this frame.
2548
+ *
2549
+ * **False whenever anything is uncertain**, including when no occluders were declared and when
2550
+ * the profile asked for no buffer — so a consumer may call it unconditionally and a scene that
2551
+ * declares nothing behaves exactly as it did before. Read it beside `visible`, which answers the
2552
+ * other half of the same question.
2553
+ */
2554
+ occluded(bounds: Bounds, model: ReadonlyMat4): boolean;
2555
+ private readonly passes;
2556
+ /** Each registered pass's declared reads, as a mask, resolved once at registration. */
2557
+ private readonly passReads;
2558
+ /**
2559
+ * Everything any registered pass says it samples, unioned.
2560
+ *
2561
+ * **This reaches `liveOut` and not the node's reads, and the difference took a failing test to
2562
+ * see.** A read declared on a node only protects an attachment from work *later in the same
2563
+ * arena*, because that is what `storeOp` is about — what happens after the pass ends. A
2564
+ * package drawing into the frame cannot sample the frame's own depth at all while it is an
2565
+ * attachment, so a read declared there would be meaningless.
2566
+ *
2567
+ * What a contributor actually means by `reads` is "I sample this somewhere", and the renderer
2568
+ * has no idea when. So it is treated as the conservative thing it is: the frame keeps it.
2569
+ */
2570
+ private passReadsUnion;
2571
+ /**
2572
+ * Mutated in place rather than rebuilt, because `draw` is a per-frame path.
2573
+ *
2574
+ * A fresh object per invocation is an allocation per registered pass per frame, which is the
2575
+ * thing the house rule about hot paths forbids. The contract in `PassDefinition.draw` says the
2576
+ * context is valid for the duration of the call for exactly this reason.
2577
+ */
2578
+ private readonly passContext;
2579
+ /**
2580
+ * Let something outside this file draw into the frame. See `Renderer.registerPass`.
2581
+ *
2582
+ * The format and the sample count travel with the device because a render pipeline cannot be
2583
+ * built without them and a package has no other way to learn what the frame is.
2584
+ */
2585
+ registerPass(definition: PassDefinition): PassHandle;
2586
+ /**
2587
+ * How many registered passes declare `prepare`, so a frame with none walks nothing.
2588
+ *
2589
+ * The registry is a slot table and enumerating it every frame to discover that nobody wants a
2590
+ * step is exactly the per-frame work the house rules are about.
2591
+ */
2592
+ private preparingPasses;
2593
+ /** Mutated in place, because `beginFrame` is a per-frame path. See `PassDefinition.prepare`. */
2594
+ private readonly prepareContext;
2595
+ /**
2596
+ * Give every pass that owns a target its chance to fill it, before the frame's own opens.
2597
+ *
2598
+ * **The ordering is a property of the encoder rather than of a schedule.** Commands recorded on
2599
+ * one `GPUCommandEncoder` execute in recording order, and this runs after `beginFrame` created
2600
+ * the frame's encoder and before anything opens the frame's render pass — so a target filled
2601
+ * here has completed by the time any frame pass could sample it. Nothing in the graph learns
2602
+ * about it and nothing is reordered, which is what keeps gate 1.2's withdrawal of dependency
2603
+ * ordering intact.
2604
+ */
2605
+ private runPreparePasses;
2606
+ /**
2607
+ * Record one, to be run where the caller put it.
2608
+ *
2609
+ * With the graph off there is nothing to record into, so it runs at once against the pass the
2610
+ * caller is drawing into — which is what every verb's direct path does, and is why the two
2611
+ * behave identically from outside.
2612
+ */
2613
+ drawPass(handle: PassHandle): void;
2614
+ /** Let go of a pass. A handle kept past this draws nothing; see `PassHandle`. */
2615
+ unregisterPass(handle: PassHandle): void;
2616
+ /** Whether this backend can run a compute definition. See `Renderer.computeSupported`. */
2617
+ readonly computeSupported = true;
2618
+ private readonly computes;
2619
+ /**
2620
+ * The binner, held only when the profile asked for clustering.
2621
+ *
2622
+ * **Registered through the public seam rather than called directly**, which is not ceremony: it
2623
+ * is the only consumer of `registerCompute` inside the engine, so if the seam were wrong for its
2624
+ * first real user that is worth finding here rather than in a package that does not exist yet.
2625
+ */
2626
+ private clusterBinner;
2627
+ private clusterHandle;
2628
+ /** The one-texel stand-in bound when there is no table. Assigned in the constructor. */
2629
+ private blankTableView;
2630
+ /** near, far, tan(fovY/2), aspect. Written per frame into the fragment block. */
2631
+ private readonly clusterFrustum;
2632
+ /** Refilled per frame; holds references and allocates nothing. */
2633
+ private readonly clusterLights;
2634
+ /**
2635
+ * Reused per dispatch rather than allocated, for the reason `passContext` gives beside it: a
2636
+ * fresh object per dispatch is an allocation on a per-frame path. The contract in
2637
+ * `ComputeDefinition.dispatch` says the context is valid for the duration of the call for
2638
+ * exactly this reason.
2639
+ */
2640
+ private readonly computeContext;
2641
+ /** Let something outside this file compute. See `Renderer.registerCompute`. */
2642
+ registerCompute(definition: ComputeDefinition): ComputeHandle;
2643
+ /**
2644
+ * Run one, now, on an encoder of its own.
2645
+ *
2646
+ * **Its own encoder, and that is the whole of the design.** `beginComputePass` cannot be opened
2647
+ * while a render pass is open, and this frame's render pass is opened late and then deliberately
2648
+ * kept open — closing and reopening it was measured at 92 MB a frame at 824x1830, which is what
2649
+ * `beginFrame` stopped paying. A dispatch that closed it would spend that again on every frame
2650
+ * that computed, which is the largest single cost this backend has ever removed.
2651
+ *
2652
+ * **Submitted here rather than deferred to `endFrame`**, because submission order is execution
2653
+ * order and a frame with a mirror in it submits three times before `endFrame` runs. Deferring
2654
+ * would land the compute *after* the reflection's submit and quietly starve the pass most likely
2655
+ * to want it. One submit per dispatch is what that costs; batching several into one compute pass
2656
+ * is a measured optimisation and would bring the ordering question back with it.
2657
+ *
2658
+ * **`flushRings` first**, for the reason `endPlanarReflection` calls it before its own submit: a
2659
+ * ring's writes have to land on the queue ahead of the commands that read them. A ring with
2660
+ * nothing in it returns immediately, so a dispatch early in the frame pays almost nothing for
2661
+ * the guarantee — and a ring in neither `flushRings` nor `beginFrame`'s reset is exactly how the
2662
+ * probe's mip chain came to read a buffer nobody uploaded.
2663
+ */
2664
+ dispatchCompute(handle: ComputeHandle): void;
2665
+ /** Let go of a definition. A handle kept past this dispatches nothing; see `ComputeHandle`. */
2666
+ unregisterCompute(handle: ComputeHandle): void;
2667
+ /**
2668
+ * Fill the froxel table for this frame, and write the four numbers that address it.
2669
+ *
2670
+ * **Dispatched here rather than at `beginFrame`**, because this is the one place a camera and an
2671
+ * environment arrive together, and a froxel grid is defined by the camera's frustum. It is also
2672
+ * before any draw the table feeds, and `dispatchCompute` submits where it is issued, so the
2673
+ * ordering the lit pass depends on holds without anything having to reason about it.
2674
+ *
2675
+ * The frustum is read off the projection rather than taken as a parameter, for the reason
2676
+ * `renderer.ts` gives at its own copy: derived numbers cannot disagree with the matrix the world
2677
+ * was actually drawn with, and a caller-supplied field of view can.
2678
+ */
2679
+ private bindClusters;
2680
+ /** Allocates, and may: registration and disposal are init-time, exactly as `passDevice` is. */
2681
+ private computeDevice;
2682
+ private passDevice;
2683
+ /**
2684
+ * Told when the drawing context comes back — which, on this backend, it does not.
2685
+ *
2686
+ * **A device is not restored, it is replaced.** WebGL2 has a `webglcontextrestored` event
2687
+ * and `renderer.ts` reports it while saying plainly that it rebuilds nothing, because every
2688
+ * resource field there is `readonly` and set in the constructor. WebGPU does not even offer
2689
+ * the event: recovering means requesting a new device and building a new renderer on it,
2690
+ * which is a decision for whatever owns the canvas.
2691
+ *
2692
+ * So this registers a listener that will not fire, and that is the honest shape rather than
2693
+ * an omission — the alternative is a missing method, which throws and takes the frame with
2694
+ * it. A consumer wanting to survive a reset listens to `onContextLost` and remounts.
2695
+ */
2696
+ onContextRestored(_listener: () => void): void;
2697
+ private readonly insetLayout;
2698
+ private readonly insetUniforms;
2699
+ private readonly insetBindGroup;
2700
+ /**
2701
+ * Draw the next thing into a rectangle of the canvas, and give the rest back with `endInset`.
2702
+ *
2703
+ * `rect` is viewport-relative — a measured DOM element — which is why the canvas's own
2704
+ * bounding box is subtracted here. `renderer.ts` makes the argument for doing that arithmetic
2705
+ * in the renderer: a caller converting CSS pixels into drawing-buffer pixels *and* flipping
2706
+ * the origin is a caller with two chances to be wrong about somebody else's state.
2707
+ *
2708
+ * **The clear is a drawn quad rather than a clear**, because WebGPU clears whole attachments
2709
+ * and cannot confine one to a scissor. See `insetPass.ts`.
2710
+ *
2711
+ * Returns the drawing-buffer height of the inset, as the other backend does.
2712
+ */
2713
+ beginInset(rect: InsetRect, clearColor: Vec3 | null): number;
2714
+ /** Give the whole canvas back. Safe to call without a matching `beginInset`. */
2715
+ endInset(): void;
2716
+ /**
2717
+ * Copy a rectangle of the frame into a 2D canvas, pixel for pixel.
2718
+ *
2719
+ * **Backend-agnostic, and the same code on both.** `drawImage` takes the canvas element,
2720
+ * which is the canvas whichever API drew into it, so nothing here touches a device. Sized to
2721
+ * the source so the copy is never resampled, and written only when it changes because
2722
+ * assigning width or height clears the target.
2723
+ */
2724
+ copyRegionTo(rect: InsetRect, target: HTMLCanvasElement): void;
2725
+ /**
2726
+ * How far into the tone curve this frame is exposed.
2727
+ *
2728
+ * Live, and it reaches the picture here rather than through a composite: this backend grades
2729
+ * in the mesh pass because that is the last pass it has. See `bindMeshPass`.
2730
+ */
2731
+ setOutputExposure(exposure: number): void;
2732
+ /** This frame's exposure into the tone curve. Starts at the profile's own. */
2733
+ private exposure;
2734
+ private bloomScale;
2735
+ private rushStrength;
2736
+ private motionBlurScale;
2737
+ /**
2738
+ * This frame's veil colour, and how much of it to composite. See `Renderer.setFrameVeil`.
2739
+ *
2740
+ * Reset to zero alpha at the end of every `endFrame`, unlike the three fields above: a
2741
+ * forgotten veil is a stuck white or black frame, which is worse than one that has to ask
2742
+ * again.
2743
+ */
2744
+ private readonly veilColor;
2745
+ private veilAlpha;
2746
+ /** How much of the frame's bloom to apply, 0 to 1. Scales the profile's `bloom`. */
2747
+ /**
2748
+ * The colour grade this and every later frame applies, until it is set again.
2749
+ *
2750
+ * The same contract `Renderer.setColourGrade` states and the same refusal: it needs a
2751
+ * composite, because without one every forward pass grades itself and there is nowhere to
2752
+ * apply a look once. Said out loud rather than silently ignored — this backend spent a while
2753
+ * telling consumers that working effects did nothing, and the correction was to make present
2754
+ * and honoured the same thing rather than to stop saying anything.
2755
+ *
2756
+ * **The bind group holds the view, so a new table rebuilds it.** A bind group is built with
2757
+ * the view it was given and cannot be handed another, which is the rule `ensureComposite`
2758
+ * already records about resized targets — and the reason the upload is gated on the object
2759
+ * changing rather than done every frame.
2760
+ */
2761
+ setColourGrade(lut: ColourGradeLut | null, strength?: number): void;
2762
+ /** The grade's view, building the identity placeholder the first time anything asks. */
2763
+ private ensureGradeView;
2764
+ /**
2765
+ * Put a table on the device, replacing whatever was there.
2766
+ *
2767
+ * `rgba8unorm` and a filtering sampler, which is what makes a 32-lattice table smooth rather
2768
+ * than banded. `bytesPerRow` is the lattice times four and `rowsPerImage` the lattice, because
2769
+ * a 3D write needs both and a wrong one silently shears the cube.
2770
+ */
2771
+ private uploadGrade;
2772
+ setBloom(scale: number): void;
2773
+ /**
2774
+ * How thick the air is, this frame: a global participating medium filling the whole frustum.
2775
+ *
2776
+ * The dial beside `globalMediumSteps`' ceiling. `renderer.ts` carries the full note; the short
2777
+ * version is that `density` is extinction per metre, 0 is a real off that allocates nothing and
2778
+ * draws nothing, and the three optional arguments are held until changed.
2779
+ */
2780
+ setGlobalMedium(density: number, albedo?: number, anisotropy?: number, maxDistance?: number): void;
2781
+ /** How much speed blur the frame should resolve with, 0 to 1. */
2782
+ setSpeedRush(strength: number): void;
2783
+ /** How much of the frame's camera motion blur to apply, 0 to 1. */
2784
+ setCameraMotionBlur(scale: number): void;
2785
+ /**
2786
+ * Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
2787
+ * to take. **The other backend's `setDepthOfField` carries the reasoning**, whole: the
2788
+ * ceiling-and-dial split, why racking focus is the caller's, and why `range` is clamped above
2789
+ * zero. This is the same setter over the same state.
2790
+ */
2791
+ setDepthOfField(distance: number, range: number, scale?: number): void;
2792
+ /** Where the lens is focused and how deep the sharp zone is, in metres. See `setDepthOfField`. */
2793
+ private focusDistance;
2794
+ private focusRange;
2795
+ /** How much of `depthOfField` this frame takes. See `setDepthOfField`. */
2796
+ private dofScale;
2797
+ /** The inverse projection, and the four elements of it a depth-to-distance needs. */
2798
+ private readonly dofInvProjection;
2799
+ private readonly dofDepthToView;
2800
+ /**
2801
+ * Composite a flat colour over the finished frame — for a cut dipping to white or to black.
2802
+ *
2803
+ * See `Renderer.setFrameVeil` for the full ruling on where this sits: after the tone map,
2804
+ * before grain and vignette, upstream of nothing bloom reads. Both backends draw it from the
2805
+ * same generated `rush.ts` shader, so there is one place that reasoning has to be written.
2806
+ */
2807
+ setFrameVeil(r: number, g: number, b: number, alpha: number): void;
2808
+ private readonly causticsLayout;
2809
+ /** A slot per batch, for the reason `causticsPass.ts`'s layout gives. */
2810
+ private readonly causticsVerts;
2811
+ private readonly causticsFrags;
2812
+ private readonly causticsBindGroup;
2813
+ private readonly causticsVertStaging;
2814
+ private readonly causticsVertFloats;
2815
+ /** The same bytes as an `i32` view, which is what a ring slot is copied through. */
2816
+ private readonly causticsVertBlock;
2817
+ private readonly causticsFragStaging;
2818
+ private readonly causticsFragFloats;
2819
+ private readonly causticsFragInts;
2820
+ /** Said once rather than every frame, for the reason `warnedFull` gives. */
2821
+ private warnedCausticsFull;
2822
+ /** The light's tint, refilled per draw rather than allocated. See `causticsRenderer.ts`. */
2823
+ private readonly causticsTint;
2824
+ /**
2825
+ * A surface lit by nearby water: a pool floor, a soffit, the mouth of a cave.
2826
+ *
2827
+ * Null on a profile with water off or with no sheets, exactly as `renderer.ts` returns null,
2828
+ * so a scene's own `drawCaustics(null, …)` guard works the same on both.
2829
+ */
2830
+ createCaustics(sheets: readonly CausticSheet[]): GpuCaustics | null;
2831
+ disposeCaustics(caustics: GpuCaustics | null): void;
2832
+ /** Add the water's light to an already-shaded scene. Draw after the opaque pass. */
2833
+ drawCaustics(caustics: GpuCaustics | null, camera: Camera, timeSeconds: number, env: Environment, windX?: number, windZ?: number, strength?: number): void;
2834
+ /** Scale one plume in a batch, 0 to hide it. See `GpuPlumes.setScale`. */
2835
+ setPlumeScale(plumes: GpuPlumes, index: number, scale: number): void;
2836
+ /** Scratch for `drawingBufferSize`, which fills a caller's object rather than allocating. */
2837
+ private readonly budgeted;
2838
+ private maxDrawingBufferPixels;
2839
+ /** A caller's exact size, or zero for "follow the CSS box". */
2840
+ private lockedWidth;
2841
+ private lockedHeight;
2842
+ /**
2843
+ * Pin the drawing buffer to an exact size, whatever the page is doing.
2844
+ *
2845
+ * For an export: a clip is 1080x1920 whatever anybody's frame rate or device ratio is. The
2846
+ * pixel budget deliberately does not apply to a locked buffer — see `resize` — because
2847
+ * quietly returning a different size writes a file that is not the size it claims.
2848
+ */
2849
+ lockDrawingBuffer(width: number, height: number): void;
2850
+ /** Back to following the CSS box. The next `resize` restores it. */
2851
+ unlockDrawingBuffer(): void;
2852
+ /** Whether this device and profile have an environment probe at all. See the constructor. */
2853
+ private readonly probeEnabled;
2854
+ /**
2855
+ * The captured cube: what the bake draws into, and the source the convolution reads.
2856
+ *
2857
+ * **Not what the lit pass samples.** It carries a box-filtered chain, which is a filter the
2858
+ * specular integral does not want and only ever wanted as a stand-in. It stays because filtered
2859
+ * importance sampling needs a chain to select a level from, and because the spherical-harmonic
2860
+ * projection reads it — the irradiance term is an integral over the capture and would be wrong
2861
+ * over a lobe-convolved one.
2862
+ */
2863
+ private readonly probe;
2864
+ /**
2865
+ * The prefiltered cube: what the lit pass samples, one GGX-convolved level per roughness.
2866
+ *
2867
+ * **A second texture rather than the capture written in place**, and the cost is one cube of
2868
+ * memory. What it buys: no level is ever convolved from a level that was already convolved,
2869
+ * which applies the lobe twice and compounds up the chain; and the capture survives for the
2870
+ * projection above. **What would make it wrong** is a probe large enough that a second cube is
2871
+ * the memory decision — at 128 with a chain this is about a megabyte at half float.
2872
+ */
2873
+ /**
2874
+ * Every probe of the grid as one array texture, octahedral, gutter included.
2875
+ *
2876
+ * **Recreated when a grid's layer count changes**, and the flat bind groups are rebuilt with it:
2877
+ * a group built at construction holds the texture that existed then, so a larger array created
2878
+ * later would never be read and the picture would keep the one-probe grid it started with.
2879
+ */
2880
+ private probeArray;
2881
+ /** Where the probes stand. A single baked probe is a grid of one at that probe's own origin. */
2882
+ private probes;
2883
+ /** Whether the grid supplies the scene's ambient. `ProbeBakeOptions.irradiance`, at grid scope. */
2884
+ private probeAmbient;
2885
+ /** Where the probe being baked stands. Reused, so a bake allocates nothing. */
2886
+ private readonly probeOrigin;
2887
+ /** Texels across one probe's map at level 0. Twice the capture's face; see `octahedralEdgeFor`. */
2888
+ private readonly probeEdge;
2889
+ /** The view bound as `uEnvironment`: the prefilter's, never the capture's. */
2890
+ private probeView;
2891
+ private readonly probeSize;
2892
+ private readonly probeMipLayout;
2893
+ /** The photometric atlas, allocated on first use. See `setIesProfiles`. */
2894
+ private iesTexture;
2895
+ private iesView;
2896
+ private cookieTexture;
2897
+ private cookieView;
2898
+ /** Tiles in the cookie atlas. 0 is the white placeholder and the whole off path. */
2899
+ private cookieTiles;
2900
+ /** Rows in that atlas. One until a consumer loads a profile, which is the row of ones. */
2901
+ private iesRows;
2902
+ /** Horizontal planes each profile occupies. 1 unless something asymmetric was loaded. */
2903
+ private iesPlanes;
2904
+ /** The convolution's own layout: the generator's binding numbers, not the mip blit's. */
2905
+ private readonly probePrefilterLayout;
2906
+ private readonly probeMipUniforms;
2907
+ private readonly probeSampler;
2908
+ /** The camera each face is drawn with. Reused, so a bake allocates nothing. */
2909
+ private readonly probeCamera;
2910
+ /** Whether anything has been rendered into it. The shader must not sample an unfilled cube. */
2911
+ /**
2912
+ * One flag a layer, because a grid may be baked a probe at a time across frames.
2913
+ *
2914
+ * **The gate is the whole grid rather than any part of it.** A fragment blending eight corners
2915
+ * would read whatever the device left in the layers that have not been filled, and a car
2916
+ * mirroring uninitialised memory is worse than a car mirroring a gradient — the rule one probe
2917
+ * already had, unchanged by there being more of them.
2918
+ */
2919
+ private probeFilled;
2920
+ private probeFilledCount;
2921
+ /**
2922
+ * Whether a bake is running, which is when the cube may be neither sampled nor bound.
2923
+ *
2924
+ * `renderer.ts` carries the same flag under the same name and for the same reason. Here it is
2925
+ * load-bearing rather than defensive: WebGPU rejects a pass that holds the texture it writes.
2926
+ */
2927
+ private probePassActive;
2928
+ /** How many mip levels the roughness blur has to work with. `flat.ts` reads this. */
2929
+ private get probeMaxLod();
2930
+ /** Whether every layer of the grid has been convolved. See `probeFilled`. */
2931
+ private get probeBaked();
2932
+ /** Record that one layer now holds a convolution, without counting a rebake twice. */
2933
+ private markProbeFilled;
2934
+ /**
2935
+ * The array every probe is a layer of, sized for a grid.
2936
+ *
2937
+ * Separate from the constructor because a grid is declared after it, and the flat bind groups
2938
+ * hold whatever texture existed when they were built — which is why the caller rebuilds them.
2939
+ */
2940
+ private allocateProbeArray;
2941
+ /**
2942
+ * Declare where a grid's probes stand, and allocate the layers for them.
2943
+ *
2944
+ * **The flat bind groups are rebuilt when the array is replaced**, and that is the trap this
2945
+ * backend documents elsewhere: a group is built once and cached, so a texture created later
2946
+ * resolves to whatever the group was built with, forever, with no error anywhere.
2947
+ */
2948
+ setProbeGrid(options: ProbeGridOptions): boolean;
2949
+ /** Bake every probe of the declared grid, in one call. See the other backend's own note. */
2950
+ bakeProbeGrid(clearColor: Vec3, drawFace: (camera: Camera) => void, options?: ProbeBakeOptions): boolean;
2951
+ /**
2952
+ * Capture the room into a cubemap, once, so reflective surfaces can mirror it.
2953
+ *
2954
+ * `drawFace` is handed a camera already aimed and submits the scene exactly as it would to the
2955
+ * screen. Six passes, one submission, at the moment a caller says the room is finished — then
2956
+ * a cubemap fetch per reflective pixel for ever after.
2957
+ *
2958
+ * **The chain afterwards is the roughness**, not a nicety: a polished surface samples level 0
2959
+ * and a satin one samples further up. WebGPU has no `generateMipmap`, so it is built here by
2960
+ * rendering each level from the one above, sampling the cube by direction so the blur crosses
2961
+ * face edges instead of seaming along them.
2962
+ *
2963
+ * Returns whether the bake happened, as `renderer.ts` does: false means a caller carries on
2964
+ * with the sky-and-ground gradient `flat.ts` had before probes existed.
2965
+ */
2966
+ bakeReflectionProbe(origin: Vec3, clearColor: Vec3, drawFace: (camera: Camera) => void, options?: ProbeBakeOptions): boolean;
2967
+ /**
2968
+ * Bake one probe of the declared grid: six faces into the scratch cube, then one convolution.
2969
+ *
2970
+ * One probe per call, so a grid can be paced across frames. See the other backend's own note on
2971
+ * why a bake is the budget question a grid raises rather than the texture units.
2972
+ */
2973
+ bakeProbe(layer: number, clearColor: Vec3, drawFace: (camera: Camera) => void, options?: ProbeBakeOptions): boolean;
2974
+ /**
2975
+ * The room's diffuse light, projected once — started here and deliberately not awaited.
2976
+ *
2977
+ * **The caller is drawing a frame around this call**, and where a bake sits inside a frame is the
2978
+ * scene's decision rather than this backend's, so `bakeReflectionProbe` cannot become
2979
+ * asynchronous to hide the fact that this is. `uEnvIrradianceEnabled` stays 0 until the promise
2980
+ * resolves, a frame or two later, and until then the picture is exactly what it was.
2981
+ *
2982
+ * **A refusal is not a failed bake.** The reflection is already in the cube and is unaffected, so
2983
+ * this is a missing improvement rather than a fault, and it says so once: a device that refuses
2984
+ * one readback refuses every one and a warning per scene is a flood.
2985
+ */
2986
+ /**
2987
+ * Light the scene from an environment it did not photograph.
2988
+ *
2989
+ * **Downstream of the cube this is exactly a bake**, which is the design rather than a
2990
+ * convenience: the faces are written into the same texture a bake fills, `buildProbeChain` runs
2991
+ * the same box chain and the same convolution over them, and `projectProbeIrradiance` reads them
2992
+ * back the same way. A loaded sky is therefore not a second lighting path that can be wrong on
2993
+ * its own.
2994
+ *
2995
+ * **Its own encoder, submitted here.** A bake has a frame's encoder to borrow; this can be
2996
+ * called before `beginFrame` has opened one, and recording into a null encoder is the shape of
2997
+ * defect `beginPlanarReflection` already paid for — it answered null before a frame existed and
2998
+ * a consumer read that as "no target" on every frame.
2999
+ */
3000
+ setEnvironmentImage(image: {
3001
+ readonly width: number;
3002
+ readonly height: number;
3003
+ readonly data: Float32Array;
3004
+ }, options?: ProbeBakeOptions): boolean;
3005
+ /**
3006
+ * Upload the photometric profiles a consumer loaded, and rebuild the bind group that holds them.
3007
+ *
3008
+ * **Once per set rather than per frame**, because a fixture's distribution is an asset and does
3009
+ * not change while the game runs. Passing an empty list clears them back to the single row of
3010
+ * ones, which is the multiplicative identity — a scene that drops its profiles goes back to
3011
+ * plain lights rather than going black.
3012
+ */
3013
+ /**
3014
+ * Upload the cookies a consumer loaded, and bind the atlas.
3015
+ *
3016
+ * The same contract `Renderer.setSpotCookies` states: one row of square tiles, `COOKIE_TILE` a
3017
+ * side, an empty list clearing back to the white texel that is the multiplicative identity.
3018
+ *
3019
+ * **`copyExternalImageToTexture` per tile**, which is what puts a decoded image on the device
3020
+ * here — the same call `createSurfaceTexture` makes, and it takes a destination origin, so one
3021
+ * call a tile fills the row without a canvas in between.
3022
+ */
3023
+ setSpotCookies(images: readonly TexImageSource[]): void;
3024
+ setIesProfiles(profiles: readonly PhotometricProfile[]): void;
3025
+ /** Each level from the one above it, six faces at a time. See `probePass.ts`. */
3026
+ private buildProbeChain;
3027
+ /**
3028
+ * Allocate the mirror at the size the frame is about to be, and rebind the water to it.
3029
+ *
3030
+ * **From `resize` rather than from the first frame that shows water**, which is the reason
3031
+ * `planarReflection.ts` gives on the other side: allocated lazily it lands in whichever frame
3032
+ * first puts water on screen, measured there as 28% of a 62 ms frame — a stall in the middle
3033
+ * of play rather than during a load.
3034
+ *
3035
+ * Rebuilding the water bind group is not optional. A bind group holds a *view*, and a resized
3036
+ * target is a different texture; keeping the old group would sample a destroyed one.
3037
+ */
3038
+ /**
3039
+ * The composite's targets, sized with the drawing buffer.
3040
+ *
3041
+ * Allocated from `resize` rather than from the first frame that needs one, which is the
3042
+ * argument `planarReflection.ts` makes and the same cost: a full-size colour target paid for
3043
+ * in the middle of play is a stall a player feels, and paid here it lands where the rest of
3044
+ * the frame's memory is paid.
3045
+ *
3046
+ * The occlusion and bloom targets are half size. Both are blurs read back at full size, so a
3047
+ * full-resolution intermediate buys nothing a bilinear fetch does not already give.
3048
+ */
3049
+ private ensureComposite;
3050
+ /**
3051
+ * Every bloom stage's uniform block, written once per size rather than once per frame.
3052
+ *
3053
+ * **By name off each stage's own layout**, and that is not ceremony: the three blocks are all
3054
+ * sixteen bytes and hold different things — a texel and a threshold, a texel, a radius — so a
3055
+ * field read off the wrong one lands at a plausible offset and writes a plausible number.
3056
+ * `postField` throws on a name a block does not have, which is what turns that into a crash at
3057
+ * init rather than an effect that quietly runs on somebody else's uniform.
3058
+ */
3059
+ private writeBloomBlocks;
3060
+ private ensureReflection;
3061
+ /** What is under a pixel. See `Renderer.registerPickable` in `renderer.ts` for why this answers only *what* and dispatches no events. */
3062
+ registerPickable(source: PickableSource, model: Float32Array): number;
3063
+ updatePickable(handle: number, model: Float32Array): void;
3064
+ unregisterPickable(handle: number): void;
3065
+ pickAt(camera: Camera, cssX: number, cssY: number): PickHit | null;
3066
+ /**
3067
+ * Give the device back, and everything registered against it first.
3068
+ *
3069
+ * **The registries are drained before `surface.dispose()`, and the order is the whole of it.**
3070
+ * That call ends in `device.destroy()` — see `device.ts` — and a definition releasing a buffer
3071
+ * against a destroyed device is the one way this goes wrong. Nothing released them at all until
3072
+ * now: `unregisterPass` and `unregisterCompute` always have, and neither was called on teardown,
3073
+ * so every pipeline and bind group a contributor built outlived the renderer that asked for it.
3074
+ *
3075
+ * Unconditional here where the WebGL2 side asks whether it still has a context, because the two
3076
+ * backends fail differently: a lost GL context makes deleting its objects an error, and a lost
3077
+ * WebGPU device swallows the call instead. Each backend does what is true of it, which is what
3078
+ * the 2026-08-13 rule asks for — one decision, bound twice.
3079
+ */
3080
+ dispose(): void;
3081
+ }
3082
+ export {};