@woosh/meep-engine 3.60.3 → 3.60.5

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 (514) hide show
  1. package/CHANGELOG.md +2602 -0
  2. package/CONTRIBUTING.md +7 -0
  3. package/MIGRATION_2_TO_3.md +136 -0
  4. package/SECURITY.md +4 -0
  5. package/editor/EditorCameraNavigation.d.ts +2 -1
  6. package/editor/EditorCameraNavigation.d.ts.map +1 -1
  7. package/editor/EditorCameraNavigation.js +51 -57
  8. package/editor/ecs/component/editors/ecs/Trail3DEditor.d.ts +3 -0
  9. package/editor/ecs/component/editors/ecs/Trail3DEditor.d.ts.map +1 -1
  10. package/editor/ecs/component/editors/ecs/Trail3DEditor.js +6 -0
  11. package/package.json +6 -2
  12. package/src/engine/EngineHarness.d.ts +3 -9
  13. package/src/engine/EngineHarness.d.ts.map +1 -1
  14. package/src/engine/EngineHarness.js +6 -8
  15. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts +9 -6
  16. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts.map +1 -1
  17. package/src/engine/graphics/camera/makeOrbitalCameraController.js +44 -71
  18. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts +64 -0
  19. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts.map +1 -0
  20. package/src/engine/graphics/camera/orbital/MomentumAxis.js +187 -0
  21. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts +86 -0
  22. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts.map +1 -0
  23. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.js +319 -0
  24. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts +34 -10
  25. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts.map +1 -1
  26. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +116 -30
  27. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts +1 -7
  28. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  29. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +1 -37
  30. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometryFlags.js +1 -2
  31. package/src/engine/graphics/ecs/trail3d/TRAIL3D_FADE_LINEAR.d.ts +14 -0
  32. package/src/engine/graphics/ecs/trail3d/TRAIL3D_FADE_LINEAR.d.ts.map +1 -0
  33. package/src/engine/graphics/ecs/trail3d/TRAIL3D_FADE_LINEAR.js +22 -0
  34. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +19 -3
  35. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
  36. package/src/engine/graphics/ecs/trail3d/Trail3D.js +31 -4
  37. package/src/engine/graphics/ecs/trail3d/advance_trail.js +1 -1
  38. package/src/engine/graphics/ecs/water/Water.d.ts +8 -0
  39. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  40. package/src/engine/graphics/ecs/water/Water.js +8 -0
  41. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.d.ts +5 -5
  42. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.d.ts.map +1 -1
  43. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +6 -6
  44. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  45. package/src/engine/graphics3/WaterSystem.js +731 -734
  46. package/src/engine/graphics3/decal/DecalTileShader.d.ts +61 -0
  47. package/src/engine/graphics3/decal/DecalTileShader.d.ts.map +1 -0
  48. package/src/engine/graphics3/decal/DecalTileShader.js +138 -0
  49. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts +10 -4
  50. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
  51. package/src/engine/graphics3/decal/GPUDecalRenderer.js +149 -44
  52. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts +25 -0
  53. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts.map +1 -0
  54. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.js +55 -0
  55. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts +22 -0
  56. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts.map +1 -0
  57. package/src/engine/graphics3/decal/graph_gather_decal_tiles.js +70 -0
  58. package/src/engine/graphics3/decal/shader_decal_composite.d.ts +7 -5
  59. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  60. package/src/engine/graphics3/decal/shader_decal_composite.js +48 -44
  61. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts +12 -0
  62. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts.map +1 -0
  63. package/src/engine/graphics3/decal/shader_decal_tile_copy.js +78 -0
  64. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts +11 -0
  65. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts.map +1 -0
  66. package/src/engine/graphics3/decal/shader_gather_decal_tiles.js +80 -0
  67. package/src/engine/graphics3/trail/trail_tube_write_geometry.d.ts +10 -0
  68. package/src/engine/graphics3/trail/trail_tube_write_geometry.d.ts.map +1 -1
  69. package/src/engine/graphics3/trail/trail_tube_write_geometry.js +13 -1
  70. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts +13 -0
  71. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts.map +1 -0
  72. package/src/engine/input/devices/mouse/MouseEventButtons.js +23 -0
  73. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts +4 -0
  74. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts.map +1 -1
  75. package/src/engine/input/ecs/controllers/KeyboardCameraController.js +9 -45
  76. package/src/engine/physics/cloth/ecs/cloth_geodesic.d.ts +2 -2
  77. package/src/engine/physics/cloth/ecs/cloth_geodesic.js +2 -2
  78. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  79. package/src/shade/playground/probe_clipmap/README.md +1 -0
  80. package/src/shade/playground/probe_clipmap/index.html +5 -0
  81. package/src/shade/playground/probe_clipmap/main.d.ts.map +1 -1
  82. package/src/shade/playground/probe_clipmap/main.js +16 -2
  83. package/src/shade/playground/vgeo_scene/README.md +1 -1
  84. package/src/shade/playground/water_refraction/README.md +5 -6
  85. package/src/shade/playground/water_refraction/index.html +1 -1
  86. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  87. package/src/shade/renderer/Renderer.js +7 -5
  88. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts +4 -6
  89. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts.map +1 -1
  90. package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.js +34 -36
  91. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts +1 -1
  92. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts.map +1 -1
  93. package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.js +162 -163
  94. package/src/shade/renderer/atmosphere/compute_mie_absorption.d.ts +1 -1
  95. package/src/shade/renderer/atmosphere/compute_mie_absorption.js +1 -1
  96. package/src/shade/renderer/atmosphere/hillaire/ATMOSPHERE_EARTH.js +1 -1
  97. package/src/shade/renderer/atmosphere/hillaire/ATMOSPHERE_PARAMETERS_STRUCT.js +1 -1
  98. package/src/shade/renderer/atmosphere/shader_multiscatter_lut.js +164 -164
  99. package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.js +1 -1
  100. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts +40 -80
  101. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts.map +1 -1
  102. package/src/shade/renderer/camera/orbital/OrbitalCameraController.js +197 -147
  103. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  104. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  105. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  106. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  107. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  108. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +4 -4
  109. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +4 -4
  110. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +2 -1
  111. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
  112. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +18 -1
  113. package/src/shade/renderer/geometry/sdf/cards/mesh_cards_raster_pass_descriptor.js +2 -2
  114. package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_histogram.d.ts +1 -1
  115. package/src/shade/renderer/geometry/sdf/cards/shader_mesh_cards_histogram.js +1 -1
  116. package/src/shade/renderer/geometry/sdf/chunk_blas_query_band_sdf.d.ts +4 -5
  117. package/src/shade/renderer/geometry/sdf/chunk_blas_query_band_sdf.d.ts.map +1 -1
  118. package/src/shade/renderer/geometry/sdf/chunk_blas_query_band_sdf.js +6 -7
  119. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts +31 -0
  120. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts.map +1 -0
  121. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.js +343 -0
  122. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts +17 -0
  123. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts.map +1 -0
  124. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.js +29 -0
  125. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -1
  126. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +7 -13
  127. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  128. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +2 -8
  129. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +5 -5
  130. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
  131. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +10 -10
  132. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSettings.d.ts +12 -0
  133. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSettings.d.ts.map +1 -1
  134. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSettings.js +22 -0
  135. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +4 -4
  136. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  137. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +50 -13
  138. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts +6 -0
  139. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.d.ts.map +1 -1
  140. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP.js +7 -0
  141. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP_RESAMPLING.d.ts +13 -0
  142. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP_RESAMPLING.d.ts.map +1 -0
  143. package/src/shade/renderer/global_illumination/probe_clipmap/probe/PROBE_CLIPMAP_RESAMPLING.js +13 -0
  144. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts +17 -0
  145. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.d.ts.map +1 -1
  146. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap.js +20 -0
  147. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts +5 -15
  148. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts.map +1 -1
  149. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.js +6 -34
  150. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_resampled.d.ts +18 -0
  151. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_resampled.d.ts.map +1 -0
  152. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_resampled.js +42 -0
  153. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.d.ts.map +1 -1
  154. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_sample_radiance.js +5 -4
  155. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts +4 -0
  156. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.d.ts.map +1 -1
  157. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_accumulate.js +13 -2
  158. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_resample.d.ts +52 -0
  159. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_resample.d.ts.map +1 -0
  160. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_resample.js +250 -0
  161. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
  162. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +1 -7
  163. package/src/shade/renderer/hiz/HZBFootprint.d.ts +14 -0
  164. package/src/shade/renderer/hiz/HZBFootprint.d.ts.map +1 -0
  165. package/src/shade/renderer/hiz/HZBFootprint.js +16 -0
  166. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts +10 -0
  167. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts.map +1 -0
  168. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.js +11 -0
  169. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts +10 -0
  170. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts.map +1 -0
  171. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.js +11 -0
  172. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +0 -7
  173. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  174. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +405 -350
  175. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts +12 -0
  176. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts.map +1 -0
  177. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.js +27 -0
  178. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts +12 -0
  179. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts.map +1 -0
  180. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.js +27 -0
  181. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts +14 -0
  182. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts.map +1 -0
  183. package/src/shade/renderer/hiz/hzb_footprint_texels.js +34 -0
  184. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +0 -5
  185. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  186. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +37 -57
  187. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts +11 -0
  188. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts.map +1 -0
  189. package/src/shade/renderer/hiz/{shader_hzb_reduce_mip0_clip.js → shader_hzb_reduce_mip0.js} +120 -111
  190. package/src/shade/renderer/light/environment/environment_level_1_blur_plan.d.ts +39 -0
  191. package/src/shade/renderer/light/environment/environment_level_1_blur_plan.d.ts.map +1 -0
  192. package/src/shade/renderer/light/environment/environment_level_1_blur_plan.js +119 -0
  193. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts +15 -7
  194. package/src/shade/renderer/light/environment/execute_filter_environment_map.d.ts.map +1 -1
  195. package/src/shade/renderer/light/environment/execute_filter_environment_map.js +66 -83
  196. package/src/shade/renderer/light/environment/graph_filter_environment_map.d.ts.map +1 -1
  197. package/src/shade/renderer/light/environment/graph_filter_environment_map.js +15 -6
  198. package/src/shade/renderer/light/environment/shader_blur_environment_map.d.ts +3 -0
  199. package/src/shade/renderer/light/environment/shader_blur_environment_map.d.ts.map +1 -0
  200. package/src/shade/renderer/light/environment/shader_blur_environment_map.js +111 -0
  201. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_blurred.d.ts +3 -0
  202. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_blurred.d.ts.map +1 -0
  203. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_blurred.js +128 -0
  204. package/src/shade/renderer/light/environment/shader_downsample_environment_map.d.ts.map +1 -1
  205. package/src/shade/renderer/light/environment/shader_downsample_environment_map.js +0 -1
  206. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  207. package/src/shade/renderer/material/GPUMaterialManager.d.ts +7 -0
  208. package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
  209. package/src/shade/renderer/material/GPUMaterialManager.js +11 -0
  210. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +12 -12
  211. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  212. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +17 -118
  213. package/src/shade/renderer/postprocess/nss/NSS.d.ts +1 -3
  214. package/src/shade/renderer/postprocess/nss/NSS.d.ts.map +1 -1
  215. package/src/shade/renderer/postprocess/nss/NSS.js +1 -3
  216. package/src/shade/renderer/postprocess/nss/NSS_NETWORK_SCHEMA.d.ts +5 -6
  217. package/src/shade/renderer/postprocess/nss/NSS_NETWORK_SCHEMA.d.ts.map +1 -1
  218. package/src/shade/renderer/postprocess/nss/NSS_NETWORK_SCHEMA.js +9 -10
  219. package/src/shade/renderer/postprocess/nss/shader_nss_extract_feedback.d.ts.map +1 -1
  220. package/src/shade/renderer/postprocess/nss/shader_nss_extract_feedback.js +48 -49
  221. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.d.ts.map +1 -1
  222. package/src/shade/renderer/postprocess/nss/shader_nss_postprocess.js +12 -16
  223. package/src/shade/renderer/postprocess/nss/shader_nss_preprocess.js +246 -246
  224. package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.d.ts +1 -2
  225. package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.d.ts.map +1 -1
  226. package/src/shade/renderer/rasterize/chunk_triangle_winding_is_reversed.js +34 -35
  227. package/src/shade/renderer/shader/chunk/atmosphere/C_MIE_SCATTERING_DECLARATION.d.ts +2 -2
  228. package/src/shade/renderer/shader/chunk/atmosphere/C_MIE_SCATTERING_DECLARATION.js +2 -2
  229. package/src/shade/renderer/shader/chunk/atmosphere/chunk_compute_extinction.js +1 -1
  230. package/src/shade/renderer/shader/chunk/atmosphere/hillaire/DENSITY_PROFILE_LAYER_STRUCT.d.ts +1 -1
  231. package/src/shade/renderer/shader/chunk/atmosphere/hillaire/DENSITY_PROFILE_LAYER_STRUCT.js +1 -1
  232. package/src/shade/renderer/shader/chunk/brdf/fresnel/chunk_F_Schlick.d.ts +3 -3
  233. package/src/shade/renderer/shader/chunk/brdf/fresnel/chunk_F_Schlick.js +3 -3
  234. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.d.ts +9 -10
  235. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.d.ts.map +1 -1
  236. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_aces_ue_matrices.js +9 -10
  237. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_tonemap_ue_filmic.d.ts +4 -4
  238. package/src/shade/renderer/shader/chunk/color/tonemap/aces/chunk_tonemap_ue_filmic.js +5 -5
  239. package/src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.d.ts +1 -2
  240. package/src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.d.ts.map +1 -1
  241. package/src/shade/renderer/shader/chunk/math/chunk_f16_round_stochastic.js +1 -2
  242. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec2.d.ts +14 -1
  243. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec2.d.ts.map +1 -1
  244. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec2.js +19 -6
  245. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec3.d.ts +10 -1
  246. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec3.d.ts.map +1 -1
  247. package/src/shade/renderer/shader/chunk/noise/chunk_stbn_sample_vec3.js +14 -6
  248. package/src/shade/renderer/shader/chunk/random/murmur/chunk_murmur3_vec3u.d.ts +1 -1
  249. package/src/shade/renderer/shader/chunk/random/murmur/chunk_murmur3_vec3u.js +1 -1
  250. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  251. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +7 -2
  252. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.d.ts +2 -3
  253. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.d.ts.map +1 -1
  254. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_DYNAMIC_LOD_BIAS.js +7 -8
  255. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_LOAD_FACTOR.d.ts +1 -1
  256. package/src/shade/renderer/shadow/vsm/DEFAULT_VSM_MAX_LOAD_FACTOR.js +12 -12
  257. package/src/shade/renderer/shadow/vsm/VSMPageManager.d.ts +2 -2
  258. package/src/shade/renderer/shadow/vsm/VSMPageManager.d.ts.map +1 -1
  259. package/src/shade/renderer/shadow/vsm/VSMPageManager.js +3 -4
  260. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +0 -6
  261. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  262. package/src/shade/renderer/water/GPUWaterRenderer.js +12 -17
  263. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts +35 -0
  264. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts.map +1 -0
  265. package/src/shade/renderer/water/medium/graph_water_meshlets.js +89 -0
  266. package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
  267. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  268. package/src/shade/renderer/water/shader_water.js +22 -13
  269. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
  270. package/src/shade/renderer/water/shader_water_avboit.js +7 -7
  271. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +1 -1
  272. package/src/shade/renderer/water/surface/graph_water_tessellation.js +1 -1
  273. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +9 -1
  274. package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts +0 -19
  275. package/src/view/elements/progress/segmented/SegmentedResourceBarView.d.ts.map +1 -1
  276. package/src/view/elements/progress/segmented/SegmentedResourceBarView.js +2 -52
  277. package/editor/REWORK_PLAN_2026_08.md +0 -692
  278. package/src/CODEC_LAYOUT_VERIFICATION.md +0 -419
  279. package/src/CORRECTNESS_HUNT_2026_07_24.md +0 -142
  280. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +0 -738
  281. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +0 -79
  282. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +0 -93
  283. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +0 -80
  284. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +0 -164
  285. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +0 -69
  286. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +0 -110
  287. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +0 -129
  288. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +0 -72
  289. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +0 -74
  290. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +0 -169
  291. package/src/DUPLICATION_AUDIT_2026_08_07.md +0 -254
  292. package/src/DUPLICATION_AUDIT_REMEDIATION.md +0 -250
  293. package/src/DUPLICATION_REVIEW_2026_10_24.md +0 -1072
  294. package/src/REVIEW_2026_08_06/adv-core.md +0 -511
  295. package/src/REVIEW_2026_08_06/adv-gfx.md +0 -480
  296. package/src/REVIEW_2026_08_06/adv-view.md +0 -446
  297. package/src/REVIEW_2026_08_06/api-core.md +0 -296
  298. package/src/REVIEW_2026_08_06/api-engine.md +0 -195
  299. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +0 -507
  300. package/src/REVIEW_2026_08_06/core-binary.md +0 -348
  301. package/src/REVIEW_2026_08_06/core-collection.md +0 -376
  302. package/src/REVIEW_2026_08_06/core-math.md +0 -221
  303. package/src/REVIEW_2026_08_06/core-process.md +0 -266
  304. package/src/REVIEW_2026_08_06/core-science.md +0 -223
  305. package/src/REVIEW_2026_08_06/engine-ai-nav.md +0 -279
  306. package/src/REVIEW_2026_08_06/engine-animation.md +0 -167
  307. package/src/REVIEW_2026_08_06/engine-asset.md +0 -166
  308. package/src/REVIEW_2026_08_06/engine-ecs.md +0 -920
  309. package/src/REVIEW_2026_08_06/engine-input.md +0 -197
  310. package/src/REVIEW_2026_08_06/engine-misc.md +0 -248
  311. package/src/REVIEW_2026_08_06/engine-terrain.md +0 -52
  312. package/src/REVIEW_2026_08_06/generation.md +0 -176
  313. package/src/REVIEW_2026_08_06/geom-2d.md +0 -434
  314. package/src/REVIEW_2026_08_06/geom-3d.md +0 -117
  315. package/src/REVIEW_2026_08_06/gfx-gi.md +0 -143
  316. package/src/REVIEW_2026_08_06/gfx-render.md +0 -740
  317. package/src/REVIEW_2026_08_06/gfx-texture.md +0 -690
  318. package/src/REVIEW_2026_08_06/prior-criticals.md +0 -49
  319. package/src/REVIEW_2026_08_06/prior-highs.md +0 -138
  320. package/src/REVIEW_2026_08_06/test-gaps.md +0 -364
  321. package/src/REVIEW_2026_08_06/view.md +0 -667
  322. package/src/REVIEW_2026_08_06.md +0 -610
  323. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +0 -542
  324. package/src/core/bvh2/bvh3/NOTES.md +0 -2
  325. package/src/core/bvh8/NOTES.md +0 -63
  326. package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +0 -243
  327. package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +0 -417
  328. package/src/core/color/COLOR_REVIEW_2026_08_28.md +0 -779
  329. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +0 -433
  330. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +0 -486
  331. package/src/core/geom/3d/atlas/BFF_PLAN.md +0 -174
  332. package/src/core/geom/3d/atlas/REVIEW_02.md +0 -392
  333. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +0 -146
  334. package/src/core/geom/3d/gjk/NOTES.md +0 -8
  335. package/src/core/geom/3d/topology/TOPO_MESH_RETIREMENT_PLAN.md +0 -135
  336. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +0 -381
  337. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +0 -684
  338. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +0 -554
  339. package/src/core/model/reactive/evaluation/PREDICATE_APPLICABILITY_PROPOSAL.md +0 -446
  340. package/src/engine/REVIEW_2026_07_03.md +0 -211
  341. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +0 -815
  342. package/src/engine/control/first-person/DESIGN.md +0 -658
  343. package/src/engine/control/first-person/DESIGN_COLLISION.md +0 -382
  344. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +0 -563
  345. package/src/engine/control/first-person/TODO.md +0 -160
  346. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +0 -531
  347. package/src/engine/ecs/HIERARCHY_REVIEW.md +0 -717
  348. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +0 -262
  349. package/src/engine/ecs/ik/PLAN.md +0 -179
  350. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +0 -272
  351. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +0 -199
  352. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +0 -521
  353. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +0 -463
  354. package/src/engine/graphics/ecs/camera/pp/NOTES.md +0 -48
  355. package/src/engine/graphics/render/frame_graph/CORRECTNESS_FIXES_2026_09_04.md +0 -659
  356. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +0 -235
  357. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +0 -490
  358. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +0 -466
  359. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +0 -717
  360. package/src/engine/input/ecs/ism/ISM_DESIGN.md +0 -1523
  361. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +0 -627
  362. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +0 -661
  363. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +0 -229
  364. package/src/engine/network/CONGESTION_CONTROL.md +0 -87
  365. package/src/engine/network/MESSAGE_TRANSPORT.md +0 -364
  366. package/src/engine/network/SMOOTHNESS_DESIGN.md +0 -400
  367. package/src/engine/physics/cloth/MEASUREMENTS.md +0 -5304
  368. package/src/engine/physics/cloth/PLAN.md +0 -3056
  369. package/src/engine/physics/fluid/GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md +0 -1382
  370. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +0 -243
  371. package/src/engine/physics/fluid/solver/NOTES.md +0 -46
  372. package/src/engine/sound/SOUND_EMITTER_RETIREMENT_PLAN.md +0 -536
  373. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +0 -432
  374. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +0 -250
  375. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +0 -322
  376. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +0 -261
  377. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +0 -121
  378. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +0 -999
  379. package/src/format/image/avif/DECISIONS.md +0 -545
  380. package/src/format/image/jpeg/NOTES.md +0 -3
  381. package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +0 -814
  382. package/src/generation/COORDINATES.md +0 -54
  383. package/src/generation/REVIEW_01_ACTION_PLAN.md +0 -628
  384. package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +0 -220
  385. package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +0 -172
  386. package/src/generation/foliage/CROWN_FULLNESS_BRIEF_2026_10_26.md +0 -86
  387. package/src/generation/foliage/FOLIAGE_DESIGN.md +0 -547
  388. package/src/generation/foliage/FOLIAGE_PLAN.md +0 -198
  389. package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +0 -158
  390. package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +0 -521
  391. package/src/generation/foliage/REAL_TIME_TREES_BRIEF_2026_10_27.md +0 -86
  392. package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +0 -347
  393. package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +0 -248
  394. package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +0 -694
  395. package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +0 -552
  396. package/src/shade/RENDERER_CONTRACT.md +0 -697
  397. package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +0 -263
  398. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +0 -569
  399. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +0 -755
  400. package/src/shade/device/mock/EXTENSION_PROPOSAL_2026_08_22.md +0 -458
  401. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +0 -255
  402. package/src/shade/device/mock/REVIEW_2026_08_22.md +0 -527
  403. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +0 -894
  404. package/src/shade/device/timing/profile/SGPT_FORMAT.md +0 -345
  405. package/src/shade/renderer/NOTES.md +0 -78
  406. package/src/shade/renderer/PLAN.md +0 -8
  407. package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +0 -145
  408. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +0 -357
  409. package/src/shade/renderer/atmosphere/NOTES.md +0 -6
  410. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts +0 -76
  411. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts.map +0 -1
  412. package/src/shade/renderer/camera/orbital/MomentumAxis.js +0 -164
  413. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts +0 -45
  414. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts.map +0 -1
  415. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.js +0 -66
  416. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts +0 -98
  417. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts.map +0 -1
  418. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.js +0 -184
  419. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts +0 -175
  420. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts.map +0 -1
  421. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.js +0 -411
  422. package/src/shade/renderer/camera/orbital/README.md +0 -81
  423. package/src/shade/renderer/camera/orbital/index.d.ts +0 -6
  424. package/src/shade/renderer/camera/orbital/index.d.ts.map +0 -1
  425. package/src/shade/renderer/camera/orbital/index.js +0 -9
  426. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +0 -964
  427. package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +0 -361
  428. package/src/shade/renderer/geometry/bvh/BLAS_REFIT_BATCH_PLAN.md +0 -619
  429. package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +0 -674
  430. package/src/shade/renderer/geometry/sdf/NOTES.md +0 -6
  431. package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +0 -540
  432. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +0 -305
  433. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +0 -913
  434. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +0 -787
  435. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +0 -483
  436. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +0 -301
  437. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +0 -448
  438. package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +0 -385
  439. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +0 -656
  440. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +0 -308
  441. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +0 -657
  442. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +0 -551
  443. package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +0 -536
  444. package/src/shade/renderer/global_illumination/lpv/NOTES.md +0 -3
  445. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +0 -4225
  446. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +0 -410
  447. package/src/shade/renderer/global_illumination/sharc/NOTES.md +0 -8
  448. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +0 -1369
  449. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +0 -202
  450. package/src/shade/renderer/gpu_primitive/indirect/NOTES.md +0 -94
  451. package/src/shade/renderer/hiz/NOTES.md +0 -3
  452. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +0 -8
  453. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +0 -1
  454. package/src/shade/renderer/light/cluster/NOTES.md +0 -11
  455. package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +0 -524
  456. package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +0 -1101
  457. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts +0 -13
  458. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.d.ts.map +0 -1
  459. package/src/shade/renderer/light/environment/shader_convolve_reflection_map_from_averages.js +0 -198
  460. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts +0 -24
  461. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts.map +0 -1
  462. package/src/shade/renderer/material/resident/shader_resident_texture_means.js +0 -120
  463. package/src/shade/renderer/particles/DESIGN.md +0 -1011
  464. package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +0 -283
  465. package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +0 -453
  466. package/src/shade/renderer/path_tracer/PATH_TRACED_MEDIA_PLAN_2026_10_02.md +0 -520
  467. package/src/shade/renderer/postprocess/bloom/NOTES.md +0 -6
  468. package/src/shade/renderer/postprocess/fsr/NOTES.md +0 -4
  469. package/src/shade/renderer/postprocess/gtao/NOTES.md +0 -6
  470. package/src/shade/renderer/postprocess/nss/NOTES.md +0 -339
  471. package/src/shade/renderer/postprocess/perspective/NOTES.md +0 -3
  472. package/src/shade/renderer/postprocess/sscs/NOTES.md +0 -5
  473. package/src/shade/renderer/postprocess/ssr/NOTES.md +0 -14
  474. package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +0 -286
  475. package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -39
  476. package/src/shade/renderer/postprocess/taa/NOTES.md +0 -11
  477. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +0 -78
  478. package/src/shade/renderer/postprocess/vrs/NOTES.md +0 -4
  479. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +0 -1218
  480. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +0 -600
  481. package/src/shade/renderer/restir/reservoir/NOTES.md +0 -6
  482. package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +0 -6
  483. package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +0 -394
  484. package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +0 -160
  485. package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +0 -322
  486. package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +0 -247
  487. package/src/shade/renderer/shader/chunk/fsr/NOTES.md +0 -3
  488. package/src/shade/renderer/shader/chunk/fsr/spd/NOTES.md +0 -4
  489. package/src/shade/renderer/shader/chunk/geometry/ray/NOTES.md +0 -4
  490. package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +0 -159
  491. package/src/shade/renderer/shader/chunk/mboit/NOTES.md +0 -38
  492. package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/NOTES.md +0 -3
  493. package/src/shade/renderer/shader/chunk/spherical_harmonics/sampling/NOTES.md +0 -8
  494. package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +0 -932
  495. package/src/shade/renderer/shadow/contact/FINDINGS.md +0 -413
  496. package/src/shade/renderer/shadow/map/csm/NOTES.md +0 -5
  497. package/src/shade/renderer/shadow/ray/NOTES.md +0 -31
  498. package/src/shade/renderer/shadow/sdf/NOTES.md +0 -9
  499. package/src/shade/renderer/shadow/sdf/brixelizer/NOTES.md +0 -5
  500. package/src/shade/renderer/shadow/sdf/cascade/NOTES.md +0 -9
  501. package/src/shade/renderer/shadow/vsm/NOTES.md +0 -12
  502. package/src/shade/renderer/shadow/vsm/PLAN.md +0 -442
  503. package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +0 -43
  504. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +0 -2444
  505. package/src/shade/renderer/texture/mipmaps/NOTES.md +0 -4
  506. package/src/shade/renderer/volumetrics/NOTES.md +0 -1329
  507. package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +0 -336
  508. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +0 -23
  509. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +0 -1
  510. package/src/shade/renderer/water/medium/GPUWaterBodies.js +0 -75
  511. package/src/shade/wgsl/NOTES.md +0 -4
  512. package/src/view/elements/progress/segmented/segment_frame_transform.d.ts +0 -15
  513. package/src/view/elements/progress/segmented/segment_frame_transform.d.ts.map +0 -1
  514. package/src/view/elements/progress/segmented/segment_frame_transform.js +0 -69
@@ -1,734 +1,731 @@
1
- import { assert } from "../../core/assert.js";
2
- import Water from "../graphics/ecs/water/Water.js";
3
- import WaterWaves, { WATER_WAVES_SHORE_REFLECTION_DEFAULT } from "../graphics/ecs/water/WaterWaves.js";
4
- import { System } from "../ecs/System.js";
5
- import { EventType } from "../ecs/EventType.js";
6
- import { Transform64 } from "../ecs/transform/Transform64.js";
7
- import { TransformAttachment } from "../ecs/transform-attachment/TransformAttachment.js";
8
- import { TRANSFORM64_EVENT_CHANGE } from "../ecs/transform/TRANSFORM64_EVENT_CHANGE.js";
9
- import { TRANSFORM_ATTACHMENT_EVENT_CHANGE } from "../ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js";
10
- import { MeshletGeometry } from "../../shade/renderer/geometry/MeshletGeometry.js";
11
- import { ResourceAccessKind } from "../../core/model/ResourceAccessKind.js";
12
- import { ResourceAccessSpecification } from "../../core/model/ResourceAccessSpecification.js";
13
- import { GPUWaterRenderer } from "../../shade/renderer/water/GPUWaterRenderer.js";
14
- import { GPUWaterBodies } from "../../shade/renderer/water/medium/GPUWaterBodies.js";
15
- import { GPUWaterQueries, WATER_QUERY_SAMPLE_FLOATS } from "../../shade/renderer/water/query/GPUWaterQueries.js";
16
- import { GPUWaterSurface } from "../../shade/renderer/water/surface/GPUWaterSurface.js";
17
- import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
18
- import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
19
- import { SceneColor } from "../../shade/renderer/extension/SceneColor.js";
20
- import { m4_invert } from "../../core/geom/3d/mat4/m4_invert.js";
21
- import { GPUReadbackStatus } from "../../shade/renderer/buffer/table/GPUReadbackStatus.js";
22
- import { generate_record_writer } from "../../shade/renderer/buffer/table/serde/generate_record_writer.js";
23
- import { WATER_WAVE_IMPULSE_LIMIT, WATER_WAVE_RECORD_STRUCT } from "../../shade/renderer/water/surface/WATER_WAVE_RECORD_STRUCT.js";
24
- import { WATER_SPECTRUM_BAND_COUNT } from "../../shade/renderer/water/surface/spectrum/WATER_SPECTRUM.js";
25
- import { GPUFluidComponent } from "../physics/fluid/ecs/GPUFluidComponent.js";
26
- import { GPUFluidSystem } from "./GPUFluidSystem.js";
27
-
28
- // Only the authored fields: a whole-record write would undo what the GPU derives into the row.
29
- const AUTHORED_WRITER = generate_record_writer(WATER_WAVE_RECORD_STRUCT, ['upwind_fetch', 'shore_reflection', 'node', 'geometry', 'simulate_hidden']);
30
- const IMPULSE_WRITER = generate_record_writer(WATER_WAVE_RECORD_STRUCT, ['impulses', 'impulse_count']);
31
- /** The WaterWaves fields a body's row follows. */
32
- const WAVE_FIELDS = ['upwindFetch', 'shoreReflection'];
33
- /** A splash's or query's world-to-local matrix: rain can splash hundreds of times a second. */
34
- const SPLASH_INVERSE = new Float64Array(16);
35
-
36
- const REQUEST_INITIAL_CAPACITY = 16;
37
-
38
- /**
39
- * @param {Int32Array|Uint32Array|Float32Array|Float64Array|Uint8Array} array
40
- * @param {number} length
41
- * @returns {any} `array`, or a copy of it at least `length` long
42
- */
43
- function fit(array, length) {
44
- if (array.length >= length) {
45
- return array;
46
- }
47
-
48
- const grown = new array.constructor(Math.max(length, 2 * array.length));
49
-
50
- grown.set(array);
51
-
52
- return grown;
53
- }
54
-
55
- /**
56
- * Registers [Water, MeshletGeometry, Transform64] in the scene's existing GPU database. Authored
57
- * edits queue writes; idle frames never walk bodies. Transform rows follow the same change events
58
- * and GPU hierarchy as shaded geometry. Water does not enter the opaque mesh table or its TLAS.
59
- *
60
- * Every body has a surface row (WATER_SURFACE_SIMULATION_PLAN_2026_10_06.md §6.1): its fetch and shore from an
61
- * optional {@link WaterWaves}, its queued splashes, and what the GPU derives from them each frame. The GPU culls
62
- * every body against the frame's view before simulating; a hidden body is paused unless
63
- * {@link Water#simulateWhenHidden} (WATER_SURFACE_DISTANCE_LOD_PROPOSAL_2026_10_06.md §3).
64
- *
65
- * **The wind is the scene's** (GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md §6, the Water row): the culling pass
66
- * samples the published wind field 10 m above each body's origin and low-passes it per body, on the GPU. What the
67
- * system decides on the CPU is only whether that field can hold wind at all ({@link GPUWaterSurface#windy}, which
68
- * animates the shared bands): while any enabled {@link GPUFluidComponent} steps a fluid, or the far field
69
- * ({@link GPUFluidSystem#far_field}) is not still. Asked every update, allocating nothing.
70
- */
71
- export class WaterSystem extends System {
72
-
73
- /**
74
- * Offer the compute pipelines the water surface query API dispatches.
75
- *
76
- * @param {import("../../shade/device/pipeline/PipelineEnquiry.js").PipelineEnquiry} enquiry
77
- */
78
- static enquirePipelines(enquiry) {
79
- GPUWaterQueries.enquirePipelines(enquiry);
80
- }
81
-
82
- dependencies = [Water, MeshletGeometry, Transform64];
83
- components_used = [ResourceAccessSpecification.from(WaterWaves, ResourceAccessKind.Read),
84
- // whether the scene's wind field has a fluid behind it
85
- ResourceAccessSpecification.from(GPUFluidComponent, ResourceAccessKind.Read)];
86
- #graphics;
87
- #scene;
88
- #context = null;
89
- #renderer = new GPUWaterRenderer();
90
- #waves = new GPUWaterSurface();
91
- #bodies = new GPUWaterBodies();
92
- #entries = new Map();
93
- #dirty = new Set();
94
- #impulses = new Set();
95
- /** @type {GPUFluidSystem|null} the far field's source, once found */
96
- #fluid = null;
97
- #far = new Float64Array(3);
98
- /** Whether {@link #visit_domain} met an enabled component. */
99
- #enabled_found = false;
100
- #visit_domain = component => {
101
- if (component.enabled) { this.#enabled_found = true; return false; }
102
- return true;
103
- };
104
- #extensions = [];
105
- #time = 0;
106
-
107
- /*
108
- The surface read API: per handle, the request's entity, local xz points, count, footprint,
109
- target, and status.
110
- */
111
- #request_entities = new Int32Array(REQUEST_INITIAL_CAPACITY);
112
- #request_point_offsets = new Uint32Array(REQUEST_INITIAL_CAPACITY);
113
- #request_counts = new Uint32Array(REQUEST_INITIAL_CAPACITY);
114
- #request_footprints = new Float32Array(REQUEST_INITIAL_CAPACITY);
115
- #request_offsets = new Uint32Array(REQUEST_INITIAL_CAPACITY);
116
- /** @type {(Float32Array|null)[]} */
117
- #request_targets = new Array(REQUEST_INITIAL_CAPACITY).fill(null);
118
- #request_points = new Float64Array(2 * REQUEST_INITIAL_CAPACITY);
119
- #request_point_count = 0;
120
- #world_to_local_xz = new Float64Array(2 * REQUEST_INITIAL_CAPACITY);
121
- #status = new Uint8Array(REQUEST_INITIAL_CAPACITY);
122
-
123
- /** Recycled handles. */
124
- #free_handles = new Uint32Array(REQUEST_INITIAL_CAPACITY);
125
- #free_count = 0;
126
-
127
- /** Handles below this have been handed out at least once. */
128
- #fresh = 0;
129
-
130
- /** Handles asked for and not yet answered headless or handed to the device. */
131
- #asked = new Uint32Array(REQUEST_INITIAL_CAPACITY);
132
- #asked_count = 0;
133
-
134
- /** Handles that settled at the last apply point, recycled at the next. */
135
- #settled = new Uint32Array(REQUEST_INITIAL_CAPACITY);
136
- #settled_count = 0;
137
-
138
- /**
139
- * The read API on the device: its database, gather and readback. Made by the first frame with a
140
- * request, dropped with the device.
141
- *
142
- * @type {GPUWaterQueries|null}
143
- */
144
- #queries = null;
145
-
146
- get settings() { return this.#renderer.settings; }
147
- get waves() { return this.#waves; }
148
- get transparency_channel() { return this.#renderer; }
149
- get scene() { return this.#scene; }
150
- request_probe(uv) { return this.#renderer.request_probe(uv); }
151
- get time() { return this.#time; }
152
- set time(seconds) {
153
- assert.ok(Number.isFinite(seconds) && seconds >= 0, 'water time must be finite and non-negative');
154
- this.#time = seconds;
155
- }
156
-
157
- /**
158
- * What became of each surface query request, by handle: a `GPUReadbackStatus`. `Pending` from
159
- * {@link request_local} / {@link request_world} until the apply point ({@link update}) that
160
- * answers it, `Delivered` (the samples are unpacked in `target`) or `Dropped` (the target is as
161
- * it was) from there until the next apply point, then `None`, the handle recycled.
162
- *
163
- * Read it fresh each time rather than keeping a reference: the array is replaced when the
164
- * handle space grows.
165
- *
166
- * @returns {Uint8Array}
167
- */
168
- get status() {
169
- return this.#status;
170
- }
171
-
172
- /**
173
- * The surface read API's device half, or null before the first frame with a request on a
174
- * device. For inspection.
175
- *
176
- * @returns {GPUWaterQueries|null}
177
- */
178
- get queries() {
179
- return this.#queries;
180
- }
181
-
182
- constructor(graphics, scene) {
183
- super(); assert.defined(graphics, 'graphics'); assert.defined(scene, 'scene');
184
- this.#graphics = graphics; this.#scene = scene;
185
- this.#renderer.scene = scene;
186
- this.#waves.onInputsConsumed.add(context => {
187
- if (context !== this.#context) { return; }
188
- for (const entry of this.#impulses) {
189
- entry.staged.impulse_count = 0;
190
- if (entry.queue.length === 0) { this.#impulses.delete(entry); }
191
- }
192
- });
193
- this.#renderer.bodies = this.#bodies;
194
- this.#renderer.waves = this.#waves;
195
- }
196
-
197
- async startup(entityManager) {
198
- this.entityManager = entityManager;
199
- assert.ok(this.#scene.dataset == null || this.#scene.dataset === entityManager.dataset, 'scene belongs to another dataset');
200
- this.#scene.dataset = entityManager.dataset;
201
- this.#graphics.add_transparency_channel(this.#renderer);
202
- this.#extensions = [this.#graphics.add_extension(new WaterExtension(this)),
203
- this.#graphics.add_extension(new WaterSurfaceExtension(this))];
204
- }
205
-
206
- async shutdown() {
207
- for (const entry of Array.from(this.#entries.values())) { this.unlink(entry.water, entry.geometry, entry.transform, entry.entity); }
208
- this.#drop_queries();
209
- this.#graphics.remove_transparency_channel(this.#renderer);
210
- for (const extension of this.#extensions) { this.#graphics.remove_extension(extension); }
211
- this.#extensions.length = 0;
212
- this.#renderer.dispose(); this.#waves.dispose(); this.#context = null;
213
- }
214
-
215
- link(water, geometry, transform, entity) {
216
- // queue: splashes not yet staged, four numbers each (local x, z, radius, kick); staged: the batch on the
217
- // GPU row until a submitted frame spends it
218
- const entry = { water, geometry, transform, entity, context: null, row: -1, wave: -1, waves: null, queue: [],
219
- staged: { impulses: Array.from({ length: WATER_WAVE_IMPULSE_LIMIT }, () => new Float64Array(4)), impulse_count: 0 } };
220
- const dataset = this.entityManager.dataset;
221
- entry.changed = () => this.#dirty.add(entry);
222
- entry.world = () => entry.context?.rows.mark_world(entity);
223
- entry.local = () => entry.context?.rows.mark_local(entity);
224
- entry.components = ({ klass }) => {
225
- if (klass === WaterWaves) { this.#watch_waves(entry); entry.changed(); }
226
- if (klass === TransformAttachment) { entry.local(); }
227
- };
228
- water.onChanged.add(entry.changed);
229
- dataset.addEntityEventListener(entity, TRANSFORM64_EVENT_CHANGE, entry.world);
230
- dataset.addEntityEventListener(entity, TRANSFORM_ATTACHMENT_EVENT_CHANGE, entry.local);
231
- dataset.addEntityEventListener(entity, EventType.ComponentAdded, entry.components);
232
- dataset.addEntityEventListener(entity, EventType.ComponentRemoved, entry.components);
233
- this.#entries.set(entity, entry);
234
- this.#watch_waves(entry);
235
- this.#dirty.add(entry);
236
- this.#renderer.body_count++;
237
- }
238
-
239
- #watch_waves(entry) {
240
- if (entry.waves != null) { for (const field of WAVE_FIELDS) { entry.waves[field].onChanged.remove(entry.changed); } }
241
- entry.waves = this.entityManager.dataset.getComponent(entry.entity, WaterWaves);
242
- if (entry.waves != null) { for (const field of WAVE_FIELDS) { entry.waves[field].onChanged.add(entry.changed); } }
243
- }
244
-
245
- unlink(water, geometry, transform, entity) {
246
- const entry = this.#entries.get(entity);
247
- if (entry === undefined) { return; }
248
- const dataset = this.entityManager.dataset;
249
- water.onChanged.remove(entry.changed);
250
- if (entry.waves != null) { for (const field of WAVE_FIELDS) { entry.waves[field].onChanged.remove(entry.changed); } }
251
- dataset.removeEntityEventListener(entity, TRANSFORM64_EVENT_CHANGE, entry.world);
252
- dataset.removeEntityEventListener(entity, TRANSFORM_ATTACHMENT_EVENT_CHANGE, entry.local);
253
- dataset.removeEntityEventListener(entity, EventType.ComponentAdded, entry.components);
254
- dataset.removeEntityEventListener(entity, EventType.ComponentRemoved, entry.components);
255
- if (entry.context !== null && entry.context === this.#graphics.scene_context(this.#scene)) {
256
- entry.context.scene_database.get('water_bodies').remove(entry.row);
257
- entry.context.rows.release(entity);
258
- }
259
- this.#entries.delete(entity); this.#dirty.delete(entry); this.#impulses.delete(entry);
260
- this.#renderer.body_count--;
261
- if (entry.context !== null) { this.#renderer.meshlet_capacity -= entry.geometry.meshlets.count; }
262
- }
263
-
264
- update(dt) {
265
- this.#recycle_requests();
266
- this.#time += dt; this.#renderer.time = this.#time; this.#waves.advance(dt);
267
- this.#waves.windy = this.#wind_can_blow();
268
- const context = this.#graphics.scene_context(this.#scene);
269
- if (context === null) {
270
- if (this.#context !== null || this.#queries !== null) {
271
- this.#drop_queries();
272
- this.#context = null;
273
- }
274
- this.#answer_requests(null);
275
- return;
276
- }
277
- if (context !== this.#context) {
278
- this.#drop_queries();
279
- this.#context = context;
280
- // Device replacement is the only whole-scene rebuild; ordinary frames drain edits.
281
- for (const entry of this.#entries.values()) {
282
- entry.context = null; entry.row = -1; entry.wave = -1;
283
- // a batch the old context never spent goes back to the front of the queue
284
- const staged = [];
285
- for (let i = 0; i < entry.staged.impulse_count; i++) { staged.push(...entry.staged.impulses[i]); }
286
- entry.queue.unshift(...staged); entry.staged.impulse_count = 0;
287
- this.#dirty.add(entry);
288
- }
289
- this.#renderer.meshlet_capacity = 0;
290
- }
291
- for (const entry of this.#dirty) { this.#write(entry, context); }
292
- this.#dirty.clear();
293
- for (const entry of this.#impulses) { this.#stage(entry, context); }
294
- this.#answer_requests(context);
295
- }
296
-
297
- /**
298
- * Whether the scene's wind field can hold wind: an enabled {@link GPUFluidComponent} on any entity, or a far
299
- * field that is not still. The lowest holder of the component answers without a walk; only when it is disabled
300
- * are the others visited.
301
- * @returns {boolean}
302
- */
303
- #wind_can_blow() {
304
- const em = this.entityManager, dataset = em.dataset;
305
- if (dataset !== null) {
306
- const lowest = dataset.getAnyComponentInstance(GPUFluidComponent);
307
- if (lowest !== null) {
308
- if (lowest.enabled) { return true; }
309
- this.#enabled_found = false;
310
- dataset.traverseComponents(GPUFluidComponent, this.#visit_domain);
311
- if (this.#enabled_found) { return true; }
312
- }
313
- }
314
- // registered later than this system, or never
315
- const fluid = this.#fluid ??= em.getSystem(GPUFluidSystem);
316
- if (fluid === null) { return false; }
317
- const far = this.#far;
318
- fluid.far_field(far);
319
- return far[0] !== 0 || far[1] !== 0 || far[2] !== 0;
320
- }
321
-
322
- /** Top up a body's batch on its surface row from its queue, up to the row's limit. */
323
- #stage(entry, context) {
324
- const staged = entry.staged, queue = entry.queue;
325
- if (entry.wave === -1 || queue.length === 0 || staged.impulse_count === WATER_WAVE_IMPULSE_LIMIT) { return; }
326
- const taken = Math.min(queue.length / 4, WATER_WAVE_IMPULSE_LIMIT - staged.impulse_count);
327
- for (let i = 0; i < taken; i++) {
328
- const impulse = staged.impulses[staged.impulse_count++];
329
- for (let c = 0; c < 4; c++) { impulse[c] = queue[4 * i + c]; }
330
- }
331
- queue.copyWithin(0, 4 * taken);
332
- queue.length -= 4 * taken;
333
- this.#waves.splashed = true;
334
- for (let i = staged.impulse_count; i < WATER_WAVE_IMPULSE_LIMIT; i++) { staged.impulses[i].fill(0); }
335
- context.scene_database.get('water_waves').set_fields(entry.wave, staged, IMPULSE_WRITER);
336
- }
337
-
338
- #write(entry, context) {
339
- const bodies = context.scene_database.get('water_bodies');
340
- const waves = context.scene_database.get('water_waves');
341
- if (entry.context === null) {
342
- context.rows.acquire(entry.entity);
343
- context.geometries.add(entry.geometry);
344
- entry.context = context;
345
- this.#renderer.meshlet_capacity += entry.geometry.meshlets.count;
346
- }
347
- const geometry = context.geometries.getAddress(entry.geometry);
348
- if (entry.wave === -1) {
349
- // the GPU seeds the wind from its first sample, and derives the weights for it
350
- entry.wave = waves.add({ upwind_fetch: 0, shore_reflection: WATER_WAVES_SHORE_REFLECTION_DEFAULT,
351
- impulses: Array.from({ length: WATER_WAVE_IMPULSE_LIMIT }, () => [0, 0, 0, 0]), impulse_count: 0,
352
- node: entry.entity + 1, geometry, simulate_hidden: 0, wind: [0, 0, 0], wind_seeded: 0, derived_for: [-1, -1, 0, 0],
353
- weights: new Array(WATER_SPECTRUM_BAND_COUNT).fill(0), slope_variance: 0,
354
- wind_from: [0, 0], wind_to: [0, 0], wind_blend: 1, displacement_bounds: [0, 0, 0, 0], particle_levels: 0 });
355
- }
356
- const waves_component = entry.waves;
357
- const reflection = Math.min(1, Math.max(0, waves_component?.shoreReflection.getValue() ?? WATER_WAVES_SHORE_REFLECTION_DEFAULT));
358
- waves.set_fields(entry.wave, { upwind_fetch: Math.max(0, waves_component?.upwindFetch.getValue() ?? 0), shore_reflection: reflection, node: entry.entity + 1, geometry,
359
- simulate_hidden: entry.water.simulateWhenHidden.getValue() ? 1 : 0 }, AUTHORED_WRITER);
360
- const water = entry.water;
361
- const record = { geometry, node: entry.entity + 1,
362
- dirty: 1,
363
- wave: entry.wave, ior: Math.max(1, water.ior.getValue()),
364
- roughness: Math.min(1, Math.max(0, water.roughness.getValue())), medium: water.medium.to_gpu_record(),
365
- backscattering: [0, 0, 0, 0], extinction: [0, 0, 0, 0], diffuse_transmittance: 0 };
366
- if (entry.row === -1) { entry.row = bodies.add(record); } else { bodies.set(entry.row, record); }
367
- }
368
-
369
- /**
370
- * A splash at a world position: rings of wave particles of the given world radius (half their wavelength)
371
- * whose height is the given vertical velocity kick, m/s, over the time a crest takes to cross them. The
372
- * position is interpreted in the CPU-owned pose; queued on the CPU and spent on the GPU, sixteen a body a frame.
373
- */
374
- splash(entity, position, radius, strength) {
375
- const entry = this.#entries.get(entity); assert.defined(entry, 'water entity');
376
- assert.greaterThan(radius, 0, 'radius'); assert.isFinite(radius, 'radius'); assert.isFinite(strength, 'strength');
377
- for (let i = 0; i < 3; i++) { assert.isFinite(position[i], `position[${i}]`); }
378
- const inverse = SPLASH_INVERSE;
379
- const inverted = m4_invert(inverse, entry.transform);
380
- assert.ok(inverted, 'water transform is singular');
381
- const x = inverse[0] * position[0] + inverse[4] * position[1] + inverse[8] * position[2] + inverse[12];
382
- const z = inverse[2] * position[0] + inverse[6] * position[1] + inverse[10] * position[2] + inverse[14];
383
- entry.queue.push(x, z, radius, strength); this.#impulses.add(entry);
384
- }
385
-
386
- /**
387
- * Ask for the water surface height and world-space unit normal at `count` points in the water
388
- * body's local `(x, z)` plane: `local_xz` holds `[x0, z0, x1, z1, ...]`, and each sample lands
389
- * in `target` from `target_offset` as {@link WATER_QUERY_SAMPLE_FLOATS} (4) floats,
390
- * `[world_height, nx, ny, nz]`, unpacked on the CPU when the request settles `Delivered` at a
391
- * later apply point ({@link update}); see {@link status}.
392
- *
393
- * Points outside the body's authored outline or inside island holes are clamped continuously to
394
- * the nearest point on the mesh's triangles so no `NaN`s are ever produced.
395
- *
396
- * @param {number} entity water entity
397
- * @param {ArrayLike<number>} local_xz interleaved `[x0, z0, x1, z1, ...]` in local body coordinates
398
- * @param {number} count number of `(x, z)` points, at least 1
399
- * @param {number} footprint spatial filter footprint in metres, at least 0 (0 resolves all wavelengths)
400
- * @param {Float32Array} target memory the caller owns and keeps until the request settles
401
- * @param {number} target_offset in floats
402
- * @returns {number} a handle into {@link status}
403
- */
404
- request_local(entity, local_xz, count, footprint, target, target_offset) {
405
- const entry = this.#entries.get(entity);
406
- assert.defined(entry, 'water entity');
407
- assert.isArrayLike(local_xz, 'local_xz');
408
- assert.isPositiveInteger(count, 'count');
409
- assert.greaterThanOrEqual(local_xz.length, 2 * count, 'local_xz');
410
- for (let i = 0; i < 2 * count; i++) {
411
- assert.isFinite(local_xz[i], `local_xz[${i}]`);
412
- }
413
- assert.isFinite(footprint, 'footprint');
414
- assert.greaterThanOrEqual(footprint, 0, 'footprint');
415
- assert.ok(target instanceof Float32Array, 'target must be a Float32Array');
416
- assert.isNonNegativeInteger(target_offset, 'target_offset');
417
- assert.lessThanOrEqual(target_offset + WATER_QUERY_SAMPLE_FLOATS * count, target.length, 'the query samples reach past the target');
418
-
419
- const h = this.#allocate_handle();
420
- const point_start = this.#request_point_count;
421
- const next_point_count = point_start + count;
422
-
423
- if (2 * next_point_count > this.#request_points.length) {
424
- this.#request_points = fit(this.#request_points, 2 * next_point_count);
425
- }
426
-
427
- const points = this.#request_points;
428
-
429
- for (let i = 0; i < 2 * count; i++) {
430
- points[2 * point_start + i] = local_xz[i];
431
- }
432
-
433
- this.#request_point_count = next_point_count;
434
- this.#request_entities[h] = entity;
435
- this.#request_point_offsets[h] = point_start;
436
- this.#request_counts[h] = count;
437
- this.#request_footprints[h] = footprint;
438
- this.#request_offsets[h] = target_offset;
439
- this.#request_targets[h] = target;
440
- this.#status[h] = GPUReadbackStatus.Pending;
441
-
442
- if (this.#asked_count === this.#asked.length) {
443
- this.#asked = fit(this.#asked, this.#asked_count + 1);
444
- }
445
-
446
- this.#asked[this.#asked_count++] = h;
447
-
448
- return h;
449
- }
450
-
451
- /**
452
- * Ask for the water surface height and world-space unit normal at `count` points in world
453
- * space: `world_xyz` holds `[x0, y0, z0, x1, y1, z1, ...]`. Internally transforms each world
454
- * point into the water body's local `(x, z)` plane via the inverse of its CPU-owned
455
- * `Transform64` and delegates to {@link request_local}.
456
- *
457
- * @param {number} entity water entity
458
- * @param {ArrayLike<number>} world_xyz interleaved `[x0, y0, z0, ...]` in world metres
459
- * @param {number} count number of `(x, y, z)` points, at least 1
460
- * @param {number} footprint spatial filter footprint in metres, at least 0 (0 resolves all wavelengths)
461
- * @param {Float32Array} target memory the caller owns and keeps until the request settles
462
- * @param {number} target_offset in floats
463
- * @returns {number} a handle into {@link status}
464
- */
465
- request_world(entity, world_xyz, count, footprint, target, target_offset) {
466
- const entry = this.#entries.get(entity);
467
- assert.defined(entry, 'water entity');
468
- assert.isArrayLike(world_xyz, 'world_xyz');
469
- assert.isPositiveInteger(count, 'count');
470
- assert.greaterThanOrEqual(world_xyz.length, 3 * count, 'world_xyz');
471
- for (let i = 0; i < 3 * count; i++) {
472
- assert.isFinite(world_xyz[i], `world_xyz[${i}]`);
473
- }
474
-
475
- const inverse = SPLASH_INVERSE;
476
- const inverted = m4_invert(inverse, entry.transform);
477
- assert.ok(inverted, 'water transform is singular');
478
-
479
- if (2 * count > this.#world_to_local_xz.length) {
480
- this.#world_to_local_xz = fit(this.#world_to_local_xz, 2 * count);
481
- }
482
-
483
- const local_xz = this.#world_to_local_xz;
484
-
485
- for (let i = 0; i < count; i++) {
486
- const wx = world_xyz[3 * i];
487
- const wy = world_xyz[3 * i + 1];
488
- const wz = world_xyz[3 * i + 2];
489
-
490
- local_xz[2 * i] = inverse[0] * wx + inverse[4] * wy + inverse[8] * wz + inverse[12];
491
- local_xz[2 * i + 1] = inverse[2] * wx + inverse[6] * wy + inverse[10] * wz + inverse[14];
492
- }
493
-
494
- return this.request_local(entity, local_xz, count, footprint, target, target_offset);
495
- }
496
-
497
- /**
498
- * @returns {number}
499
- */
500
- #allocate_handle() {
501
- if (this.#free_count > 0) {
502
- return this.#free_handles[--this.#free_count];
503
- }
504
-
505
- const h = this.#fresh++;
506
-
507
- if (h === this.#status.length) {
508
- const capacity = 2 * h;
509
-
510
- this.#request_entities = fit(this.#request_entities, capacity);
511
- this.#request_point_offsets = fit(this.#request_point_offsets, capacity);
512
- this.#request_counts = fit(this.#request_counts, capacity);
513
- this.#request_footprints = fit(this.#request_footprints, capacity);
514
- this.#request_offsets = fit(this.#request_offsets, capacity);
515
- this.#status = fit(this.#status, capacity);
516
- this.#free_handles = fit(this.#free_handles, capacity);
517
- this.#settled = fit(this.#settled, capacity);
518
-
519
- for (let i = this.#request_targets.length; i < capacity; i++) {
520
- this.#request_targets.push(null);
521
- }
522
- }
523
-
524
- return h;
525
- }
526
-
527
- /**
528
- * A request answered: its status set, readable until the next apply point.
529
- *
530
- * @param {number} h
531
- * @param {number} status
532
- */
533
- #settle(h, status) {
534
- this.#status[h] = status;
535
- this.#request_targets[h] = null;
536
- this.#settled[this.#settled_count++] = h;
537
- }
538
-
539
- /**
540
- * The apply point's first half: the handles answered at the last one go back to the free list.
541
- */
542
- #recycle_requests() {
543
- const settled = this.#settled;
544
- const status = this.#status;
545
- const free = this.#free_handles;
546
-
547
- for (let i = 0; i < this.#settled_count; i++) {
548
- const h = settled[i];
549
-
550
- status[h] = GPUReadbackStatus.None;
551
- free[this.#free_count++] = h;
552
- }
553
-
554
- this.#settled_count = 0;
555
- }
556
-
557
- /**
558
- * The apply point's second half: what the device delivered or dropped, and, headless, every
559
- * request asked since from the body's rest surface.
560
- *
561
- * @param {object|null} context
562
- */
563
- #answer_requests(context) {
564
- const queries = this.#queries;
565
-
566
- if (queries !== null) {
567
- queries.apply();
568
- this.#take_settled(queries);
569
- }
570
-
571
- if (context !== null || this.#asked_count === 0) {
572
- return;
573
- }
574
-
575
- const asked = this.#asked;
576
- const points = this.#request_points;
577
-
578
- for (let i = 0; i < this.#asked_count; i++) {
579
- const h = asked[i];
580
- const entry = this.#entries.get(this.#request_entities[h]);
581
-
582
- if (entry === undefined) {
583
- this.#settle(h, GPUReadbackStatus.Dropped);
584
- continue;
585
- }
586
-
587
- const m = entry.transform;
588
- const inverse = SPLASH_INVERSE;
589
- const inverted = m4_invert(inverse, m);
590
- let nx = 0, ny = 1, nz = 0;
591
- if (inverted) {
592
- const rx = inverse[1], ry = inverse[5], rz = inverse[9];
593
- const len = Math.hypot(rx, ry, rz);
594
- if (len > 1e-20) {
595
- nx = rx / len;
596
- ny = ry / len;
597
- nz = rz / len;
598
- }
599
- }
600
-
601
- const box = entry.geometry.bounding_box;
602
- const mid_y = 0.5 * (box[1] + box[4]);
603
- const count = this.#request_counts[h];
604
- const point_base = 2 * this.#request_point_offsets[h];
605
- const target = this.#request_targets[h];
606
- let at = this.#request_offsets[h];
607
-
608
- for (let k = 0; k < count; k++, at += WATER_QUERY_SAMPLE_FLOATS) {
609
- const lx = Math.min(Math.max(points[point_base + 2 * k], box[0]), box[3]);
610
- const lz = Math.min(Math.max(points[point_base + 2 * k + 1], box[2]), box[5]);
611
- const world_y = m[1] * lx + m[5] * mid_y + m[9] * lz + m[13];
612
-
613
- target[at] = world_y;
614
- target[at + 1] = nx;
615
- target[at + 2] = ny;
616
- target[at + 3] = nz;
617
- }
618
-
619
- this.#settle(h, GPUReadbackStatus.Delivered);
620
- }
621
-
622
- this.#request_point_count = 0;
623
- this.#asked_count = 0;
624
- }
625
-
626
- /**
627
- * @param {GPUWaterQueries} queries after an apply or a destroy
628
- */
629
- #take_settled(queries) {
630
- for (let i = 0; i < queries.settled_count; i++) {
631
- this.#settle(queries.settled_tag(i), queries.settled_status(i));
632
- }
633
- }
634
-
635
- /**
636
- * Let go of the read API's device half: whatever it had in flight is dropped, its status set now.
637
- */
638
- #drop_queries() {
639
- const queries = this.#queries;
640
-
641
- if (queries === null) {
642
- return;
643
- }
644
-
645
- queries.destroy();
646
-
647
- this.#take_settled(queries);
648
-
649
- this.#queries = null;
650
- }
651
-
652
- /**
653
- * Hand the frame's requests to the device, after the frame's surface simulation passes.
654
- *
655
- * @param {import("../graphics/render/frame_graph/FrameGraph.js").FrameGraph} graph
656
- * @param {object} scene
657
- * @param {GPUDevice} device
658
- */
659
- #record_requests(graph, scene, device) {
660
- const count = this.#asked_count;
661
-
662
- if (count === 0) {
663
- return;
664
- }
665
-
666
- let queries = this.#queries;
667
-
668
- if (queries === null) {
669
- const graphics = this.#graphics.renderer?.graphics;
670
- if (graphics?.compute_pipelines !== undefined) {
671
- const enquiry = { device, render: [], compute: [] };
672
- WaterSystem.enquirePipelines(enquiry);
673
- for (let i = 0; i < enquiry.compute.length; i++) {
674
- graphics.compute_pipelines.prepare(enquiry.compute[i]);
675
- }
676
- }
677
- queries = this.#queries = new GPUWaterQueries({ device });
678
- }
679
-
680
- const asked = this.#asked;
681
- const points = this.#request_points;
682
-
683
- for (let i = 0; i < count; i++) {
684
- const h = asked[i];
685
- const entry = this.#entries.get(this.#request_entities[h]);
686
-
687
- if (entry === undefined || entry.row === -1) {
688
- this.#settle(h, GPUReadbackStatus.Dropped);
689
- continue;
690
- }
691
-
692
- queries.request(
693
- entry.row,
694
- points,
695
- 2 * this.#request_point_offsets[h],
696
- this.#request_counts[h],
697
- this.#request_footprints[h],
698
- this.#request_targets[h],
699
- this.#request_offsets[h],
700
- h
701
- );
702
- }
703
-
704
- this.#request_point_count = 0;
705
- this.#asked_count = 0;
706
-
707
- queries.record({ graph, scene, surface: this.#waves });
708
- }
709
-
710
- record_surface(frame) {
711
- const view = frame.view, scene = view.scene;
712
- if (scene.scene !== this.#scene) { return; }
713
- const device = this.#graphics.renderer.graphics.device;
714
- this.#waves.record({ graph: frame.graph, device, scene, time: this.#time,
715
- view: { camera: view.camera.gpu_buffer, projection: view.camera.camera.projection_matrix, height: view.resolution[1] },
716
- pixels: this.#renderer.settings.tessellation_pixels });
717
- this.#record_requests(frame.graph, scene, device);
718
- }
719
- record(frame) {
720
- const inspection = this.#renderer.inspect(frame.graph, frame.view, frame.resolution);
721
- if (inspection !== undefined) { frame.get(SceneColor).color = inspection; }
722
- }
723
- }
724
-
725
- export class WaterExtension extends RenderExtension {
726
- name = 'water'; phase = FramePhase.BeforePresent;
727
- constructor(system) { super(); this.system = system; }
728
- record(frame) { this.system.record(frame); }
729
- }
730
- export class WaterSurfaceExtension extends RenderExtension {
731
- name = 'water surface'; phase = FramePhase.FrameStart;
732
- constructor(system) { super(); this.system = system; }
733
- record(frame) { this.system.record_surface(frame); }
734
- }
1
+ import { assert } from "../../core/assert.js";
2
+ import Water from "../graphics/ecs/water/Water.js";
3
+ import WaterWaves, { WATER_WAVES_SHORE_REFLECTION_DEFAULT } from "../graphics/ecs/water/WaterWaves.js";
4
+ import { System } from "../ecs/System.js";
5
+ import { EventType } from "../ecs/EventType.js";
6
+ import { Transform64 } from "../ecs/transform/Transform64.js";
7
+ import { TransformAttachment } from "../ecs/transform-attachment/TransformAttachment.js";
8
+ import { TRANSFORM64_EVENT_CHANGE } from "../ecs/transform/TRANSFORM64_EVENT_CHANGE.js";
9
+ import { TRANSFORM_ATTACHMENT_EVENT_CHANGE } from "../ecs/transform-attachment/TRANSFORM_ATTACHMENT_EVENT_CHANGE.js";
10
+ import { MeshletGeometry } from "../../shade/renderer/geometry/MeshletGeometry.js";
11
+ import { ResourceAccessKind } from "../../core/model/ResourceAccessKind.js";
12
+ import { ResourceAccessSpecification } from "../../core/model/ResourceAccessSpecification.js";
13
+ import { GPUWaterRenderer } from "../../shade/renderer/water/GPUWaterRenderer.js";
14
+ import { GPUWaterQueries, WATER_QUERY_SAMPLE_FLOATS } from "../../shade/renderer/water/query/GPUWaterQueries.js";
15
+ import { GPUWaterSurface } from "../../shade/renderer/water/surface/GPUWaterSurface.js";
16
+ import { FramePhase } from "../../shade/renderer/extension/FramePhase.js";
17
+ import { RenderExtension } from "../../shade/renderer/extension/RenderExtension.js";
18
+ import { SceneColor } from "../../shade/renderer/extension/SceneColor.js";
19
+ import { m4_invert } from "../../core/geom/3d/mat4/m4_invert.js";
20
+ import { GPUReadbackStatus } from "../../shade/renderer/buffer/table/GPUReadbackStatus.js";
21
+ import { generate_record_writer } from "../../shade/renderer/buffer/table/serde/generate_record_writer.js";
22
+ import { WATER_WAVE_IMPULSE_LIMIT, WATER_WAVE_RECORD_STRUCT } from "../../shade/renderer/water/surface/WATER_WAVE_RECORD_STRUCT.js";
23
+ import { WATER_SPECTRUM_BAND_COUNT } from "../../shade/renderer/water/surface/spectrum/WATER_SPECTRUM.js";
24
+ import { GPUFluidComponent } from "../physics/fluid/ecs/GPUFluidComponent.js";
25
+ import { GPUFluidSystem } from "./GPUFluidSystem.js";
26
+
27
+ // Only the authored fields: a whole-record write would undo what the GPU derives into the row.
28
+ const AUTHORED_WRITER = generate_record_writer(WATER_WAVE_RECORD_STRUCT, ['upwind_fetch', 'shore_reflection', 'node', 'geometry', 'simulate_hidden']);
29
+ const IMPULSE_WRITER = generate_record_writer(WATER_WAVE_RECORD_STRUCT, ['impulses', 'impulse_count']);
30
+ /** The WaterWaves fields a body's row follows. */
31
+ const WAVE_FIELDS = ['upwindFetch', 'shoreReflection'];
32
+ /** A splash's or query's world-to-local matrix: rain can splash hundreds of times a second. */
33
+ const SPLASH_INVERSE = new Float64Array(16);
34
+
35
+ const REQUEST_INITIAL_CAPACITY = 16;
36
+
37
+ /**
38
+ * @param {Int32Array|Uint32Array|Float32Array|Float64Array|Uint8Array} array
39
+ * @param {number} length
40
+ * @returns {any} `array`, or a copy of it at least `length` long
41
+ */
42
+ function fit(array, length) {
43
+ if (array.length >= length) {
44
+ return array;
45
+ }
46
+
47
+ const grown = new array.constructor(Math.max(length, 2 * array.length));
48
+
49
+ grown.set(array);
50
+
51
+ return grown;
52
+ }
53
+
54
+ /**
55
+ * Registers [Water, MeshletGeometry, Transform64] in the scene's existing GPU database. Authored
56
+ * edits queue writes; idle frames never walk bodies. Transform rows follow the same change events
57
+ * and GPU hierarchy as shaded geometry. Water does not enter the opaque mesh table or its TLAS.
58
+ *
59
+ * Every body has a surface row (WATER_SURFACE_SIMULATION_PLAN_2026_10_06.md §6.1): its fetch and shore from an
60
+ * optional {@link WaterWaves}, its queued splashes, and what the GPU derives from them each frame. The GPU culls
61
+ * every body against the frame's view before simulating; a hidden body is paused unless
62
+ * {@link Water#simulateWhenHidden} (WATER_SURFACE_DISTANCE_LOD_PROPOSAL_2026_10_06.md §3).
63
+ *
64
+ * **The wind is the scene's** (GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md §6, the Water row): the culling pass
65
+ * samples the published wind field 10 m above each body's origin and low-passes it per body, on the GPU. What the
66
+ * system decides on the CPU is only whether that field can hold wind at all ({@link GPUWaterSurface#windy}, which
67
+ * animates the shared bands): while any enabled {@link GPUFluidComponent} steps a fluid, or the far field
68
+ * ({@link GPUFluidSystem#far_field}) is not still. Asked every update, allocating nothing.
69
+ */
70
+ export class WaterSystem extends System {
71
+
72
+ /**
73
+ * Offer the compute pipelines the water surface query API dispatches.
74
+ *
75
+ * @param {import("../../shade/device/pipeline/PipelineEnquiry.js").PipelineEnquiry} enquiry
76
+ */
77
+ static enquirePipelines(enquiry) {
78
+ GPUWaterQueries.enquirePipelines(enquiry);
79
+ }
80
+
81
+ dependencies = [Water, MeshletGeometry, Transform64];
82
+ components_used = [ResourceAccessSpecification.from(WaterWaves, ResourceAccessKind.Read),
83
+ // whether the scene's wind field has a fluid behind it
84
+ ResourceAccessSpecification.from(GPUFluidComponent, ResourceAccessKind.Read)];
85
+ #graphics;
86
+ #scene;
87
+ #context = null;
88
+ #renderer = new GPUWaterRenderer();
89
+ #waves = new GPUWaterSurface();
90
+ #entries = new Map();
91
+ #dirty = new Set();
92
+ #impulses = new Set();
93
+ /** @type {GPUFluidSystem|null} the far field's source, once found */
94
+ #fluid = null;
95
+ #far = new Float64Array(3);
96
+ /** Whether {@link #visit_domain} met an enabled component. */
97
+ #enabled_found = false;
98
+ #visit_domain = component => {
99
+ if (component.enabled) { this.#enabled_found = true; return false; }
100
+ return true;
101
+ };
102
+ #extensions = [];
103
+ #time = 0;
104
+
105
+ /*
106
+ The surface read API: per handle, the request's entity, local xz points, count, footprint,
107
+ target, and status.
108
+ */
109
+ #request_entities = new Int32Array(REQUEST_INITIAL_CAPACITY);
110
+ #request_point_offsets = new Uint32Array(REQUEST_INITIAL_CAPACITY);
111
+ #request_counts = new Uint32Array(REQUEST_INITIAL_CAPACITY);
112
+ #request_footprints = new Float32Array(REQUEST_INITIAL_CAPACITY);
113
+ #request_offsets = new Uint32Array(REQUEST_INITIAL_CAPACITY);
114
+ /** @type {(Float32Array|null)[]} */
115
+ #request_targets = new Array(REQUEST_INITIAL_CAPACITY).fill(null);
116
+ #request_points = new Float64Array(2 * REQUEST_INITIAL_CAPACITY);
117
+ #request_point_count = 0;
118
+ #world_to_local_xz = new Float64Array(2 * REQUEST_INITIAL_CAPACITY);
119
+ #status = new Uint8Array(REQUEST_INITIAL_CAPACITY);
120
+
121
+ /** Recycled handles. */
122
+ #free_handles = new Uint32Array(REQUEST_INITIAL_CAPACITY);
123
+ #free_count = 0;
124
+
125
+ /** Handles below this have been handed out at least once. */
126
+ #fresh = 0;
127
+
128
+ /** Handles asked for and not yet answered headless or handed to the device. */
129
+ #asked = new Uint32Array(REQUEST_INITIAL_CAPACITY);
130
+ #asked_count = 0;
131
+
132
+ /** Handles that settled at the last apply point, recycled at the next. */
133
+ #settled = new Uint32Array(REQUEST_INITIAL_CAPACITY);
134
+ #settled_count = 0;
135
+
136
+ /**
137
+ * The read API on the device: its database, gather and readback. Made by the first frame with a
138
+ * request, dropped with the device.
139
+ *
140
+ * @type {GPUWaterQueries|null}
141
+ */
142
+ #queries = null;
143
+
144
+ get settings() { return this.#renderer.settings; }
145
+ get waves() { return this.#waves; }
146
+ get transparency_channel() { return this.#renderer; }
147
+ get scene() { return this.#scene; }
148
+ request_probe(uv) { return this.#renderer.request_probe(uv); }
149
+ get time() { return this.#time; }
150
+ set time(seconds) {
151
+ assert.ok(Number.isFinite(seconds) && seconds >= 0, 'water time must be finite and non-negative');
152
+ this.#time = seconds;
153
+ }
154
+
155
+ /**
156
+ * What became of each surface query request, by handle: a `GPUReadbackStatus`. `Pending` from
157
+ * {@link request_local} / {@link request_world} until the apply point ({@link update}) that
158
+ * answers it, `Delivered` (the samples are unpacked in `target`) or `Dropped` (the target is as
159
+ * it was) from there until the next apply point, then `None`, the handle recycled.
160
+ *
161
+ * Read it fresh each time rather than keeping a reference: the array is replaced when the
162
+ * handle space grows.
163
+ *
164
+ * @returns {Uint8Array}
165
+ */
166
+ get status() {
167
+ return this.#status;
168
+ }
169
+
170
+ /**
171
+ * The surface read API's device half, or null before the first frame with a request on a
172
+ * device. For inspection.
173
+ *
174
+ * @returns {GPUWaterQueries|null}
175
+ */
176
+ get queries() {
177
+ return this.#queries;
178
+ }
179
+
180
+ constructor(graphics, scene) {
181
+ super(); assert.defined(graphics, 'graphics'); assert.defined(scene, 'scene');
182
+ this.#graphics = graphics; this.#scene = scene;
183
+ this.#renderer.scene = scene;
184
+ this.#waves.onInputsConsumed.add(context => {
185
+ if (context !== this.#context) { return; }
186
+ for (const entry of this.#impulses) {
187
+ entry.staged.impulse_count = 0;
188
+ if (entry.queue.length === 0) { this.#impulses.delete(entry); }
189
+ }
190
+ });
191
+ this.#renderer.waves = this.#waves;
192
+ }
193
+
194
+ async startup(entityManager) {
195
+ this.entityManager = entityManager;
196
+ assert.ok(this.#scene.dataset == null || this.#scene.dataset === entityManager.dataset, 'scene belongs to another dataset');
197
+ this.#scene.dataset = entityManager.dataset;
198
+ this.#graphics.add_transparency_channel(this.#renderer);
199
+ this.#extensions = [this.#graphics.add_extension(new WaterExtension(this)),
200
+ this.#graphics.add_extension(new WaterSurfaceExtension(this))];
201
+ }
202
+
203
+ async shutdown() {
204
+ for (const entry of Array.from(this.#entries.values())) { this.unlink(entry.water, entry.geometry, entry.transform, entry.entity); }
205
+ this.#drop_queries();
206
+ this.#graphics.remove_transparency_channel(this.#renderer);
207
+ for (const extension of this.#extensions) { this.#graphics.remove_extension(extension); }
208
+ this.#extensions.length = 0;
209
+ this.#renderer.dispose(); this.#waves.dispose(); this.#context = null;
210
+ }
211
+
212
+ link(water, geometry, transform, entity) {
213
+ // queue: splashes not yet staged, four numbers each (local x, z, radius, kick); staged: the batch on the
214
+ // GPU row until a submitted frame spends it
215
+ const entry = { water, geometry, transform, entity, context: null, row: -1, wave: -1, waves: null, queue: [],
216
+ staged: { impulses: Array.from({ length: WATER_WAVE_IMPULSE_LIMIT }, () => new Float64Array(4)), impulse_count: 0 } };
217
+ const dataset = this.entityManager.dataset;
218
+ entry.changed = () => this.#dirty.add(entry);
219
+ entry.world = () => entry.context?.rows.mark_world(entity);
220
+ entry.local = () => entry.context?.rows.mark_local(entity);
221
+ entry.components = ({ klass }) => {
222
+ if (klass === WaterWaves) { this.#watch_waves(entry); entry.changed(); }
223
+ if (klass === TransformAttachment) { entry.local(); }
224
+ };
225
+ water.onChanged.add(entry.changed);
226
+ dataset.addEntityEventListener(entity, TRANSFORM64_EVENT_CHANGE, entry.world);
227
+ dataset.addEntityEventListener(entity, TRANSFORM_ATTACHMENT_EVENT_CHANGE, entry.local);
228
+ dataset.addEntityEventListener(entity, EventType.ComponentAdded, entry.components);
229
+ dataset.addEntityEventListener(entity, EventType.ComponentRemoved, entry.components);
230
+ this.#entries.set(entity, entry);
231
+ this.#watch_waves(entry);
232
+ this.#dirty.add(entry);
233
+ this.#renderer.body_count++;
234
+ }
235
+
236
+ #watch_waves(entry) {
237
+ if (entry.waves != null) { for (const field of WAVE_FIELDS) { entry.waves[field].onChanged.remove(entry.changed); } }
238
+ entry.waves = this.entityManager.dataset.getComponent(entry.entity, WaterWaves);
239
+ if (entry.waves != null) { for (const field of WAVE_FIELDS) { entry.waves[field].onChanged.add(entry.changed); } }
240
+ }
241
+
242
+ unlink(water, geometry, transform, entity) {
243
+ const entry = this.#entries.get(entity);
244
+ if (entry === undefined) { return; }
245
+ const dataset = this.entityManager.dataset;
246
+ water.onChanged.remove(entry.changed);
247
+ if (entry.waves != null) { for (const field of WAVE_FIELDS) { entry.waves[field].onChanged.remove(entry.changed); } }
248
+ dataset.removeEntityEventListener(entity, TRANSFORM64_EVENT_CHANGE, entry.world);
249
+ dataset.removeEntityEventListener(entity, TRANSFORM_ATTACHMENT_EVENT_CHANGE, entry.local);
250
+ dataset.removeEntityEventListener(entity, EventType.ComponentAdded, entry.components);
251
+ dataset.removeEntityEventListener(entity, EventType.ComponentRemoved, entry.components);
252
+ if (entry.context !== null && entry.context === this.#graphics.scene_context(this.#scene)) {
253
+ entry.context.scene_database.get('water_bodies').remove(entry.row);
254
+ entry.context.rows.release(entity);
255
+ }
256
+ this.#entries.delete(entity); this.#dirty.delete(entry); this.#impulses.delete(entry);
257
+ this.#renderer.body_count--;
258
+ if (entry.context !== null) { this.#renderer.meshlet_capacity -= entry.geometry.meshlets.count; }
259
+ }
260
+
261
+ update(dt) {
262
+ this.#recycle_requests();
263
+ this.#time += dt; this.#renderer.time = this.#time; this.#waves.advance(dt);
264
+ this.#waves.windy = this.#wind_can_blow();
265
+ const context = this.#graphics.scene_context(this.#scene);
266
+ if (context === null) {
267
+ if (this.#context !== null || this.#queries !== null) {
268
+ this.#drop_queries();
269
+ this.#context = null;
270
+ }
271
+ this.#answer_requests(null);
272
+ return;
273
+ }
274
+ if (context !== this.#context) {
275
+ this.#drop_queries();
276
+ this.#context = context;
277
+ // Device replacement is the only whole-scene rebuild; ordinary frames drain edits.
278
+ for (const entry of this.#entries.values()) {
279
+ entry.context = null; entry.row = -1; entry.wave = -1;
280
+ // a batch the old context never spent goes back to the front of the queue
281
+ const staged = [];
282
+ for (let i = 0; i < entry.staged.impulse_count; i++) { staged.push(...entry.staged.impulses[i]); }
283
+ entry.queue.unshift(...staged); entry.staged.impulse_count = 0;
284
+ this.#dirty.add(entry);
285
+ }
286
+ this.#renderer.meshlet_capacity = 0;
287
+ }
288
+ for (const entry of this.#dirty) { this.#write(entry, context); }
289
+ this.#dirty.clear();
290
+ for (const entry of this.#impulses) { this.#stage(entry, context); }
291
+ this.#answer_requests(context);
292
+ }
293
+
294
+ /**
295
+ * Whether the scene's wind field can hold wind: an enabled {@link GPUFluidComponent} on any entity, or a far
296
+ * field that is not still. The lowest holder of the component answers without a walk; only when it is disabled
297
+ * are the others visited.
298
+ * @returns {boolean}
299
+ */
300
+ #wind_can_blow() {
301
+ const em = this.entityManager, dataset = em.dataset;
302
+ if (dataset !== null) {
303
+ const lowest = dataset.getAnyComponentInstance(GPUFluidComponent);
304
+ if (lowest !== null) {
305
+ if (lowest.enabled) { return true; }
306
+ this.#enabled_found = false;
307
+ dataset.traverseComponents(GPUFluidComponent, this.#visit_domain);
308
+ if (this.#enabled_found) { return true; }
309
+ }
310
+ }
311
+ // registered later than this system, or never
312
+ const fluid = this.#fluid ??= em.getSystem(GPUFluidSystem);
313
+ if (fluid === null) { return false; }
314
+ const far = this.#far;
315
+ fluid.far_field(far);
316
+ return far[0] !== 0 || far[1] !== 0 || far[2] !== 0;
317
+ }
318
+
319
+ /** Top up a body's batch on its surface row from its queue, up to the row's limit. */
320
+ #stage(entry, context) {
321
+ const staged = entry.staged, queue = entry.queue;
322
+ if (entry.wave === -1 || queue.length === 0 || staged.impulse_count === WATER_WAVE_IMPULSE_LIMIT) { return; }
323
+ const taken = Math.min(queue.length / 4, WATER_WAVE_IMPULSE_LIMIT - staged.impulse_count);
324
+ for (let i = 0; i < taken; i++) {
325
+ const impulse = staged.impulses[staged.impulse_count++];
326
+ for (let c = 0; c < 4; c++) { impulse[c] = queue[4 * i + c]; }
327
+ }
328
+ queue.copyWithin(0, 4 * taken);
329
+ queue.length -= 4 * taken;
330
+ this.#waves.splashed = true;
331
+ for (let i = staged.impulse_count; i < WATER_WAVE_IMPULSE_LIMIT; i++) { staged.impulses[i].fill(0); }
332
+ context.scene_database.get('water_waves').set_fields(entry.wave, staged, IMPULSE_WRITER);
333
+ }
334
+
335
+ #write(entry, context) {
336
+ const bodies = context.scene_database.get('water_bodies');
337
+ const waves = context.scene_database.get('water_waves');
338
+ if (entry.context === null) {
339
+ context.rows.acquire(entry.entity);
340
+ context.geometries.add(entry.geometry);
341
+ entry.context = context;
342
+ this.#renderer.meshlet_capacity += entry.geometry.meshlets.count;
343
+ }
344
+ const geometry = context.geometries.getAddress(entry.geometry);
345
+ if (entry.wave === -1) {
346
+ // the GPU seeds the wind from its first sample, and derives the weights for it
347
+ entry.wave = waves.add({ upwind_fetch: 0, shore_reflection: WATER_WAVES_SHORE_REFLECTION_DEFAULT,
348
+ impulses: Array.from({ length: WATER_WAVE_IMPULSE_LIMIT }, () => [0, 0, 0, 0]), impulse_count: 0,
349
+ node: entry.entity + 1, geometry, simulate_hidden: 0, wind: [0, 0, 0], wind_seeded: 0, derived_for: [-1, -1, 0, 0],
350
+ weights: new Array(WATER_SPECTRUM_BAND_COUNT).fill(0), slope_variance: 0,
351
+ wind_from: [0, 0], wind_to: [0, 0], wind_blend: 1, displacement_bounds: [0, 0, 0, 0], particle_levels: 0 });
352
+ }
353
+ const waves_component = entry.waves;
354
+ const reflection = Math.min(1, Math.max(0, waves_component?.shoreReflection.getValue() ?? WATER_WAVES_SHORE_REFLECTION_DEFAULT));
355
+ waves.set_fields(entry.wave, { upwind_fetch: Math.max(0, waves_component?.upwindFetch.getValue() ?? 0), shore_reflection: reflection, node: entry.entity + 1, geometry,
356
+ simulate_hidden: entry.water.simulateWhenHidden.getValue() ? 1 : 0 }, AUTHORED_WRITER);
357
+ const water = entry.water;
358
+ const record = { geometry, node: entry.entity + 1,
359
+ dirty: 1,
360
+ wave: entry.wave, ior: Math.max(1, water.ior.getValue()),
361
+ roughness: Math.min(1, Math.max(0, water.roughness.getValue())), medium: water.medium.to_gpu_record(),
362
+ backscattering: [0, 0, 0, 0], extinction: [0, 0, 0, 0], diffuse_transmittance: 0 };
363
+ if (entry.row === -1) { entry.row = bodies.add(record); } else { bodies.set(entry.row, record); }
364
+ }
365
+
366
+ /**
367
+ * A splash at a world position: rings of wave particles of the given world radius (half their wavelength)
368
+ * whose height is the given vertical velocity kick, m/s, over the time a crest takes to cross them. The
369
+ * position is interpreted in the CPU-owned pose; queued on the CPU and spent on the GPU, sixteen a body a frame.
370
+ */
371
+ splash(entity, position, radius, strength) {
372
+ const entry = this.#entries.get(entity); assert.defined(entry, 'water entity');
373
+ assert.greaterThan(radius, 0, 'radius'); assert.isFinite(radius, 'radius'); assert.isFinite(strength, 'strength');
374
+ for (let i = 0; i < 3; i++) { assert.isFinite(position[i], `position[${i}]`); }
375
+ const inverse = SPLASH_INVERSE;
376
+ const inverted = m4_invert(inverse, entry.transform);
377
+ assert.ok(inverted, 'water transform is singular');
378
+ const x = inverse[0] * position[0] + inverse[4] * position[1] + inverse[8] * position[2] + inverse[12];
379
+ const z = inverse[2] * position[0] + inverse[6] * position[1] + inverse[10] * position[2] + inverse[14];
380
+ entry.queue.push(x, z, radius, strength); this.#impulses.add(entry);
381
+ }
382
+
383
+ /**
384
+ * Ask for the water surface height and world-space unit normal at `count` points in the water
385
+ * body's local `(x, z)` plane: `local_xz` holds `[x0, z0, x1, z1, ...]`, and each sample lands
386
+ * in `target` from `target_offset` as {@link WATER_QUERY_SAMPLE_FLOATS} (4) floats,
387
+ * `[world_height, nx, ny, nz]`, unpacked on the CPU when the request settles `Delivered` at a
388
+ * later apply point ({@link update}); see {@link status}.
389
+ *
390
+ * Points outside the body's authored outline or inside island holes are clamped continuously to
391
+ * the nearest point on the mesh's triangles so no `NaN`s are ever produced.
392
+ *
393
+ * @param {number} entity water entity
394
+ * @param {ArrayLike<number>} local_xz interleaved `[x0, z0, x1, z1, ...]` in local body coordinates
395
+ * @param {number} count number of `(x, z)` points, at least 1
396
+ * @param {number} footprint spatial filter footprint in metres, at least 0 (0 resolves all wavelengths)
397
+ * @param {Float32Array} target memory the caller owns and keeps until the request settles
398
+ * @param {number} target_offset in floats
399
+ * @returns {number} a handle into {@link status}
400
+ */
401
+ request_local(entity, local_xz, count, footprint, target, target_offset) {
402
+ const entry = this.#entries.get(entity);
403
+ assert.defined(entry, 'water entity');
404
+ assert.isArrayLike(local_xz, 'local_xz');
405
+ assert.isPositiveInteger(count, 'count');
406
+ assert.greaterThanOrEqual(local_xz.length, 2 * count, 'local_xz');
407
+ for (let i = 0; i < 2 * count; i++) {
408
+ assert.isFinite(local_xz[i], `local_xz[${i}]`);
409
+ }
410
+ assert.isFinite(footprint, 'footprint');
411
+ assert.greaterThanOrEqual(footprint, 0, 'footprint');
412
+ assert.ok(target instanceof Float32Array, 'target must be a Float32Array');
413
+ assert.isNonNegativeInteger(target_offset, 'target_offset');
414
+ assert.lessThanOrEqual(target_offset + WATER_QUERY_SAMPLE_FLOATS * count, target.length, 'the query samples reach past the target');
415
+
416
+ const h = this.#allocate_handle();
417
+ const point_start = this.#request_point_count;
418
+ const next_point_count = point_start + count;
419
+
420
+ if (2 * next_point_count > this.#request_points.length) {
421
+ this.#request_points = fit(this.#request_points, 2 * next_point_count);
422
+ }
423
+
424
+ const points = this.#request_points;
425
+
426
+ for (let i = 0; i < 2 * count; i++) {
427
+ points[2 * point_start + i] = local_xz[i];
428
+ }
429
+
430
+ this.#request_point_count = next_point_count;
431
+ this.#request_entities[h] = entity;
432
+ this.#request_point_offsets[h] = point_start;
433
+ this.#request_counts[h] = count;
434
+ this.#request_footprints[h] = footprint;
435
+ this.#request_offsets[h] = target_offset;
436
+ this.#request_targets[h] = target;
437
+ this.#status[h] = GPUReadbackStatus.Pending;
438
+
439
+ if (this.#asked_count === this.#asked.length) {
440
+ this.#asked = fit(this.#asked, this.#asked_count + 1);
441
+ }
442
+
443
+ this.#asked[this.#asked_count++] = h;
444
+
445
+ return h;
446
+ }
447
+
448
+ /**
449
+ * Ask for the water surface height and world-space unit normal at `count` points in world
450
+ * space: `world_xyz` holds `[x0, y0, z0, x1, y1, z1, ...]`. Internally transforms each world
451
+ * point into the water body's local `(x, z)` plane via the inverse of its CPU-owned
452
+ * `Transform64` and delegates to {@link request_local}.
453
+ *
454
+ * @param {number} entity water entity
455
+ * @param {ArrayLike<number>} world_xyz interleaved `[x0, y0, z0, ...]` in world metres
456
+ * @param {number} count number of `(x, y, z)` points, at least 1
457
+ * @param {number} footprint spatial filter footprint in metres, at least 0 (0 resolves all wavelengths)
458
+ * @param {Float32Array} target memory the caller owns and keeps until the request settles
459
+ * @param {number} target_offset in floats
460
+ * @returns {number} a handle into {@link status}
461
+ */
462
+ request_world(entity, world_xyz, count, footprint, target, target_offset) {
463
+ const entry = this.#entries.get(entity);
464
+ assert.defined(entry, 'water entity');
465
+ assert.isArrayLike(world_xyz, 'world_xyz');
466
+ assert.isPositiveInteger(count, 'count');
467
+ assert.greaterThanOrEqual(world_xyz.length, 3 * count, 'world_xyz');
468
+ for (let i = 0; i < 3 * count; i++) {
469
+ assert.isFinite(world_xyz[i], `world_xyz[${i}]`);
470
+ }
471
+
472
+ const inverse = SPLASH_INVERSE;
473
+ const inverted = m4_invert(inverse, entry.transform);
474
+ assert.ok(inverted, 'water transform is singular');
475
+
476
+ if (2 * count > this.#world_to_local_xz.length) {
477
+ this.#world_to_local_xz = fit(this.#world_to_local_xz, 2 * count);
478
+ }
479
+
480
+ const local_xz = this.#world_to_local_xz;
481
+
482
+ for (let i = 0; i < count; i++) {
483
+ const wx = world_xyz[3 * i];
484
+ const wy = world_xyz[3 * i + 1];
485
+ const wz = world_xyz[3 * i + 2];
486
+
487
+ local_xz[2 * i] = inverse[0] * wx + inverse[4] * wy + inverse[8] * wz + inverse[12];
488
+ local_xz[2 * i + 1] = inverse[2] * wx + inverse[6] * wy + inverse[10] * wz + inverse[14];
489
+ }
490
+
491
+ return this.request_local(entity, local_xz, count, footprint, target, target_offset);
492
+ }
493
+
494
+ /**
495
+ * @returns {number}
496
+ */
497
+ #allocate_handle() {
498
+ if (this.#free_count > 0) {
499
+ return this.#free_handles[--this.#free_count];
500
+ }
501
+
502
+ const h = this.#fresh++;
503
+
504
+ if (h === this.#status.length) {
505
+ const capacity = 2 * h;
506
+
507
+ this.#request_entities = fit(this.#request_entities, capacity);
508
+ this.#request_point_offsets = fit(this.#request_point_offsets, capacity);
509
+ this.#request_counts = fit(this.#request_counts, capacity);
510
+ this.#request_footprints = fit(this.#request_footprints, capacity);
511
+ this.#request_offsets = fit(this.#request_offsets, capacity);
512
+ this.#status = fit(this.#status, capacity);
513
+ this.#free_handles = fit(this.#free_handles, capacity);
514
+ this.#settled = fit(this.#settled, capacity);
515
+
516
+ for (let i = this.#request_targets.length; i < capacity; i++) {
517
+ this.#request_targets.push(null);
518
+ }
519
+ }
520
+
521
+ return h;
522
+ }
523
+
524
+ /**
525
+ * A request answered: its status set, readable until the next apply point.
526
+ *
527
+ * @param {number} h
528
+ * @param {number} status
529
+ */
530
+ #settle(h, status) {
531
+ this.#status[h] = status;
532
+ this.#request_targets[h] = null;
533
+ this.#settled[this.#settled_count++] = h;
534
+ }
535
+
536
+ /**
537
+ * The apply point's first half: the handles answered at the last one go back to the free list.
538
+ */
539
+ #recycle_requests() {
540
+ const settled = this.#settled;
541
+ const status = this.#status;
542
+ const free = this.#free_handles;
543
+
544
+ for (let i = 0; i < this.#settled_count; i++) {
545
+ const h = settled[i];
546
+
547
+ status[h] = GPUReadbackStatus.None;
548
+ free[this.#free_count++] = h;
549
+ }
550
+
551
+ this.#settled_count = 0;
552
+ }
553
+
554
+ /**
555
+ * The apply point's second half: what the device delivered or dropped, and, headless, every
556
+ * request asked since from the body's rest surface.
557
+ *
558
+ * @param {object|null} context
559
+ */
560
+ #answer_requests(context) {
561
+ const queries = this.#queries;
562
+
563
+ if (queries !== null) {
564
+ queries.apply();
565
+ this.#take_settled(queries);
566
+ }
567
+
568
+ if (context !== null || this.#asked_count === 0) {
569
+ return;
570
+ }
571
+
572
+ const asked = this.#asked;
573
+ const points = this.#request_points;
574
+
575
+ for (let i = 0; i < this.#asked_count; i++) {
576
+ const h = asked[i];
577
+ const entry = this.#entries.get(this.#request_entities[h]);
578
+
579
+ if (entry === undefined) {
580
+ this.#settle(h, GPUReadbackStatus.Dropped);
581
+ continue;
582
+ }
583
+
584
+ const m = entry.transform;
585
+ const inverse = SPLASH_INVERSE;
586
+ const inverted = m4_invert(inverse, m);
587
+ let nx = 0, ny = 1, nz = 0;
588
+ if (inverted) {
589
+ const rx = inverse[1], ry = inverse[5], rz = inverse[9];
590
+ const len = Math.hypot(rx, ry, rz);
591
+ if (len > 1e-20) {
592
+ nx = rx / len;
593
+ ny = ry / len;
594
+ nz = rz / len;
595
+ }
596
+ }
597
+
598
+ const box = entry.geometry.bounding_box;
599
+ const mid_y = 0.5 * (box[1] + box[4]);
600
+ const count = this.#request_counts[h];
601
+ const point_base = 2 * this.#request_point_offsets[h];
602
+ const target = this.#request_targets[h];
603
+ let at = this.#request_offsets[h];
604
+
605
+ for (let k = 0; k < count; k++, at += WATER_QUERY_SAMPLE_FLOATS) {
606
+ const lx = Math.min(Math.max(points[point_base + 2 * k], box[0]), box[3]);
607
+ const lz = Math.min(Math.max(points[point_base + 2 * k + 1], box[2]), box[5]);
608
+ const world_y = m[1] * lx + m[5] * mid_y + m[9] * lz + m[13];
609
+
610
+ target[at] = world_y;
611
+ target[at + 1] = nx;
612
+ target[at + 2] = ny;
613
+ target[at + 3] = nz;
614
+ }
615
+
616
+ this.#settle(h, GPUReadbackStatus.Delivered);
617
+ }
618
+
619
+ this.#request_point_count = 0;
620
+ this.#asked_count = 0;
621
+ }
622
+
623
+ /**
624
+ * @param {GPUWaterQueries} queries after an apply or a destroy
625
+ */
626
+ #take_settled(queries) {
627
+ for (let i = 0; i < queries.settled_count; i++) {
628
+ this.#settle(queries.settled_tag(i), queries.settled_status(i));
629
+ }
630
+ }
631
+
632
+ /**
633
+ * Let go of the read API's device half: whatever it had in flight is dropped, its status set now.
634
+ */
635
+ #drop_queries() {
636
+ const queries = this.#queries;
637
+
638
+ if (queries === null) {
639
+ return;
640
+ }
641
+
642
+ queries.destroy();
643
+
644
+ this.#take_settled(queries);
645
+
646
+ this.#queries = null;
647
+ }
648
+
649
+ /**
650
+ * Hand the frame's requests to the device, after the frame's surface simulation passes.
651
+ *
652
+ * @param {import("../graphics/render/frame_graph/FrameGraph.js").FrameGraph} graph
653
+ * @param {object} scene
654
+ * @param {GPUDevice} device
655
+ */
656
+ #record_requests(graph, scene, device) {
657
+ const count = this.#asked_count;
658
+
659
+ if (count === 0) {
660
+ return;
661
+ }
662
+
663
+ let queries = this.#queries;
664
+
665
+ if (queries === null) {
666
+ const graphics = this.#graphics.renderer?.graphics;
667
+ if (graphics?.compute_pipelines !== undefined) {
668
+ const enquiry = { device, render: [], compute: [] };
669
+ WaterSystem.enquirePipelines(enquiry);
670
+ for (let i = 0; i < enquiry.compute.length; i++) {
671
+ graphics.compute_pipelines.prepare(enquiry.compute[i]);
672
+ }
673
+ }
674
+ queries = this.#queries = new GPUWaterQueries({ device });
675
+ }
676
+
677
+ const asked = this.#asked;
678
+ const points = this.#request_points;
679
+
680
+ for (let i = 0; i < count; i++) {
681
+ const h = asked[i];
682
+ const entry = this.#entries.get(this.#request_entities[h]);
683
+
684
+ if (entry === undefined || entry.row === -1) {
685
+ this.#settle(h, GPUReadbackStatus.Dropped);
686
+ continue;
687
+ }
688
+
689
+ queries.request(
690
+ entry.row,
691
+ points,
692
+ 2 * this.#request_point_offsets[h],
693
+ this.#request_counts[h],
694
+ this.#request_footprints[h],
695
+ this.#request_targets[h],
696
+ this.#request_offsets[h],
697
+ h
698
+ );
699
+ }
700
+
701
+ this.#request_point_count = 0;
702
+ this.#asked_count = 0;
703
+
704
+ queries.record({ graph, scene, surface: this.#waves });
705
+ }
706
+
707
+ record_surface(frame) {
708
+ const view = frame.view, scene = view.scene;
709
+ if (scene.scene !== this.#scene) { return; }
710
+ const device = this.#graphics.renderer.graphics.device;
711
+ this.#waves.record({ graph: frame.graph, device, scene, time: this.#time,
712
+ view: { camera: view.camera.gpu_buffer, projection: view.camera.camera.projection_matrix, height: view.resolution[1] },
713
+ pixels: this.#renderer.settings.tessellation_pixels });
714
+ this.#record_requests(frame.graph, scene, device);
715
+ }
716
+ record(frame) {
717
+ const inspection = this.#renderer.inspect(frame.graph, frame.view, frame.resolution);
718
+ if (inspection !== undefined) { frame.get(SceneColor).color = inspection; }
719
+ }
720
+ }
721
+
722
+ export class WaterExtension extends RenderExtension {
723
+ name = 'water'; phase = FramePhase.BeforePresent;
724
+ constructor(system) { super(); this.system = system; }
725
+ record(frame) { this.system.record(frame); }
726
+ }
727
+ export class WaterSurfaceExtension extends RenderExtension {
728
+ name = 'water surface'; phase = FramePhase.FrameStart;
729
+ constructor(system) { super(); this.system = system; }
730
+ record(frame) { this.system.record_surface(frame); }
731
+ }