@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,1218 +0,0 @@
1
- # AVBOIT — ledger
2
-
3
- Companion to [`AVBOIT_PLAN_2026_09_03.md`](./AVBOIT_PLAN_2026_09_03.md). One entry per step of §8, in the
4
- order they landed, each recording what was built, how it was verified, and what the step found that the plan
5
- did not predict. Findings that change the plan are noted here and the plan's revision header is bumped, never
6
- silently edited.
7
-
8
- Branch: `claude/avboit-transparency-pipeline-192cff`, worktree
9
- `.claude/worktrees/avboit-transparency-pipeline-192cff`, from `9ab84a154`.
10
-
11
- ---
12
-
13
- ## 0. Survey — 2026-09-03
14
-
15
- **Done.** Read the deck (text extraction via `pdftotext`; the page renderer is not installed here) and the
16
- MBOIT pipeline end to end: orchestrator, three shaders, frame-graph API, resource descriptors, the bucket →
17
- draw-command prep, the emulator's capabilities, the software device's recording API, and the `Renderer` call
18
- site and mode-switch precedents.
19
-
20
- **Findings that shaped the plan:**
21
-
22
- - The sorted meshlet buffer the draws consume has no count header
23
- (`shader_sort_meshlets_by_material.js:20` writes a bare `array<MeshletDefinition>`); the collection *before*
24
- sorting does, and the prep helper already builds an indirect dispatch command over it. The occupancy pass
25
- needs both, so the helper returns two more handles (plan §3.1).
26
- - No render pipeline in the tree writes a storage buffer from the fragment stage, and the pipeline-layout
27
- helper applies one visibility to all groups. The splat pass builds its layout per group (plan §3.11).
28
- - The emulator models `atomic<T>` as `{ value }` objects and implements `atomicAdd/Min/Max/Or`, `textureStore`
29
- on 3D `CPUBitmapData`, `extractBits/insertBits`, `countOneBits`; barriers are no-ops and there is no
30
- workgroup-cooperative dispatch. The warp build is therefore a single invocation so it stays
31
- emulator-testable (plan §3.4).
32
- - View-space depth sign: the volumetrics take `-(view_matrix × p).z`, so the camera looks down −Z; the
33
- occupancy pass uses the same (plan §3.3).
34
- - `float32-blendable` is on the device floor purely for MBOIT's `r32float`/`rgba32float` targets; AVBOIT
35
- blends `rgba16float` only (plan §3.11).
36
- - The in-app browser pane cannot run the full renderer (immediate data unsupported there, per memory), so the
37
- playground look (step 9) needs a real Chrome tab; module compilation (step 8) works in either.
38
-
39
- ---
40
-
41
- ## 1. Skeleton and switch — 2026-09-03
42
-
43
- **Done.** `ShadeTransparencyMode` (`MBOIT = 0`, `AVBOIT = 1`), `Renderer.transparency_mode` defaulting to
44
- MBOIT, a ternary at the single transparency call site selecting the recorder, and
45
- `graph_prepare_meshlet_draw_commands_by_material` returning `meshlet_collection` and
46
- `meshlet_iterator_command` beside what it returned before. `Renderer.js` and the prep helper are CRLF files;
47
- edited byte-exactly.
48
-
49
- **Verified.** The rasterize spec tree (14 files, 81 tests) passes with the helper change; the source-level spec
50
- in `graph_rasterize_meshes_transparent_avboit.spec.js` pins the import, the default and the ternary.
51
-
52
- **Found.** Nothing the plan did not predict.
53
-
54
- ---
55
-
56
- ## 2. Depth curve, packing, warp — 2026-09-03
57
-
58
- **Done.** `chunk_avboit_depth_curve` (t(z), z(t)), `chunk_avboit_pack` (10|10|10, counts ↔ extinction ↔
59
- transmittance, the overflow predicate), `AVBOIT_WARP_STRUCT`, `chunk_avboit_warp` (φ, and the view-depth →
60
- virtual → physical composition), each with an emulator spec.
61
-
62
- **Found.**
63
-
64
- - The emulator hands a `u32` with its top bit set back as a negative JS number (`0xFFFFFF00` reads as
65
- `-256`). Specs compare through `>>> 0`. `countOneBits` and the shift operators are unsigned internally, so
66
- the shaders themselves are unaffected.
67
- - `vec3<f32>` values travel as `Float32Array` through the emulator, so round-trip expectations are f32-exact
68
- (six digits), not f64.
69
-
70
- ---
71
-
72
- ## 3. Occupancy and warp build — 2026-09-03
73
-
74
- **Done.** `chunk_avboit_occupancy` (range, word mask, dilate, fold), `shader_avboit_occupancy` (per meshlet,
75
- indirect over the filtered collection), `shader_avboit_build_warp` (one invocation: dilate → count → halve
76
- until it fits, then the exclusive-prefix LUT), with specs for the chunk and for the whole build shader
77
- dispatched through the emulator.
78
-
79
- **Found.** The implementation dilates at *every* level it evaluates rather than once at the finest and once at
80
- the settled level, which is the same thing where it matters (the settled level) and simpler to reason about.
81
- `AVBOIT_OCCUPANCY_DILATION` at the prepass is therefore only a belt-and-braces one slice. Plan §3.4 describes
82
- the per-level version.
83
-
84
- Specs pin: empty → `V_max` and an all-zero LUT; a run that dilates to exactly `P` does not halve; one over
85
- halves once and is re-dilated at the coarser level; everywhere-occupied halves down to `V = P`; the prefix is
86
- exclusive, monotone, and never exceeds `P`.
87
-
88
- ---
89
-
90
- ## 4. Splat — 2026-09-03
91
-
92
- **Done.** `shader_avboit_splat`: low-resolution render pass, no depth attachment, a `r8unorm` dummy target
93
- with `writeMask: 0`, per-group pipeline layout with the atomics group fragment-only, linear splat with
94
- per-channel overflow marks stored inverted under `atomicMax`. `chunk_avboit_tint` factored out so the splat
95
- and the draw compute the transmission tint from one function.
96
-
97
- **Found.** No un-jitter in the splat: `unjitter_uv` is derivative-based and the jitter is in
98
- internal-resolution pixels, so at 1/8 resolution the correction would be eight times too large. Recorded in
99
- the shader's docblock; the plan did not mention it.
100
-
101
- **Verified.** Validity tier (vertex + fragment) and the tier-3 layout spec: group 3 entries are `FRAGMENT`
102
- with `buffer.type === "storage"`, groups 0–2 are `VERTEX | FRAGMENT`, the target's `writeMask` is 0, no
103
- `depthStencil`.
104
-
105
- ---
106
-
107
- ## 5. Integrate — 2026-09-03
108
-
109
- **Done.** `shader_avboit_integrate`, 8×8 workgroups, exclusive prefix into a `P + 1`-deep `rgba8unorm` 3D
110
- storage texture, per-channel saturation and overflow pinning. Emulator spec over one column: exclusive
111
- prefix, independent channels, saturation, a summed field past 255 saturating in one step, an overflow mark
112
- pinning only its channel, a mark at slice 0.
113
-
114
- **Found.** Nothing unpredicted.
115
-
116
- ---
117
-
118
- ## 6. Draw and sample — 2026-09-03
119
-
120
- **Done.** `chunk_avboit_sample_transmittance` (bias in virtual slices, warp, `(p + 0.5) / (P + 1)` depth
121
- coordinate, `exp`), `avboit_draw_fragment_body` (one WGSL text shared by the IBL and Brick4 pipelines, where
122
- the two MBOIT resolve shaders share theirs by duplication), `shader_avboit_draw_ibl`,
123
- `shader_avboit_draw_brick4`, `AVBOIT_INTEGRAL_SAMPLER_DESCRIPTOR`. Emulator spec of the sample chunk against a
124
- hand-built integral: run boundaries land on texel centres, an event in front attenuates by `exp(-E × ln 256)`,
125
- the bias clears a surface's own linear splat, a gap sample reads the previous run's total and none of the
126
- next run's, beyond the far plane the sample reads the total.
127
-
128
- **Found.** The emulator's `null` sampler is the linear clamp-to-edge fallback the spec wants, so no sampler
129
- object is built there.
130
-
131
- ---
132
-
133
- ## 7. Resolve, orchestrator, tier 3 — 2026-09-03
134
-
135
- **Done.** `shader_avboit_resolve` (reads the scene, writes a fresh `rgba16float`; RGB total transmittance
136
- cannot blend in place), the full `graph_rasterize_meshes_transparent_avboit` with its sub-recorders exported
137
- (`graph_avboit_occupancy`, `_build_warp`, `_splat`, `_integrate`, `_draw`),
138
- `avboit_shaders_wgsl_validity.spec.js` (10 modules, zero diagnostics),
139
- `graph_rasterize_meshes_transparent_avboit.spec.js` on the software device.
140
-
141
- **Found.**
142
-
143
- - `graph_compute_pass` cannot create a `read_write` storage buffer from a descriptor: the helper puts every
144
- readable binding through the read path first, so the descriptor arrives at `pass.write` as an object. The
145
- warp build is recorded by hand instead, and its `settings` uniform is serialized with
146
- `encoder.allocateTransientValueBuffer` — the plain-object convenience only exists inside the graph
147
- helpers, not in `ComputeShader.dispatch`. Plan §3.1's table is unchanged; the pass is the same, just not
148
- via the helper.
149
- - No spec builds a `GPUViewContext` over a scene on the mock, so the tier-3 spec pins the exported
150
- sub-recorders (warp build: one workgroup into a buffer of the struct's size; integration: the dispatch
151
- covers the voxel grid and the texture is `3d`, `P + 1` deep, storage + sampled; resolve: one 3-vertex draw
152
- into a fresh target) and the `Renderer` switch at source level. The whole orchestrator's first encode is on
153
- a real device (steps 8–9).
154
-
155
- **Verified.** 62 tests across the avboit directory; 81 across `rasterize/`; the full `src/shade` suite
156
- (489 files, 3360 tests) passes; `scripts/check-types.mjs` reports the declaration emit clean and zero
157
- consumer-visible errors.
158
-
159
- ---
160
-
161
- ## 8–9. Real compile and the look — 2026-09-03, handed over
162
-
163
- **Done.** `src/shade/playground/avboit/`: the deck's harness in miniature (a stack of cards at a controlled
164
- spacing, a tinted glass pane, an opaque backdrop), the MBOIT/AVBOIT switch, and a **Compile check** that
165
- pushes all ten AVBOIT modules through `createShaderModule` on the live device and prints
166
- `getCompilationInfo`. The README carries the one scripted session (six steps) with what "pass" looks like
167
- for each.
168
-
169
- **Not done here, and why.** The in-app browser pane returned `null` from `requestAdapter()` this session
170
- (its `wgslLanguageFeatures` are present, the adapter is not — the same state
171
- `playground-needs-immediate-data` recorded on 2026-09-02), and no Claude-in-Chrome browser was connected.
172
- So neither the real compile nor the picture happened on this side. What the pane did prove: the page
173
- loads under the dev server, every import resolves (the ten shader modules assemble at import time, so a
174
- chunk-dependency or struct error would have thrown there), and `start()` runs up to
175
- `Renderer.initialize`, which reports `requestAdapter returned null` cleanly. The sources were dumped
176
- (`scratchpad/wgsl/*.wgsl`, 217 KB) for a pane compile should an adapter come back. What is known: every
177
- module passes the validator, which has no type checker; the type-level risks are listed below so the first
178
- compile failure, if any, is quick to place.
179
-
180
- **Type-level things the validator could not see** (in the order a compiler would meet them):
181
-
182
- - `avboit_warp` takes `ptr<storage, AVBOITWarp, read>`; the splat and draw pass `&warp` where `warp` is
183
- declared through `addStorageBuffer(..., false)`. `var<storage>` without an explicit mode is `read`, which
184
- matches.
185
- - `atomicAdd(&extinction_volume[column + u32(s)], …)` indexes an `array<atomic<u32>>` binding from the
186
- fragment stage — core WGSL, but the first such pipeline in the tree.
187
- - `textureStore(integral, vec3<i32>, vec4<f32>)` on `texture_storage_3d<rgba8unorm, write>`.
188
- - `select(vec3<u32>, vec3<u32>, vec3<bool>)` and `select(vec3<f32>, vec3<f32>, vec3<bool>)` in the
189
- integration; both are the component-wise overload.
190
- - The splat fragment entry has no return type against a pipeline with one colour target at `writeMask: 0`.
191
-
192
- ---
193
-
194
- ## 10. Docs — 2026-09-03
195
-
196
- **Done.** `RENDERER_CONTRACT.md` §2 says which mode needs `float32-blendable`; §9 lists
197
- `transparency_mode`. `CHANGELOG.md` carries the entry under the unreleased 4.0.0 "Added". The plan is at
198
- revision 1; nothing in steps 1–9 required a revision of its design sections.
199
-
200
- **Open, for the session in the playground README:** the ten real compiles; the first frame under AVBOIT;
201
- the stack at the default spacing (does the bar behind it show through?); the spacing at which the cards
202
- fall into one slice; the red pane's tint on the backdrop. Results go here as entry 11.
203
-
204
- ---
205
-
206
- ## 11. Path-traced reference on the playground — 2026-09-03
207
-
208
- **Done.** A third mode on the page, "path traced (reference)": `feature_path_tracing_enabled` on, TAA
209
- forced off while it is, the tracer's running-mean floor lowered from 0.01 to 0.002 so the image keeps
210
- settling past 100 samples, a **Reset accumulation** button, and a once-a-second readout of how converged
211
- the image is. That readout needed one small engine addition: `AccumulatingPathTracer.min_sample_count`,
212
- the sample count of the tile that has had the fewest (the per-tile counters were private, and frames
213
- elapsed is not a sample count — a frame advances one tile).
214
-
215
- **What the reference is good for, and not** (README "The reference"):
216
-
217
- - The tracer rolls each transparent hit against alpha and continues the ray, so it converges to the
218
- exact order-independent composite of the card stack — the thing both OIT modes approximate. Good.
219
- - It has no per-channel transmission tint (thin-walled transmission carries only `(1 - F)`), so it
220
- cannot confirm AVBOIT's glass tint; the pane is compared with the tint off.
221
- - Its shadow rays treat geometry as opaque (the tracer's own `TODO`), so the cards cast full shadows
222
- there and none under either OIT mode. The stack, not its shadow, is the comparison.
223
-
224
- **Verified.** `path_tracer/` has no specs of its own; the getter is a read of a private array. The
225
- page's module graph was re-loaded in the pane (see entry 8–9 for what the pane can and cannot prove).
226
-
227
- ---
228
-
229
- ## 12. First renders on a real device — 2026-09-03
230
-
231
- Alex ran the playground and pasted three canvas captures at the default settings (four cards, alpha
232
- 0.85, spacing 1.5, red glass pane): path traced, AVBOIT, MBOIT. Read against the code, not measured.
233
-
234
- **The stack — the deck's depth-complexity claim reproduces.** Under MBOIT the green card, two layers
235
- back, shows through both the red and the blue card as a lighter rectangle inside the red one. Under
236
- the path tracer and under AVBOIT it does not; the overlap is red with the blue card directly behind
237
- faintly visible. AVBOIT is the closer of the two to the reference on the stack. Both rasterized modes
238
- render the cards darker than the tracer, and they agree with each other, so that is a forward-shading
239
- versus tracer difference and not this pipeline's.
240
-
241
- **Also as predicted:** the tracer casts full shadows from the cards (its occlusion query treats
242
- geometry as opaque), neither OIT mode does.
243
-
244
- **The glass pane — the tint is working as written, and is the wrong default.** Under AVBOIT the pane
245
- is deep red; under the tracer and MBOIT it is clear glass. The material is transmission 1 with a red
246
- base colour, and `chunk_avboit_tint` multiplies transmitted light by that base colour, so green and
247
- blue drop to ~14 %. It is not opaque — the horizon shows through as the dark-to-bright step at floor
248
- level — but it is a material semantic the other paths do not have: the forward shader states there is
249
- no per-channel transmission tint in the material model, and the tracer's thin-walled transmission
250
- carries only the Fresnel split. The contract's promise that the two modes take the same inputs and
251
- are safe to flip per frame does not survive a flip that recolours glass.
252
-
253
- Two corrections to what the code says about itself: the chunk docblock calls base-colour-as-tint
254
- "the OpenPBR default", which it is not (OpenPBR's transmission colour defaults to white; glTF's
255
- transmission extension is the one that uses the base colour); and plan §4's first row rests on the
256
- same reading.
257
-
258
- **Proposed:** keep the RGB plumbing, default the tint off, expose it as a runtime switch
259
- (`renderer.avboit.transmission_tint_from_base_color`, carried in the warp header so the splat and
260
- draw agree per frame), a checkbox on the page, plan to revision 2. The alternative — keep it on and
261
- document it as an AVBOIT-only semantic — makes one asset mean two things depending on a renderer
262
- switch. Alex chose the first; entry 14.
263
-
264
- **Also closed by these renders:** step 8's real compile, for the IBL variant. An AVBOIT frame on a real
265
- device means the occupancy, warp, splat, integrate, IBL draw and resolve modules all compiled and the
266
- pipeline ran end to end. The Brick4 draw variant is not exercised by the page (`indirect_lighting_mode`
267
- is IBL there) and remains compiled only by the validator.
268
-
269
- ---
270
-
271
- ## 13. The deck's opaque-card case on the page — 2026-09-03
272
-
273
- **Done.** At Alex's request: a small pale quad, alpha 1 on a `Transparent` material, one unit behind
274
- the last card and offset so part of it is seen through the stack and part stands against the backdrop
275
- and the dark bar. Both pipelines clamp its coverage at `OIT_COVERAGE_MAX` (0.997), so it is the "0 %
276
- transmittance card" of deck slides 35–38: the case where a moment reconstruction cannot hold the
277
- step, leaks the background through the surface, and tears along the depth discontinuity at its edge.
278
- A checkbox (`opaque_quad`, default on) and step 8 of the README's session.
279
-
280
- **What to expect from AVBOIT here, from the design:** the quad's extinction saturates its slice
281
- (`-ln(1 - 0.997) / ln 256 ≈ 1.05`, clamped to 1.0), so the integral behind it reads 1.0 and every
282
- fragment further back is weighted by a transmittance of 1/256. Its edge is a step in the extinction
283
- volume at 1/8 resolution, softened by the draw's trilinear sample over about one voxel — so a slight
284
- halo of that width is the expected artefact, not a leak. Anything behind the quad showing through its
285
- interior would be a finding.
286
-
287
- ---
288
-
289
- ## 14. The tint, kept and off by default — 2026-09-03
290
-
291
- **Done.** `AVBOITSettings` on `Renderer.avboit` with one switch, `transmission_tint_from_base_color`
292
- (default false). The orchestrator takes `avboit` beside its other inputs (the MBOIT one ignores it), the
293
- warp build copies it into a new `tint_from_base_color` header word, and both the splat and the draw
294
- weight `chunk_avboit_tint` by `transmission_factor × f32(warp.tint_from_base_color)`. The chunk's
295
- docblock now says whose semantics the mapping is (glTF's, not OpenPBR's). Plan to revision 2 (§3.5,
296
- §4, §7). Contract §9 lists `avboit`. The page has a checkbox for it, off by default, and step 6 of the
297
- session turns it on before comparing the pane.
298
-
299
- **Why a header word and not a shader constant:** the splat and the draw must agree within a frame, and
300
- the header is already the one place both read per-frame facts from; a runtime flag lets the page do
301
- the A/B without a rebuild.
302
-
303
- **Verified.** Build-warp emulator spec asserts the header copies the flag; validity tier over the
304
- edited modules; rasterize spec tree.
305
-
306
- ---
307
-
308
- ## 15. Decisions on the open items — 2026-09-03
309
-
310
- Alex's answers to the open list, and what each became.
311
-
312
- **Slice floor — mine to call; 5 cm is a spacing worth resolving.** Virtual slice thickness is
313
- `(z0 + z - near) · ln(1 + (far - near) / z0) / V`. At the page's camera (`near 0.1`, `far 200`,
314
- `z0 = 0.8`) and `V = 1024` that was 5.8 cm at 10 m: one slice, under the two-slice bias, so 5 cm
315
- blended. `AVBOIT_VIRTUAL_SLICES_MAX` is now 4096 — 1.5 cm at 10 m, 2.8 cm at 20 m, 7 cm at 50 m — so
316
- 5 cm resolves out to about 17 m before the bias eats it, and the adaptive halving still pulls it back
317
- when more than 128 slices are occupied. The bias stays at 2.0 rather than dropping to the analytic
318
- 1.5: the half-slice margin is what covers the depth difference between a splat fragment (a
319
- 1/8-resolution pixel centre) and a draw fragment on a sloped surface. Cost: 128 occupancy words, a
320
- 16 KB LUT, a longer single-lane loop in the warp build. Specs unchanged in intent (they read the
321
- constant) and green.
322
-
323
- **The opaque quad — fine as is.** Stays on the page.
324
-
325
- **Camera.** `OrbitalCameraController` on the page (drag, wheel, right-drag), **Copy camera** to the
326
- clipboard and a box, **Apply camera** from the box, `camera_state()` / `apply_camera()` /
327
- `configure({ camera })` from the console. Plan §9 gains the slope risk in its own words: a splat
328
- fragment carries its low-resolution pixel centre's depth, and on a grazing surface that can exceed
329
- the bias.
330
-
331
- **Brick4 — should land.** The variant exists and is validator-clean; the page now has an
332
- indirect-lighting select (IBL / Brick4) so the Brick4 draw pipeline compiles and draws on a device.
333
- The scene has no probes, so the indirect term is black either way; the step is in the session.
334
-
335
- **Timings — hook up the profiler.** Done as on `volumetrics_froxel`: **Profile** starts a
336
- `GPUProfileSession` at `WORKLOAD`, pressing it again drains and downloads
337
- `avboit-<mode>-<timestamp>.sgpt` for the gpu-inspector-tool, with the mode and the page's settings in
338
- the note and the adapter in the meta. Same settings, two recordings, one per mode, is the comparison.
339
-
340
- **Software device — wrong tool for an end-to-end spec, and that is fine.** Plan §6 tier 3 now says
341
- so; the full-orchestrator spec is off the list.
342
-
343
- **Phase 2 — only clean, unambiguous, non-situational.** Decided item by item in plan §7 revision 3.
344
- One landed: the draw discards a fragment the volume already calls extinct (`T ≤ 1/256` in every
345
- channel) *before* forward shading. No new resource or pass; the error is bounded by `1/256` of the
346
- background (`AVBOIT_EXTINCT_TRANSMITTANCE`), below the 8-bit volume's own resolution. Tile skipping
347
- (no: sub-millisecond passes), the zero-transmittance depth cull (not now: situational, touches the
348
- depth other passes read, wants its own plan and a scene that needs it), the monochrome buffer (no:
349
- two representations for content we do not have), the sparse texture (no: speculative).
350
-
351
- **Back-sided materials — ignored for now.** Plan §7 keeps the note.
352
-
353
- **AVBOIT is the default.** `Renderer.transparency_mode = ShadeTransparencyMode.AVBOIT`; the enum,
354
- the contract (§2 keeps `float32-blendable` on the floor while MBOIT is selectable; §9 row), the
355
- changelog and the page's default select follow. The source-level spec pins the new default.
356
-
357
- **Committed** on the branch after this entry.
358
-
359
- ---
360
-
361
- ## 16. The page's camera and profile buttons, reported not working — 2026-09-03
362
-
363
- Alex: the camera could not be moved, and **Profile** appeared to do nothing with no way to stop it.
364
-
365
- **What was verified here.** The page was restructured so the camera, its controller and every control
366
- are wired *before* the device is requested. That made the input path testable in the in-app pane,
367
- which has no adapter: a synthetic pointer drag dispatched on the canvas orbited the camera from
368
- `(0, 2.5, 12)` to `(-7.3, 3.7, 9.4)` and a wheel event zoomed it, read back through `camera_state()`.
369
- So `OrbitalCameraController` on `document.body`, the pointer device and the transform write are
370
- correct as wired. What could not be reproduced is the failure in a real session.
371
-
372
- **What the restructure found.** Building geometry before `Renderer.initialize` threw
373
- `Cannot read properties of undefined (reading 'exports')`: the meshlet builder runs on a WASM
374
- instance (`MeshoptClusterizer`) that instantiates asynchronously at import, and the renderer's own
375
- initialization had been what gave it time. The page now awaits `MeshoptClusterizer.ready` itself. Not
376
- the reported fault (the committed order built geometry after the device), but the same class of
377
- thing: a step that silently depended on another step's timing.
378
-
379
- **Made robust, since the fault could not be seen.** Every control handler runs through a guard that
380
- puts a failure into the report panel (`<what> failed: <message>`) instead of only the console; the
381
- final settings application is guarded so a settings error cannot stop the frame loop from starting;
382
- the Profile button carries a `recording` state (label and red border) so a running capture is
383
- unmistakable and the same button stops it; the controls are spelled out under the canvas. If a real
384
- session still does not orbit, the report panel now says why.
385
-
386
- **Suspicion, unverified.** With HMR off, a reload taken while the page's files were being rewritten
387
- on disk can pair one version of `index.html` with another of `main.js` or `Renderer.js`; a
388
- `TypeError` in the settings step then killed `start()` before the frame loop, which is the one
389
- sequence that leaves the buttons registered, the camera frozen and no picture of the cause. A hard
390
- reload after this commit is the first thing to try.
391
-
392
- ---
393
-
394
- ## 17. First timings — 2026-09-03
395
-
396
- Alex ran a short profiling session on the page (the deck's harness in miniature: four cards, the
397
- glass pane, the opaque quad, 1280x720): **AVBOIT 289.9 us, MBOIT 282.4 us**, averages of the
398
- transparency work, which he reads as within the noise. Device and capture files not recorded here.
399
-
400
- **What that says.** On this scene the two pipelines cost the same. That is the expected shape, not a
401
- surprise: the deck's cost argument (AVBOIT approaching no-OIT, slides 106-110) comes from VFX content
402
- with tens of millions of overdrawn transparent pixels, where the 1/8-resolution splat, the saturated
403
- integral and the early-out remove most of the shading work. Six quads at 720p have almost nothing
404
- for those to remove, and AVBOIT's fixed costs — the occupancy pass, the warp build, the integration
405
- over a 160x90x129 volume, the resolve into a fresh target — are of the same order as MBOIT's two
406
- full-resolution passes over the same few quads. So the number closes the question "does the new path
407
- cost more on light content" (no), and leaves open the one the deck actually answers, which needs a
408
- scene with real transparent overdraw. Plan §9's contention risk is unmeasured until then.
409
-
410
- **Committed** with this entry.
411
-
412
- ---
413
-
414
- ## 18. Review against the engine — 2026-09-03
415
-
416
- Alex asked three questions of the code: is anything reinvented, does it follow one function per
417
- chunk, does it fight the engine. Surveyed and fixed; plan to revision 4.
418
-
419
- **Reinvented, now reused.**
420
-
421
- - *The depth curve.* `chunk_avboit_depth_curve` (t(z), z(t), a `z0` knob) was a second copy of what the
422
- light clusters and the froxels already use: `cluster_depth_to_z_slice` with parameters from
423
- `build_light_cluster_parameters`. That builder's `scale` argument is the linearization the deck
424
- describes; `avboit_build_depth_curve` chooses it so the curve is pure log from the near offset,
425
- because the warp makes near precision free. Gone: the chunk, its inverse, its spec, the
426
- `AVBOIT_DEPTH_CURVE_LINEARIZATION` constant, and the camera binding of the warp build (the curve now
427
- arrives as three floats in the settings and in the occupancy pass's own settings uniform).
428
- - *The bit fold.* `avboit_fold_pairs` is the OR of the even bits compacted and the odd bits compacted,
429
- which is `decode32_morton2`'s job. Found on the way: for a 32-bit code that function's `.y` is *not*
430
- the odd bits — the `<< 31` that separates them in the 16-bit variant (`<< 15`) cannot work in 32
431
- bits — and the first reuse trusted it; the warp-build spec caught it as an occupied count of 70
432
- against 66. The fold now goes through the correct `.x` half twice, once on `x >> 1`, and has a spec
433
- against a bit-by-bit reference. `decode32_morton2` is used nowhere else in the tree; its `.y` is a
434
- latent engine defect, noted for whoever reaches for it next.
435
- - *The sampler.* `AVBOIT_INTEGRAL_SAMPLER_DESCRIPTOR` duplicated `SAMPLER_LINEAR_UPSCALE` (linear,
436
- clamped on every axis), which the draw already binds as `sLinear` for the split-sum LUT. The integral
437
- samples through that; one binding fewer in the view group.
438
- - *The draw loop.* `material_draw` existed privately in both orchestrators. It is
439
- `rasterize/native/material_draw_indirect.js` now, with the material bind-group layout it needs,
440
- imported by both.
441
-
442
- **Convention.** 650 of the engine's ~700 chunk files hold exactly one function named after the file;
443
- mine held two to six. Five chunks became seventeen (`chunk_avboit_pack_counts`, `_unpack_counts`,
444
- `_transmittance_to_extinction`, `_extinction_to_transmittance`, `_extinction_to_counts`,
445
- `_counts_to_extinction`, `_overflows`, `_occupancy_range`, `_occupancy_word_mask`, `_dilate_word`,
446
- `_fold_pairs`, `_warp`, `_view_depth_to_virtual`, `_view_depth_to_physical`,
447
- `_integral_depth_coordinate`, `_sample_transmittance`, `_transmission_tint`), each with its spec; the
448
- hand-built warp and the curve's inverse the specs share live in `avboit_spec_support.js`, test-only.
449
-
450
- **Kept, on purpose.** `avboit_draw_fragment_body.js` shares one WGSL text between the IBL and Brick4
451
- draw pipelines where MBOIT duplicates its resolve body. No precedent either way in the tree; two
452
- 45-line bodies drifting apart is the worse failure, and the two files stay concrete modules with no
453
- conditionals in them.
454
-
455
- **Also checked and found conforming:** the manual frame-graph passes match the MBOIT orchestrator's
456
- shape; `AVBOITSettings` on `Renderer.avboit` matches the other sub-object settings; the settings
457
- structs defined beside their shaders match `shader_read_out_bucket`; the bit packing uses WGSL's
458
- `insertBits`/`extractBits` directly, which is what the engine's `bitfield_*` chunks are thin proxies
459
- for; no `safe_*` math helper was reinvented — the one floor (`max(T, 1e-6)` before a log) has no
460
- counterpart in `shader/chunk/math/`.
461
-
462
- **Verified.** Every spec in `rasterize/` (the avboit directory's now-larger set, MBOIT's, the shared
463
- bucket code) and the type check.
464
-
465
- ## 19. Side channels, and particles as the first one — 2026-09-04
466
-
467
- Alex: *"let's widen out AVBOIT so we can put particle render in there, that's where particles should
468
- go. Make sure the extension is clean. No need to bother with MBOIT, it's going to be deprecated...
469
- I don't think packing particle quads into meshlets is the way to go, so likely a side channel."*
470
-
471
- **The seam.** `AVBOITSideChannel.js` is a JSDoc contract, no base class: three methods, called by
472
- the orchestrator at the three points where the meshlet buckets have just contributed theirs —
473
- `avboit_occupancy` before the warp is built, `avboit_splat` before integrate, `avboit_draw` before
474
- resolve. Each takes the handles the orchestrator holds and returns the handles the next reader must
475
- take, which is the frame graph's own threading rule and nothing new. The orchestrator gained one
476
- parameter, `side_channels = []`, and one changed condition: it used to return early when no
477
- transparent material had meshlets, and now returns early only if there are also no channels.
478
- Integrate and resolve were not touched — they cannot tell a channel's fragment from a meshlet's,
479
- which is the property that makes this a seam rather than a special case.
480
-
481
- **The fourth accumulator.** An additive fragment covers nothing: it writes zero extinction and zero
482
- norm, so the resolve's renormalization — `(1 - T_total) / Σnorm`, which redistributes exactly the
483
- light the covering surfaces took from the scene — must not scale it. It cannot share the `color`
484
- accumulator for that reason. So `emission` joins as a fourth target, added to the resolve outside
485
- the normalization, holding light the draw already attenuated by the transmittance in front of it.
486
- The cost is honest and paid whether or not a channel is present: one more `rgba16float` at internal
487
- resolution, and every transparent mesh fragment writing `vec4(0)` to it. Buying it back would mean a
488
- three-target draw pipeline beside the four-target one — a variant, which the house rule and the
489
- plan's §5 both refuse — or a post-resolve additive pass with its own pipeline and bind group. One
490
- target is the cheaper of the three.
491
-
492
- **Shared, not copied.** The splat's voxel write (the two-slice linear split, the overflow mark) came
493
- out of the meshlet splat into `chunk_avboit_splat_slice`, so the particle splat writes the volume
494
- through the same code, not a second implementation of it. On the particle side the billboard vertex
495
- stage came out of the standalone renderer into `chunk_particle_billboard_vertex`, shared by three
496
- pipelines, and the emitter's world bounds sphere into `chunk_particle_emitter_world_sphere`, shared
497
- by the tick's frustum cull and the new occupancy.
498
-
499
- **What the review found.** Four things, all fixed here:
500
-
501
- - *A binding that lied.* The shared vertex chunk read its slot list from a binding named `sorted`.
502
- The standalone path binds the depth-sorted list there; the AVBOIT passes bind the alive list,
503
- because the volume makes order irrelevant and the sort does not run on that path. Renamed
504
- `draw_list` in the chunk and all three pipelines: which list it is, is the caller's choice.
505
- - *Bounds with new teeth.* The occupancy is per emitter, from the bounds sphere — the field that
506
- until now only fed the optional simulation culling and that an emitter not opting in could leave
507
- at its default. A particle outside it still draws, but its extinction lands in whatever physical
508
- slice the warp put nearest, so it occludes at slightly the wrong depth. Documented on the field
509
- and in the particle design, because it is now a rendering input.
510
- - *One simulation per view.* `simulate` steps the system and hands out handles into the graph it
511
- recorded into, so two views of one scene would step it twice. The renderer has one view per
512
- `render` call, so it holds today; the fix when that changes is to separate the step from the
513
- import. Documented on the method rather than built speculatively.
514
- - *A teardown that leaked.* `Renderer.destroy` did not destroy the particle systems it creates per
515
- scene context. It does now.
516
-
517
- **Checked and found sound.** The volume and the accumulators are always created by the meshlet
518
- passes before a channel is asked, even at zero materials, so a particles-only frame has them; the
519
- splat's dummy attachment is cleared by the meshlet splat before the particle splat loads it; passes
520
- that write imported buffers are marked as side effects by `FramePassBuilder`, so the simulation
521
- still advances in a frame whose transparency pass is off; a fresh `FrameGraph` per frame makes the
522
- "simulated into this graph" assertion catch a stale handle set loudly; and the depth texture is
523
- bound as a read-only attachment and a sampled texture in the same pass, which WebGPU allows and the
524
- soft fade needs.
525
-
526
- **Under MBOIT.** Particles simulate and do not draw. MBOIT is the legacy path and gets no new
527
- sources; `Renderer.feature_particles_enabled` says so where a reader will hit it.
528
-
529
- **Verified.** Tiers 1–3: the WGSL validity spec (the three new pipelines parse, validate, and stay
530
- inside the eight-storage-buffer budget), the occupancy pass on the emulator against the depth
531
- curve's own arithmetic, the three graph passes on the software device (pass order, attachments,
532
- indirect draws, the read-only depth), the orchestrator's call order and the renderer's wiring, and
533
- the whole engine suite — 2321 files, 16119 tests — plus the type check. Not verified: any of it on a
534
- device. The Browser pane handed out no WebGPU adapter this session, so even the real Tint compile
535
- that ledger 18's review leaned on is still owed for these three shaders.
536
-
537
- ## 20. Adversarial review of the side channel — 2026-09-04
538
-
539
- Alex ran the playground, said it works, and asked for an adversarial review. A picture that looks
540
- right is weak evidence for this pipeline: every defect below survives one, because each is either
541
- a second copy of something small, a disagreement between two passes about the same fragment, or a
542
- path the scene on screen does not take.
543
-
544
- **What the volume and the accumulators disagreed about.** The pipeline's whole contract is that the
545
- extinction the splat records is the coverage the draw weights its accumulators by
546
- (`avboit_draw_fragment_body`'s own header says so). Two fragments broke it:
547
-
548
- - *The soft fade.* The draw faded a particle's alpha against the scene depth; the splat had no depth
549
- binding at all and recorded the unfaded coverage. A smoke card fading out where it meets the floor
550
- drew nothing there and still wrote full extinction, so the floor was darkened through a card that
551
- was invisible — the seam soft particles exist to remove, one layer down. The splat now samples the
552
- same depth (one texel per voxel) and applies the same fade, through
553
- `chunk_particle_scene_view_depth`, which the draw took its inline copy of the linearization from.
554
- `avboit_splat` therefore takes `depth_texture` — the one contract change.
555
- - *Additive and the fog.* The draw composited volumetric in-scatter into every fragment and then put
556
- additive ones in `emission`, which the resolve adds *outside* the normalization. The scene behind
557
- already carries that in-scatter, and nothing subtracts it: a hundred overlapping sparks added a
558
- hundred copies of the fog. An emitting fragment is now dimmed by the fog's transmittance and
559
- carries none of its light; a covering one still re-adds it, weighted by the coverage it took,
560
- because it hides the part of the scene it owes it to.
561
-
562
- **Two more in the same fragment.** `PREMULTIPLIED` with zero alpha is the engine's other spelling of
563
- additive — the billboard pipeline blends it that way — and the draw branched on the blend enum, so
564
- such a sprite was treated as covering, written to `color` with zero `norm`, and multiplied by a
565
- normalization of zero: it disappeared. The routing is on coverage now, which is what the splat
566
- already used, so the two agree by construction. And the coverage ceiling was applied to `norm` and
567
- `extinction` but not to `color`, unbounded above (a curve can drive alpha past 1) and unguarded
568
- below (a negative alpha made a negative extinction, and `exp(-sum)` then brightened the scene).
569
-
570
- All four are pinned on the emulator: the fragment's decision came out into
571
- `chunk_particle_avboit_contribution` as its own no-behaviour-change step, then nine tests, four of
572
- them red against the old code.
573
-
574
- **Teardown.** `destroy()` freed the buffers and left every emitter node holding a row of the table it
575
- had just freed. A renderer rebuilt after a device loss makes a fresh system whose membership sweep
576
- reads `row !== -1` as "already mine", takes the moved branch rather than the arrived one, and never
577
- registers the emitter: it stops emitting for good. It retires them now. The default 1×1 atlas was
578
- also unreachable — only its view was kept — and is held and freed.
579
-
580
- **The orchestrator can be run at tier 3 after all.** This file said it could not: it needs a view
581
- context over meshlet geometry no spec builds. That is true of a frame *with transparent meshes* —
582
- and the path side channels added is the other one. A scene with no transparent materials and one
583
- channel reaches for no geometry, so the whole orchestrator now runs on the mock, and the ordering
584
- contract and the handle threading are pinned from the outside instead of by reading the source for
585
- call order. The mock gained `indirect-first-instance` for it, on the argument its own header already
586
- makes for `subgroups`: it permits a field in an indirect draw the mock never reads, issuing a draw it
587
- never runs, and refusing it left the transparency orchestration untestable for a reason unrelated to
588
- what the tier asserts.
589
-
590
- **Found and left alone, on purpose:**
591
-
592
- - *The occupancy marks every emitter*, with no frustum test and no liveness test, where the meshlet
593
- occupancy dispatches over the HZB-filtered collection. An idle or off-screen emitter still reserves
594
- its depth run, and the warp then fits more occupied virtual slices into the same 128 physical ones.
595
- It costs precision, never correctness. A frustum test is the obvious fix and belongs with a scene
596
- that shows the cost.
597
- - *The occupancy sphere bounds particle centres*, not the quads' own depth extent, which for a
598
- stretched or world-oriented particle reaches a size (or more) past the centre. Documented on
599
- `ParticleEmitter.bounds_radius` rather than inflated by a guess.
600
- - *Particles have no motion vectors.* The transparency composite happens before TAA, so a fast spark
601
- is reprojected with the background's motion and ghosts. Transparent meshes have had this property
602
- since before AVBOIT; particles make it easier to see. The playground has a TAA switch.
603
- - *One system per scene context is never released*, mirroring `SceneManager`'s own map. A per-scene
604
- release path is a design addition, not a fix.
605
- - *`simulate` steps once per view*, so two views of one scene would step it twice. Documented on the
606
- method; the renderer has one view per `render` call today.
607
-
608
- **Verified.** The whole engine suite and the type check at budget. The playground page was loaded
609
- from the dev server again — import graph, WASM meshlet build, scene, camera controller under a
610
- synthetic drag, and every control handler including the two error paths added here. Still no adapter
611
- in the Browser pane, so still no picture and still no real Tint compile.
612
-
613
- ## 21. What the device caught and the review did not — 2026-09-04
614
-
615
- Alex ran entry 20's work and got:
616
-
617
- ```
618
- Binding entry textureView not set.
619
- - While validating entries[6] against { binding: 6, ..., texture: {sampleType: UnfilterableFloat, ...} }
620
- - While encoding [RenderPassEncoder "Particle AVBOIT Splat"].SetBindGroup(0, ...)
621
- ```
622
-
623
- Entry 20 added `scene_depth` to the splat's bindings between the atlas and its sampler, and added
624
- the depth view to the *end* of the positional array the pass hands to `BindGroupDescriptor.from`.
625
- Everything after the insertion point slid along by one: binding 6 wanted a texture and got the
626
- sampler. Nothing on this side of the device could see it. The frame graph does not know what a
627
- binding means, the mock does not validate an entry against its layout, and both of entry 20's new
628
- tests looked for the depth view's *presence* in the group rather than its position.
629
-
630
- The fix is not the index. Both particle render passes now build their bindings by name, through
631
- `ShaderResourceSetDescriptor#generateBindings` — the path the standalone billboard pass already
632
- used, and the reason it never had this bug — so the order a shader declares its bindings in is the
633
- shader's own business and nothing here mirrors it. `constructRenderPass` sets the pipeline and every
634
- group from that, which also took the manual `setBindGroup` calls out.
635
-
636
- And the tier that missed it can see it now: `expect_bindings_match_layout` walks a recorded pass and
637
- asserts every entry is the *kind* — buffer, texture or sampler — its pipeline layout declares at that
638
- binding. It fails against the shipped code. That check is worth copying to any pass built by hand.
639
-
640
- **The lesson for this directory:** a positional bind-entry list is a latent version of this defect in
641
- every pass that has one, and it surfaces only on hardware, only when that pass first encodes. The
642
- meshlet passes here still build theirs positionally.
643
-
644
- ## 22. Trails as the second side channel — 2026-09-24
645
-
646
- Alex asked whether `Trail3D` went through AVBOIT (it did not: `Trail3DSystem` drew its tubes with
647
- `GPUDynamicMeshRenderer` from an `AfterTransparency` extension, straight alpha over the resolved
648
- frame, depth-tested against the opaque scene only — so a trail behind a transparent card was drawn
649
- on top of it), then: *"implement this please. Keep within the parameters - AVBOIT for Trails only,
650
- and refactoring necessary to make it happen."* MBOIT is being deprecated and was out of scope.
651
-
652
- **The door.** Side channels could only come from inside the renderer: `#particle_side_channels`
653
- held the GPU particle system and nothing else, and `RENDER_EXTENSION_DESIGN.md` §9 names "extensions
654
- cannot participate in OIT" as the phase model's ceiling. `Renderer.add_transparency_channel` /
655
- `remove_transparency_channel` / `transparency_channel_count` open it, and `GraphicsEngine` holds
656
- channels across a restart exactly as it holds extensions. The frame's list (now `#side_channels`) is
657
- the particles, then every registered channel that does not say it is empty — `avboit_is_empty`, an
658
- optional fourth member of the contract — because the orchestrator runs whenever it is handed a
659
- channel, and one that is always registered would otherwise buy every frame the pipeline's fixed
660
- cost. Registering one channel twice is refused: it would draw everything twice.
661
-
662
- **The channel.** `DynamicMeshAVBOITChannel` meets the three contracts for a `DynamicMeshBatch`:
663
-
664
- - *Liveness is decided once, at occupancy*: a mesh is live when visible, with positive flat alpha,
665
- and with a triangle one vertex of which has positive alpha. The splat and the draw take the same
666
- list — and refuse a graph the occupancy was not recorded into — so the occupancy, the volume and
667
- the accumulators agree about which meshes exist.
668
- - *Occupancy from CPU boxes.* The vertices are CPU data already, so a box per live mesh is taken
669
- there (`dynamic_mesh_live_bounds`), carried to world space by the mesh's transform, uploaded as a
670
- transient buffer and marked on the GPU through the particles' own chunks. **Per triangle, not
671
- per vertex**: a trail's faint tail is a triangle whose far corners have aged to zero, and bounds
672
- over the live vertices would stop a segment short; while an aged-out knot keeps its old position,
673
- so bounds over every position would reserve depth for a stopped trail's whole history.
674
- - *The upload happens in the occupancy pass*, the channel's first to execute; the splat and the draw
675
- read the buffers later in the same frame, behind the warp the occupancy feeds.
676
- - *Splat and draw* are the particle passes' shape with the dynamic-mesh vertex stage: coverage
677
- through `avboit_coverage` in both, the draw routed through `avboit_premultiplied_contribution`,
678
- fogged and weighted by the transmittance in front. Straight alpha with no coverage carries no
679
- light, so the draw masks the emission target rather than blending zeros into it.
680
- - Group 0 of both pipelines is `DYNAMIC_MESH_RESOURCES`' own group — the same object — so a mesh's
681
- bindings are the ones `GPUDynamicMeshResidency.create_bindings` already builds; the pass groups are
682
- built by name (entry 21).
683
-
684
- **Shared, not copied.** Out of the particle channel into chunks, the particles switched onto them
685
- with their specs unchanged: `avboit_coverage`, `avboit_premultiplied_contribution` (everything
686
- `particle_avboit_contribution` did after its premultiply), `aabb3_view_depth_range` and
687
- `avboit_occupancy_mark`. Out of `GPUDynamicMeshRenderer`: `GPUDynamicMeshResidencyTable` (buffers,
688
- staging, the once-per-version upload) and `chunk_dynamic_mesh_vertex` (the vertex pull) — pinned by
689
- a new execute-level spec of the renderer written against the old code first, since there was none.
690
- Entry 21's layout-kind check is `recorded_binding_kind_mismatches` now, shared by both recorder
691
- specs. **Left alone on purpose:** the meshlet occupancy and the meshlet splat keep their own copies
692
- of the slice marking and the two-slice split — this change does not touch the core passes.
693
-
694
- **Trails.** `Trail3DSystem` registers its batch's channel with the facade instead of an extension;
695
- `TrailExtension` is gone (breaking, and its only use was internal). A trail is now occluded and
696
- tinted by transparent surfaces and particles in front of it, occludes them behind it, and picks up
697
- the volumetric fog at its depth — it was drawn unfogged over a fogged scene. Under MBOIT, or with
698
- transparency off, trails are not drawn, as particles are not. Fog of war still covers them: it runs
699
- at `AfterTransparency`, after the resolve they are now part of.
700
-
701
- **Verified.** Tiers 1–3: emulator specs for the four chunks, the dynamic-mesh occupancy pass and the
702
- live bounds; the WGSL validity tier for every new and changed shader; software-device specs for the
703
- channel's three passes (order, attachments, per-draw binding kinds, liveness, refusal, residency),
704
- for `GPUDynamicMeshRenderer`, for registration on the renderer and the facade, and **the whole
705
- orchestrator run with a real channel and no transparent materials**; `Trail3DSystem`'s specs; the
706
- type check at budget. Tier 4, on a Metal adapter in the Browser pane — which hands out an adapter
707
- again: every new and changed module compiles and every pipeline builds with no validation error.
708
- On the playground page here, a real `Trail3D` driven by `seed_trail_tube` / `advance_trail` /
709
- `trail_tube_write_geometry` and registered with `renderer.add_transparency_channel`:
710
-
711
- - laid in front of the card stack it is drawn over all four cards; laid behind it, it is hidden by
712
- them at alpha 0.997 (the old path drew it on top), dimmed card by card at 0.3, and faint at 0.85;
713
- - behind the red pane with **tint transmitted light by base colour** on, it is filtered to the
714
- desaturated tone the resolve predicts, `(0.19, 0.14, 0.14)`-ish against the pane's red, while its
715
- head clear of the pane stays cyan;
716
- - under MBOIT it is absent, and back under AVBOIT it returns.
717
-
718
- On `../../playground/particle_ecs/` in `?debug` (frames driven by `__step`, the pane being hidden), a
719
- real `Trail3DSystem` added to the running `EntityManager` and a `Trail3D` entity circling the fire: the
720
- stretch behind the plume is dimmed and warmed by the smoke and flames in front of it, the stretch in
721
- front hides the smoke and embers behind it; removing the system takes its channel out of the live
722
- renderer and adding it back re-uploads from nothing. **Zero uncaptured device errors** across every
723
- frame of both sessions. The bonfire's sprites need `build/bundle-worker-image-decoder.js`, which the
724
- dev server does not build: `npx rollup -c rollup.config.js` first (the output is gitignored).
725
-
726
- ## 23. Adversarial review of the trail channel — 2026-09-25
727
-
728
- Alex asked for an adversarial review, "especially on the blast radius". Three reviewers read
729
- `master...feature/trail-avboit` independently — blast radius, GPU correctness, lifecycle and API —
730
- and every finding below was checked against the code before it was acted on. None was critical or
731
- high. Both reviewers who compared the shared chunks with master found them textually equivalent, the
732
- dynamic-mesh upload moved without change, and the meshlet passes untouched; the GPU reviewer
733
- confirmed the splat and the draw agree on coverage, depth and fragments, that emission is
734
- unreachable from straight alpha, and that the fog is not counted twice.
735
-
736
- **Fixed.**
737
-
738
- - *Buffers from the old device.* An `Engine` with an external `EntityManager` stops `GraphicsEngine`
739
- without shutting its systems down (`Engine.stop`), and a restart then replays the channel into a
740
- renderer on a new device while its residency holds buffers made on the old one — written with
741
- `queue.writeBuffer` and bound into bind groups on the new device, a validation error that loses the
742
- frame's commands. Not new: `GPUDynamicMeshRenderer`'s map did the same for path display and debug
743
- draw, and `TrailExtension` was replayed the same way. But entry 22 advertised channels as held across
744
- a restart, so `GPUDynamicMeshResidencyTable` now drops everything when the device changes — the
745
- pattern `GPUParticleEmitterSystem` already has for its atlas — which fixes all three drawers.
746
- - *The last frame kept alive.* The channel held `#frame_graph` — the whole frame graph, every pass
747
- closure in it, the imported canvas — and its live meshes until it was next asked, which for a
748
- channel whose last trail had faded, or whose renderer had been replaced, is never; `Renderer.js`
749
- calls exactly that retention a bug in its profiler path. The decision is now a per-frame record,
750
- dropped when the draw is recorded; the passes carry their own references to the list and the boxes.
751
- Each frame's list and boxes being its own also closes the aliasing the GPU reviewer called fragile
752
- (record A, record B, execute A drew B's meshes into A): a spec now records two frames before
753
- executing either, and fails against the old code.
754
- - *Consulted when nothing would draw it.* `avboit_is_empty` ran every frame under MBOIT and with
755
- transparency off; registered channels are now consulted only when the pipeline that takes them will
756
- run.
757
- - *An assert that shipped builds strip.* Registering a channel twice was refused by `assert` — gone
758
- in a shipped build, where the channel would then be drawn twice — and the facade recorded a
759
- registration the renderer had refused. Both are idempotent now; a repeat is a no-op.
760
- - *The optional method unchecked.* A non-function `avboit_is_empty` passed registration and threw in
761
- the middle of every frame; it is refused at the door.
762
- - *`TrailExtension` removed outright.* meep ships ES modules, and an import that stops linking stops
763
- the application loading, not just its trails — the reason `Trail3DSystem3` is kept. It is a
764
- deprecated extension again, recording nothing and warning once.
765
- - *No frustum test in the occupancy.* A live trail behind the camera or off to the side reserved its
766
- whole depth run, and the warp fitted the on-screen surfaces into what was left. The dynamic-mesh
767
- occupancy now tests each box against `camera.frustum` first. On the device, the real camera's planes
768
- keep a visible trail's box and cull one behind the camera. The particles' occupancy still has no
769
- such test (entry 20's open item); this change does not touch it.
770
- - *Nothing pinned "the fog covers trails".* It used to be a declaration in
771
- `after_transparency_order.spec.js`; it is construction now — the transparency call site precedes the
772
- `AfterTransparency` phase — and a source pin says so.
773
-
774
- **Disclosed, not changed** — each is in the CHANGELOG entry too:
775
-
776
- - *Draw order at `AfterTransparency`.* Trails were the last content extension there; they are
777
- resolved before that phase now, so path display, the selection highlight and the deprecated CPU
778
- `ParticleEmitterSystem` draw over a trail whatever its depth, where they drew under it. Neither
779
- order was depth-correct; the GPU particles are the ones that now sort against trails.
780
- - *The fixed cost.* A frame with a live trail runs the transparency pipeline even with nothing else
781
- transparent — occupancy, warp, integration, the accumulator clears, the resolve — where it had one
782
- blended pass. Of the order of entry 17's 0.3 ms at 720p on light content.
783
- - *Shared precision.* A trail spanning a deep range marks a long run and the warp halves its virtual
784
- resolution to fit: the GPU reviewer's example, a box spanning 10–60 m at near 0.1 / far 200, takes
785
- the warp from 4096 to 256 virtual slices, about 1.6 cm to 25 cm at 10 m. Emitter boxes do the same;
786
- it is how the adaptive warp shares its 128 slices. Off-screen trails no longer take part.
787
- - *Thin trails and the 1/8-resolution splat.* A tube narrower than about one voxel reaches the volume
788
- only in the columns whose low-resolution pixel centres it covers. The GPU reviewer's numbers: a 0.1 m
789
- trail at 20 m, 1080p, 60° is 0.58 voxel wide, and in a missed column a smoke card behind an α 0.8
790
- trail comes out 3.5× too strong through it, crawling in 8-pixel blocks as the trail moves. Plan §9's
791
- sub-pixel risk, which meshlets and small particles share. The suggested mitigation — widen the
792
- splat's triangles by half a voxel — trades the misses for halos wherever the widened footprint falls
793
- between two other layers, and is left for a decision rather than taken.
794
- - *Renderer-wide channels.* A thumbnail rendered through the same renderer includes live trails that
795
- fall in its view, as it did when they were an extension — and now brings the pipeline with them on
796
- those frames. Particles are per scene; channels, like extensions, are not.
797
- - *Work done twice.* The emptiness query and the gather both walk the first live mesh's triangles, and
798
- each live mesh's group-0 bindings — a transient uniform — are built once for the splat and once for
799
- the draw. Small at trail counts; noted, not shaved.
800
-
801
- **Verified.** Before the fixes, the whole engine suite: the only failures were
802
- `VGeoResidency.spec.js`, failing identically on master, and a `PromiseWatcher` timeout under load that
803
- passes alone. After them, the suites the change reaches (217 files, 2386 tests) and the type check at
804
- budget. On the device: every new and changed module compiles and its pipeline builds; on the bonfire
805
- page, the trail through the plume again, with every fix in, and no uncaptured device errors.
806
-
807
- ## 24. Thin trails: a coverage-weighted splat — 2026-09-25
808
-
809
- Alex said *"continue please"*, then *"Try again"*; the open item was entry 23's thin trail, disclosed and
810
- left for a decision. It is taken here, for the trail channel only — the meshlet and particle splats are
811
- unchanged.
812
-
813
- **Confirmed on the device first.** On the playground page, a dimmed trail at α 0.8, 0.04 wide (about
814
- 3.7 pixels, 0.46 of a voxel), laid across the red card at eight offsets an eighth of a voxel apart —
815
- one voxel's period — and read at the trail's centre. TAA off:
816
-
817
- | offset | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
818
- |---|---|---|---|---|---|---|---|---|
819
- | before | 107,144 | 107,144 | 77,166 | 76,167 | 73,168 | 72,169 | 111,140 | 111,140 |
820
- | after | 95,155 | 93,156 | 90,158 | 88,160 | 85,162 | 85,162 | 95,154 | 100,151 |
821
-
822
- (red, green; the card alone is 147,77; the same trail 0.3 wide, which covers whole voxels, is 72,169.)
823
- Before, the trail either crossed a voxel centre and occluded the card fully or missed it and did not
824
- occlude it at all: two colours and a jump of 39 between neighbouring offsets. With TAA on it was the
825
- same split (70–111, a jump of 38), so the temporal filter does not hide it: it moves as the trail does.
826
-
827
- **The options.**
828
-
829
- - *Widen the splat's triangles by half a voxel* (entry 23's suggestion): every voxel the trail touches
830
- gets its full extinction — halos wherever the widened footprint falls between two other layers.
831
- - *Multisample and write the full extinction if any sample is covered*: the same halo, and worse — the
832
- triangles of one ribbon sharing a voxel would each add a whole layer.
833
- - *Multisample and weight by the covered share* — taken. Four samples, the only count above one WebGPU
834
- guarantees; the fragment runs where any is covered and scales its extinction by
835
- `countOneBits(sample_mask) / 4`. The triangles of one mesh cover disjoint samples, so their shares in
836
- a voxel add to at most one layer, and a fully covered voxel is written exactly as before. The voxel
837
- gets the trail's extinction averaged over its footprint, which is what a volume that coarse can hold.
838
- - *More samples* — a raster at twice the volume's resolution, sixteen per voxel. A model of the lookup
839
- (a 0.46-voxel band at α 0.8, transmittance behind the trail's centre over every position) says it is
840
- not worth four times the fragments: one sample 0.20–1.00, four 0.45–0.78, sixteen 0.45–0.73, and an
841
- exact area average 0.48–0.69. What is left at four is mostly the floor: a band thinner than a voxel,
842
- box-averaged into voxels and read back through the draw's bilinear lookup, still sees more of itself
843
- where it sits on a voxel centre than between two.
844
-
845
- **What changed.** `shader_dynamic_mesh_avboit_splat` has `multisample: { count: 4 }` and the weight;
846
- `graph_dynamic_mesh_avboit_splat` makes its own 4-sample `r8unorm` target at the volume's resolution
847
- (never written, cleared and discarded — about 130 KB at 1080p, transient) and hands the volume's
848
- single-sampled one back untouched, since a multisampled pipeline cannot draw into it; the channel passes
849
- it the `voxel_resolution` the orchestrator already offered. The shared vertex chunk interpolates at the
850
- **centroid**: a thin triangle covering samples but not the pixel centre would otherwise have its depth
851
- and alpha extrapolated past its edge, which on a ribbon seen nearly edge-on moves the extinction off the
852
- ribbon. The draw is single-sampled, where a fragment exists only if the centre is covered and the
853
- centroid is the centre, so its output does not change.
854
-
855
- **After, on the device.** The table's second row, and with TAA on 82–102 with a largest jump of 19. On a
856
- curved thin trail — the case that beaded — the per-column red at the trail's centre over 225 columns:
857
-
858
- | | stdev | range | mean | neighbours ≥ 10 apart |
859
- |---|---|---|---|---|
860
- | TAA off, before → after | 13.7 → 4.8 | 67–109 → 78–101 | 85.8 → 85.5 | 6 → 0 |
861
- | TAA on, before → after | 12.7 → 5.0 | 58–108 → 71–98 | 79.8 → 81.5 | 20 → 5 |
862
-
863
- Two and a half to three times steadier, and the same on average, as an area average of point samples
864
- should be. The wide trail reads 69,170 against 72,169 before.
865
-
866
- **What remains, and what it costs.**
867
-
868
- - *The average's own bias.* A trail a fraction of a voxel wide occludes what is behind it by about that
869
- fraction of its extinction, steadily. The trail itself still draws at full resolution and full
870
- opacity; only its occlusion of what is behind it is diluted. That is the resolution the volume has.
871
- - *Faint trails can round away.* The volume counts 255 steps per `ln 256` of extinction, so a voxel a
872
- trail below about α 0.04 only grazes gets nothing, where before a centre it happened to cross got a
873
- step. Occlusion of about 2 %.
874
- - *More fragments.* Every voxel a thin trail crosses runs the fragment, not only those whose centres
875
- it covers — two to three times as many along a thin trail, one more row along each edge of a wide
876
- one, and never more than four per voxel per layer.
877
-
878
- The emulator compiles WGSL to JavaScript and takes names as they are, so a fragment parameter named
879
- `in` — a JavaScript keyword — failed to load; the splat's is `fragment`.
880
-
881
- **Verified.** A new emulator spec runs the splat's fragment with each sample mask and checks the share it
882
- writes, that four disjoint quarters add to one layer, and that a faded fragment writes nothing; with the
883
- weight removed it fails five of its eight tests. The channel's recorder spec checks the target is the
884
- pass's own, has the pipeline's sample count, is cleared and discarded, and that the volume's target
885
- comes back untouched; with the old recorder it fails exactly those two. Every spec the change can
886
- reach — the dynamic-mesh directory, the orchestrator run with a real channel, `Trail3DSystem`: 11 files,
887
- 93 tests — passes, and the type check is at budget. On the Metal adapter, `@builtin(sample_mask)` and
888
- `@interpolate(perspective, centroid)` compile and the multisampled pipeline builds; no uncaptured device
889
- errors, including 240 frames of a live trail on a moving path, fading in and out.
890
-
891
- ## 25. A trail's draw constants go up once a frame — 2026-09-27
892
-
893
- Entry 23 left each live mesh's constants built twice — a transient uniform and a bind group of its own
894
- for the splat, and again for the draw — noted, not shaved. They are shaved here, as part of moving
895
- every dynamic mesh's per-draw constants into a table (CHANGELOG, Unreleased, Rendering): the
896
- occupancy, after its upload, writes the frame's `GPUDynamicMeshDrawList` — one transient storage
897
- buffer and one write per 1,024 draws — and the splat and the draw both bind it and name their rows by
898
- `first_instance`. The splat's layout is four groups now (constants, geometry, warp, volume), which is
899
- WebGPU's default `maxBindGroups`.
900
-
901
- **Measured** with 1,500 live meshes through the channel at 1280×720 on the RTX 4090, asserts
902
- stripped, paired: 9.8–11.4 ms of CPU a frame to 2.0–2.5; 3,032 copies a frame to 34 and 7,624
903
- `setBindGroup` to 3,131. The pictures after equal frame counts are bit-identical to before.
904
-
905
- **Verified.** `DynamicMeshAVBOITChannel.draws.spec.js` checks the splat and the draw read each mesh's own
906
- constants from one table, and fails with a draw pass that uploads its own copy (two tables).
907
- `chunk_dynamic_mesh_vertex.spec.js` runs the shared AVBOIT vertex stage on the emulator and checks a
908
- vertex is carried by the row its draw names. The other half of the item — the emptiness query and the
909
- gather both walking the first live mesh's triangles — is left as it was.
910
-
911
-
912
- ## 26. Review against the deck and the Godot proof of concept — 2026-10-04
913
-
914
- Alex asked for the implementation to be checked against the deck and against
915
- `github.com/IsaacMarovitz/godot-avoit` (*"AVBOIT works and is shipped, but I'm worried there might be
916
- mistakes"*), then for an adversarial review of the proposal before it was carried out, and for the
917
- particle off-centre read to be looked at again. The work is on a branch, not on master, for him to look at
918
- first.
919
-
920
- **Checked against the deck and found faithful.** The 10|10|10 packing with two guard bits, and the
921
- per-channel first-overflow mark the integration pins from (slides 57–60); the exclusive-prefix integral at
922
- `P + 1` slice boundaries; the continuous piecewise-linear warp, which gets what the deck's "filterable"
923
- begin/end flags are for (slide 95) without them; the two-slice bias taken in virtual space; halving with a
924
- fresh dilation per level; the resolve (slides 13–15). Two choices differ from the deck without being
925
- defects: halving keeps the curve's shape and doubles every slice, where the deck re-adjusts its log curve so
926
- that precision near the camera holds; and the per-fragment extinct discard stands in for the
927
- zero-transmittance depth cull.
928
-
929
- **The splat's rounding — measured, not a defect.** Meshes, particles and trails split a fragment's float
930
- extinction between its two slices and round each share; the water rounded once and partitioned the counts.
931
- Through the shipped chunks: the first makes a faint surface's count depend on where it sits in its slice
932
- (α 3 %: one count or two) and is right on average over depth; the second is stable over depth and biased
933
- (α 1.5 %: one count where 0.7 is exact). Either way an event below α ≈ 1.1 % registers nothing — the deck's
934
- 8-bit floor.
935
-
936
- **Found and measured.** Items 1 and 3 are fixed, each red then green; item 2 is open.
937
-
938
- 1. *Tilted surfaces striped* — plan §9's sloped-surface risk, never measured until now. Every voxel column
939
- holds a surface at the depth under its centre, and the draw reads the four nearest columns bilinearly; on a
940
- surface tilted away from the camera the near-side neighbour holds the same surface nearer by its depth change
941
- over up to a voxel, and past the two-slice bias the surface occluded itself by an amount that depended on
942
- where the pixel sat in its voxel. A model of two overlapping α 0.5 cards at 1080p, through the shipped warp
943
- build, curve, rounding and lookup, put it from about 30° (red −13 %, green +25 % at 45° with the front one
944
- tilted). On the RTX 4090, the playground at 720p with a card tilted behind a camera-facing one: a sawtooth
945
- locked to `x mod 8`, 52.2–55.5 sRGB codes in red at 45° and 50.5–55.6 at 60°, against a flat 52.3 under
946
- MBOIT and a flat camera-facing control. Fixed by `avboit_footprint_bias`: the lookup goes a further
947
- `8 × fwidth(v)` virtual slices toward the camera — the bilinear reach — capped at `2 V / P`, the base
948
- bias of the coarsest slicing (§3.8). After, on the device: 52.3–52.4 at 45°, flat in the interior at 60°.
949
- The cost is the band: a tilted fragment is not occluded by what lies within its own voxel's depth span in
950
- front of it. Past the cap — about 85° at 1080p in a sparse volume — the stripes can return.
951
- 2. *The water pinned the warp at its floor.* Its occupancy marked every 8×8 tile's depth range, and a water
952
- plane covers every depth from the shore to the horizon, so the warp halved to `V = P` whenever water was on
953
- screen — AVBOIT became plain 128-slice VBOIT. Modelled with the shipped warp build: two α 0.5 cards at 10 m
954
- separate at 0.25 m without water (V = 4096) and blend into one average at 0.25–0.5 m with a water plane
955
- spanning 2–200 m in view, partly at 1 m. **Open.** A fix was built and taken out again: the water
956
- moved out of the volume into a full-resolution *transmittance layer* that every other draw compared its
957
- depth against. Alex: *"It's Anti-OIT."* It put a water special case into every transparent pipeline —
958
- two bindings and a depth comparison per fragment in the mesh, particle and trail draws, a pass every
959
- frame without water to bind something, and a contract every channel inherits — where order independence
960
- means every source goes through the one volume. The water is in the volume again, exactly as before
961
- this entry, and now takes the footprint like every surface that splats (its splat takes the nearest
962
- depth over its 8×8 tile, so a voxel's width covers its own column; a neighbour's nearest point can lie
963
- further). What bears on the pinning from inside the warp — hysteresis on halving and the deck's curve
964
- re-adjustment — is measured in entry 27.
965
- 3. *The particle splat read the soft fade at the voxel's corner*, `8 × voxel`: the top-left pixel, and not
966
- the voxel's own footprint at all when the volume does not divide the frame. On a floor the row four pixels
967
- up is farther by a good part of the 0.5 m fade range, so a smoke card standing on the floor went into the
968
- volume at up to 2.4× the coverage it was drawn with (0.94 against a voxel average of 0.39 at 10 m) — entry
969
- 20's seam, one layer down again. Fixed by `chunk_particle_avboit_voxel_fade`: four reads at WebGPU's
970
- standard 4× multisample positions over the voxel's footprint, through the exact low-to-full mapping.
971
-
972
- **The adversarial review of the proposal**, before any of it was built, changed it in four places:
973
-
974
- - *The footprint's reach.* A throwaway version at half a voxel cut the device sawtooth to 0.5 and 1.5 codes;
975
- the bilinear lookup reaches a full voxel, so the full voxel is what removes it, and the spec holds that to no
976
- self-occlusion at all.
977
- - *The footprint's cap.* Unbounded, it runs away at silhouettes and grazing angles — a sphere's rim would
978
- ignore everything in front of it. It is capped at the coarsest slicing's base bias, the separation every
979
- surface already has whenever the volume is full: the extra a fragment ignores is never more than that.
980
- The whole reach, base bias included, is at most `2 + 2 V / P` virtual slices.
981
- - *The fade's sampling.* A read at the centre under-records the floor contact (0.24 against 0.39 at 10 m).
982
- Over fifteen modelled contacts a 2×2 grid was off the voxel average by 0.05 on average and 0.12 at worst;
983
- the rotated grid by 0.05 and 0.08, because its four reads fall on four rows and four columns.
984
- - *Withdrawn:* the meshlet splat takes glass's Fresnel from the geometric normal where the draw uses the
985
- normal-mapped one. At the splat's coarse mip a high-frequency normal map averages back to the geometric
986
- normal, and glass's Fresnel at near-normal incidence barely moves (about 0.04 across ±20°), so the two agree
987
- well within what the volume resolves.
988
-
989
- **Corrected.** `AVBOIT_EXTINCT_TRANSMITTANCE`'s docblock bounded the discard's error by `1/256` of the
990
- background. That holds where the 1/8-resolution volume is right at the full-resolution pixel; behind a gap
991
- narrower than a voxel in a near-opaque layer the volume calls the pixel extinct and the surface seen through
992
- the gap is discarded, where the resolve's normalization would have drawn it. Constructed, not seen on a
993
- scene; the deck's zero-transmittance cull has the same limit. **Out of date:** entry 20's "the occupancy
994
- marks every emitter, with no frustum test" — the particles now mark the box their bounds pass measures from
995
- the quads, and skip additive emitters and unknown bounds.
996
-
997
- **The Godot proof of concept** is monochrome and composited over Godot's own transparents; every piece of it
998
- ports to WGSL. It splats at full resolution weighted by the cell's pixel area (64× the atomics), stores 16.16
999
- fixed point per slice (no 8-bit floor), reconstructs with a 4×4×4 cubic B-spline (whose wider XY footprint
1000
- would stripe worse on slopes), takes occupancy from rasterized fragments, and keeps hysteresis on halving — it
1001
- refines only once the occupied count drops below three quarters of the budget. That last is the one idea worth
1002
- having here, where the virtual resolution is decided afresh every frame and a scene hovering at the budget
1003
- could flip between two slicings; unmeasured.
1004
-
1005
- **An independent review of the commits**, by a reviewer who had only the diffs, the plan and this
1006
- ledger, found nothing high or medium; it checked `fwidth` before any branch or discard at every call site
1007
- and the spec's splat against the shader's. Taken from it:
1008
-
1009
- - *Additive particles took a footprint.* They write nothing into the volume, so nothing of theirs can
1010
- occlude them; the derivative is still taken in uniform control flow and selected away.
1011
- - *Wording.* Camera-facing and stretched billboards are built from the camera's right and up, so their
1012
- footprint is zero (the draw's comment said they were tilted); the cap bounds the band, not the whole
1013
- reach; the side-channel contract now names the footprint.
1014
- - Its findings about the layer went with the layer.
1015
- - *Left:* the fade's four reads land on pixel boundaries at a scale of 8 and take the pixel past each, half a
1016
- pixel off the voxel's centre — inside what four reads resolve.
1017
-
1018
- **Verified.** Each fix red, then green, as above. The suites the change reaches (130 files, 1,518 tests
1019
- with the layer gone); the whole engine suite (2,897 files, 22,247 tests, none failing, before the layer
1020
- came out); the type check (declaration emit clean, 18 consumer-visible errors against the budget of 19, as
1021
- before). On the RTX 4090, from the scratchpad and not in the repo: the slope probe; every AVBOIT module
1022
- through `createShaderModule` and a frame under Brick4; the water page with the water back in the volume
1023
- and taking the footprint, the card below the surface filtered by it (red 65 → 2 against the same view
1024
- dry); no WebGPU validation error on any page (the water page's "Worker error" is the image-decoder worker
1025
- bundle the dev server does not build, entry 22). A dev server left running across the revert's
1026
- `git checkout` served the old recorder beside the new shader and threw; restarted, clean.
1027
-
1028
- ## 27. Hysteresis and the curve re-adjustment, measured — 2026-10-04
1029
-
1030
- Alex, after entry 26's water layer was taken out: *"Go ahead and try hysteresis and the curve
1031
- re-adjustment, I want you to measure the results."* Both are in the warp build behind two switches on
1032
- `Renderer.avboit`, off by default, so the shipped frame is unchanged until one is turned on.
1033
-
1034
- **The deck's re-adjustment does nothing from a pure-log curve — measured, not built.** When the deck lowers
1035
- its virtual slice count it re-parametrizes its log curve so slices near the camera keep their thickness.
1036
- Searched over the free parameter of the engine's curve family (`build_light_cluster_parameters`:
1037
- `slice = scale · log2(B z + O)`, pinned at the offset plane and the far plane), for each reference depth,
1038
- the thinnest slice any member allows after halving 4096 → 2048 (near 0.1, far 200):
1039
-
1040
- | at | the deck wants | uniform halving | best member |
1041
- |---|---|---|---|
1042
- | 0.5 m | 0.08 cm | 0.16 cm | 0.10 cm — and 1.37× thicker at 50 m |
1043
- | 1 m | 0.16 cm | 0.32 cm | 0.30 cm |
1044
- | 2 m | 0.32 cm | 0.64 cm | 0.64 cm (pure log) |
1045
- | 5 m | 0.79 cm | 1.59 cm | 1.48 cm |
1046
-
1047
- The same ratios hold for every halving. The deck's base curve is linearized and its re-adjustment moves it
1048
- toward log; AVBOIT's curve is pure log at every level, the near-preserving end of the family already.
1049
-
1050
- **What re-adjusting can recover is the octave's waste** (`AVBOITSettings.warp_steps_per_octave`). Halving
1051
- lands the occupied count anywhere from about half the budget to all of it. With four steps per octave the
1052
- build tries `2 L · 2^(-i/4)` between the octave that fits and the one above it, finest first, rewriting the
1053
- finest bits into those slices conservatively, run by run; consumers take any virtual count unchanged.
1054
-
1055
- - *Random scenes* (emulator, the shipped shader; 4–64 objects at 1–150 m log-uniform, extents 0.1–20 % of
1056
- their depth; 120 scenes per count): where the warp halves, the virtual resolution reached is 1.28–1.43×
1057
- finer on average (medians 1.19–1.41, at most 1.68), and the share of the 128 slices used goes from
1058
- 72–83 % to 93–95 %.
1059
- - *The bonfire* (`playground/particle_ecs`, `?debug`, 400 stepped frames, RTX 4090, the virtual count read
1060
- back every frame through a hook on the history buffer, no engine change): octaves sit at **128, the
1061
- floor, every frame**; four steps at **215**, 1.68× finer.
1062
- - *Cost* (the build dispatched alone on the 4090, warmed, configurations interleaved, medians of 320):
1063
- 12 runs of occupied slices 34 → 54 µs, 40 runs 22.5 → 53 µs, 600 fragmented runs 22.5 → 317 µs — the
1064
- single-lane run walk, three times per frame at most.
1065
-
1066
- **Hysteresis** (`AVBOITSettings.warp_hysteresis`, `AVBOIT_WARP_RELEASE` = 0.75, Godot's figure). The
1067
- virtual resolution the warp settled on is kept per view in a one-word GPU buffer; a finer one is taken only
1068
- when it fits in three quarters of the budget, coarsening is immediate.
1069
-
1070
- - *Synthetic* (emulator, the shipped shader, 600 frames; a smoke column's extent jittering frame to frame at
1071
- the extent where the level steps): without hysteresis the level flips about 290 times, every other frame,
1072
- at 1, 3 and 10 % jitter alike; with it, none — the frames that flipped to the finer level are held at the
1073
- coarser one instead (2× coarser with octaves, 1.19× with steps). A slow drift through the threshold:
1074
- 25 flips → 1 with octaves, 62 → 2 with steps.
1075
- - *The release* (a replica of the decision, agreeing with the shader on 400 of 400 frames): it sets the
1076
- jitter tolerated and the precision given up while a scene shrinks. With four steps:
1077
-
1078
- | release | flips at 10 % jitter | at 25 % | budget used, shrinking scene |
1079
- |---|---|---|---|
1080
- | 1 (off) | 314 | 310 | 0.90 |
1081
- | 0.95 | 104 | 259 | 0.87 |
1082
- | 0.9 | 0 | 198 | 0.84 |
1083
- | 0.75 | 0 | 34 | 0.75 |
1084
-
1085
- With octaves the cost is larger, down to 0.59 of the budget at 0.75: refining needs the next level's count,
1086
- twice this one's, to fit in three quarters.
1087
- - *The bonfire*: with the camera still, **no flip in 400 frames** at any setting — real particle bounds did
1088
- not jitter across a threshold here. Orbiting 90°: octaves flipped twice (128 ↔ 256), steps twice
1089
- (215 ↔ 256), steps with hysteresis never — held at 215 for the 21 frames where 256 was available.
1090
- - *Cost*: one load and one store in a single-invocation pass; nothing measurable.
1091
-
1092
- **Found on the way: the warp build's serial LUT costs 136 µs whenever the volume is sparse.** With the
1093
- occupancy fitting at 4096 virtual slices the build took 136 µs on the 4090, against 22.5 µs at 128: one
1094
- invocation writing 4,097 LUT entries. Entry 15 raised `AVBOIT_VIRTUAL_SLICES_MAX` to 4096 counting "a longer
1095
- single-lane loop in the warp build" among costs that do not register; it registers, against entry 17's
1096
- 290 µs for the whole of the transparency work on a page whose occupancy fits at 4096. A workgroup prefix
1097
- sum would take it to a few microseconds, and the steps' run walk with it. Not done here.
1098
-
1099
- **Verified.** Seven new emulator specs on the build: the first step down that fits, the fall-through to the
1100
- octave, a step's slices covering every finest slice they overlap, hysteresis holding without room to spare
1101
- and refining with it, coarsening at once, the history recorded when hysteresis is off. The suites the change
1102
- reaches (132 files, 1,537 tests); the type check at 18 against the budget of 19. On the 4090 the build
1103
- compiles and runs with both switches in the bonfire, no validation error.
1104
-
1105
- **Open:** whether to turn either on — the steps are a measured 1.2–1.7× in over-budget scenes for 20–300 µs
1106
- of single-lane work, hysteresis a guard against a flicker the bonfire did not show; the release, if
1107
- hysteresis goes on (0.9 with steps by the table above); a parallel warp build, which would make the steps
1108
- nearly free and take back the 136 µs.
1109
-
1110
- ## 28. The warp build, one workgroup — 2026-10-04
1111
-
1112
- Alex: *"go ahead and parallelize the warp build please, go ahead and use subgroups too if it's relevant"*,
1113
- with a snippet from the IOI Smolder talk — CuteSort, which ranks a lane by `WaveActiveBallot` per key bit
1114
- and `countbits`.
1115
-
1116
- **What it was.** One invocation — §3.4 had it that a 1 K-entry prefix sum is not a workload, and that a single
1117
- lane keeps the shader runnable in the emulator. Entry 27 measured it at 22.5 µs halving to 128 virtual
1118
- slices, 136 µs whenever the occupancy fits at 4096 (the 4,097-entry LUT written serially), and 317 µs with
1119
- four steps over 600 fragmented runs (the run walk).
1120
-
1121
- **What it is.** One workgroup of 128 invocations, one per occupancy word, each holding its finest word in a
1122
- register for the whole build.
1123
-
1124
- - *A resolution tried:* every invocation marks the virtual slices its word's runs touch
1125
- (`chunk_avboit_warp_marks`) — finest slice `f` touches `floor(f V / V_max)` to `ceil((f + 1) V / V_max) - 1`,
1126
- division-free because `V_max` is a power of two, and 32 finest slices touch at most two virtual words — and
1127
- `atomicOr`s them into workgroup memory. Each invocation then dilates its own word of that resolution
1128
- (`chunk_avboit_warp_level_word`), and a `subgroupAdd` per subgroup and one pass over the partials count
1129
- them. Octaves are marked by the same rule, so halving is no longer a fold of the level above; where
1130
- `V_max / V` is a power of two the overlap rule is the fold.
1131
- - *Decisions* are made on that count read back through `workgroupUniformLoad`, which is what lets Tint accept
1132
- loops with barriers and subgroup operations inside: the octave loop bounded at 13, the steps loop by the
1133
- uniform setting.
1134
- - *The LUT:* `subgroupExclusiveAdd` over the chosen words' counts plus the earlier subgroups' totals, then 32
1135
- coalesced writes per invocation (`chunk_avboit_warp_lut_entry`).
1136
- - The marks alternate between two halves of a workgroup array, each try zeroing the half the next will mark,
1137
- so a try costs three barriers.
1138
-
1139
- A first cut had each invocation compute its virtual word straight from the finest bits — 34 slices, two
1140
- integer divisions each. Bit-exact, and 7 µs at 4096, but slower than the single lane wherever the warp halves
1141
- (26.6 µs against 22.5 at 128): at 128 virtual slices four invocations had a word and 124 waited. Marking from
1142
- the finest side keeps all 128 busy at every resolution.
1143
-
1144
- **The CuteSort snippet.** Its rank — the popcount of a ballot masked to the lower lanes — is what a LUT entry
1145
- already is: `countOneBits(word & ((1u << bit) - 1u))`. An occupancy word is that ballot, 32 slices to a
1146
- word, so within a word no subgroup operation is needed and nothing anywhere needs sorting; subgroups carry the
1147
- counts across words.
1148
-
1149
- **Measured** (RTX 4090, headless Chrome; the build dispatched alone, warm, single-lane and parallel
1150
- interleaved in alternating order, medians of 240):
1151
-
1152
- | occupancy | V | single-lane | one workgroup |
1153
- |---|---|---|---|
1154
- | sparse, fits at 4096 (steps 1 or 4) | 4096 | 136.2 µs | 3.1 µs |
1155
- | 12 runs, steps 1 | 512 | 33.8 µs | 3.1 µs |
1156
- | 12 runs, steps 4 | 724 | 54.3 µs | 4.1 µs |
1157
- | 40 runs (bonfire-like), steps 1 | 128 | 22.5 µs | 4.1 µs |
1158
- | 40 runs, steps 4 | 128 | 53.3 µs | 5.1 µs |
1159
- | 600 fragmented runs, steps 1 | 128 | 22.5 µs | 7.2 µs |
1160
- | 600 fragmented runs, steps 4 | 128 | 317.4 µs | 9.2 µs |
1161
- | occupied everywhere, steps 1 | 128 | 22.5 µs | 4.1 µs |
1162
- | occupied everywhere, steps 4 | 128 | 34.8 µs | 5.1 µs |
1163
-
1164
- A step tried now costs about a microsecond, so the cost half of entry 27's open question is gone.
1165
-
1166
- **Verified.**
1167
-
1168
- - *Bit-exact against the single-lane build on the 4090:* both shaders in one page on the same inputs, every
1169
- word of the warp and the history word compared. 5,304 cases: 397 occupancy patterns (empty, full, single
1170
- slices at word edges, runs sized around every octave's budget at seven offsets, trains of runs, 160 random
1171
- run sets, random bits at seven densities) × physical 128 (96 and 200 on a third of them) × steps 1, 2, 4, 7
1172
- × hysteresis off and on with assorted previous resolutions. Every level from 64 to 4096 was reached; no
1173
- word differs. Tint compiles it with no message.
1174
- - *Through the shipped pipeline* (the bonfire, entry 27's probe, 80 frames per setting): 128 virtual slices
1175
- with octaves, 215 with steps, the same two flips orbiting and none with hysteresis; no validation error.
1176
- - *Not exercised:* subgroup sizes other than the 4090's 32. The scan takes invocations to fill subgroups in
1177
- index order, as `shader_prefix_scan_csdldf` does.
1178
- - *Specs:* the emulator cannot run the workgroup, so — as with the bucket sorts (entry 22's sort, the
1179
- rasterization buckets) — the shader's spec holds the assembled source to the parser and the validator, and
1180
- the five new chunks carry their own: the marks against the overlap definition at octaves and between, runs
1181
- split across words, a word straddling a virtual word; the dilation and the cut at `V`; the budget; the
1182
- steps; the LUT entry. Flooring a run's last slice instead of ceiling it fails four of the marks specs. The
1183
- 14 emulator tests of the whole single-lane build went with it; their scenarios are among the device cases.
1184
- The suites the change reaches: 160 files, 1,619 tests; the type check at 18 against 19.
1185
-
1186
- `chunk_avboit_fold_pairs` has no consumer now; kept.
1187
-
1188
- The water playground got a tickbox for the steps on the way (d915095b2). Its one view sits at 256 virtual
1189
- slices in all six presets with or without them: the water's occupancy fits at 256 and at none of 431, 362
1190
- or 304.
1191
-
1192
- **Open:** entry 27's — whether to turn the steps or hysteresis on, the steps now at about a microsecond per
1193
- resolution tried.
1194
-
1195
- ## 29. Four steps per octave by default; hysteresis removed — 2026-10-04
1196
-
1197
- Alex, after the warp, its defaults and entries 27–28 were explained: *"make 4 default, remove hysteresis."*
1198
-
1199
- **Steps.** `AVBOITSettings.warp_steps_per_octave` is 4. What it was measured to buy (entry 27): 1.28–1.43×
1200
- finer virtual slices on average where the warp halves, 1.68× in the bonfire (128 → 215), and smaller level
1201
- changes when the count crosses a threshold (215 ↔ 256 orbiting the bonfire, against 128 ↔ 256). What it
1202
- costs since entry 28: about a microsecond per resolution tried, at most three per view per frame. Where
1203
- halving already lands close to the budget no step fits and the warp is the octave's, as before. The water
1204
- playground's tickbox starts ticked, mirroring the default.
1205
-
1206
- **Hysteresis** is gone: `AVBOITSettings.warp_hysteresis`, `AVBOIT_WARP_RELEASE`, `chunk_avboit_warp_budget`,
1207
- the warp build's `history` binding and the per-view buffer behind it (`graph_avboit_build_warp` no longer
1208
- takes a `history_key`). It guarded against a flicker measured only in synthetic jitter — the bonfire with a
1209
- still camera never changed level — and cost precision whenever a scene shrinks, holding the coarser
1210
- slicing until a quarter of the budget was free.
1211
-
1212
- **Verified.** The build without the history binding against the single-lane build of entry 27 with
1213
- hysteresis off, on the 4090: entry 28's 397 occupancy patterns × physical counts × steps 1, 2, 4, 7, 2,652
1214
- cases, every word of the warp identical; timing unchanged (3.1–9.2 µs). The suites the change reaches; the
1215
- type check.
1216
-
1217
- **Open:** none from this.
1218
-