@driftengine/core 3.62.0 → 4.0.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 (428) hide show
  1. package/CHANGELOG.json +122 -0
  2. package/NOTICE +20 -0
  3. package/README.md +1 -1
  4. package/dist/core/loop.js +18 -2
  5. package/dist/core/storage.js +5 -0
  6. package/dist/geometry/meshBuilder.d.ts +1 -1
  7. package/dist/geometry/meshBuilder.js +6 -2
  8. package/dist/index.d.ts +70 -4
  9. package/dist/index.js +123 -2
  10. package/dist/input/input.js +6 -1
  11. package/dist/math/exact.d.ts +13 -3
  12. package/dist/math/exact.js +68 -3
  13. package/dist/math/frustum.d.ts +17 -2
  14. package/dist/math/frustum.js +24 -0
  15. package/dist/render/backend/api.d.ts +34 -1
  16. package/dist/render/backend/api.js +22 -1
  17. package/dist/render/backend/budget.d.ts +7 -6
  18. package/dist/render/backend/budget.js +7 -6
  19. package/dist/render/backend/createRenderer.d.ts +57 -0
  20. package/dist/render/backend/createRenderer.js +51 -2
  21. package/dist/render/backend/materialChanges.d.ts +41 -0
  22. package/dist/render/backend/materialChanges.js +38 -0
  23. package/dist/render/backend/select.js +8 -0
  24. package/dist/render/backend/webgl2/renderer.d.ts +80 -1
  25. package/dist/render/backend/webgl2/renderer.js +208 -10
  26. package/dist/render/backend/webgpu/buffers.d.ts +20 -1
  27. package/dist/render/backend/webgpu/buffers.js +116 -58
  28. package/dist/render/backend/webgpu/fieldCompose.d.ts +156 -0
  29. package/dist/render/backend/webgpu/fieldCompose.js +396 -0
  30. package/dist/render/backend/webgpu/giFieldPass.d.ts +66 -0
  31. package/dist/render/backend/webgpu/giFieldPass.js +137 -0
  32. package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +728 -0
  33. package/dist/render/backend/webgpu/gpuDrivenPass.js +2473 -0
  34. package/dist/render/backend/webgpu/linePass.d.ts +4 -14
  35. package/dist/render/backend/webgpu/linePass.js +4 -33
  36. package/dist/render/backend/webgpu/probeBake.d.ts +148 -0
  37. package/dist/render/backend/webgpu/probeBake.js +493 -0
  38. package/dist/render/backend/webgpu/renderer.d.ts +379 -24
  39. package/dist/render/backend/webgpu/renderer.js +1369 -76
  40. package/dist/render/backend/webgpu/scatterPass.js +15 -1
  41. package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +47 -14
  42. package/dist/render/backend/webgpu/surfaceTexturePass.js +128 -80
  43. package/dist/render/causticsRenderer.d.ts +3 -1
  44. package/dist/render/causticsRenderer.js +4 -1
  45. package/dist/render/depthConvention.d.ts +12 -0
  46. package/dist/render/depthConvention.js +14 -0
  47. package/dist/render/deviceFeatures.d.ts +32 -0
  48. package/dist/render/deviceFeatures.js +41 -0
  49. package/dist/render/fog.d.ts +79 -0
  50. package/dist/render/fog.js +138 -0
  51. package/dist/render/frame/alias.d.ts +30 -0
  52. package/dist/render/frame/alias.js +61 -0
  53. package/dist/render/frame/deps.d.ts +37 -0
  54. package/dist/render/frame/deps.js +122 -0
  55. package/dist/render/frame/flushGraph.d.ts +43 -0
  56. package/dist/render/frame/flushGraph.js +84 -0
  57. package/dist/render/frame/graphSchedule.d.ts +49 -0
  58. package/dist/render/frame/graphSchedule.js +120 -0
  59. package/dist/render/frame/index.d.ts +15 -0
  60. package/dist/render/frame/index.js +8 -0
  61. package/dist/render/frame/lifetime.d.ts +24 -0
  62. package/dist/render/frame/lifetime.js +36 -0
  63. package/dist/render/frame/passTimings.d.ts +42 -0
  64. package/dist/render/frame/passTimings.js +38 -0
  65. package/dist/render/frame/validate.d.ts +15 -0
  66. package/dist/render/frame/validate.js +22 -0
  67. package/dist/render/frame/virtual.d.ts +44 -0
  68. package/dist/render/frame/virtual.js +61 -0
  69. package/dist/render/gi/chain.d.ts +82 -0
  70. package/dist/render/gi/chain.js +135 -0
  71. package/dist/render/gi/denoise.d.ts +114 -0
  72. package/dist/render/gi/denoise.js +169 -0
  73. package/dist/render/gi/fieldScene.d.ts +125 -0
  74. package/dist/render/gi/fieldScene.js +219 -0
  75. package/dist/render/gi/globalField.d.ts +157 -0
  76. package/dist/render/gi/globalField.js +373 -0
  77. package/dist/render/gi/probeGridFit.d.ts +24 -0
  78. package/dist/render/gi/probeGridFit.js +71 -0
  79. package/dist/render/gi/probeTrace.d.ts +82 -0
  80. package/dist/render/gi/probeTrace.js +128 -0
  81. package/dist/render/gi/probeVolume.d.ts +136 -0
  82. package/dist/render/gi/probeVolume.js +191 -0
  83. package/dist/render/gi/reflection.d.ts +73 -0
  84. package/dist/render/gi/reflection.js +109 -0
  85. package/dist/render/gi/traceField.d.ts +91 -0
  86. package/dist/render/gi/traceField.js +129 -0
  87. package/dist/render/gi/traceScreen.d.ts +68 -0
  88. package/dist/render/gi/traceScreen.js +82 -0
  89. package/dist/render/gpuScene.d.ts +38 -0
  90. package/dist/render/gpuScene.js +41 -0
  91. package/dist/render/gpudriven/alphaTest.d.ts +28 -0
  92. package/dist/render/gpudriven/alphaTest.js +32 -0
  93. package/dist/render/gpudriven/clusterUpload.d.ts +59 -0
  94. package/dist/render/gpudriven/clusterUpload.js +75 -0
  95. package/dist/render/gpudriven/clusterWorld.d.ts +57 -0
  96. package/dist/render/gpudriven/clusterWorld.js +155 -0
  97. package/dist/render/gpudriven/compact.d.ts +51 -0
  98. package/dist/render/gpudriven/compact.js +68 -0
  99. package/dist/render/gpudriven/cullClusters.d.ts +129 -0
  100. package/dist/render/gpudriven/cullClusters.js +237 -0
  101. package/dist/render/gpudriven/decodeTables.d.ts +64 -0
  102. package/dist/render/gpudriven/decodeTables.js +219 -0
  103. package/dist/render/gpudriven/frustum.d.ts +25 -0
  104. package/dist/render/gpudriven/frustum.js +59 -0
  105. package/dist/render/gpudriven/geometryHarness.d.ts +15 -0
  106. package/dist/render/gpudriven/geometryHarness.js +31 -0
  107. package/dist/render/gpudriven/hzb.d.ts +60 -0
  108. package/dist/render/gpudriven/hzb.js +97 -0
  109. package/dist/render/gpudriven/ibl.d.ts +85 -0
  110. package/dist/render/gpudriven/ibl.js +129 -0
  111. package/dist/render/gpudriven/indirect.d.ts +67 -0
  112. package/dist/render/gpudriven/indirect.js +77 -0
  113. package/dist/render/gpudriven/instances.d.ts +43 -0
  114. package/dist/render/gpudriven/instances.js +172 -0
  115. package/dist/render/gpudriven/lit.d.ts +165 -0
  116. package/dist/render/gpudriven/lit.js +165 -0
  117. package/dist/render/gpudriven/lodCut.d.ts +28 -0
  118. package/dist/render/gpudriven/lodCut.js +38 -0
  119. package/dist/render/gpudriven/materialBin.d.ts +60 -0
  120. package/dist/render/gpudriven/materialBin.js +108 -0
  121. package/dist/render/gpudriven/materialTable.d.ts +88 -0
  122. package/dist/render/gpudriven/materialTable.js +115 -0
  123. package/dist/render/gpudriven/pipeline.d.ts +135 -0
  124. package/dist/render/gpudriven/pipeline.js +237 -0
  125. package/dist/render/gpudriven/rangeAlloc.d.ts +50 -0
  126. package/dist/render/gpudriven/rangeAlloc.js +112 -0
  127. package/dist/render/gpudriven/sceneUpload.d.ts +96 -0
  128. package/dist/render/gpudriven/sceneUpload.js +115 -0
  129. package/dist/render/gpudriven/shadeBins.d.ts +92 -0
  130. package/dist/render/gpudriven/shadeBins.js +182 -0
  131. package/dist/render/gpudriven/shadow.d.ts +136 -0
  132. package/dist/render/gpudriven/shadow.js +224 -0
  133. package/dist/render/gpudriven/shadowCamera.d.ts +87 -0
  134. package/dist/render/gpudriven/shadowCamera.js +345 -0
  135. package/dist/render/gpudriven/streamScene.d.ts +194 -0
  136. package/dist/render/gpudriven/streamScene.js +459 -0
  137. package/dist/render/gpudriven/surfaceFrame.d.ts +25 -0
  138. package/dist/render/gpudriven/surfaceFrame.js +51 -0
  139. package/dist/render/gpudriven/twoPhase.d.ts +122 -0
  140. package/dist/render/gpudriven/twoPhase.js +160 -0
  141. package/dist/render/gpudriven/visbuffer.d.ts +25 -0
  142. package/dist/render/gpudriven/visbuffer.js +37 -0
  143. package/dist/render/halfFloat.d.ts +10 -0
  144. package/dist/render/halfFloat.js +23 -4
  145. package/dist/render/inference/attentionTiled.d.ts +27 -0
  146. package/dist/render/inference/attentionTiled.js +117 -0
  147. package/dist/render/inference/denseKernels.d.ts +29 -0
  148. package/dist/render/inference/denseKernels.js +312 -0
  149. package/dist/render/inference/device.d.ts +21 -0
  150. package/dist/render/inference/device.js +32 -0
  151. package/dist/render/inference/deviceGraph.d.ts +34 -0
  152. package/dist/render/inference/deviceGraph.js +17 -0
  153. package/dist/render/inference/fuse.d.ts +15 -0
  154. package/dist/render/inference/fuse.js +37 -0
  155. package/dist/render/inference/kernelKit.d.ts +52 -0
  156. package/dist/render/inference/kernelKit.js +70 -0
  157. package/dist/render/inference/kernels.d.ts +3 -0
  158. package/dist/render/inference/kernels.js +11 -0
  159. package/dist/render/inference/plan.d.ts +13 -0
  160. package/dist/render/inference/plan.js +18 -0
  161. package/dist/render/inference/reuse.d.ts +39 -0
  162. package/dist/render/inference/reuse.js +65 -0
  163. package/dist/render/inference/runner.d.ts +31 -0
  164. package/dist/render/inference/runner.js +175 -0
  165. package/dist/render/inference/schedule.d.ts +39 -0
  166. package/dist/render/inference/schedule.js +59 -0
  167. package/dist/render/inference/shapeKernels.d.ts +10 -0
  168. package/dist/render/inference/shapeKernels.js +80 -0
  169. package/dist/render/inference/spatialKernels.d.ts +21 -0
  170. package/dist/render/inference/spatialKernels.js +179 -0
  171. package/dist/render/inference/splatRaster.wgsl.d.ts +75 -0
  172. package/dist/render/inference/splatRaster.wgsl.js +337 -0
  173. package/dist/render/inference/splatTiles.d.ts +79 -0
  174. package/dist/render/inference/splatTiles.js +161 -0
  175. package/dist/render/lineBatch.d.ts +2 -5
  176. package/dist/render/lineBatch.js +4 -24
  177. package/dist/render/mesh.d.ts +4 -0
  178. package/dist/render/mesh.js +45 -1
  179. package/dist/render/pass.d.ts +145 -8
  180. package/dist/render/prefilterEnvMap.d.ts +22 -0
  181. package/dist/render/prefilterEnvMap.js +31 -0
  182. package/dist/render/readback.d.ts +30 -0
  183. package/dist/render/readback.js +47 -0
  184. package/dist/render/recon/clamp.d.ts +36 -0
  185. package/dist/render/recon/clamp.js +92 -0
  186. package/dist/render/recon/disocclusion.d.ts +60 -0
  187. package/dist/render/recon/disocclusion.js +109 -0
  188. package/dist/render/recon/frameGen.d.ts +81 -0
  189. package/dist/render/recon/frameGen.js +150 -0
  190. package/dist/render/recon/frameSizes.d.ts +34 -0
  191. package/dist/render/recon/frameSizes.js +58 -0
  192. package/dist/render/recon/jitter.d.ts +60 -0
  193. package/dist/render/recon/jitter.js +118 -0
  194. package/dist/render/recon/motionVectors.d.ts +55 -0
  195. package/dist/render/recon/motionVectors.js +107 -0
  196. package/dist/render/recon/reproject.d.ts +41 -0
  197. package/dist/render/recon/reproject.js +131 -0
  198. package/dist/render/recon/resolve.d.ts +90 -0
  199. package/dist/render/recon/resolve.js +313 -0
  200. package/dist/render/recon/tier.d.ts +24 -0
  201. package/dist/render/recon/tier.js +8 -0
  202. package/dist/render/reflectionProbe.d.ts +26 -0
  203. package/dist/render/reflectionProbe.js +33 -0
  204. package/dist/render/renderQuality.d.ts +69 -0
  205. package/dist/render/renderQuality.js +22 -0
  206. package/dist/render/screenSpaceReflection.d.ts +23 -0
  207. package/dist/render/screenSpaceReflection.js +22 -3
  208. package/dist/render/sdfFont.js +2 -0
  209. package/dist/render/segmentQuads.d.ts +13 -0
  210. package/dist/render/segmentQuads.js +32 -0
  211. package/dist/render/shaders/flat/lobes.d.ts +27 -0
  212. package/dist/render/shaders/flat/lobes.js +33 -3
  213. package/dist/render/shaders/flat/preamble.d.ts +7 -0
  214. package/dist/render/shaders/flat/preamble.js +24 -1
  215. package/dist/render/shaders/flat/probeGrid.d.ts +1 -1
  216. package/dist/render/shaders/flat/probeGrid.js +71 -2
  217. package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -8
  218. package/dist/render/shaders/generated/flat.wgsl.js +64 -24
  219. package/dist/render/shaders/gi/composeField.wgsl.d.ts +56 -0
  220. package/dist/render/shaders/gi/composeField.wgsl.js +231 -0
  221. package/dist/render/shaders/gi/marchField.wgsl.d.ts +30 -0
  222. package/dist/render/shaders/gi/marchField.wgsl.js +141 -0
  223. package/dist/render/shaders/gi/probeBake.wgsl.d.ts +113 -0
  224. package/dist/render/shaders/gi/probeBake.wgsl.js +635 -0
  225. package/dist/render/shaders/gi/probeTrace.wgsl.d.ts +72 -0
  226. package/dist/render/shaders/gi/probeTrace.wgsl.js +261 -0
  227. package/dist/render/shaders/gi/probeVolume.wgsl.d.ts +49 -0
  228. package/dist/render/shaders/gi/probeVolume.wgsl.js +242 -0
  229. package/dist/render/shaders/gi/sampleField.wgsl.d.ts +32 -0
  230. package/dist/render/shaders/gi/sampleField.wgsl.js +134 -0
  231. package/dist/render/shaders/gpudriven/blendRaster.wgsl.d.ts +26 -0
  232. package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +339 -0
  233. package/dist/render/shaders/gpudriven/compact.wgsl.d.ts +29 -0
  234. package/dist/render/shaders/gpudriven/compact.wgsl.js +58 -0
  235. package/dist/render/shaders/gpudriven/cull.wgsl.d.ts +90 -0
  236. package/dist/render/shaders/gpudriven/cull.wgsl.js +423 -0
  237. package/dist/render/shaders/gpudriven/decode.wgsl.d.ts +72 -0
  238. package/dist/render/shaders/gpudriven/decode.wgsl.js +349 -0
  239. package/dist/render/shaders/gpudriven/environment.wgsl.d.ts +44 -0
  240. package/dist/render/shaders/gpudriven/environment.wgsl.js +137 -0
  241. package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +28 -0
  242. package/dist/render/shaders/gpudriven/fog.wgsl.js +107 -0
  243. package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +13 -0
  244. package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +62 -0
  245. package/dist/render/shaders/gpudriven/materialBin.wgsl.d.ts +51 -0
  246. package/dist/render/shaders/gpudriven/materialBin.wgsl.js +112 -0
  247. package/dist/render/shaders/gpudriven/materialTable.wgsl.d.ts +25 -0
  248. package/dist/render/shaders/gpudriven/materialTable.wgsl.js +43 -0
  249. package/dist/render/shaders/gpudriven/meshTransform.wgsl.d.ts +18 -0
  250. package/dist/render/shaders/gpudriven/meshTransform.wgsl.js +37 -0
  251. package/dist/render/shaders/gpudriven/present.wgsl.d.ts +43 -0
  252. package/dist/render/shaders/gpudriven/present.wgsl.js +127 -0
  253. package/dist/render/shaders/gpudriven/shade.wgsl.d.ts +127 -0
  254. package/dist/render/shaders/gpudriven/shade.wgsl.js +874 -0
  255. package/dist/render/shaders/gpudriven/shadow.wgsl.d.ts +44 -0
  256. package/dist/render/shaders/gpudriven/shadow.wgsl.js +308 -0
  257. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.d.ts +28 -0
  258. package/dist/render/shaders/gpudriven/visbufferRaster.wgsl.js +245 -0
  259. package/dist/render/shaders/network.wgsl.d.ts +101 -0
  260. package/dist/render/shaders/network.wgsl.js +206 -0
  261. package/dist/render/shaders/recon/motion.wgsl.d.ts +44 -0
  262. package/dist/render/shaders/recon/motion.wgsl.js +96 -0
  263. package/dist/render/shaders/recon/resolve.wgsl.d.ts +51 -0
  264. package/dist/render/shaders/recon/resolve.wgsl.js +733 -0
  265. package/dist/render/temporalAa.js +8 -33
  266. package/dist/render/textLayout.js +12 -6
  267. package/dist/render/textRenderer.d.ts +3 -1
  268. package/dist/render/textRenderer.js +4 -1
  269. package/dist/render/translucentQueue.d.ts +6 -0
  270. package/dist/render/translucentQueue.js +11 -0
  271. package/dist/render/windStreakRenderer.d.ts +2 -1
  272. package/dist/render/windStreakRenderer.js +3 -1
  273. package/dist/world/cell.d.ts +63 -0
  274. package/dist/world/cell.js +181 -0
  275. package/dist/world/cellStream.d.ts +103 -0
  276. package/dist/world/cellStream.js +198 -0
  277. package/dist/world/freeze.d.ts +81 -0
  278. package/dist/world/freeze.js +114 -0
  279. package/dist/world/rebase.d.ts +29 -0
  280. package/dist/world/rebase.js +41 -0
  281. package/package.json +8 -3
  282. package/scripts/frames.mjs +92 -0
  283. package/scripts/gpuCompute.mjs +323 -0
  284. package/scripts/png.d.mts +16 -0
  285. package/scripts/png.mjs +25 -0
  286. package/src/core/loop.ts +19 -2
  287. package/src/core/storage.ts +5 -0
  288. package/src/geometry/meshBuilder.ts +5 -1
  289. package/src/index.ts +261 -4
  290. package/src/input/input.ts +6 -1
  291. package/src/math/exact.ts +69 -3
  292. package/src/math/frustum.ts +52 -3
  293. package/src/render/backend/api.ts +42 -1
  294. package/src/render/backend/budget.ts +7 -6
  295. package/src/render/backend/createRenderer.ts +92 -3
  296. package/src/render/backend/materialChanges.ts +54 -0
  297. package/src/render/backend/select.ts +8 -0
  298. package/src/render/backend/webgl2/renderer.ts +211 -9
  299. package/src/render/backend/webgpu/buffers.ts +182 -69
  300. package/src/render/backend/webgpu/fieldCompose.ts +482 -0
  301. package/src/render/backend/webgpu/giFieldPass.ts +200 -0
  302. package/src/render/backend/webgpu/gpuDrivenPass.ts +2993 -0
  303. package/src/render/backend/webgpu/linePass.ts +4 -39
  304. package/src/render/backend/webgpu/probeBake.ts +588 -0
  305. package/src/render/backend/webgpu/renderer.ts +1481 -76
  306. package/src/render/backend/webgpu/scatterPass.ts +15 -1
  307. package/src/render/backend/webgpu/surfaceTexturePass.ts +158 -88
  308. package/src/render/causticsRenderer.ts +5 -2
  309. package/src/render/depthConvention.ts +15 -0
  310. package/src/render/deviceFeatures.ts +77 -0
  311. package/src/render/fog.ts +185 -0
  312. package/src/render/frame/alias.ts +97 -0
  313. package/src/render/frame/deps.ts +155 -0
  314. package/src/render/frame/flushGraph.ts +135 -0
  315. package/src/render/frame/graphSchedule.ts +170 -0
  316. package/src/render/frame/index.ts +52 -0
  317. package/src/render/frame/lifetime.ts +61 -0
  318. package/src/render/frame/passTimings.ts +69 -0
  319. package/src/render/frame/validate.ts +40 -0
  320. package/src/render/frame/virtual.ts +91 -0
  321. package/src/render/gi/chain.ts +225 -0
  322. package/src/render/gi/denoise.ts +276 -0
  323. package/src/render/gi/fieldScene.ts +264 -0
  324. package/src/render/gi/globalField.ts +543 -0
  325. package/src/render/gi/probeGridFit.ts +89 -0
  326. package/src/render/gi/probeTrace.ts +143 -0
  327. package/src/render/gi/probeVolume.ts +288 -0
  328. package/src/render/gi/reflection.ts +163 -0
  329. package/src/render/gi/traceField.ts +223 -0
  330. package/src/render/gi/traceScreen.ts +106 -0
  331. package/src/render/gpuScene.ts +80 -0
  332. package/src/render/gpudriven/alphaTest.ts +33 -0
  333. package/src/render/gpudriven/clusterUpload.ts +123 -0
  334. package/src/render/gpudriven/clusterWorld.ts +239 -0
  335. package/src/render/gpudriven/compact.ts +80 -0
  336. package/src/render/gpudriven/cullClusters.ts +333 -0
  337. package/src/render/gpudriven/decodeTables.ts +332 -0
  338. package/src/render/gpudriven/frustum.ts +70 -0
  339. package/src/render/gpudriven/geometryHarness.ts +51 -0
  340. package/src/render/gpudriven/hzb.ts +113 -0
  341. package/src/render/gpudriven/ibl.ts +147 -0
  342. package/src/render/gpudriven/indirect.ts +89 -0
  343. package/src/render/gpudriven/instances.ts +234 -0
  344. package/src/render/gpudriven/lit.ts +329 -0
  345. package/src/render/gpudriven/lodCut.ts +62 -0
  346. package/src/render/gpudriven/materialBin.ts +131 -0
  347. package/src/render/gpudriven/materialTable.ts +164 -0
  348. package/src/render/gpudriven/pipeline.ts +336 -0
  349. package/src/render/gpudriven/rangeAlloc.ts +122 -0
  350. package/src/render/gpudriven/sceneUpload.ts +173 -0
  351. package/src/render/gpudriven/shadeBins.ts +222 -0
  352. package/src/render/gpudriven/shadow.ts +285 -0
  353. package/src/render/gpudriven/shadowCamera.ts +406 -0
  354. package/src/render/gpudriven/streamScene.ts +575 -0
  355. package/src/render/gpudriven/surfaceFrame.ts +68 -0
  356. package/src/render/gpudriven/twoPhase.ts +214 -0
  357. package/src/render/gpudriven/visbuffer.ts +41 -0
  358. package/src/render/halfFloat.ts +22 -4
  359. package/src/render/inference/attentionTiled.ts +132 -0
  360. package/src/render/inference/denseKernels.ts +369 -0
  361. package/src/render/inference/device.ts +44 -0
  362. package/src/render/inference/deviceGraph.ts +39 -0
  363. package/src/render/inference/fuse.ts +47 -0
  364. package/src/render/inference/kernelKit.ts +120 -0
  365. package/src/render/inference/kernels.ts +18 -0
  366. package/src/render/inference/plan.ts +26 -0
  367. package/src/render/inference/reuse.ts +79 -0
  368. package/src/render/inference/runner.ts +204 -0
  369. package/src/render/inference/schedule.ts +80 -0
  370. package/src/render/inference/shapeKernels.ts +112 -0
  371. package/src/render/inference/spatialKernels.ts +220 -0
  372. package/src/render/inference/splatRaster.wgsl.ts +348 -0
  373. package/src/render/inference/splatTiles.ts +186 -0
  374. package/src/render/lineBatch.ts +4 -24
  375. package/src/render/mesh.ts +46 -1
  376. package/src/render/pass.ts +155 -10
  377. package/src/render/prefilterEnvMap.ts +33 -0
  378. package/src/render/readback.ts +73 -0
  379. package/src/render/recon/clamp.ts +113 -0
  380. package/src/render/recon/disocclusion.ts +143 -0
  381. package/src/render/recon/frameGen.ts +191 -0
  382. package/src/render/recon/frameSizes.ts +64 -0
  383. package/src/render/recon/jitter.ts +126 -0
  384. package/src/render/recon/motionVectors.ts +138 -0
  385. package/src/render/recon/reproject.ts +179 -0
  386. package/src/render/recon/resolve.ts +444 -0
  387. package/src/render/recon/tier.ts +31 -0
  388. package/src/render/reflectionProbe.ts +34 -0
  389. package/src/render/renderQuality.ts +87 -0
  390. package/src/render/screenSpaceReflection.ts +37 -3
  391. package/src/render/sdfFont.ts +2 -0
  392. package/src/render/segmentQuads.ts +39 -0
  393. package/src/render/shaders/flat/lobes.ts +35 -3
  394. package/src/render/shaders/flat/preamble.ts +25 -1
  395. package/src/render/shaders/flat/probeGrid.ts +77 -2
  396. package/src/render/shaders/generated/flat.wgsl.ts +64 -24
  397. package/src/render/shaders/gi/composeField.wgsl.ts +239 -0
  398. package/src/render/shaders/gi/marchField.wgsl.ts +146 -0
  399. package/src/render/shaders/gi/probeBake.wgsl.ts +649 -0
  400. package/src/render/shaders/gi/probeTrace.wgsl.ts +269 -0
  401. package/src/render/shaders/gi/probeVolume.wgsl.ts +246 -0
  402. package/src/render/shaders/gi/sampleField.wgsl.ts +140 -0
  403. package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +343 -0
  404. package/src/render/shaders/gpudriven/compact.wgsl.ts +59 -0
  405. package/src/render/shaders/gpudriven/cull.wgsl.ts +433 -0
  406. package/src/render/shaders/gpudriven/decode.wgsl.ts +373 -0
  407. package/src/render/shaders/gpudriven/environment.wgsl.ts +141 -0
  408. package/src/render/shaders/gpudriven/fog.wgsl.ts +110 -0
  409. package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +62 -0
  410. package/src/render/shaders/gpudriven/materialBin.wgsl.ts +116 -0
  411. package/src/render/shaders/gpudriven/materialTable.wgsl.ts +46 -0
  412. package/src/render/shaders/gpudriven/meshTransform.wgsl.ts +37 -0
  413. package/src/render/shaders/gpudriven/present.wgsl.ts +129 -0
  414. package/src/render/shaders/gpudriven/shade.wgsl.ts +884 -0
  415. package/src/render/shaders/gpudriven/shadow.wgsl.ts +322 -0
  416. package/src/render/shaders/gpudriven/visbufferRaster.wgsl.ts +251 -0
  417. package/src/render/shaders/network.wgsl.ts +238 -0
  418. package/src/render/shaders/recon/motion.wgsl.ts +101 -0
  419. package/src/render/shaders/recon/resolve.wgsl.ts +741 -0
  420. package/src/render/temporalAa.ts +9 -33
  421. package/src/render/textLayout.ts +12 -6
  422. package/src/render/textRenderer.ts +5 -2
  423. package/src/render/translucentQueue.ts +11 -0
  424. package/src/render/windStreakRenderer.ts +4 -2
  425. package/src/world/cell.ts +231 -0
  426. package/src/world/cellStream.ts +278 -0
  427. package/src/world/freeze.ts +157 -0
  428. package/src/world/rebase.ts +56 -0
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Running a WGSL compute shader and reading its buffers back.
3
+ *
4
+ * **This is what lets a compute shader have a test.** The repository's discipline for anything
5
+ * written twice — the splat shader, the decode interpreter, the heightfield triangulation — is one
6
+ * authored expression, two consumers, and a test that they have not drifted. Every compute pass in
7
+ * the GPU-driven pipeline is arithmetic written once in TypeScript as the reference and once in
8
+ * WGSL for the device, and until now there was no way to compare them: the TypeScript half had
9
+ * tests and the WGSL half had a generator.
10
+ *
11
+ * **A page rather than a headless device, because WebGPU needs a secure context.** `navigator.gpu`
12
+ * is not exposed on `about:blank` — `browser.mjs`'s own header records that, and records that the
13
+ * first reading of the absence was that the card had no adapter. `http://localhost` is a secure
14
+ * context, so this serves one blank page from a loopback server of its own. No dev server, no
15
+ * build step, nothing to start first.
16
+ *
17
+ * **One browser for many shaders.** Launching Chrome costs a second or two and a parity check runs
18
+ * hundreds of cases, so `openGpuCompute` hands back a handle that keeps the device and the page.
19
+ *
20
+ * Not a `*.test.mjs`: it needs a GPU, and `npm run test:scripts` runs on machines that have none.
21
+ * The checks that use it are run by hand, the way `probe-check.mjs` and `ibl-check.mjs` are.
22
+ */
23
+ import { createServer } from 'node:http';
24
+
25
+ import { launch } from './browser.mjs';
26
+ import { connect } from './cdp.mjs';
27
+
28
+ const PAGE = `<!doctype html><meta charset="utf-8"><title>gpu compute</title><body></body>`;
29
+
30
+ /** A loopback server serving one blank page, which is all a secure context needs. */
31
+ async function servePage() {
32
+ const server = createServer((request, response) => {
33
+ response.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
34
+ response.end(PAGE);
35
+ });
36
+ await new Promise((resolve) => {
37
+ server.listen(0, '127.0.0.1', resolve);
38
+ });
39
+ const { port } = server.address();
40
+ return {
41
+ url: `http://localhost:${port}/`,
42
+ close: () =>
43
+ new Promise((resolve) => {
44
+ server.close(resolve);
45
+ }),
46
+ };
47
+ }
48
+
49
+ /**
50
+ * The script the page runs, as a string, because it executes in the browser rather than here.
51
+ *
52
+ * Buffers cross as plain number arrays. That costs a copy per call and buys not having to agree
53
+ * about a binary encoding across the CDP boundary — at the sizes a parity check uses, hundreds of
54
+ * spheres rather than millions, the copy is not the cost that matters.
55
+ */
56
+ function dispatchScript(request) {
57
+ return `(async () => {
58
+ if (!('gpu' in navigator)) return { error: 'no navigator.gpu' };
59
+ const adapter = await navigator.gpu.requestAdapter();
60
+ if (adapter === null) return { error: 'no adapter' };
61
+ const request = ${JSON.stringify(request)};
62
+ /* Asked for by name and refused by name: a check that quietly ran without its feature did not run. */
63
+ const missing = request.features.filter((feature) => !adapter.features.has(feature));
64
+ if (missing.length > 0) return { error: 'the adapter lacks ' + missing.join(', ') };
65
+ const device = await adapter.requestDevice({ requiredFeatures: request.features });
66
+
67
+ const errors = [];
68
+ device.addEventListener('uncapturederror', (event) => errors.push(String(event.error.message)));
69
+
70
+ const module = device.createShaderModule({ code: request.wgsl });
71
+ const info = await module.getCompilationInfo();
72
+ const fatal = info.messages.filter((message) => message.type === 'error');
73
+ if (fatal.length > 0) {
74
+ return { error: fatal.map((m) => \`\${m.lineNum}:\${m.linePos} \${m.message}\`).join('\\n') };
75
+ }
76
+
77
+ const entries = [];
78
+ const bindings = [];
79
+ const readbacks = [];
80
+ for (let at = 0; at < request.buffers.length; at += 1) {
81
+ const spec = request.buffers[at];
82
+ if (spec.kind === 'texture2dArray') {
83
+ const texture = device.createTexture({
84
+ size: [spec.size, spec.size, spec.layers],
85
+ format: 'rgba8unorm',
86
+ mipLevelCount: spec.levels.length,
87
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
88
+ });
89
+ for (let level = 0; level < spec.levels.length; level += 1) {
90
+ const edge = Math.max(1, spec.size >> level);
91
+ device.queue.writeTexture(
92
+ { texture, mipLevel: level },
93
+ new Uint8Array(spec.levels[level]),
94
+ { bytesPerRow: edge * 4, rowsPerImage: edge },
95
+ [edge, edge, spec.layers],
96
+ );
97
+ }
98
+ entries.push({ binding: at, resource: texture.createView({ dimension: '2d-array' }) });
99
+ bindings.push({
100
+ binding: at,
101
+ visibility: GPUShaderStage.COMPUTE,
102
+ texture: { sampleType: 'float', viewDimension: '2d-array' },
103
+ });
104
+ continue;
105
+ }
106
+ if (spec.kind === 'texture2d') {
107
+ const texture = device.createTexture({
108
+ size: [spec.width, spec.height, 1],
109
+ format: spec.format,
110
+ usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
111
+ });
112
+ const channels = spec.format === 'r32float' ? 1 : 4;
113
+ device.queue.writeTexture(
114
+ { texture },
115
+ new Float32Array(spec.values),
116
+ { bytesPerRow: spec.width * channels * 4, rowsPerImage: spec.height },
117
+ [spec.width, spec.height, 1],
118
+ );
119
+ entries.push({ binding: at, resource: texture.createView() });
120
+ bindings.push({
121
+ binding: at,
122
+ visibility: GPUShaderStage.COMPUTE,
123
+ /* A 32-bit float format is not filterable, so a shader reading one loads its texels. */
124
+ texture: { sampleType: 'unfilterable-float', viewDimension: '2d' },
125
+ });
126
+ continue;
127
+ }
128
+ if (spec.kind === 'sampler') {
129
+ const sampler = device.createSampler({
130
+ magFilter: 'linear',
131
+ minFilter: 'linear',
132
+ mipmapFilter: 'linear',
133
+ addressModeU: spec.address,
134
+ addressModeV: spec.address,
135
+ });
136
+ entries.push({ binding: at, resource: sampler });
137
+ bindings.push({ binding: at, visibility: GPUShaderStage.COMPUTE, sampler: { type: 'filtering' } });
138
+ continue;
139
+ }
140
+ const Ctor =
141
+ spec.type === 'u32' ? Uint32Array
142
+ : spec.type === 'i32' ? Int32Array
143
+ : spec.type === 'f16' ? Uint16Array
144
+ : Float32Array;
145
+ /*
146
+ * An f16 buffer is written as the half-precision bits the caller hands over, and padded to
147
+ * four bytes, which is the least a storage binding may be sized in.
148
+ */
149
+ const count = spec.values.length > 0 ? spec.values.length : spec.length;
150
+ const values = new Ctor(spec.type === 'f16' ? Math.ceil(count / 2) * 2 : count);
151
+ if (spec.values.length > 0) values.set(spec.values);
152
+ /* A uniform binding is sized in whole sixteen-byte rows, and is never read back. */
153
+ const uniform = spec.kind === 'uniform';
154
+ const buffer = device.createBuffer({
155
+ size: uniform
156
+ ? Math.max(16, Math.ceil(values.byteLength / 16) * 16)
157
+ : Math.max(4, values.byteLength),
158
+ usage: uniform
159
+ ? GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
160
+ : GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
161
+ });
162
+ device.queue.writeBuffer(buffer, 0, values);
163
+ entries.push({ binding: at, resource: { buffer } });
164
+ bindings.push({
165
+ binding: at,
166
+ visibility: GPUShaderStage.COMPUTE,
167
+ buffer: { type: uniform ? 'uniform' : spec.readOnly ? 'read-only-storage' : 'storage' },
168
+ });
169
+ if (spec.read && !uniform) readbacks.push({ at, buffer, size: values.byteLength, type: spec.type });
170
+ }
171
+
172
+ const layout = device.createBindGroupLayout({ entries: bindings });
173
+ const pipeline = device.createComputePipeline({
174
+ layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
175
+ compute: { module, entryPoint: request.entryPoint },
176
+ });
177
+ const group = device.createBindGroup({ layout, entries });
178
+
179
+ const encoder = device.createCommandEncoder();
180
+ const pass = encoder.beginComputePass();
181
+ pass.setPipeline(pipeline);
182
+ pass.setBindGroup(0, group);
183
+ pass.dispatchWorkgroups(request.workgroups[0], request.workgroups[1], request.workgroups[2]);
184
+ pass.end();
185
+
186
+ const staging = readbacks.map((entry) => {
187
+ const copy = device.createBuffer({
188
+ size: entry.size,
189
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
190
+ });
191
+ encoder.copyBufferToBuffer(entry.buffer, 0, copy, 0, entry.size);
192
+ return copy;
193
+ });
194
+ device.queue.submit([encoder.finish()]);
195
+ await device.queue.onSubmittedWorkDone();
196
+
197
+ const out = {};
198
+ for (let at = 0; at < readbacks.length; at += 1) {
199
+ await staging[at].mapAsync(GPUMapMode.READ);
200
+ const entry = readbacks[at];
201
+ const Ctor = entry.type === 'u32' ? Uint32Array : entry.type === 'i32' ? Int32Array : Float32Array;
202
+ out[entry.at] = Array.from(new Ctor(staging[at].getMappedRange().slice(0)));
203
+ staging[at].unmap();
204
+ }
205
+ return errors.length > 0 ? { error: errors.join('\\n'), buffers: out } : { buffers: out };
206
+ })()`;
207
+ }
208
+
209
+ /**
210
+ * Open a browser with a WebGPU device on it.
211
+ *
212
+ * `run` takes the shader, the buffers it binds at group 0 in order, and the workgroup counts, and
213
+ * hands back whichever buffers were asked to be read. Buffers are declared rather than inferred:
214
+ * a harness that guessed which binding was an output would guess wrong on the first shader that
215
+ * writes into what it reads.
216
+ */
217
+ export async function openGpuCompute(options = {}) {
218
+ const page = await servePage();
219
+ const browser = await launch(options.launch ?? {});
220
+ const client = await connect(await browser.port);
221
+ const tab = await client.page(page.url, 320, 240);
222
+
223
+ return {
224
+ /**
225
+ * `features` are the device features the shader needs, `'shader-f16'` for a half-precision one;
226
+ * an adapter without one of them is an error rather than a device created without it.
227
+ *
228
+ * Each entry of `buffers` is bound at the binding its index names, as one of four kinds:
229
+ *
230
+ * - `'storage'`, the default — `type`, `values` or `length`, `read`, `readOnly`. An `'f16'`
231
+ * buffer takes half-precision bits as its values, and is never read back.
232
+ * - `'uniform'` — `{ kind: 'uniform', type: 'u32' | 'f32', values }`, bound as a uniform
233
+ * buffer padded to whole sixteen-byte rows, and never read back.
234
+ * - `'texture2dArray'` — `{ kind: 'texture2dArray', size, layers, levels }`: an `rgba8unorm`
235
+ * array of `size`-square layers with `levels.length` mip levels, where `levels[k]` is every
236
+ * layer's RGBA bytes at level `k`, layer after layer.
237
+ * - `'texture2d'` — `{ kind: 'texture2d', width, height, format, values }`: one mip level of
238
+ * `'r32float'` or `'rgba32float'`, written from `values` as float32, row after row. **Bound
239
+ * as `unfilterable-float`**, because no 32-bit float format is filterable without an optional
240
+ * feature — a shader reading one uses `textureLoad` and does any interpolation itself, which
241
+ * is what makes a comparison against a reference exact rather than a tolerance on whatever
242
+ * the device's sampler rounds to.
243
+ * - `'sampler'` — `{ kind: 'sampler', address: 'clamp-to-edge' | 'repeat' }`, linear in
244
+ * magnification, minification and between levels.
245
+ *
246
+ * @param {{ wgsl: string, entryPoint?: string, workgroups?: number[], features?: string[],
247
+ * buffers: { kind?: 'storage'|'uniform'|'texture2dArray'|'texture2d'|'sampler',
248
+ * type?: 'f32'|'f16'|'u32'|'i32', values?: number[], length?: number,
249
+ * read?: boolean, readOnly?: boolean, size?: number, layers?: number,
250
+ * levels?: ArrayLike<number>[], address?: string,
251
+ * width?: number, height?: number,
252
+ * format?: 'r32float'|'rgba32float' }[] }} request
253
+ */
254
+ async run(request) {
255
+ const prepared = {
256
+ wgsl: request.wgsl,
257
+ entryPoint: request.entryPoint ?? 'main',
258
+ features: request.features ?? [],
259
+ workgroups: [
260
+ request.workgroups?.[0] ?? 1,
261
+ request.workgroups?.[1] ?? 1,
262
+ request.workgroups?.[2] ?? 1,
263
+ ],
264
+ buffers: request.buffers.map((buffer) => ({
265
+ kind: buffer.kind ?? 'storage',
266
+ type: buffer.type ?? 'f32',
267
+ values: buffer.values === undefined ? [] : Array.from(buffer.values),
268
+ length: buffer.length ?? 0,
269
+ read: buffer.read === true,
270
+ readOnly: buffer.readOnly === true,
271
+ size: buffer.size ?? 0,
272
+ layers: buffer.layers ?? 0,
273
+ width: buffer.width ?? 0,
274
+ height: buffer.height ?? 0,
275
+ format: buffer.format ?? 'rgba32float',
276
+ levels:
277
+ buffer.levels === undefined ? [] : buffer.levels.map((level) => Array.from(level)),
278
+ address: buffer.address ?? 'clamp-to-edge',
279
+ })),
280
+ };
281
+ const result = await tab.eval(dispatchScript(prepared));
282
+ const parsed = typeof result === 'string' ? JSON.parse(result) : result;
283
+ if (parsed.error !== undefined && parsed.buffers === undefined) {
284
+ throw new Error(`gpu compute: ${parsed.error}`);
285
+ }
286
+ if (parsed.error !== undefined) throw new Error(`gpu compute: ${parsed.error}`);
287
+ return parsed.buffers;
288
+ },
289
+
290
+ /**
291
+ * What the page's adapter says it is — vendor, architecture, whether it is a fallback — so a
292
+ * figure a check prints names the device that produced it, and a software adapter is seen.
293
+ */
294
+ async adapter() {
295
+ const result = await tab.eval(`(async () => {
296
+ const adapter = await navigator.gpu?.requestAdapter();
297
+ if (!adapter) return { error: 'no adapter' };
298
+ const info = adapter.info ?? {};
299
+ return {
300
+ vendor: info.vendor ?? '',
301
+ architecture: info.architecture ?? '',
302
+ description: info.description ?? '',
303
+ fallback: adapter.isFallbackAdapter === true || info.isFallbackAdapter === true,
304
+ features: [...adapter.features],
305
+ };
306
+ })()`);
307
+ const parsed = typeof result === 'string' ? JSON.parse(result) : result;
308
+ if (parsed.error !== undefined) throw new Error(`gpu compute: ${parsed.error}`);
309
+ return parsed;
310
+ },
311
+
312
+ async close() {
313
+ try {
314
+ await tab.close();
315
+ } catch {
316
+ /* the page may already be gone; the browser close below is what matters */
317
+ }
318
+ await client.close?.();
319
+ await browser.close();
320
+ await page.close();
321
+ },
322
+ };
323
+ }
package/scripts/png.d.mts CHANGED
@@ -15,3 +15,19 @@ export function readPng(path: string): DecodedPng;
15
15
  * Node-side only — `node:zlib` is the whole of it, and nothing a browser loads links this.
16
16
  */
17
17
  export function encodePng(width: number, height: number, rgba: Buffer | Uint8Array): Buffer;
18
+
19
+ /** An image as four bytes a pixel, row-major. */
20
+ export interface RgbaImage {
21
+ readonly width: number;
22
+ readonly height: number;
23
+ readonly rgba: Uint8Array;
24
+ }
25
+
26
+ /** A decoded PNG as four bytes a pixel, whether it was written with three channels or four. */
27
+ export function rgbaOf(decoded: DecodedPng): RgbaImage;
28
+
29
+ /** A rectangle of a four-byte-a-pixel image, as a new image. */
30
+ export function crop(
31
+ image: RgbaImage,
32
+ rect: { readonly x: number; readonly y: number; readonly width: number; readonly height: number },
33
+ ): RgbaImage;
package/scripts/png.mjs CHANGED
@@ -199,3 +199,28 @@ function chunk(type, body) {
199
199
  out.writeUInt32BE(crc32(out.subarray(4, 8 + body.length)), 8 + body.length);
200
200
  return out;
201
201
  }
202
+
203
+ /** A decoded PNG as four bytes a pixel, whether it was written with three channels or four. */
204
+ export function rgbaOf(decoded) {
205
+ const count = decoded.width * decoded.height;
206
+ if (decoded.channels === 4)
207
+ return { width: decoded.width, height: decoded.height, rgba: decoded.pixels };
208
+ const rgba = new Uint8Array(count * 4);
209
+ for (let at = 0; at < count; at += 1) {
210
+ rgba[at * 4] = decoded.pixels[at * decoded.channels];
211
+ rgba[at * 4 + 1] = decoded.pixels[at * decoded.channels + 1];
212
+ rgba[at * 4 + 2] = decoded.pixels[at * decoded.channels + 2];
213
+ rgba[at * 4 + 3] = 255;
214
+ }
215
+ return { width: decoded.width, height: decoded.height, rgba };
216
+ }
217
+
218
+ /** A rectangle of a four-byte-a-pixel image, as a new image. */
219
+ export function crop(image, rect) {
220
+ const rgba = new Uint8Array(rect.width * rect.height * 4);
221
+ for (let y = 0; y < rect.height; y += 1) {
222
+ const from = ((rect.y + y) * image.width + rect.x) * 4;
223
+ rgba.set(image.rgba.subarray(from, from + rect.width * 4), y * rect.width * 4);
224
+ }
225
+ return { width: rect.width, height: rect.height, rgba };
226
+ }
package/src/core/loop.ts CHANGED
@@ -124,7 +124,19 @@ export function startLoop(hooks: LoopHooks, options: LoopOptions = {}): () => vo
124
124
  const fixedDt = options.fixedDt ?? 1 / 60;
125
125
  const maxFrameTime = options.maxFrameTime ?? 0.25;
126
126
 
127
- let last = performance.now();
127
+ /**
128
+ * When the last frame arrived, on the timebase the frames themselves use.
129
+ *
130
+ * **Null until the first frame whenever a source was supplied, because that source owns the
131
+ * timebase and this loop has no way to ask what it is.** Seeding from `performance.now()`
132
+ * regardless is what this did, and for the window it is right — a `DOMHighResTimeStamp` and
133
+ * `performance.now()` share an origin. For anything else it mixes two clocks: the synthetic
134
+ * session in `@driftengine/xr` counts from its own zero, so the first delta became sixteen
135
+ * milliseconds minus a process uptime, the accumulator went as negative as that, and the
136
+ * simulation stopped ticking until it climbed back — one dead frame per sixteen milliseconds the
137
+ * process had been alive. A caller that supplies the frames supplies the timebase.
138
+ */
139
+ let last: number | null = options.frameSource === undefined ? performance.now() : null;
128
140
  let accumulator = 0;
129
141
  /**
130
142
  * Fixed steps taken, offset by `startTick`.
@@ -145,8 +157,10 @@ export function startLoop(hooks: LoopHooks, options: LoopOptions = {}): () => vo
145
157
  * another, because the two run at different rates and share no accumulator worth carrying across.
146
158
  */
147
159
  const source: FrameSource = options.frameSource ?? {
160
+ // platform: browser default — `LoopOptions.frameSource` is the seam
148
161
  requestAnimationFrame: (callback) => requestAnimationFrame(callback),
149
162
  cancelAnimationFrame: (handle) => {
163
+ // platform: browser default — as above
150
164
  cancelAnimationFrame(handle);
151
165
  },
152
166
  };
@@ -154,6 +168,9 @@ export function startLoop(hooks: LoopHooks, options: LoopOptions = {}): () => vo
154
168
  const frame = (now: number, xrFrame?: unknown): void => {
155
169
  rafId = source.requestAnimationFrame(frame);
156
170
 
171
+ /* The first frame of a supplied source establishes the origin, so it advances nothing. */
172
+ const previous = last ?? now;
173
+
157
174
  /*
158
175
  * **Held: draw nothing, advance nothing, and bank nothing.**
159
176
  *
@@ -173,7 +190,7 @@ export function startLoop(hooks: LoopHooks, options: LoopOptions = {}): () => vo
173
190
  return;
174
191
  }
175
192
 
176
- let frameDt = (now - last) / 1000;
193
+ let frameDt = (now - previous) / 1000;
177
194
  last = now;
178
195
  const wallDt = frameDt;
179
196
  if (frameDt > maxFrameTime) frameDt = maxFrameTime;
@@ -65,6 +65,7 @@ export class BrowserStore implements KeyValueStore {
65
65
  read(key: string): string | null {
66
66
  if (!this.available) return this.fallback.read(key);
67
67
  try {
68
+ // platform: browser default — `KeyValueStore` is the seam; see `MemoryStore` beside it
68
69
  return localStorage.getItem(key);
69
70
  } catch {
70
71
  return this.fallback.read(key);
@@ -75,6 +76,7 @@ export class BrowserStore implements KeyValueStore {
75
76
  this.fallback.write(key, value);
76
77
  if (!this.available) return;
77
78
  try {
79
+ // platform: browser default — as above
78
80
  localStorage.setItem(key, value);
79
81
  } catch {
80
82
  // Quota exceeded or blocked mid-session; the memory copy still stands.
@@ -85,6 +87,7 @@ export class BrowserStore implements KeyValueStore {
85
87
  this.fallback.remove(key);
86
88
  if (!this.available) return;
87
89
  try {
90
+ // platform: browser default — as above
88
91
  localStorage.removeItem(key);
89
92
  } catch {
90
93
  // As above.
@@ -97,7 +100,9 @@ function probeLocalStorage(): boolean {
97
100
  // Reading is not enough of a probe: some browsers allow the read and throw
98
101
  // only on write, which would strand a store that reports itself healthy.
99
102
  const probe = '__driftengine_probe__';
103
+ // platform: feature probe — a blocked store must be found by trying it
100
104
  localStorage.setItem(probe, '1');
105
+ // platform: feature probe — as above
101
106
  localStorage.removeItem(probe);
102
107
  return true;
103
108
  } catch {
@@ -808,8 +808,12 @@ export class MeshBuilder {
808
808
  emissive = 0,
809
809
  segments = 12,
810
810
  rings = 6,
811
+ /* `addCapsule` has taken one since it existed; forwarding it is what was missing, and a
812
+ sphere that cannot carry the specular a box can is the reason no page here could draw a
813
+ polished surface under a direct light. */
814
+ specular = 0,
811
815
  ): this {
812
- return this.addCapsule(center, radius, 0, color, emissive, segments, rings);
816
+ return this.addCapsule(center, radius, 0, color, emissive, segments, rings, specular);
813
817
  }
814
818
 
815
819
  /** Add a faceted cylinder aligned to one principal axis. */