@woosh/meep-engine 3.60.4 → 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 (379) 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/package.json +6 -2
  9. package/src/engine/EngineHarness.d.ts +3 -9
  10. package/src/engine/EngineHarness.d.ts.map +1 -1
  11. package/src/engine/EngineHarness.js +6 -8
  12. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts +9 -6
  13. package/src/engine/graphics/camera/makeOrbitalCameraController.d.ts.map +1 -1
  14. package/src/engine/graphics/camera/makeOrbitalCameraController.js +44 -71
  15. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts +64 -0
  16. package/src/engine/graphics/camera/orbital/MomentumAxis.d.ts.map +1 -0
  17. package/src/engine/graphics/camera/orbital/MomentumAxis.js +187 -0
  18. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts +86 -0
  19. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.d.ts.map +1 -0
  20. package/src/engine/graphics/camera/orbital/OrbitalCameraRig.js +319 -0
  21. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts +34 -10
  22. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.d.ts.map +1 -1
  23. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +116 -30
  24. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts +1 -7
  25. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.d.ts.map +1 -1
  26. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraControllerSystem.js +1 -37
  27. package/src/engine/graphics/ecs/water/Water.d.ts +8 -0
  28. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  29. package/src/engine/graphics/ecs/water/Water.js +8 -0
  30. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  31. package/src/engine/graphics3/WaterSystem.js +731 -734
  32. package/src/engine/graphics3/decal/DecalTileShader.d.ts +61 -0
  33. package/src/engine/graphics3/decal/DecalTileShader.d.ts.map +1 -0
  34. package/src/engine/graphics3/decal/DecalTileShader.js +138 -0
  35. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts +10 -4
  36. package/src/engine/graphics3/decal/GPUDecalRenderer.d.ts.map +1 -1
  37. package/src/engine/graphics3/decal/GPUDecalRenderer.js +149 -44
  38. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts +25 -0
  39. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.d.ts.map +1 -0
  40. package/src/engine/graphics3/decal/chunk_decal_tile_vertex.js +55 -0
  41. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts +22 -0
  42. package/src/engine/graphics3/decal/graph_gather_decal_tiles.d.ts.map +1 -0
  43. package/src/engine/graphics3/decal/graph_gather_decal_tiles.js +70 -0
  44. package/src/engine/graphics3/decal/shader_decal_composite.d.ts +7 -5
  45. package/src/engine/graphics3/decal/shader_decal_composite.d.ts.map +1 -1
  46. package/src/engine/graphics3/decal/shader_decal_composite.js +48 -44
  47. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts +12 -0
  48. package/src/engine/graphics3/decal/shader_decal_tile_copy.d.ts.map +1 -0
  49. package/src/engine/graphics3/decal/shader_decal_tile_copy.js +78 -0
  50. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts +11 -0
  51. package/src/engine/graphics3/decal/shader_gather_decal_tiles.d.ts.map +1 -0
  52. package/src/engine/graphics3/decal/shader_gather_decal_tiles.js +80 -0
  53. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts +13 -0
  54. package/src/engine/input/devices/mouse/MouseEventButtons.d.ts.map +1 -0
  55. package/src/engine/input/devices/mouse/MouseEventButtons.js +23 -0
  56. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts +4 -0
  57. package/src/engine/input/ecs/controllers/KeyboardCameraController.d.ts.map +1 -1
  58. package/src/engine/input/ecs/controllers/KeyboardCameraController.js +9 -45
  59. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  60. package/src/shade/playground/water_refraction/README.md +5 -6
  61. package/src/shade/playground/water_refraction/index.html +1 -1
  62. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  63. package/src/shade/renderer/Renderer.js +7 -5
  64. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts +40 -80
  65. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts.map +1 -1
  66. package/src/shade/renderer/camera/orbital/OrbitalCameraController.js +197 -147
  67. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  68. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  69. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  70. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  71. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  72. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +2 -2
  73. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +2 -2
  74. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +2 -1
  75. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
  76. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +18 -1
  77. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts +31 -0
  78. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts.map +1 -0
  79. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.js +343 -0
  80. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts +17 -0
  81. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts.map +1 -0
  82. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.js +29 -0
  83. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -1
  84. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +7 -13
  85. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  86. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +1 -8
  87. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +5 -5
  88. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
  89. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +10 -10
  90. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +1 -4
  91. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  92. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +1 -10
  93. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts +5 -15
  94. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts.map +1 -1
  95. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.js +6 -34
  96. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
  97. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +1 -7
  98. package/src/shade/renderer/hiz/HZBFootprint.d.ts +14 -0
  99. package/src/shade/renderer/hiz/HZBFootprint.d.ts.map +1 -0
  100. package/src/shade/renderer/hiz/HZBFootprint.js +16 -0
  101. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts +10 -0
  102. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts.map +1 -0
  103. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.js +11 -0
  104. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts +10 -0
  105. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts.map +1 -0
  106. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.js +11 -0
  107. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +0 -7
  108. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  109. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +405 -350
  110. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts +12 -0
  111. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts.map +1 -0
  112. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.js +27 -0
  113. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts +12 -0
  114. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts.map +1 -0
  115. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.js +27 -0
  116. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts +14 -0
  117. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts.map +1 -0
  118. package/src/shade/renderer/hiz/hzb_footprint_texels.js +34 -0
  119. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +0 -5
  120. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  121. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +37 -57
  122. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts +11 -0
  123. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts.map +1 -0
  124. package/src/shade/renderer/hiz/{shader_hzb_reduce_mip0_clip.js → shader_hzb_reduce_mip0.js} +120 -111
  125. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  126. package/src/shade/renderer/material/GPUMaterialManager.d.ts +7 -0
  127. package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
  128. package/src/shade/renderer/material/GPUMaterialManager.js +11 -0
  129. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +12 -12
  130. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  131. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +17 -118
  132. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  133. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +7 -2
  134. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +0 -6
  135. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  136. package/src/shade/renderer/water/GPUWaterRenderer.js +12 -17
  137. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts +35 -0
  138. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts.map +1 -0
  139. package/src/shade/renderer/water/medium/graph_water_meshlets.js +89 -0
  140. package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
  141. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  142. package/src/shade/renderer/water/shader_water.js +22 -13
  143. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
  144. package/src/shade/renderer/water/shader_water_avboit.js +7 -7
  145. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +1 -1
  146. package/src/shade/renderer/water/surface/graph_water_tessellation.js +1 -1
  147. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +9 -1
  148. package/editor/REWORK_PLAN_2026_08.md +0 -692
  149. package/src/CODEC_LAYOUT_VERIFICATION.md +0 -419
  150. package/src/CORRECTNESS_HUNT_2026_07_24.md +0 -142
  151. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +0 -738
  152. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +0 -79
  153. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +0 -93
  154. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +0 -80
  155. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +0 -164
  156. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +0 -69
  157. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +0 -110
  158. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +0 -129
  159. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +0 -72
  160. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +0 -74
  161. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +0 -169
  162. package/src/DUPLICATION_AUDIT_2026_08_07.md +0 -254
  163. package/src/DUPLICATION_AUDIT_REMEDIATION.md +0 -250
  164. package/src/DUPLICATION_REVIEW_2026_10_24.md +0 -1072
  165. package/src/REVIEW_2026_08_06/adv-core.md +0 -511
  166. package/src/REVIEW_2026_08_06/adv-gfx.md +0 -480
  167. package/src/REVIEW_2026_08_06/adv-view.md +0 -446
  168. package/src/REVIEW_2026_08_06/api-core.md +0 -296
  169. package/src/REVIEW_2026_08_06/api-engine.md +0 -195
  170. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +0 -507
  171. package/src/REVIEW_2026_08_06/core-binary.md +0 -348
  172. package/src/REVIEW_2026_08_06/core-collection.md +0 -376
  173. package/src/REVIEW_2026_08_06/core-math.md +0 -221
  174. package/src/REVIEW_2026_08_06/core-process.md +0 -266
  175. package/src/REVIEW_2026_08_06/core-science.md +0 -223
  176. package/src/REVIEW_2026_08_06/engine-ai-nav.md +0 -279
  177. package/src/REVIEW_2026_08_06/engine-animation.md +0 -167
  178. package/src/REVIEW_2026_08_06/engine-asset.md +0 -166
  179. package/src/REVIEW_2026_08_06/engine-ecs.md +0 -920
  180. package/src/REVIEW_2026_08_06/engine-input.md +0 -197
  181. package/src/REVIEW_2026_08_06/engine-misc.md +0 -248
  182. package/src/REVIEW_2026_08_06/engine-terrain.md +0 -52
  183. package/src/REVIEW_2026_08_06/generation.md +0 -176
  184. package/src/REVIEW_2026_08_06/geom-2d.md +0 -434
  185. package/src/REVIEW_2026_08_06/geom-3d.md +0 -117
  186. package/src/REVIEW_2026_08_06/gfx-gi.md +0 -143
  187. package/src/REVIEW_2026_08_06/gfx-render.md +0 -740
  188. package/src/REVIEW_2026_08_06/gfx-texture.md +0 -690
  189. package/src/REVIEW_2026_08_06/prior-criticals.md +0 -49
  190. package/src/REVIEW_2026_08_06/prior-highs.md +0 -138
  191. package/src/REVIEW_2026_08_06/test-gaps.md +0 -364
  192. package/src/REVIEW_2026_08_06/view.md +0 -667
  193. package/src/REVIEW_2026_08_06.md +0 -610
  194. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +0 -542
  195. package/src/core/bvh2/bvh3/NOTES.md +0 -2
  196. package/src/core/bvh8/NOTES.md +0 -63
  197. package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +0 -243
  198. package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +0 -417
  199. package/src/core/color/COLOR_REVIEW_2026_08_28.md +0 -779
  200. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +0 -433
  201. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +0 -486
  202. package/src/core/geom/3d/atlas/BFF_PLAN.md +0 -174
  203. package/src/core/geom/3d/atlas/REVIEW_02.md +0 -392
  204. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +0 -146
  205. package/src/core/geom/3d/gjk/NOTES.md +0 -8
  206. package/src/core/geom/3d/topology/TOPO_MESH_RETIREMENT_PLAN.md +0 -135
  207. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +0 -381
  208. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +0 -684
  209. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +0 -554
  210. package/src/core/model/reactive/evaluation/PREDICATE_APPLICABILITY_PROPOSAL.md +0 -446
  211. package/src/engine/REVIEW_2026_07_03.md +0 -211
  212. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +0 -815
  213. package/src/engine/control/first-person/DESIGN.md +0 -658
  214. package/src/engine/control/first-person/DESIGN_COLLISION.md +0 -382
  215. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +0 -563
  216. package/src/engine/control/first-person/TODO.md +0 -160
  217. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +0 -531
  218. package/src/engine/ecs/HIERARCHY_REVIEW.md +0 -717
  219. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +0 -262
  220. package/src/engine/ecs/ik/PLAN.md +0 -179
  221. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +0 -272
  222. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +0 -199
  223. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +0 -521
  224. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +0 -463
  225. package/src/engine/graphics/ecs/camera/pp/NOTES.md +0 -48
  226. package/src/engine/graphics/render/frame_graph/CORRECTNESS_FIXES_2026_09_04.md +0 -659
  227. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +0 -235
  228. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +0 -490
  229. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +0 -466
  230. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +0 -717
  231. package/src/engine/input/ecs/ism/ISM_DESIGN.md +0 -1523
  232. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +0 -627
  233. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +0 -661
  234. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +0 -229
  235. package/src/engine/network/CONGESTION_CONTROL.md +0 -87
  236. package/src/engine/network/MESSAGE_TRANSPORT.md +0 -364
  237. package/src/engine/network/SMOOTHNESS_DESIGN.md +0 -400
  238. package/src/engine/physics/cloth/MEASUREMENTS.md +0 -5304
  239. package/src/engine/physics/cloth/PLAN.md +0 -3056
  240. package/src/engine/physics/fluid/GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md +0 -1382
  241. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +0 -243
  242. package/src/engine/physics/fluid/solver/NOTES.md +0 -46
  243. package/src/engine/sound/SOUND_EMITTER_RETIREMENT_PLAN.md +0 -536
  244. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +0 -432
  245. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +0 -250
  246. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +0 -322
  247. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +0 -261
  248. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +0 -121
  249. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +0 -999
  250. package/src/format/image/avif/DECISIONS.md +0 -545
  251. package/src/format/image/jpeg/NOTES.md +0 -3
  252. package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +0 -814
  253. package/src/generation/COORDINATES.md +0 -54
  254. package/src/generation/REVIEW_01_ACTION_PLAN.md +0 -628
  255. package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +0 -220
  256. package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +0 -172
  257. package/src/generation/foliage/CROWN_FULLNESS_BRIEF_2026_10_26.md +0 -86
  258. package/src/generation/foliage/FOLIAGE_DESIGN.md +0 -547
  259. package/src/generation/foliage/FOLIAGE_PLAN.md +0 -198
  260. package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +0 -158
  261. package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +0 -521
  262. package/src/generation/foliage/REAL_TIME_TREES_BRIEF_2026_10_27.md +0 -86
  263. package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +0 -347
  264. package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +0 -248
  265. package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +0 -694
  266. package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +0 -552
  267. package/src/shade/RENDERER_CONTRACT.md +0 -697
  268. package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +0 -263
  269. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +0 -569
  270. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +0 -755
  271. package/src/shade/device/mock/EXTENSION_PROPOSAL_2026_08_22.md +0 -458
  272. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +0 -255
  273. package/src/shade/device/mock/REVIEW_2026_08_22.md +0 -527
  274. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +0 -894
  275. package/src/shade/device/timing/profile/SGPT_FORMAT.md +0 -345
  276. package/src/shade/renderer/NOTES.md +0 -78
  277. package/src/shade/renderer/PLAN.md +0 -8
  278. package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +0 -145
  279. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +0 -357
  280. package/src/shade/renderer/atmosphere/NOTES.md +0 -6
  281. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts +0 -76
  282. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts.map +0 -1
  283. package/src/shade/renderer/camera/orbital/MomentumAxis.js +0 -164
  284. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts +0 -45
  285. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts.map +0 -1
  286. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.js +0 -66
  287. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts +0 -98
  288. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts.map +0 -1
  289. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.js +0 -184
  290. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts +0 -175
  291. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts.map +0 -1
  292. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.js +0 -411
  293. package/src/shade/renderer/camera/orbital/README.md +0 -81
  294. package/src/shade/renderer/camera/orbital/index.d.ts +0 -6
  295. package/src/shade/renderer/camera/orbital/index.d.ts.map +0 -1
  296. package/src/shade/renderer/camera/orbital/index.js +0 -9
  297. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +0 -964
  298. package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +0 -361
  299. package/src/shade/renderer/geometry/bvh/BLAS_REFIT_BATCH_PLAN.md +0 -619
  300. package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +0 -674
  301. package/src/shade/renderer/geometry/sdf/NOTES.md +0 -6
  302. package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +0 -540
  303. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +0 -305
  304. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +0 -913
  305. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +0 -787
  306. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +0 -481
  307. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +0 -301
  308. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +0 -448
  309. package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +0 -385
  310. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +0 -656
  311. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +0 -308
  312. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +0 -657
  313. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +0 -551
  314. package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +0 -529
  315. package/src/shade/renderer/global_illumination/lpv/NOTES.md +0 -3
  316. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +0 -4306
  317. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +0 -410
  318. package/src/shade/renderer/global_illumination/sharc/NOTES.md +0 -8
  319. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +0 -1369
  320. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +0 -202
  321. package/src/shade/renderer/gpu_primitive/indirect/NOTES.md +0 -94
  322. package/src/shade/renderer/hiz/NOTES.md +0 -3
  323. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +0 -8
  324. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +0 -1
  325. package/src/shade/renderer/light/cluster/NOTES.md +0 -11
  326. package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +0 -524
  327. package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +0 -1101
  328. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts +0 -24
  329. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts.map +0 -1
  330. package/src/shade/renderer/material/resident/shader_resident_texture_means.js +0 -120
  331. package/src/shade/renderer/particles/DESIGN.md +0 -1011
  332. package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +0 -280
  333. package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +0 -453
  334. package/src/shade/renderer/path_tracer/PATH_TRACED_MEDIA_PLAN_2026_10_02.md +0 -520
  335. package/src/shade/renderer/postprocess/bloom/NOTES.md +0 -6
  336. package/src/shade/renderer/postprocess/fsr/NOTES.md +0 -4
  337. package/src/shade/renderer/postprocess/gtao/NOTES.md +0 -6
  338. package/src/shade/renderer/postprocess/nss/NOTES.md +0 -332
  339. package/src/shade/renderer/postprocess/perspective/NOTES.md +0 -3
  340. package/src/shade/renderer/postprocess/sscs/NOTES.md +0 -5
  341. package/src/shade/renderer/postprocess/ssr/NOTES.md +0 -14
  342. package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +0 -286
  343. package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -39
  344. package/src/shade/renderer/postprocess/taa/NOTES.md +0 -11
  345. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +0 -78
  346. package/src/shade/renderer/postprocess/vrs/NOTES.md +0 -4
  347. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +0 -1218
  348. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +0 -600
  349. package/src/shade/renderer/restir/reservoir/NOTES.md +0 -6
  350. package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +0 -6
  351. package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +0 -391
  352. package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +0 -153
  353. package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +0 -322
  354. package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +0 -247
  355. package/src/shade/renderer/shader/chunk/fsr/NOTES.md +0 -3
  356. package/src/shade/renderer/shader/chunk/fsr/spd/NOTES.md +0 -4
  357. package/src/shade/renderer/shader/chunk/geometry/ray/NOTES.md +0 -4
  358. package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +0 -159
  359. package/src/shade/renderer/shader/chunk/mboit/NOTES.md +0 -38
  360. package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/NOTES.md +0 -3
  361. package/src/shade/renderer/shader/chunk/spherical_harmonics/sampling/NOTES.md +0 -8
  362. package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +0 -927
  363. package/src/shade/renderer/shadow/contact/FINDINGS.md +0 -413
  364. package/src/shade/renderer/shadow/map/csm/NOTES.md +0 -5
  365. package/src/shade/renderer/shadow/ray/NOTES.md +0 -31
  366. package/src/shade/renderer/shadow/sdf/NOTES.md +0 -9
  367. package/src/shade/renderer/shadow/sdf/brixelizer/NOTES.md +0 -5
  368. package/src/shade/renderer/shadow/sdf/cascade/NOTES.md +0 -9
  369. package/src/shade/renderer/shadow/vsm/NOTES.md +0 -12
  370. package/src/shade/renderer/shadow/vsm/PLAN.md +0 -442
  371. package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +0 -43
  372. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +0 -2444
  373. package/src/shade/renderer/texture/mipmaps/NOTES.md +0 -3
  374. package/src/shade/renderer/volumetrics/NOTES.md +0 -1329
  375. package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +0 -336
  376. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +0 -23
  377. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +0 -1
  378. package/src/shade/renderer/water/medium/GPUWaterBodies.js +0 -75
  379. package/src/shade/wgsl/NOTES.md +0 -4
@@ -1,432 +0,0 @@
1
- # Acoustic Simulation — Implementation Plan
2
-
3
- > A geometry-driven acoustic **simulation** layer for meep that feeds the **sopra** sound engine.
4
- > Steam-Audio-equivalent in *role* (occlusion, transmission, medium, later reflections / reverb /
5
- > pathing), built ECS-first and almost entirely in JS for **real-time, in-browser** use. The simulation
6
- > computes scalars / IRs against meep geometry; **sopra renders them through WebAudio**. The two never
7
- > blur: this folder produces data, sopra turns data into sound.
8
- >
9
- > Status: **P0–P6 BUILT + GREEN, all render paths wired** (full `engine/sound` suite 360 pass / 0
10
- > regressions). The simulation engine + the occlusion/transmission/medium RENDER seam + the **probe-baked
11
- > reverb RENDER** + the **corner-leakage (pathing) RENDER** are complete and wired into sopra + the ECS
12
- > (`AudioEmitterSystem`). Reverb render uses **option B** — a new acoustic-owned `ProbeReverbRenderer`
13
- > (its own double-buffered ConvolverNode crossfade, IR from the nearest probe's baked decay), leaving
14
- > sopra's static `ReverbEffect` untouched. Pathing render is an **engine-wide `AcousticSimulator.pathing`
15
- > toggle** (off by default, zero cost when off): one single-source Dijkstra from the listener per frame
16
- > (`AcousticPathGraph.computeExitField`) gives every probe its exit point; each occluded source routes a
17
- > second exit-spatialized send (`EventInstance` path branch). No per-source flag — threaded from the
18
- > engine config like `acoustic`. **ECS WIRING HELPER LANDED (2026-06-20):** `configureAcousticSimulation`
19
- > (`simulation/configureAcousticSimulation.js`) — one call from a config builds/reuses the shared
20
- > `AcousticSimulator`, adds `AcousticSimulationSystem` + `AcousticVolumeSystem` (via `addManySystems`),
21
- > and injects the simulator (+ optional `ProbeReverbRenderer`) into the `AudioEmitterSystem`. So the only
22
- > meep-internal integration is done; **Remaining:** the EXTERNAL config call (`makeMirEngineConfig`, in moh
23
- > — out of meep scope) + save/load serialization registration, both gated on the sopra strangler
24
- > migration. Serialization adapters are deliberately NOT built yet — they mirror `AudioEmitter`, which has
25
- > none either (the new sound stack defers binary save integration); the acoustic components carry JSON
26
- > `toJSON`/`fromJSON` + `typeName` for when the migration lands. To wire reverb: connect a sopra bus send →
27
- > `ProbeReverbRenderer.input`, pass `probeReverb` to `configureAcousticSimulation` (or set
28
- > `system.probeReverb`) + `renderer.setProbeField(bakedField)`. To wire pathing: `sim.pathing = true` +
29
- > `sim.setPathGraph(bakeVisibility(probeField, sim.occluderIndex))`.
30
- >
31
- > **What landed per phase:**
32
- > - **P0** — `AcousticMaterial`, `RayHit`, `AcousticOccluderIndex` (per-body local ebvh BLAS, O(1) `setTransform`).
33
- > - **P1** — `OcclusionSolver` (volumetric fibonacci rays + seeded jitter + EMA), `VolumeField`, `AcousticMedium`/`AcousticSolution`/`AcousticSourceState`.
34
- > - **P2** — `AcousticBody` + `AcousticSimulationSystem` (reactive); `AcousticSimulator`; occluder shapes raycast directly via `AbstractShape3D.raycast` (no triangulation).
35
- > - **P3** — `EventInstance.setAcoustic` + node insert (occlusion gain + lowpass + transmission); `AudioEmitter.acoustic`; `AudioEmitterSystem` apply + `LiveEmitterSet` acoustic threading. **First audible occlusion — wired.**
36
- > - **P4** — `AcousticVolume` + `AcousticVolumeSystem` (medium, blended, live-tracked).
37
- > - **P5** — world-AABB broadphase (measurement-driven via `bench/occlusion.bench.spec.js`); temporal amortization proven.
38
- > - **P6** — `AcousticProbe` + `bakeReverb` + `ProbeField` + `bakeProbeField` (geometry-derived reverb, density knob, serialized); `AcousticPathGraph` + `bakeVisibility` + `findAcousticPath` (probe-visibility routing around obstacles).
39
- >
40
- > **Post-P6 cleanup + accel pass (2026-06-20, all committed, `engine/sound` 373 pass):** the occluder
41
- > index now **raycasts shapes directly** via `AbstractShape3D.raycast` (no triangulation;
42
- > `acoustic_geometry_from_shape` deleted) and sits behind a **TLAS** — a dynamic BVH over the bodies'
43
- > world AABBs (P5's deferred TLAS, landed early). `RayHit` carries the world hit **point + normal** (no
44
- > `hit` flag, no `triangleIndex`). Per-source state is **pooled** in a `BinaryElementPool`
45
- > (`Map<EventInstance, id>`, flyweight cursor) instead of a `Map<…, AcousticSourceState>`. The probe
46
- > substrate moved to **`RowFirstTable` + a probe BVH** (nearest via `bvh_query_user_data_nearest_to_point`)
47
- > and **`CSRGraph`** connectivity. `sourceRadius` moved onto `AudioEmitter`/`EventInstance`;
48
- > `pathMaxDistance` was removed (path falloff now derives from the source's authored attenuation curve);
49
- > `AcousticSolution.exit` is a vector. Below, §3.1 / §4 / D5 / P5 describe the v1 triangle-BLAS +
50
- > linear-scan design that this pass superseded — read them as history. The remaining headline work is the
51
- > ray-traced reflection IR (see `REFLECTION_IR_PLAN.md`).
52
- >
53
- > **FOA directional reverb LANDED (2026-06-20):** the first of P6's two premium tiers — the **FOA
54
- > (first-order ambisonic / B-format) directional reverb field**. The bake now captures, per band, the
55
- > energy-weighted **arrival direction** (a 0..1 anisotropy vector) by reciprocity — a ray's reverberant
56
- > energy arrives from its launch direction (`acoustic_trace_energy` accumulates it, `bakeReverbBands`
57
- > normalises it). `ProbeField` widened stride 6→15 to store the three per-band direction vectors (JSON
58
- > round-trips them). Render: a new `render/FoaProbeReverbRenderer` convolves the mono send into a B-format
59
- > set (`foaReverbImpulseResponse` — `W` omni + `X/Y/Z` steered by the baked direction, one-factor W-energy
60
- > normalised) and decodes to stereo by **rotating the decoder, not the soundfield** (two virtual cardioids
61
- > whose look dirs are rotated into world space by the listener orientation — six decode gains/frame, the IR
62
- > re-baked only on probe-cell change). `AudioEmitterSystem` now threads the listener rotation to
63
- > `probeReverb.update()` (the scalar `ProbeReverbRenderer` ignores it). Stereo decode is isolated so a
64
- > binaural/HRTF (SOFA) decoder can replace it later.
65
- >
66
- > **Source directivity LANDED (2026-06-20):** the second of P6's premium tiers. `AcousticDirectivity`
67
- > (`simulation/definition/`) is the Steam-Audio dipole model — `gain = |(1−w) + w·cosθ|^p`
68
- > (`w` = `dipoleWeight` 0=omni/0.5=cardioid/1=dipole, `p` = `dipolePower`), `cosθ` = source forward · the
69
- > source→listener direction. Authored on `AudioEmitter.directivity` (default omni → zero cost; `isOmni`
70
- > short-circuits). The simulator's `apply` computes it per non-omni source (forward = `Vector3.forward`
71
- > rotated by the source's orientation) into `AcousticSolution.directivityGain`, which `EventInstance.setAcoustic`
72
- > multiplies into the direct-path gain. Orientation (shared `Transform.rotation` ref) + the directivity ref
73
- > plumb through `playEvent`/`LiveEmitterSet` exactly like `sourceRadius`/`position`. **P6 premium tiers now
74
- > both done;** the only remaining acoustic work is the external `makeMirEngineConfig` activation + save/load
75
- > serialization (gated on the sopra strangler migration) and the deferred optional refinements (NEE early
76
- > reflections, pathing live-recheck, door variants, binaural/SOFA decode).
77
- >
78
- > **DISTANCE AIR ABSORPTION LANDED (2026-06-20):** "low frequencies travel further." `AcousticMedium.airAbsorption`
79
- > is now **dB/metre**; `VolumeField.integrateAbsorption` line-integrates it over the source→listener path
80
- > (`∫ α ds`; no-region case is closed-form `ambient·distance`, else 8-midpoint march reusing `sample`) into the
81
- > new `AcousticSolution.airLoss[3]` (per-band total dB). The point-sampled `AcousticSolution.medium` was
82
- > REMOVED (superseding §6's `airAbsLP.frequency ← solution.medium.airAbsorption` — air is now path-integrated,
83
- > not point-sampled). Render (`EventInstance.setAcoustic`) anchors the tilt at the LOW band — only
84
- > `airLoss[high] − airLoss[low]` darkens the cutoff (`OPEN·2^(−tilt/6dB-oct)`, floored), so a uniform loss (the
85
- > distance **attenuation curve's** job, a windowed/irradiance falloff — *not* physical) causes no darkening:
86
- > the curve owns loudness, air owns the spectral tilt, and the two compose via separate series nodes. Default
87
- > ambient = a small low-anchored atmosphere (`DEFAULT_AIR_ABSORPTION [0, 0.03, 0.1] dB/m`, tunable). Applied to
88
- > the DIRECT path only (the leak send's longer path is a deferred refinement, like directivity-on-leak).
89
- >
90
- > **CORNER-LEAK REFINEMENTS + DYNAMIC RECHECK + SERIALIZATION LANDED (2026-06-21):** the three corner-leak
91
- > gaps are closed, plus the acoustic binary adapters. (1) **Leak now carries directivity + air.**
92
- > `AcousticSimulator.#applyPath` walks the leaked polyline ONCE (source→…→listener via
93
- > `ProbeFieldPathingSolution.next`) and does three things: integrates air per leg
94
- > (`VolumeField.integrateAbsorption`) into the new `AcousticSolution.pathAirLoss[3]`; folds **directivity toward
95
- > the FIRST hop** (the corner the source radiates into, not the blocked listener — `#directivityGainToward`)
96
- > into `pathGain`; and marks each traversed edge for the recheck. `EventInstance`'s leak branch renders through
97
- > its own 3-band crossover (`#leak3Band`), gained per band by `(1 − transmission[b])` × the `pathAirLoss`
98
- > low-anchored tilt (so a longer routed leak darkens more, and a band-selective wall leaks only its blocked
99
- > bands around the corner — consistent with the direct path's 3-band render).
100
- > (2) **Budgeted dynamic-geometry recheck** (`AcousticSimulator.pathingRecheck` + `recheckBudget`, off by
101
- > default): `ProbeVisibilityRecheck` re-verifies (within budget, FIFO round-robin) the edges leaks traverse
102
- > against live geometry (`probe_edge_visible`, extracted + shared with `bakeVisibility`); a now-blocked edge →
103
- > `EDGE_WEIGHT_BLOCKED` (Float32 max — finite, no topology change, blows the path distance past audibility),
104
- > restored to the inter-probe distance when it re-opens. No structural mutation; the baked graph self-heals.
105
- > (3) **Binary serialization** adapters for `AcousticMaterial`/`AcousticMedium`/`AcousticDirectivity` (value
106
- > types) + `AcousticBody`/`AcousticVolume` (components, delegating; shape omitted like `toJSON`), with
107
- > `populateAcousticSerializationRegistry`. (`AudioEmitter`'s own adapter — needing the full sopra
108
- > `EventDescription` clip-graph chain — is still deferred.) Plus a **two-rooms-and-a-corridor integration
109
- > test** (`two_rooms_corridor.spec.js`) verifying bake (per-band reverb), reachability, leak direction
110
- > (emerges from the corridor, not through the wall), routed-vs-straight attenuation, and a sealed-divider
111
- > disconnection control. **Remaining deferred** (optional/premium): NEE early reflections, binaural/SOFA
112
- > decode, opt-in door variants + re-bake-on-streaming, region-aware air over the leak polyline, the full
113
- > external `makeMirEngineConfig` activation (gated on the sopra strangler migration).
114
-
115
- ---
116
-
117
- ## 0. The one rule: simulation vs. render
118
-
119
- ```
120
- SIMULATION (this folder — engine geometry, JS, no WebAudio) RENDER (sopra — WebAudio)
121
- ─────────────────────────────────────────────────────────── ─────────────────────────
122
- AcousticBody (occluder shapes + material) ─┐
123
- AcousticVolume (medium) ├─► AcousticSolution ─► EventInstance.setAcoustic()
124
- ebvh rays + Ray3 + sphere_fibonacci_point ───┘ (a class) (gain + biquad insert)
125
- ```
126
-
127
- `simulation/core` and `simulation/ecs` import **no WebAudio**. The *only* WebAudio touch is the seam
128
- where `AudioEmitterSystem` hands an `AcousticSolution` to `EventInstance.setAcoustic(...)` (that method
129
- lives in sopra, where the node chain already lives). Consequence: the whole simulation is unit-testable
130
- on real geometry with **zero `MockAudioContext`** — only the one render seam needs it.
131
-
132
- We do **not** put medium on `EventDescription`. An event is a recipe for *a source*; the medium (air,
133
- room) is a property of *the world*. Medium lives on `AcousticVolume` components; a source merely **opts
134
- in** to simulation via one flag (`AudioEmitter.acoustic`). No god-object `AcousticScene` — the occluder
135
- set is just the entities that have an `AcousticBody`, indexed by a system-owned acceleration structure.
136
- This is an ECS engine at the top level; the data model is components.
137
-
138
- ---
139
-
140
- ## 1. Layout
141
-
142
- ```
143
- sound/simulation/
144
- IMPLEMENTATION_PLAN.md
145
- definition/ authored, immutable, serializable (sopra/definition style)
146
- AcousticMaterial.js value class: absorption[3], scattering, transmission[3]
147
- AcousticMaterialSerializationAdapter.js
148
- core/ ECS-agnostic AND WebAudio-agnostic — the testable heart
149
- RayHit.js result class: t / material / bodyHandle / world position + normal
150
- AcousticOccluderIndex.js OccluderBody shapes + world transform behind a TLAS BVH; closestHit / anyHit
151
- AcousticMedium.js value class: airAbsorption (per-band)
152
- AcousticSolution.js result class the render seam consumes: occlusion, transmission[3], medium
153
- AcousticSourceState.js transient per-source accumulation (rolling occlusion, smoothing)
154
- OcclusionSolver.js solve(listener, source, index, rayCount, state, out) — explicit args, out-param
155
- VolumeField.js sample(volumes, x, y, z, out) — out-param, no allocation
156
- sampleDirections.js sphere_fibonacci_point set + per-tick seededRandom rotation
157
- ecs/ thin ECS binding (real-component tested)
158
- AcousticBody.js component: AcousticMaterial + "is occluder"; pairs with Collider + Transform
159
- AcousticBodySerializationAdapter.js
160
- AcousticVolume.js component: AbstractShape3D + AcousticMedium + transition band
161
- AcousticVolumeSerializationAdapter.js
162
- AcousticSimulationSystem.js System: maintain index + volume set (reactive); apply(liveInstances, listener)
163
- probe/ P6 — the baked substrate for reverb + pathing (see D6)
164
- bench/
165
- occlusion.bench.spec.js rays/tick + ms/tick calibration (raycast.bench methodology)
166
- ```
167
-
168
- The render-side additions (`EventInstance.setAcoustic`, the node insert, `AudioEmitter.acoustic`) live in
169
- **sopra**, not here — see §6.
170
-
171
- ---
172
-
173
- ## 2. Reused meep primitives (verified to exist)
174
-
175
- | Primitive | Path | Used for |
176
- |---|---|---|
177
- | `ebvh_geometry_query_nearest_triangle_ray` | `core/bvh2/bvh3/` | closest-hit ray → `[triangleIndex,u,v,t]` |
178
- | `ebvh_build_for_geometry_morton` | `core/bvh2/bvh3/` | build a body's BLAS from its triangle soup |
179
- | `BVH` | `core/bvh2/bvh3/BVH.js` | the per-body tree (`new BVH()`, `release_all`, `allocate_linear`, `root`) |
180
- | `Ray3` (`Float32Array(7)`: o,d,tMax) | `core/geom/3d/ray/Ray3.js` | the ray type; slots into the ebvh query as-is; `applyMatrix4` for local transform |
181
- | `m4_invert` / `m4_inverse_rotation_translation` | `core/geom/3d/mat4/` | per-body world→local inverse |
182
- | `sphere_fibonacci_point` | `core/geom/3d/sphere/` | even source-sphere / hemisphere sampling |
183
- | `v3_orthonormal_matrix_from_normal` + `v3_matrix3_rotate` | `core/geom/vec3/` | hemisphere sampling around a normal (P6) |
184
- | `seededRandom` (Mulberry32) | `core/math/random/` | per-tick ray jitter; **never `Math.random`** |
185
- | `AbstractShape3D.signed_distance_at_point` / `contains_point` | `core/geom/3d/shape/` | `AcousticVolume` boundary blend |
186
- | `AnimationCurve` | `engine/animation/curve/` | distance→cutoff curves (reuse, don't invent) |
187
- | `BinarySerializationRegistry` + adapter pattern | `ecs/storage/binary/` | serialize materials/bodies/volumes |
188
- | SH toolkit (`sh3_rotate_bl`, `sh3_sample_by_direction`, `sh3_add`) | `core/geom/3d/sphere/harmonics/` | ambisonic field (P6) |
189
- | `AssetManager` + `OfflineAudioContext` | `engine/asset/` / browser | baked probe data + offline IR (P6) |
190
-
191
- ---
192
-
193
- ## 3. Encapsulation (the clean core)
194
-
195
- **Two non-negotiables, applied everywhere below:**
196
- - **No opaque config bags.** Tunables (`rayCount`, …) are passed as explicit arguments. Configuration
197
- objects rot — a function that needs a ray count takes a ray count.
198
- - **No magic structs / `@typedef`.** Every result/value shape is a real **class** (`RayHit`,
199
- `AcousticSolution`, `AcousticMedium`, `AcousticSourceState`, `AcousticMaterial`). Hot-path queries are
200
- **allocation-free**: the caller allocates the result and passes it in; the function writes into it.
201
-
202
- ### 3.1 `AcousticOccluderIndex.js` — the (internal) acceleration structure, fully dynamic
203
- Not a public "scene". Owns a list of **bodies**, each a *local-space* triangle BLAS (`BVH` built once via
204
- `ebvh_build_for_geometry_morton`) plus a world transform (and its cached inverse) and one
205
- `AcousticMaterial`. API:
206
-
207
- ```js
208
- addBody(positions, indices, material, matrix?) → handle // BLAS built once from local triangles
209
- setTransform(handle, matrix) // O(1): store matrix + recompute inverse
210
- removeBody(handle)
211
- closestHit(ray /* Ray3 */, out /* RayHit */) → boolean // nearest hit across all bodies, into `out`
212
- anyHit(ray /* Ray3 */) → boolean // early-out
213
- ```
214
-
215
- `closestHit` transforms the world ray into each body's local frame (a module-static `Ray3` scratch +
216
- `applyMatrix4(inverse)`), runs the ebvh query against that body's BLAS bounded by the running best-`t`,
217
- and keeps the nearest; `out` receives `{ t, material, bodyHandle, triangleIndex }`. **Fully dynamic**:
218
- a moving body is just `setTransform` (O(1), no rebuild) — the ECS layer drives it from
219
- `Transform.position.onChanged`. Material is **per body** (a wall has one material); per-triangle
220
- materials are a later refinement, not a v1 need. *Precondition: rigid transforms (no scale), so local
221
- `t` equals world `t`* (documented, not guarded — see house rules). A body-level BVH (TLAS) over the bodies
222
- is the **P5** scaling step; v1 linear-scans the (few) bodies, BLAS does the real work.
223
-
224
- ### 3.2 `OcclusionSolver.js` — the per-source solve (pure, explicit args, out-param)
225
- ```js
226
- solve(listenerPose, sourceX, sourceY, sourceZ, sourceRadius, index, rayCount, state, out) → void
227
- ```
228
- Shoots `rayCount` rays from the listener toward a `sphere_fibonacci_point` set over the source's sphere
229
- (`sourceRadius`), rotated per tick by a `seededRandom` orientation (so accumulation covers the sphere).
230
- Each ray is bounded (`tMax` = listener→sample distance) so far subtrees prune. Blocked-ray fraction →
231
- instantaneous occlusion; blocked rays' materials accumulate the transmission 3-band. Folded into `state`
232
- (`AcousticSourceState`, a rolling window) and smoothed; written into `out` (`AcousticSolution`).
233
- **Pure function of geometry + state**, deterministic for a seed; no ECS, no WebAudio, no allocation.
234
-
235
- ### 3.3 `VolumeField.js` — medium zones (pure, out-param)
236
- ```js
237
- sample(volumes, x, y, z, out /* AcousticMedium */) → void
238
- ```
239
- Each `AcousticVolume` has an `AbstractShape3D` and a `transition` band. Weight =
240
- `smooth(-signed_distance / transition)` clamped to `[0,1]` (1 deep inside, 0 outside, smooth across the
241
- boundary). Normalize weights over overlapping volumes; blend the medium into `out`. A few
242
- `signed_distance_at_point` evals per query — cheap. Queried at the listener every tick; at sources
243
- optionally. **Pure**, allocation-free.
244
-
245
- ### 3.4 result + state classes
246
- `RayHit`, `AcousticSolution`, `AcousticMedium`, `AcousticSourceState`, `AcousticMaterial` are all real
247
- classes with named fields and a `reset()`/`clear()` where they are reused. The system owns a
248
- `Map<EventInstance, AcousticSourceState>`, dropped on `instance.onEnded` — so `EventInstance` stays
249
- ignorant of solver internals.
250
-
251
- ---
252
-
253
- ## 4. The component model (ECS)
254
-
255
- ### `AcousticBody` (occluder) — pairs with `Collider` + `Transform`
256
- Serializable component carrying an `AcousticMaterial` and the "this entity occludes audio" semantic. The
257
- **shape** comes from the entity's existing `Collider`/mesh; `AcousticBody` contributes the acoustic
258
- *properties*. The system triangulates the body's collider shape once into the occluder index (local
259
- space) and drives its transform reactively. Tagging a wall = adding the component; nothing
260
- acoustically-inert occludes unless tagged — that closes the "physics BVH contains everything" problem the
261
- ECS way.
262
-
263
- ### `AcousticVolume` (medium) — pairs with `Transform`
264
- Serializable component: an `AbstractShape3D` (its region), a `transition` distance (boundary blend band),
265
- and an `AcousticMedium` (air-absorption coefficients). Volumes overlap and blend (§3.3). **Medium only —
266
- no reverb here.** (Authored "reverb zones" are the wrong primitive; real reverb is the geometry-traced /
267
- baked output of the probe volume in P6. Until then, sopra's existing reverb stands unchanged.)
268
-
269
- ### `AcousticSimulationSystem` — the binding
270
- `extends System`, `dependencies` covering `AcousticBody`/`AcousticVolume`:
271
- - `link/unlink AcousticBody` → `index.addBody/removeBody` + subscribe `transform.position.onChanged`
272
- (and rotation) → `index.setTransform`. **Reactive, no per-tick refit loop** (mirrors
273
- `SpatialAudioIndex`).
274
- - `link/unlink AcousticVolume` → add/remove from the volume list.
275
- - `apply(liveInstances, listenerPose)` — **called by `AudioEmitterSystem`** between `liveSet.refresh`
276
- and `sopra.update`. Samples the listener medium once, then for each **live 3D** instance opted into
277
- acoustics: `OcclusionSolver.solve(...)` (+ optional per-source `VolumeField.sample`) into a pooled
278
- `AcousticSolution` → `instance.setAcoustic(solution)`. Bounded to ≤ budget (consumes the live set,
279
- never iterates all registered emitters).
280
-
281
- `AudioEmitterSystem` takes an **optional injected** `acousticSim` collaborator (default none → behaviour
282
- identical to today). Feature is off-by-default and testable in isolation.
283
-
284
- ---
285
-
286
- ## 5. Key decisions
287
-
288
- - **D1 — Event carries no medium; one opt-in flag.** `AudioEmitter.acoustic` (default `false`) plumbs to
289
- the instance; `EventInstance` builds the acoustic nodes only when set; `setAcoustic` is a no-op
290
- otherwise. `EventDescription` stays medium-free. (Owner-directed.)
291
- - **D2 — No `AcousticScene`; geometry is `AcousticBody` components, indexed internally.**
292
- - **D3 — `AcousticVolume` is medium-only.** No authored reverb zones. Reverb is the P6 traced/baked
293
- output; sopra's existing reverb stands until then.
294
- - **D4 — `simulation/` imports no WebAudio.** It emits `AcousticSolution`s; sopra renders. The whole
295
- simulation is testable without `MockAudioContext`.
296
- - **D5 — Occluders are fully dynamic.** No static merge. Per-body local BLAS + an O(1) `setTransform`;
297
- the ECS layer refits reactively via `Transform.onChanged` (meep is reactive end-to-end — there is no
298
- static/dynamic flag and no per-tick iteration). A body-level TLAS is the P5 scaling step, not a
299
- correctness requirement.
300
- - **D6 — Using the sim requires a built probe volume; the bake is automatic.** When acoustics is enabled
301
- for a scene, a **probe volume** must be built (it is the substrate for reverb + pathing). The bake is
302
- **fully automatic** save for a few knobs (chiefly **maximum probe density**): it places probes across
303
- the playable space, computes their data (reflection/reverb IR + inter-probe visibility for pathing),
304
- and **serializes** the result. Triggering the bake is the user's responsibility (an explicit call /
305
- editor action — it is not free), but the user authors no probes by hand. Runtime then just loads and
306
- selects/blends baked probe data. (This is why pathing/reverb are P6: they consume the probe volume.)
307
- - **D7 — No opaque config bags; explicit args.** Tunables are parameters, not config objects.
308
- - **D8 — No magic structs; classes + caller-allocated out-params.** Hot-path queries allocate nothing.
309
- - **D9 — Cosmetic-only, so no determinism gate.** Rays use `seededRandom` (reproducible tests, clean
310
- jitter), but there is no lockstep/networking requirement. If acoustics ever become gameplay-affecting
311
- (AI hearing), that is a different project with a different bar.
312
- - **D10 — Bounded, amortized, allocation-free.** Work is capped to the ≤ budget (64) live voices;
313
- `rayCount` is amortized across frames into `AcousticSourceState`; apply is throttleable; results +
314
- scratch are pooled. JS constant factors are accepted; the budget is the contract.
315
-
316
- ---
317
-
318
- ## 6. Render seam (in sopra — the only WebAudio change)
319
-
320
- `EventInstance.#buildOutputChain` (today `voices → instanceGain → attenuationGain → panner → bus`) gains,
321
- **only when the instance is acoustic**, an insert + a parallel transmission branch:
322
-
323
- ```
324
- voices → instanceGain → attenuationGain → airAbsLP → occlusionGain → occlusionLP → panner → bus
325
- └→ transmission 3-band (parallel GainNodes) ─┘
326
- ```
327
-
328
- `EventInstance.setAcoustic(solution)` ramps those `AudioParam`s (smoothing — no zipper):
329
- `occlusionGain ← 1-occlusion`, `occlusionLP.frequency ← f(occlusion)`, the three transmission band gains
330
- ← `(1-occlusion) + occlusion·transmission[band]` (**parallel crossover, not series shelves** — band gains
331
- are independent and linear), `airAbsLP.frequency ← solution.medium.airAbsorption`. All nodes default to
332
- unity / open → an acoustics-off or 2D event is byte-identical to today. (Reverb is untouched here; it is
333
- the P6 probe-driven concern.)
334
-
335
- ---
336
-
337
- ## 7. Phased plan
338
-
339
- Each phase ships behind black-box `*.spec.js` run from `H:/git/moh` root
340
- (`npx jest --config jest.conf.json engine/sound/simulation`). Real objects per house rules; the WebAudio
341
- boundary (only P3+) uses `MockAudioContext`.
342
-
343
- - **P0 — Core occluder substrate (no ECS, no WebAudio). ← building now.** `AcousticMaterial` class +
344
- adapter; `RayHit` class; `AcousticOccluderIndex` (per-body local BLAS via `ebvh_build_for_geometry_morton`,
345
- `Ray3`-based `closestHit`/`anyHit` into a caller `RayHit`, O(1) `setTransform`).
346
- *Exit:* a hand-built box room answers closest-hit + per-body material correctly; a ray through an
347
- opening misses; moving a body via `setTransform` changes the result; `anyHit` early-outs; material
348
- round-trips. Zero ECS/WebAudio.
349
- - **P1 — `OcclusionSolver` + `VolumeField` + state (still no ECS/WebAudio).** Volumetric sampling
350
- (`sphere_fibonacci_point` + `seededRandom` rotation), temporal accumulation in `AcousticSourceState`,
351
- smoothing; `VolumeField` blend with `AcousticMedium`.
352
- *Exit:* occlusion monotonic vs. wall coverage; fully walled → 1 with the wall's material in
353
- transmission; open → 0; frame-stable; deterministic for a seed; volume blend + boundary interpolation
354
- correct. Pure functions on geometry, allocation-free.
355
- - **P2 — ECS binding for occlusion.** `AcousticBody` + adapter; `AcousticSimulationSystem`
356
- (reactive link/unlink + `setTransform`; `apply` loops live 3D instances → solver; owns the per-instance
357
- state map).
358
- *Exit:* real `EntityManager` + real `AcousticBody`/`Transform`/`Collider`; add/remove/move a body
359
- changes the solution; cost bounded to ≤ budget. No WebAudio (assert on returned solutions).
360
- - **P3 — Render seam (first audible occlusion + transmission).** `EventInstance.setAcoustic` + the
361
- no-op-default node insert; `AudioEmitter.acoustic` flag; `AudioEmitterSystem` calls `acousticSim.apply`
362
- between `refresh` and `sopra.update`.
363
- *Exit (MockAudioContext):* occlusion moves the gain + lowpass; transmission bands respond to material;
364
- acoustics-off events identical to today; off-by-default.
365
- - **P4 — `AcousticVolume` (medium).** Component + adapter; `VolumeField` wired so air-absorption follows
366
- the listener's blended volume.
367
- *Exit:* blend weights + boundary interpolation correct; air absorption follows the listener's volume;
368
- no pop crossing a boundary.
369
- - **P5 — Perf hardening + occluder TLAS.** Amortization/throttle knobs; scratch-pool audit (zero
370
- per-frame alloc); a body-level BVH (TLAS) so `closestHit` is O(log bodies); `bench/` calibrating
371
- ms/tick + rays/tick at budget=64.
372
- *Exit:* measured ≤ target ms/tick; no GC churn; many-body scenes scale.
373
- - **P6 — Probe volume + premium tiers (gated).** The automatic probe-volume **bake** (D6: density-knobbed
374
- probe placement over the playable space, per-probe reflection/reverb IR + inter-probe visibility,
375
- serialized via the registry, user-triggered) → **reverb** (probe IR into a double-buffered
376
- `ConvolverNode` on sopra's reverb send bus, crossfaded as the listener crosses probe cells) and
377
- **pathing** (baked probe-visibility graph — **NOT** navmesh; navmesh connectivity ≠ acoustic
378
- connectivity: a window/cliff/ramp carries sound with no navmesh edge). Plus **FOA directional reverb
379
- field** (✓ LANDED 2026-06-20 — per-band baked arrival direction + B-format `FoaProbeReverbRenderer`; see
380
- the dated note up top) and **source directivity** (✓ LANDED 2026-06-20 — `AcousticDirectivity`
381
- monopole/dipole on `AudioEmitter`, solved into `directivityGain`; see the dated note up top).
382
-
383
- ---
384
-
385
- ## 8. Runtime cost (the contract)
386
-
387
- Bounded by the ≤ 64 live voices, **world-size-independent**. Calibration constant `c` = per-ray ebvh
388
- closest-hit (≈1 µs occlusion in JS — measure with `bench/`). Occlusion+transmission =
389
- `liveVoices × rayCount × c`: 8 rays/voice ≈ 0.5 ms, 16 ≈ 1.0 ms; temporal amortization (rayCount spread
390
- over F frames) + throttle (15–20 Hz) cut it 3–4×. Medium (air absorption) + volume blend are closed-form
391
- (< 0.05 ms). Realistic v1 (occlusion + transmission + medium, reverb still sopra's existing) ≈
392
- **0.3–1.0 ms/tick** main-thread, dominated by occlusion ray count. Render adds ~5 native nodes per
393
- acoustic voice on the **separate** audio thread. The lever is `liveVoices × rayCount`; the only spike
394
- (traced reflections, P6) is precomputed into the probe volume and never on the per-frame path.
395
-
396
- ---
397
-
398
- ## 9. Integration & blast radius
399
-
400
- | Touch | Change | Risk |
401
- |---|---|---|
402
- | `AudioEmitterSystem` | optional injected `acousticSim`; one `apply()` call between refresh & tick; read `listenerTransform.rotation` | low |
403
- | `LiveEmitterSet` | +1 accessor to enumerate live instances (bounded) | low |
404
- | `EventInstance` (sopra) | `setAcoustic` + no-op-default node insert in `#buildOutputChain` | **med** (hot chain — unity default, re-run `spatialization.spec`) |
405
- | `AudioEmitter` | `acoustic` opt-in flag (+ later `directivity`) | low |
406
- | `ReverbEffect`/`BusGraph` (sopra) | double-buffered convolver for probe reverb (**P6 only**) | med (reverb path, deferred) |
407
- | **New** | all of `sound/simulation/**` | n/a (self-contained) |
408
-
409
- **Untouched:** `SopraEngine` public API, the clip graph / containers / parameters / `VoiceManager` /
410
- voice-stealing / bus tree / snapshots / ducking, the `SpatialAudioIndex`/`LiveEmitterSet` cull+budget
411
- logic, the legacy `SoundEmitter` path, save-format back-compat. The simulation **consumes** the live set;
412
- it never alters culling, polyphony, or the mixer graph. `EventDescription` carries no medium.
413
-
414
- **Gate:** lands on the `AudioEmitter`/`AudioEmitterSystem` path, not yet in `makeMirEngineConfig`. Nothing
415
- here is observable in-game until the sopra strangler cut-over completes.
416
-
417
- ---
418
-
419
- ## ECS wiring notes (probe field + regions)
420
-
421
- **`AcousticProbeFieldSystem` — single field (v1).** The baked `AcousticProbeField` is an ECS component
422
- (`typeName` + `toJSON`/`fromJSON` + `AcousticProbeFieldSerializationAdapter`); the system links it and wires
423
- it into the simulator (`setProbeField`) for pathing + probe reverb. **Only one field is supported for now:**
424
- the FIRST linked field is wired, any others are silently ignored (no error). TODO — **multiple probe fields**:
425
- e.g. per-region / streamed fields with the active field selected by listener position, or merged into one
426
- graph. Needs a field registry + a selection rule on the simulator (today `setProbeField` holds exactly one).
427
- Until then a scene has exactly one probe field.
428
-
429
- **Region index refit.** `AcousticRegionIndex` is BVH-backed (O(log n) blend queries); regions track
430
- `Transform.position` by reference, so the simulator re-fits ALL region leaves once per frame in `apply()`.
431
- TODO — refit only the regions that actually moved (needs move tracking on `AcousticVolumeSystem`, like the
432
- occluder system's `onChanged` subscription); fine while region counts are small.