@woosh/meep-engine 3.60.4 → 3.60.6

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 (419) hide show
  1. package/CHANGELOG.md +2621 -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 +11 -0
  63. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  64. package/src/shade/renderer/Renderer.js +50 -6
  65. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts +40 -80
  66. package/src/shade/renderer/camera/orbital/OrbitalCameraController.d.ts.map +1 -1
  67. package/src/shade/renderer/camera/orbital/OrbitalCameraController.js +197 -147
  68. package/src/shade/renderer/fluid/GPUFluidCascade.d.ts +3 -3
  69. package/src/shade/renderer/fluid/GPUFluidCascadeStack.d.ts +1 -1
  70. package/src/shade/renderer/fluid/forcing/GPUFluidForcing.d.ts +6 -6
  71. package/src/shade/renderer/fluid/pressure/GPUFluidPressureSolve.d.ts +1 -1
  72. package/src/shade/renderer/fluid/query/GPUFluidQueries.d.ts +1 -1
  73. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts +28 -45
  74. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.d.ts.map +1 -1
  75. package/src/shade/renderer/geometry/sdf/GPUGeometrySDFManager.js +111 -119
  76. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts +22 -3
  77. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.d.ts.map +1 -1
  78. package/src/shade/renderer/geometry/sdf/cards/GPUMeshCardSets.js +84 -3
  79. package/src/shade/renderer/geometry/virtual/proxy/vgeo_proxy_triangle_limit.d.ts +19 -0
  80. package/src/shade/renderer/geometry/virtual/proxy/vgeo_proxy_triangle_limit.d.ts.map +1 -0
  81. package/src/shade/renderer/geometry/virtual/proxy/vgeo_proxy_triangle_limit.js +25 -0
  82. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts +31 -0
  83. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.d.ts.map +1 -0
  84. package/src/shade/renderer/geometry/virtual/proxy/vgeo_to_proxy_mesh.js +343 -0
  85. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts +15 -0
  86. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.d.ts.map +1 -1
  87. package/src/shade/renderer/geometry/virtual/runtime/VirtualGeometry.js +38 -0
  88. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts +17 -0
  89. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.d.ts.map +1 -0
  90. package/src/shade/renderer/geometry/virtual/runtime/vgeo_meshlet_metadata_write.js +29 -0
  91. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.d.ts.map +1 -1
  92. package/src/shade/renderer/geometry/virtual/runtime/vgeo_runtime_meshlet_batch.js +7 -13
  93. package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.d.ts +28 -59
  94. package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.d.ts.map +1 -1
  95. package/src/shade/renderer/geometry/virtual/sdf/VGeoSDFProxies.js +84 -789
  96. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.d.ts.map +1 -1
  97. package/src/shade/renderer/global_illumination/probe_clipmap/GPUProbeClipmapGI.js +1 -8
  98. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts +7 -6
  99. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.d.ts.map +1 -1
  100. package/src/shade/renderer/global_illumination/probe_clipmap/ProbeClipmapSceneHolds.js +12 -11
  101. package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.d.ts +2 -2
  102. package/src/shade/renderer/global_illumination/probe_clipmap/brick/mesh/chunk_mesh_brick_instance.js +2 -2
  103. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts +1 -4
  104. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.d.ts.map +1 -1
  105. package/src/shade/renderer/global_illumination/probe_clipmap/probe/GPUProbeClipmap.js +1 -10
  106. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts +5 -15
  107. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.d.ts.map +1 -1
  108. package/src/shade/renderer/global_illumination/probe_clipmap/probe/chunk_probe_clipmap_hit_surface.js +6 -34
  109. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.d.ts.map +1 -1
  110. package/src/shade/renderer/global_illumination/probe_clipmap/probe/shader_probe_clipmap_shade.js +1 -7
  111. package/src/shade/renderer/hiz/HZBFootprint.d.ts +14 -0
  112. package/src/shade/renderer/hiz/HZBFootprint.d.ts.map +1 -0
  113. package/src/shade/renderer/hiz/HZBFootprint.js +16 -0
  114. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts +10 -0
  115. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.d.ts.map +1 -0
  116. package/src/shade/renderer/hiz/HZB_FOOTPRINT_X.js +11 -0
  117. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts +10 -0
  118. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.d.ts.map +1 -0
  119. package/src/shade/renderer/hiz/HZB_FOOTPRINT_Y.js +11 -0
  120. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts +0 -7
  121. package/src/shade/renderer/hiz/HierarchicalZBuffer.d.ts.map +1 -1
  122. package/src/shade/renderer/hiz/HierarchicalZBuffer.js +405 -350
  123. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts +12 -0
  124. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.d.ts.map +1 -0
  125. package/src/shade/renderer/hiz/chunk_hzb_footprint_x.js +27 -0
  126. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts +12 -0
  127. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.d.ts.map +1 -0
  128. package/src/shade/renderer/hiz/chunk_hzb_footprint_y.js +27 -0
  129. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts +14 -0
  130. package/src/shade/renderer/hiz/hzb_footprint_texels.d.ts.map +1 -0
  131. package/src/shade/renderer/hiz/hzb_footprint_texels.js +34 -0
  132. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts +0 -5
  133. package/src/shade/renderer/hiz/shader_hzb_reduce_any.d.ts.map +1 -1
  134. package/src/shade/renderer/hiz/shader_hzb_reduce_any.js +37 -57
  135. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts +11 -0
  136. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0.d.ts.map +1 -0
  137. package/src/shade/renderer/hiz/{shader_hzb_reduce_mip0_clip.js → shader_hzb_reduce_mip0.js} +120 -111
  138. package/src/shade/renderer/loader/gltf/tiny-gltf.d.ts +1 -1
  139. package/src/shade/renderer/material/GPUMaterialManager.d.ts +11 -1
  140. package/src/shade/renderer/material/GPUMaterialManager.d.ts.map +1 -1
  141. package/src/shade/renderer/material/GPUMaterialManager.js +16 -8
  142. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts +12 -12
  143. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.d.ts.map +1 -1
  144. package/src/shade/renderer/material/resident/GPUResidentMaterialContext.js +17 -118
  145. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts +7 -3
  146. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
  147. package/src/shade/renderer/postprocess/gtao/GTAO.js +26 -13
  148. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts +14 -0
  149. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts.map +1 -1
  150. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.js +196 -68
  151. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
  152. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +42 -28
  153. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.d.ts.map +1 -1
  154. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +429 -414
  155. package/src/shade/renderer/postprocess/gtao/shader_gtao_spatial_denoise.d.ts +16 -0
  156. package/src/shade/renderer/postprocess/gtao/shader_gtao_spatial_denoise.d.ts.map +1 -0
  157. package/src/shade/renderer/postprocess/gtao/shader_gtao_spatial_denoise.js +156 -0
  158. package/src/shade/renderer/scene/GPUSceneContext.d.ts +2 -2
  159. package/src/shade/renderer/scene/GPUSceneContext.js +5 -5
  160. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.d.ts.map +1 -1
  161. package/src/shade/renderer/shadow/map/GPUSceneShadowmapContext.js +7 -2
  162. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +0 -6
  163. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  164. package/src/shade/renderer/water/GPUWaterRenderer.js +12 -17
  165. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts +35 -0
  166. package/src/shade/renderer/water/medium/graph_water_meshlets.d.ts.map +1 -0
  167. package/src/shade/renderer/water/medium/graph_water_meshlets.js +89 -0
  168. package/src/shade/renderer/water/query/GPUWaterQueries.d.ts +1 -1
  169. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  170. package/src/shade/renderer/water/shader_water.js +22 -13
  171. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -1
  172. package/src/shade/renderer/water/shader_water_avboit.js +7 -7
  173. package/src/shade/renderer/water/surface/graph_water_tessellation.d.ts +1 -1
  174. package/src/shade/renderer/water/surface/graph_water_tessellation.js +1 -1
  175. package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +9 -1
  176. package/editor/REWORK_PLAN_2026_08.md +0 -692
  177. package/src/CODEC_LAYOUT_VERIFICATION.md +0 -419
  178. package/src/CORRECTNESS_HUNT_2026_07_24.md +0 -142
  179. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +0 -738
  180. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +0 -79
  181. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +0 -93
  182. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +0 -80
  183. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +0 -164
  184. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +0 -69
  185. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +0 -110
  186. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +0 -129
  187. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +0 -72
  188. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +0 -74
  189. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +0 -169
  190. package/src/DUPLICATION_AUDIT_2026_08_07.md +0 -254
  191. package/src/DUPLICATION_AUDIT_REMEDIATION.md +0 -250
  192. package/src/DUPLICATION_REVIEW_2026_10_24.md +0 -1072
  193. package/src/REVIEW_2026_08_06/adv-core.md +0 -511
  194. package/src/REVIEW_2026_08_06/adv-gfx.md +0 -480
  195. package/src/REVIEW_2026_08_06/adv-view.md +0 -446
  196. package/src/REVIEW_2026_08_06/api-core.md +0 -296
  197. package/src/REVIEW_2026_08_06/api-engine.md +0 -195
  198. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +0 -507
  199. package/src/REVIEW_2026_08_06/core-binary.md +0 -348
  200. package/src/REVIEW_2026_08_06/core-collection.md +0 -376
  201. package/src/REVIEW_2026_08_06/core-math.md +0 -221
  202. package/src/REVIEW_2026_08_06/core-process.md +0 -266
  203. package/src/REVIEW_2026_08_06/core-science.md +0 -223
  204. package/src/REVIEW_2026_08_06/engine-ai-nav.md +0 -279
  205. package/src/REVIEW_2026_08_06/engine-animation.md +0 -167
  206. package/src/REVIEW_2026_08_06/engine-asset.md +0 -166
  207. package/src/REVIEW_2026_08_06/engine-ecs.md +0 -920
  208. package/src/REVIEW_2026_08_06/engine-input.md +0 -197
  209. package/src/REVIEW_2026_08_06/engine-misc.md +0 -248
  210. package/src/REVIEW_2026_08_06/engine-terrain.md +0 -52
  211. package/src/REVIEW_2026_08_06/generation.md +0 -176
  212. package/src/REVIEW_2026_08_06/geom-2d.md +0 -434
  213. package/src/REVIEW_2026_08_06/geom-3d.md +0 -117
  214. package/src/REVIEW_2026_08_06/gfx-gi.md +0 -143
  215. package/src/REVIEW_2026_08_06/gfx-render.md +0 -740
  216. package/src/REVIEW_2026_08_06/gfx-texture.md +0 -690
  217. package/src/REVIEW_2026_08_06/prior-criticals.md +0 -49
  218. package/src/REVIEW_2026_08_06/prior-highs.md +0 -138
  219. package/src/REVIEW_2026_08_06/test-gaps.md +0 -364
  220. package/src/REVIEW_2026_08_06/view.md +0 -667
  221. package/src/REVIEW_2026_08_06.md +0 -610
  222. package/src/core/binary/lz4/LZ4_HIGH_COMPRESSION_PLAN_2026_09_14.md +0 -542
  223. package/src/core/bvh2/bvh3/NOTES.md +0 -2
  224. package/src/core/bvh8/NOTES.md +0 -63
  225. package/src/core/cache/wtinylfu/benchmark/CacheWTinylfu.bench.md +0 -243
  226. package/src/core/color/COLOR_FIX_PLAN_2026_08_28.md +0 -417
  227. package/src/core/color/COLOR_REVIEW_2026_08_28.md +0 -779
  228. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +0 -433
  229. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +0 -486
  230. package/src/core/geom/3d/atlas/BFF_PLAN.md +0 -174
  231. package/src/core/geom/3d/atlas/REVIEW_02.md +0 -392
  232. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +0 -146
  233. package/src/core/geom/3d/gjk/NOTES.md +0 -8
  234. package/src/core/geom/3d/topology/TOPO_MESH_RETIREMENT_PLAN.md +0 -135
  235. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +0 -381
  236. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +0 -684
  237. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +0 -554
  238. package/src/core/model/reactive/evaluation/PREDICATE_APPLICABILITY_PROPOSAL.md +0 -446
  239. package/src/engine/REVIEW_2026_07_03.md +0 -211
  240. package/src/engine/asset/loaders/image/IMAGE_CODEC_REVIEW_2026_08_28.md +0 -815
  241. package/src/engine/control/first-person/DESIGN.md +0 -658
  242. package/src/engine/control/first-person/DESIGN_COLLISION.md +0 -382
  243. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +0 -563
  244. package/src/engine/control/first-person/TODO.md +0 -160
  245. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +0 -531
  246. package/src/engine/ecs/HIERARCHY_REVIEW.md +0 -717
  247. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +0 -262
  248. package/src/engine/ecs/ik/PLAN.md +0 -179
  249. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +0 -272
  250. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +0 -199
  251. package/src/engine/ecs/transform/TRANSFORM64_MIGRATION_PLAN_2026_09_04.md +0 -521
  252. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +0 -463
  253. package/src/engine/graphics/ecs/camera/pp/NOTES.md +0 -48
  254. package/src/engine/graphics/render/frame_graph/CORRECTNESS_FIXES_2026_09_04.md +0 -659
  255. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +0 -235
  256. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +0 -490
  257. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +0 -466
  258. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +0 -717
  259. package/src/engine/input/ecs/ism/ISM_DESIGN.md +0 -1523
  260. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +0 -627
  261. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +0 -661
  262. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +0 -229
  263. package/src/engine/network/CONGESTION_CONTROL.md +0 -87
  264. package/src/engine/network/MESSAGE_TRANSPORT.md +0 -364
  265. package/src/engine/network/SMOOTHNESS_DESIGN.md +0 -400
  266. package/src/engine/physics/cloth/MEASUREMENTS.md +0 -5304
  267. package/src/engine/physics/cloth/PLAN.md +0 -3056
  268. package/src/engine/physics/fluid/GPU_FLUID_CASCADES_PROPOSAL_2026_10_16.md +0 -1382
  269. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +0 -243
  270. package/src/engine/physics/fluid/solver/NOTES.md +0 -46
  271. package/src/engine/sound/SOUND_EMITTER_RETIREMENT_PLAN.md +0 -536
  272. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +0 -432
  273. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +0 -250
  274. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +0 -322
  275. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +0 -261
  276. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +0 -121
  277. package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +0 -999
  278. package/src/format/image/avif/DECISIONS.md +0 -545
  279. package/src/format/image/jpeg/NOTES.md +0 -3
  280. package/src/format/scene/gltf/MESHOPT_COMPRESSION_PLAN.md +0 -814
  281. package/src/generation/COORDINATES.md +0 -54
  282. package/src/generation/REVIEW_01_ACTION_PLAN.md +0 -628
  283. package/src/generation/foliage/ANNUAL_SHOOTS_BRIEF_2026_10_25.md +0 -220
  284. package/src/generation/foliage/CROWN_FILLING_BRIEF_2026_10_26.md +0 -172
  285. package/src/generation/foliage/CROWN_FULLNESS_BRIEF_2026_10_26.md +0 -86
  286. package/src/generation/foliage/FOLIAGE_DESIGN.md +0 -547
  287. package/src/generation/foliage/FOLIAGE_PLAN.md +0 -198
  288. package/src/generation/foliage/LEAF_ARCHITECTURE_BRIEF_2026_10_26.md +0 -158
  289. package/src/generation/foliage/LOD_REWORK_BRIEF_2026_10_27.md +0 -521
  290. package/src/generation/foliage/REAL_TIME_TREES_BRIEF_2026_10_27.md +0 -86
  291. package/src/generation/foliage/SELF_ORGANIZING_FRAMEWORK_BRIEF_2026_10_26.md +0 -347
  292. package/src/generation/foliage/SHOOT_SYSTEMS_ALL_SPECIES_BRIEF_2026_10_26.md +0 -248
  293. package/src/generation/foliage/SPEEDTREE_COMPARISON_2026_10_24.md +0 -694
  294. package/src/generation/foliage/TWO_FRONT_ENDS_BRIEF_2026_10_28.md +0 -552
  295. package/src/shade/RENDERER_CONTRACT.md +0 -697
  296. package/src/shade/SHADER_F16_PROPOSAL_2026_10_24.md +0 -263
  297. package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +0 -569
  298. package/src/shade/device/PASS_ENCODER_PROPOSAL_2026_08_31.md +0 -755
  299. package/src/shade/device/mock/EXTENSION_PROPOSAL_2026_08_22.md +0 -458
  300. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +0 -255
  301. package/src/shade/device/mock/REVIEW_2026_08_22.md +0 -527
  302. package/src/shade/device/timing/GPU_PROFILER_PROPOSAL_2026_08_28.md +0 -894
  303. package/src/shade/device/timing/profile/SGPT_FORMAT.md +0 -345
  304. package/src/shade/renderer/NOTES.md +0 -78
  305. package/src/shade/renderer/PLAN.md +0 -8
  306. package/src/shade/renderer/animation/ADDITIVE_BLENDING_PLAN.md +0 -145
  307. package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +0 -357
  308. package/src/shade/renderer/atmosphere/NOTES.md +0 -6
  309. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts +0 -76
  310. package/src/shade/renderer/camera/orbital/MomentumAxis.d.ts.map +0 -1
  311. package/src/shade/renderer/camera/orbital/MomentumAxis.js +0 -164
  312. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts +0 -45
  313. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.d.ts.map +0 -1
  314. package/src/shade/renderer/camera/orbital/OrbitalCameraConfig.js +0 -66
  315. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts +0 -98
  316. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.d.ts.map +0 -1
  317. package/src/shade/renderer/camera/orbital/OrbitalCameraInput.js +0 -184
  318. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts +0 -175
  319. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.d.ts.map +0 -1
  320. package/src/shade/renderer/camera/orbital/OrbitalCameraRig.js +0 -411
  321. package/src/shade/renderer/camera/orbital/README.md +0 -81
  322. package/src/shade/renderer/camera/orbital/index.d.ts +0 -6
  323. package/src/shade/renderer/camera/orbital/index.d.ts.map +0 -1
  324. package/src/shade/renderer/camera/orbital/index.js +0 -9
  325. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +0 -964
  326. package/src/shade/renderer/fluid/GPU_FLUID_OCCUPANCY_AND_SOTA_REVIEW_2026_10_26.md +0 -361
  327. package/src/shade/renderer/geometry/bvh/BLAS_REFIT_BATCH_PLAN.md +0 -619
  328. package/src/shade/renderer/geometry/meshlet/COMPACTION_PLAN_2026_09_16.md +0 -674
  329. package/src/shade/renderer/geometry/sdf/NOTES.md +0 -6
  330. package/src/shade/renderer/geometry/virtual/ATTRIBUTE_PLAN_2026_09_18.md +0 -540
  331. package/src/shade/renderer/geometry/virtual/MICRON_COMPARISON_2026_08_20.md +0 -305
  332. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +0 -913
  333. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_DESIGN.md +0 -787
  334. package/src/shade/renderer/geometry/virtual/VIRTUAL_GEOMETRY_PLAN.md +0 -481
  335. package/src/shade/renderer/geometry/virtual/VK_LOD_CLUSTERS_COMPARISON_2026_08_20.md +0 -301
  336. package/src/shade/renderer/geometry/virtual/WEDGE_PLAN_2026_09_18.md +0 -448
  337. package/src/shade/renderer/geometry/virtual/runtime/CULL_PLAN_2026_09_16.md +0 -385
  338. package/src/shade/renderer/geometry/virtual/runtime/CUT_PLAN_2026_09_14.md +0 -656
  339. package/src/shade/renderer/geometry/virtual/runtime/REVIEW_LEDGER.md +0 -308
  340. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN.md +0 -657
  341. package/src/shade/renderer/geometry/virtual/runtime/RUNTIME_PLAN_2026_09_14.md +0 -551
  342. package/src/shade/renderer/geometry/virtual/runtime/SHADOW_PLAN_2026_09_28.md +0 -529
  343. package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.d.ts +0 -47
  344. package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.d.ts.map +0 -1
  345. package/src/shade/renderer/geometry/virtual/sdf/VGEO_SDF.js +0 -80
  346. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.d.ts +0 -16
  347. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.d.ts.map +0 -1
  348. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_collect.js +0 -67
  349. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.d.ts +0 -17
  350. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.d.ts.map +0 -1
  351. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_decode.js +0 -145
  352. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.d.ts +0 -12
  353. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.d.ts.map +0 -1
  354. package/src/shade/renderer/geometry/virtual/sdf/shader_vgeo_sdf_traverse.js +0 -105
  355. package/src/shade/renderer/global_illumination/lpv/NOTES.md +0 -3
  356. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_CLIPMAP_PROPOSAL_2026_10_27.md +0 -4306
  357. package/src/shade/renderer/global_illumination/probe_clipmap/PROBE_RAYS_VARIABLE_AND_SORTED_SCOPE_2026_10_30.md +0 -410
  358. package/src/shade/renderer/global_illumination/sharc/NOTES.md +0 -8
  359. package/src/shade/renderer/gpu_primitive/bvh/GPU_BVH_BUILD_PLAN.md +0 -1369
  360. package/src/shade/renderer/gpu_primitive/bvh/PHASE1_HANDOVER.md +0 -202
  361. package/src/shade/renderer/gpu_primitive/indirect/NOTES.md +0 -94
  362. package/src/shade/renderer/hiz/NOTES.md +0 -3
  363. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts +0 -8
  364. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.d.ts.map +0 -1
  365. package/src/shade/renderer/light/cluster/NOTES.md +0 -11
  366. package/src/shade/renderer/light/cluster/PROPOSAL_ALGORITHM_2026_10_16.md +0 -524
  367. package/src/shade/renderer/light/cluster/PROPOSAL_HIERARCHICAL_2026_10_16.md +0 -1101
  368. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts +0 -24
  369. package/src/shade/renderer/material/resident/shader_resident_texture_means.d.ts.map +0 -1
  370. package/src/shade/renderer/material/resident/shader_resident_texture_means.js +0 -120
  371. package/src/shade/renderer/particles/DESIGN.md +0 -1011
  372. package/src/shade/renderer/particles/EMITTER_GLOBAL_STATE_PLAN_2026_10_29.md +0 -280
  373. package/src/shade/renderer/particles/ORIENTATION_PLAN_2026_10_15.md +0 -453
  374. package/src/shade/renderer/path_tracer/PATH_TRACED_MEDIA_PLAN_2026_10_02.md +0 -520
  375. package/src/shade/renderer/postprocess/bloom/NOTES.md +0 -6
  376. package/src/shade/renderer/postprocess/fsr/NOTES.md +0 -4
  377. package/src/shade/renderer/postprocess/gtao/NOTES.md +0 -6
  378. package/src/shade/renderer/postprocess/nss/NOTES.md +0 -332
  379. package/src/shade/renderer/postprocess/perspective/NOTES.md +0 -3
  380. package/src/shade/renderer/postprocess/sscs/NOTES.md +0 -5
  381. package/src/shade/renderer/postprocess/ssr/NOTES.md +0 -14
  382. package/src/shade/renderer/postprocess/ssr/REPROJECTION_PROPOSAL.md +0 -286
  383. package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -39
  384. package/src/shade/renderer/postprocess/taa/NOTES.md +0 -11
  385. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +0 -78
  386. package/src/shade/renderer/postprocess/vrs/NOTES.md +0 -4
  387. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_LEDGER.md +0 -1218
  388. package/src/shade/renderer/rasterize/native/avboit/AVBOIT_PLAN_2026_09_03.md +0 -600
  389. package/src/shade/renderer/restir/reservoir/NOTES.md +0 -6
  390. package/src/shade/renderer/shader/chunk/atmosphere/NOTES.md +0 -6
  391. package/src/shade/renderer/shader/chunk/brdf/FRESNEL_BRIEF_2026_10_16.md +0 -391
  392. package/src/shade/renderer/shader/chunk/color/tonemap/ACES_TONE_MAPPING_BRIEF_2026_10_17.md +0 -153
  393. package/src/shade/renderer/shader/chunk/color/tonemap/HDR_TONE_MAPPING_FORMS_BRIEF_2026_10_16.md +0 -322
  394. package/src/shade/renderer/shader/chunk/color/tonemap/SDR_TONE_MAPPING_BRIEF_2026_10_16.md +0 -247
  395. package/src/shade/renderer/shader/chunk/fsr/NOTES.md +0 -3
  396. package/src/shade/renderer/shader/chunk/fsr/spd/NOTES.md +0 -4
  397. package/src/shade/renderer/shader/chunk/geometry/ray/NOTES.md +0 -4
  398. package/src/shade/renderer/shader/chunk/material/MULTISCATTER.md +0 -159
  399. package/src/shade/renderer/shader/chunk/mboit/NOTES.md +0 -38
  400. package/src/shade/renderer/shader/chunk/spherical_harmonics/l1i/NOTES.md +0 -3
  401. package/src/shade/renderer/shader/chunk/spherical_harmonics/sampling/NOTES.md +0 -8
  402. package/src/shade/renderer/shader/compiler/SHADER_SWITCH_PROPOSAL_2026_10_16.md +0 -927
  403. package/src/shade/renderer/shadow/contact/FINDINGS.md +0 -413
  404. package/src/shade/renderer/shadow/map/csm/NOTES.md +0 -5
  405. package/src/shade/renderer/shadow/ray/NOTES.md +0 -31
  406. package/src/shade/renderer/shadow/sdf/NOTES.md +0 -9
  407. package/src/shade/renderer/shadow/sdf/brixelizer/NOTES.md +0 -5
  408. package/src/shade/renderer/shadow/sdf/cascade/NOTES.md +0 -9
  409. package/src/shade/renderer/shadow/vsm/NOTES.md +0 -12
  410. package/src/shade/renderer/shadow/vsm/PLAN.md +0 -442
  411. package/src/shade/renderer/shadow/vsm/REVIEW_LEDGER.md +0 -43
  412. package/src/shade/renderer/texture/COMPRESSED_TEXTURE_PLAN_2026_09_01.md +0 -2444
  413. package/src/shade/renderer/texture/mipmaps/NOTES.md +0 -3
  414. package/src/shade/renderer/volumetrics/NOTES.md +0 -1329
  415. package/src/shade/renderer/volumetrics/VOLUMETRIC_NOISE_PLAN_2026_10_15.md +0 -336
  416. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts +0 -23
  417. package/src/shade/renderer/water/medium/GPUWaterBodies.d.ts.map +0 -1
  418. package/src/shade/renderer/water/medium/GPUWaterBodies.js +0 -75
  419. package/src/shade/wgsl/NOTES.md +0 -4
@@ -1,531 +0,0 @@
1
- # Asynchronous Systems — Design Proposal
2
-
3
- **Status:** proposal, 2026-08-14
4
- **Scope:** ECS `System`s whose work executes outside the main-thread tick — Web Workers and WebGPU — while keeping the authoring interface as small as possible.
5
-
6
- > **What was actually built.** The design space was narrowed after this document
7
- > to *shared memory* (`SharedArrayBuffer` + `Atomics`) rather than `postMessage`,
8
- > and that variant is implemented and shipping — see
9
- > [`async/WORKER_SYSTEM_PLAN.md`](async/WORKER_SYSTEM_PLAN.md), `async/WorkerSystem.js`,
10
- > and its first consumer `physics/fluid/ecs/WorkerFluidSystem.js`.
11
- >
12
- > Three things changed versus the sketch below, all for the better:
13
- >
14
- > - **`AsyncPump` collapsed into four shared integers.** With shared memory the
15
- > pump's whole bookkeeping — in-flight, capacity, completion — is the control
16
- > header, so there is no separate object to own it.
17
- > - **The gate became real, and load-bearing.** §7 deferred the fixed-latency
18
- > contract as future work. Shared memory makes "is step T done?" a synchronous
19
- > question, so `EntityManager` gained a fixed-step gate and the deterministic
20
- > mode is the *only* mode rather than an upgrade path.
21
- > - **Capacity is fixed at one, not configurable.** Under in-place mutation the
22
- > main thread has nowhere to build a second step's input, so a deeper pipeline
23
- > is a different design rather than a knob.
24
- >
25
- > The invariant that motivated the whole document is unchanged and is what the
26
- > implementation enforces: **async transports, synchronous integration.**
27
- >
28
- > Everything below remains the reference for the parts not yet built — the
29
- > `postMessage` transport (`WorkerChannel`/`WorkerHost`) and the WebGPU tier.
30
-
31
- ---
32
-
33
- ## 1. Summary
34
-
35
- meep's `System` contract is already asynchronous where it needs to be for *lifecycle*: `startup`/`shutdown` return Promises, the `EntityManager` gates `RUNNING` on them, and entity linking only begins after startup resolves. What is missing is a story for *per-tick* asynchrony — systems whose computation is a round trip (postMessage → worker → response; submit → GPU → `mapAsync`) and therefore cannot complete inside the synchronous `simulate()` pass.
36
-
37
- The proposal is deliberately **not** to make the tick asynchronous. `EntityManager.simulate` stays exactly as it is. Instead:
38
-
39
- 1. **One invariant**: async continuations never touch the `EntityComponentDataset`. They only enqueue results. All dataset reads and writes happen inside the system's normal synchronous slot in the tick, in system order.
40
- 2. **One primitive**: `AsyncPump` — bookkeeping for in-flight round trips. Each tick it *drains* completed results (applying them), then *launches* new work if capacity allows. It owns tick-stamping, dataset-epoch invalidation, entity-generation staleness, backpressure, and shutdown settling.
41
- 3. **One convenience base**: `AsyncSystem extends System` — wires a pump to `update()` and exposes a three-method contract: `collect()` → `process(request)` (async, off-tick) → `apply(result, request)` (sync, on-tick). Systems that already extend another base (`AbstractContextSystem` etc.) use the pump directly; the base class is ten lines of sugar.
42
- 4. **Two transports**, both just "things `process()` can await":
43
- - `WorkerChannel`/`WorkerHost` — a modernized worker RPC (module workers, transferables in both directions, ready handshake, injectable port for tests), superseding the codegen `WorkerBuilder` path for new code.
44
- - WebGPU — a `WebGPUDeviceSystem` that owns adapter/device, plus a `GpuReadbackRing` for the only genuinely async part (readback). GPU-resident compute that feeds rendering needs **no new machinery at all** — async `startup` for pipeline creation plus synchronous `queue.submit` in `update` already works today.
45
-
46
- **Zero changes to `System`, `EntityManager`, or `EntityComponentDataset` are required.** Everything is additive library code under `engine/ecs/async/` (plus `core/process/worker/` for the channel). The execution-order scheduler keeps working unchanged, because an async system's dataset writes still happen inside its own `update` slot — `components_used` declarations remain truthful.
47
-
48
- ---
49
-
50
- ## 2. What exists today
51
-
52
- Survey of the current codebase (paths relative to `meep/src/`).
53
-
54
- ### 2.1 The System contract is already async where it matters
55
-
56
- `engine/ecs/System.js` — `async startup(entityManager)` / `async shutdown(entityManager)`. `engine/ecs/EntityManager.js` (`startSystem`, ~line 682) asserts the returned thenable, arms a 5 s deadlock watchdog, and only sets `RUNNING` / attaches the `EntityObserver` / calls `handleDatasetAttached` after the promise resolves. The callback form was already deprecated in meep 2.128.0 in favor of `async`.
57
-
58
- This means **spawning a worker, handshaking, requesting a GPU device, and compiling pipelines already have a correct home**: `startup`. Nothing new is needed for lifecycle.
59
-
60
- ### 2.2 The tick is synchronous — and must stay so
61
-
62
- `EntityManager.simulate` (~line 423): a lock-step fixed-update loop over all systems on a single shared accumulator (with `fixedStepTick`, the monotonic simulation tick id, incremented inside the loop), followed by the variable-rate `update` pass. Per-system try/catch, wall-clock runaway guard. Simulation and rendering run on **separate loops** (`engine/simulation/Ticker.js` drives `simulate`; `Engine.#animation_frame` drives `render`).
63
-
64
- Two consequences worth stating explicitly, because the design leans on them:
65
-
66
- - Because `simulate()` is one synchronous call, **promise continuations can never run between two systems within a pass**, nor between fixed steps. The JS microtask queue only drains when the stack empties.
67
- - Continuations **can** run between `simulate` and `render`, and between frames. Today, any code that applies worker results inside a `.then(...)` mutates components at an uncontrolled point relative to both loops.
68
-
69
- ### 2.3 Worker infrastructure exists, one generation old
70
-
71
- `core/process/worker/`:
72
-
73
- - `WorkerProxy` — RPC client over `{methodName, id, parameters}` / `{methodName, id, result|error}`. Queues requests while stopped, rejects everything on worker error (`FAILED` is terminal). `cancelRequest` throws "not implemented". **Transferables are only used on the response path** — requests always structured-clone.
74
- - `WorkerBuilder` — builds the worker by **string-concatenating code** into a Blob URL (`codeToURL`), with an inlined duplicate of `extractTransferables`. Generated workers use `importScripts` (classic worker semantics) to load rollup bundles that are emitted as `format: 'esm'` — this works only because the entry scripts happen to export nothing.
75
- - `OnDemandWorkerManager` — idle-termination wrapper (start on demand, stop after a grace period).
76
-
77
- Consumers: PNG decode (`ThreadedImageDecoder`), terrain tile builds (`makeTerrainWorkerProxy`, one worker **per `Terrain` component**), and the deprecated `Metis` binding. A fourth worker entry (`sh3/lpv/worker/lpv_build_worker.js`) is dead/unfinished.
78
-
79
- `SharedArrayBuffer` has a seam (`core/binary/makeArrayBuffer.js`, gated on `crossOriginIsolated`) but is never actually used with a worker. No `Atomics`, no `OffscreenCanvas`.
80
-
81
- ### 2.4 The scheduler and its Promise bridge
82
-
83
- `core/process/executor/ConcurrentExecutor.js` + `core/process/task/Task.js` — cooperative, single-threaded, time-sliced (`setTimeout`-driven, not frame-synced; `Engine.executor = new ConcurrentExecutor(1, 10)`). This is meep's answer to *main-thread* long-running work, and it is the dominant pattern: the lightmap baker, LPG builder, `ThemeEngine`, and `ShadedGeometrySystem`'s standing optimization/maintenance tasks all run as `Task`/`TaskGroup` cycles.
84
-
85
- `promiseTask` (`core/process/task/util/promiseTask.js`) is the existing Promise→Task bridge — it polls with `TaskSignal.Yield` and discards the resolved value. It bridges *completion*, not *results*.
86
-
87
- ### 2.5 Result-delivery precedents — the design in embryo
88
-
89
- Two places already solve pieces of this problem, differently:
90
-
91
- - **`AssetManager`** (`engine/asset/AssetManager.js`): async results are never delivered from whatever stack settled them. They go into `#response_queue`, drained by a `#response_processor` Task on the executor, with a microtask-deferred wake. This is precisely the "continuations only enqueue" invariant — applied to asset loads.
92
- - **`TerrainTileManager.build`** (~line 748): the worker round trip resolves into a `.then` that hand-rolls three staleness guards — tile identity (`this.tiles[tile_index] !== tile`), a monotonic `tile.version`, and an `isBuildInProgress` flag — then mutates main-thread objects **directly in the promise callback**. It works, but every guard is bespoke, and delivery timing is uncontrolled (§2.2).
93
-
94
- The proposal is essentially: take what `AssetManager` and `TerrainTileManager` each got right, and make it the standard, so the next bridged system doesn't reinvent either half.
95
-
96
- ### 2.6 WebGPU: greenfield
97
-
98
- There is **no WebGPU code** in `meep/src/` — no `navigator.gpu`, no WGSL, nothing. (The build already has a `.wgsl` raw-loader rule in the app webpack config, so shader imports are ready.) All existing GPU→CPU readback is synchronous WebGL (`readPixels`, `getBufferSubData`) inside utilities, not systems. WebGPU support is a clean-slate design, constrained only by three.js interop at the rendering seam.
99
-
100
- ---
101
-
102
- ## 3. Problem statement
103
-
104
- Bridging a System to a Worker or a WebGPU queue means every tick-relevant computation becomes a **round trip with ≥ 1 frame of latency**. Concretely, an author today has to solve, per system, with no engine help:
105
-
106
- 1. **When do results land?** Ad hoc `.then(apply)` mutates the dataset between `simulate` and `render`, or mid-frame relative to other systems' expectations. Nothing guarantees writes happen at the system's slot in execution order, so `components_used`-based ordering silently stops describing reality.
107
- 2. **Is the result still valid?** Entities die, datasets get detached/attached (dataset switching is a supported scenario), systems get removed. The dataset has an entity **generation table** (`EntityComponentDataset.getEntityGeneration`) — ideal for this — but nothing uses it for async staleness; terrain invented three parallel guards instead.
108
- 3. **Backpressure.** Nothing stops a system from launching a new request every tick while the previous one is still in flight. A slow worker turns into an unbounded queue (the network review found this exact failure mode in the pending-action log).
109
- 4. **Cancellation/shutdown.** `WorkerProxy.cancelRequest` is unimplemented; nothing drains in-flight work at system shutdown; `Engine.stop()` has a standing `// TODO shutdown executor`.
110
- 5. **Transport boilerplate.** Codegen workers, transferables on one path only, per-component worker instances, no ready handshake, no typed wrapper convention.
111
-
112
- And for WebGPU specifically: no device ownership story, no readback pattern, no device-loss handling.
113
-
114
- ---
115
-
116
- ## 4. Design principles
117
-
118
- 1. **The tick stays synchronous.** Determinism, ordering, and the mental model of `simulate` are load-bearing (physics determinism gate, network lockstep design). Async work is *pipelined around* the tick, never awaited inside it.
119
- 2. **Continuations never touch the dataset.** The single rule that eliminates the whole "results land at random times" bug class. Continuations enqueue; ticks apply.
120
- 3. **Composition first.** Systems already extend intermediate bases (`AbstractContextSystem`, `HighlightSystem`). The primitive must be an object a system *owns*, with the base class as optional sugar.
121
- 4. **Zero core changes.** `System`/`EntityManager`/`EntityComponentDataset` are untouched in v1. Everything is additive library code. (This also keeps the door open for a job-graph scheduler later — §11.)
122
- 5. **Uniform control flow.** One delivery order (launch order), one staleness mechanism (epoch + generation), one failure path (reject → discard + loud log). No auto-retry, no auto-restart, no "null means auto" modes.
123
- 6. **Transports are just Promises.** The pump doesn't know about workers or GPUs. `process()` returns a Promise; where the work happens is the subclass's business. This is what makes Worker and WebGPU the *same* feature.
124
- 7. **Fail loudly.** Rejections and dead workers surface with system names via `console.error`, matching `simulate`'s per-system error style. No silent catch.
125
-
126
- ---
127
-
128
- ## 5. Proposal
129
-
130
- New package: `engine/ecs/async/`. Transport: `core/process/worker/v2/` (or sibling files; naming below is provisional).
131
-
132
- ### 5.1 `AsyncPump` — the tick-integration primitive
133
-
134
- Owns the in-flight set and the settled-but-unapplied queue. All bookkeeping, no policy about *what* the work is.
135
-
136
- ```js
137
- export class AsyncPump {
138
-
139
- /** max round trips in flight; default 1 = coalescing pipeline */
140
- capacity = 1;
141
-
142
- #inflight = []; // [{request, context, epoch, settled, result, error}] in launch order
143
- #epoch = 0;
144
-
145
- /**
146
- * Launch a round trip. `promise` must not touch the dataset in its
147
- * continuations — the pump only records settlement.
148
- * @param {Promise} promise
149
- * @param {*} context opaque; returned to apply/discard (tick stamp, entity handles, ...)
150
- */
151
- launch(promise, context) { /* record; .then only sets settled/result flags */ }
152
-
153
- /** @returns {boolean} capacity available for another launch */
154
- get is_ready() { }
155
-
156
- /**
157
- * Deliver settled results IN LAUNCH ORDER to `apply(result, context)`.
158
- * A settled head unblocks delivery; an unsettled head holds younger settled
159
- * entries back (single-worker FIFO and mapAsync both settle in launch order
160
- * anyway, so head-of-line blocking is theoretical for one transport).
161
- * Entries from an older epoch, and rejected entries, go to
162
- * `discard(context, error)` instead. Called from the system's update slot.
163
- */
164
- drain(apply, discard) { }
165
-
166
- /** Invalidate everything currently in flight (dataset detached, reset). */
167
- invalidate() { this.#epoch++; }
168
-
169
- /**
170
- * Stop accepting launches and resolve when all in-flight work settles
171
- * (results are discarded — the dataset is already detached by the time
172
- * shutdown runs, see §5.7). For use in System.shutdown.
173
- * @returns {Promise<void>}
174
- */
175
- settle() { }
176
- }
177
- ```
178
-
179
- Notes:
180
-
181
- - **Launch-order delivery** is a deliberate choice over completion-order: it makes multi-request pipelines reason like FIFO queues, and it costs nothing for the two real transports (a single worker responds in request order; `mapAsync` resolves in submit order). One rule, no configuration.
182
- - `capacity = 1` is the default and the most important policy: while a request is in flight, no new one launches; when it completes, the *next* launch snapshots *fresh* state. That is natural coalescing — the pattern `TerrainTileManager` approximates with `isBuildInProgress`, and the right default for "recompute X from current world state" workloads (path fields, GI, LODs).
183
- - The pump is deliberately dumb: no request queue (pending work is regenerated from world state by the next `collect`), no retry, no timers.
184
-
185
- ### 5.2 `AsyncSystem` — the convenience base
186
-
187
- ```js
188
- export class AsyncSystem extends System {
189
-
190
- /** @readonly */
191
- pump = new AsyncPump();
192
-
193
- /**
194
- * Build a request from current world state. Called only when the pump has
195
- * capacity. Return undefined to launch nothing this tick.
196
- * Runs on-tick: full dataset access.
197
- * @returns {*} request
198
- */
199
- collect() { }
200
-
201
- /**
202
- * Perform the round trip. Runs OFF-tick: must not touch the dataset or
203
- * entityManager. Typically: channel.request(...) or submit+mapAsync.
204
- * @param {*} request
205
- * @returns {Promise<*>} result
206
- */
207
- async process(request) { }
208
-
209
- /**
210
- * Integrate a completed result. Runs on-tick, inside this system's update
211
- * slot, in execution order. Full dataset access. Validate entity handles
212
- * here (§5.3).
213
- * @param {*} result
214
- * @param {*} request
215
- */
216
- apply(result, request) { }
217
-
218
- /**
219
- * A request whose result will never be applied: rejected, or invalidated
220
- * by dataset detach. Default: log rejections, drop invalidations.
221
- * @param {*} request
222
- * @param {Error} [error]
223
- */
224
- discard(request, error) { }
225
-
226
- update(dt) {
227
- this.pump.drain(
228
- (result, req) => this.apply(result, req),
229
- (req, err) => this.discard(req, err),
230
- );
231
-
232
- if (this.pump.is_ready) {
233
- const request = this.collect();
234
- if (request !== undefined) {
235
- this.pump.launch(this.process(request), request);
236
- }
237
- }
238
- }
239
-
240
- handleDatasetDetached(dataset) {
241
- this.pump.invalidate();
242
- }
243
- }
244
- ```
245
-
246
- The authoring surface for a bridged system is **three methods** (`collect`/`process`/`apply`), all of which are independently testable — `process` doesn't need an ECS at all.
247
-
248
- Systems that can't extend `AsyncSystem` (e.g. `AbstractContextSystem` descendants) own an `AsyncPump` directly and put the same six lines in their `update`. Systems with several independent streams own several pumps. Systems that want fixed-cadence launching call the drain/launch pair from `fixedUpdate` instead — the pump doesn't care.
249
-
250
- Drain-then-launch order matters: results integrate first, so the subsequent `collect` sees post-apply state, and downstream systems in this tick's pass see the newly written components.
251
-
252
- ### 5.3 Staleness: epochs and entity handles
253
-
254
- Two mechanisms, two failure modes:
255
-
256
- - **Dataset epoch** (pump-level, automatic): `handleDatasetDetached` bumps the epoch; anything launched under an older epoch drains to `discard`. Covers dataset switching, `clear()`-style resets (systems can call `invalidate()` manually), and shutdown.
257
- - **Entity generation** (request-level, author's job, one helper): requests that reference entities stamp `(entity_id, generation)` pairs at `collect` time using the dataset's existing generation table. `apply` revalidates:
258
-
259
- ```js
260
- // engine/ecs/async/entity_handle_is_live.js
261
- export function entity_handle_is_live(ecd, entity, generation) {
262
- return ecd.entityExists(entity) && ecd.getEntityGeneration(entity) === generation;
263
- }
264
- ```
265
-
266
- This replaces terrain's three bespoke guards with the mechanism the dataset already maintains. Per-request domain versions (like `tile.version`) remain domain logic when finer granularity is needed — the pump's `context` carries them.
267
-
268
- Tick stamping: the pump records `entityManager.fixedStepTick` at launch time into the context (the base class does this), so `apply` always knows the age of what it is integrating — needed for interpolation-aware consumers and for the deterministic mode later (§7).
269
-
270
- ### 5.4 `WorkerChannel` / `WorkerHost` — transport v2
271
-
272
- A small, modern replacement for the `WorkerBuilder` codegen path. Real worker *files* instead of string concatenation — bundlers understand `new Worker(new URL(...), { type: 'module' })` statically (webpack 5 in the app today; the library's prebuilt-bundle path keeps working because the channel takes a factory).
273
-
274
- ```js
275
- // ---- main thread ----
276
- const channel = await WorkerChannel.start(
277
- () => new Worker(new URL('./path_field.worker.js', import.meta.url), { type: 'module' })
278
- );
279
-
280
- const reply = await channel.request('find_path', payload, [payload.buffer]); // transfers OUT
281
- channel.notify('set_costs', costs, [costs.buffer]); // one-way
282
- channel.on('progress', p => this.progress.set(p)); // worker-initiated
283
- await channel.stop(); // settle in-flight, then terminate
284
-
285
- // ---- path_field.worker.js ----
286
- import { WorkerHost } from '.../worker/v2/WorkerHost.js';
287
- import { find_path } from '../nav/find_path.js'; // plain engine imports — it's a module worker
288
-
289
- WorkerHost.serve({
290
- async find_path(payload) {
291
- const data = find_path(payload);
292
- return WorkerHost.result(data, [data.path.buffer]); // transfers BACK
293
- },
294
- set_costs(costs) { /* update local state */ },
295
- });
296
- ```
297
-
298
- Contract:
299
-
300
- - **Wire format** stays `{ id, name, payload }` / `{ id, result | error: {message, stack} }` — same spirit as `WorkerProxy`, plus `{ type: 'ready' }` handshake so `start()` resolves only when the worker booted (and boot errors reject startup instead of hanging until the 5 s watchdog).
301
- - **Transferables both directions, always explicit.** Fixes the current request-path gap. `extractTransferables` remains available as an opt-in convenience for callers who want the recursive walk, but the default is explicit lists — moving a buffer the caller still needs is too easy to do by accident.
302
- - **Transport-injectable**: the channel core operates on anything with `postMessage`/`message` events. `WorkerChannel.start(factory)` wraps a real `Worker`; tests construct it over a `MessageChannel` port pair (a real platform object, available in Node/Jest) with `WorkerHost.serve` on the other port — no mocks.
303
- - **Failure**: worker `error`/`messageerror` rejects all in-flight and all future requests and fires `channel.on_dead`. No auto-restart — the owning system decides (usually: log loudly; recovery is `removeSystem`/`addSystem`, which the `EntityManager` already supports at runtime).
304
- - **Typed wrappers over string calls**: systems wrap `channel.request('find_path', ...)` in real methods (as `makeTerrainWorkerProxy` already does), keeping string names confined to the transport seam.
305
-
306
- The old `WorkerProxy`/`WorkerBuilder`/`OnDemandWorkerManager` stack stays — meep is a library, its consumers keep working. New code targets the channel; migrating image-decode/terrain is optional follow-up (§10). `OnDemandWorkerManager` remains the right tool for *shared utility* workers (decode pools); an `AsyncSystem`'s channel is instead owned 1:1 by the system, started in `startup`, stopped in `shutdown` — one worker per system, not per component (terrain's per-`Terrain` worker is a consolidation candidate when it migrates).
307
-
308
- ### 5.5 Payloads: three tiers
309
-
310
- The pump/channel don't care what a payload is; pick per system:
311
-
312
- - **Tier 0 — structured clone.** POJOs / `toJSON` output. Right for prototyping and small requests (a path query). This is what everything uses today.
313
- - **Tier 1 — binary + transfer.** Serialize into an `ArrayBuffer` (`core/binary/BinaryBuffer`), transfer it (zero-copy move), deserialize on the other side. For component-shaped data, the object-v2 serializer (`engine/ecs/storage/binary/object-v2/`) and the network package's per-component adapters (`engine/network/adapters/`) already define exactly this discipline — a worker mirror is "replication to a peer that happens to share your address space". Transferred buffers make a natural **ping-pong**: the worker sends the buffer back with the response, so steady-state runs allocation-free.
314
- - **Tier 2 — SharedArrayBuffer.** For high-frequency bulk state (a physics mirror's transform block). `makeArrayBuffer(size, true)` already gates on `crossOriginIsolated`; the dev server and any host must send COOP/COEP headers. Explicitly **optional and last** — Tier 1 with ping-pong buffers covers most workloads without the deployment constraint, and no current code ships SAB.
315
-
316
- ### 5.6 WebGPU
317
-
318
- Three distinct shapes, only one of which needs the pump:
319
-
320
- **a) Device ownership — `WebGPUDeviceSystem`.** One system owns the adapter/device:
321
-
322
- ```js
323
- class WebGPUDeviceSystem extends System {
324
- /** @type {GPUDevice} */ device = null;
325
-
326
- async startup(em) {
327
- const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });
328
- this.device = await adapter.requestDevice({ /* limits/features */ });
329
- this.device.lost.then(info => this.on_device_lost.send1(info)); // enqueue-only, like everything else
330
- }
331
- async shutdown(em) { this.device.destroy(); }
332
- }
333
- ```
334
-
335
- Consumers resolve it in their own startup via the **existing** facility — no new dependency mechanism:
336
-
337
- ```js
338
- async startup(em) {
339
- const gpu = await em.promiseSystemInState(WebGPUDeviceSystem, SystemState.RUNNING);
340
- this.#pipeline = gpu.device.createComputePipeline({ ... });
341
- }
342
- ```
343
-
344
- Device loss v1 = surface the signal and log; full restart-all-GPU-systems recovery mirrors the WebGL context-loss monitor work and is follow-up, not blocking.
345
-
346
- **b) GPU-resident compute — needs nothing new.** A particle/skinning/fluid step whose results are consumed by rendering: `startup` (async) builds pipelines; `update`/`fixedUpdate` encode and `queue.submit()` — which is synchronous fire-and-forget. No readback, no pump, no core changes. This works **today** and the proposal's only contribution is writing the pattern down. (Interop caveat: sharing buffers with a three.js/WebGL renderer isn't possible — GPU-resident systems make sense once rendering itself has a WebGPU path, or when the system renders via its own WebGPU pass. Until then, GPU compute for the WebGL renderer must round-trip through the readback shape below.)
347
-
348
- **c) Readback — the pump again.** Culling/picking/GPGPU queries where the CPU needs the numbers. The only async part of WebGPU is `mapAsync`; wrap the staging discipline once:
349
-
350
- ```js
351
- export class GpuReadbackRing {
352
- constructor(device, { byte_size, depth = 3 }) { /* depth staging buffers */ }
353
- get is_ready() { } // a free staging buffer exists
354
- /** copy src into a free staging buffer inside the caller's encoder pass,
355
- * returns a Promise<ArrayBuffer copy> that settles after mapAsync+copy+unmap */
356
- read(encoder, src_buffer, byte_offset, byte_length) { }
357
- }
358
- ```
359
-
360
- An occlusion-query system is then a plain `AsyncSystem`: `collect` gathers query AABBs, `process` encodes the compute pass + `ring.read(...)` + `queue.submit()` and awaits the mapped copy, `apply` writes visibility components. `capacity` = ring depth. `mapAsync` resolves in submit order, matching the pump's launch-order delivery exactly.
361
-
362
- Worker + WebGPU compose for free: WebGPU is available inside workers, so a worker-side `process` may itself own a device (with `OffscreenCanvas` if it presents). Nothing in the design distinguishes the cases.
363
-
364
- ### 5.7 Failure and shutdown semantics
365
-
366
- - **Rejected round trip** → drains to `discard(request, error)`; default implementation `console.error`s with the system name (same voice as `simulate`'s per-system catch). No retry machinery: if the work is still wanted, the next tick's `collect` regenerates it from current state. Uniform flow, no hidden loops.
367
- - **Dead worker** → all in-flight reject (flows through the same discard path), `on_dead` fires, system stays `RUNNING` but launches will reject immediately. Escalation is the owner's choice; the engine offers `removeSystem`/`addSystem` for a clean restart.
368
- - **Shutdown ordering is already safe.** Both teardown paths (`EntityManager.shutdown` and `removeSystem`) detach the dataset / call `handleDatasetDetached` **before** invoking `System.shutdown` — so by the time an `AsyncSystem.shutdown` runs, the epoch is already bumped and nothing can be applied. Shutdown is therefore just:
369
-
370
- ```js
371
- async shutdown(em) {
372
- await this.pump.settle(); // in-flight work resolves into discard
373
- await this.#channel.stop(); // then tear down the transport
374
- }
375
- ```
376
-
377
- A settle timeout (reusing the 5 s startup-watchdog convention, warn-not-kill) covers a hung worker.
378
-
379
- ---
380
-
381
- ## 6. Worked examples
382
-
383
- ### 6.1 Path planning in a worker (capacity-1 coalescing)
384
-
385
- ```js
386
- class PathPlannerSystem extends AsyncSystem {
387
- dependencies = [PathRequest, Transform];
388
- components_used = [ResourceAccessSpecification.from(PathPlan, ResourceAccessKind.Create)];
389
-
390
- #channel = null;
391
- #dirty = []; // entities with un-serviced requests, maintained by link/unlink
392
-
393
- async startup(em) {
394
- this.#channel = await WorkerChannel.start(
395
- () => new Worker(new URL('./path_planner.worker.js', import.meta.url), { type: 'module' })
396
- );
397
- }
398
-
399
- async shutdown(em) {
400
- await this.pump.settle();
401
- await this.#channel.stop();
402
- }
403
-
404
- link(request, transform, entity) { this.#dirty.push(entity); }
405
- unlink(request, transform, entity) { /* drop from #dirty */ }
406
-
407
- collect() {
408
- if (this.#dirty.length === 0) return undefined;
409
-
410
- const ecd = this.entityManager.dataset;
411
- const queries = this.#dirty.map(entity => ({
412
- entity,
413
- generation: ecd.getEntityGeneration(entity),
414
- from: ecd.getComponent(entity, Transform).position.toJSON(),
415
- to: ecd.getComponent(entity, PathRequest).target.toJSON(),
416
- }));
417
- this.#dirty.length = 0;
418
- return { queries };
419
- }
420
-
421
- process(request) {
422
- return this.#channel.request('plan', request);
423
- }
424
-
425
- apply(result, request) {
426
- const ecd = this.entityManager.dataset;
427
- for (const path of result.paths) {
428
- if (!entity_handle_is_live(ecd, path.entity, path.generation)) continue; // died mid-flight
429
- ecd.addComponentToEntity(path.entity, PathPlan.fromFlatArray(path.points));
430
- }
431
- }
432
- }
433
- ```
434
-
435
- Behavior that falls out for free: while the worker chews on a batch, new requests pile into `#dirty`; the moment the pump frees up, the *next* batch snapshots all of them (coalescing); a slow worker means bigger batches, never a deeper queue; entities that died mid-flight are skipped by the generation check; shutdown drains cleanly.
436
-
437
- ### 6.2 Terrain tiles, rebuilt on the primitives
438
-
439
- Not a commitment to migrate — a demonstration that the bespoke guards collapse. `tile.version`/identity/`isBuildInProgress` become: epoch (dataset detach), `(entity, generation)` (terrain entity death), and `capacity` (in-flight limit). One system-owned channel replaces one worker per `Terrain` component. The result-application moves from a bare `.then` into `apply`, which lands at the system's slot instead of between `simulate` and `render`. Delivery of built geometry stays a transferred `ArrayBuffer` (Tier 1) exactly as today.
440
-
441
- ### 6.3 GPU-resident compute needs nothing new
442
-
443
- ```js
444
- class GpuParticleSystem extends System {
445
- async startup(em) {
446
- const gpu = await em.promiseSystemInState(WebGPUDeviceSystem, SystemState.RUNNING);
447
- this.#device = gpu.device;
448
- this.#pipeline = this.#device.createComputePipeline({ /* WGSL via .wgsl import */ });
449
- }
450
-
451
- update(dt) {
452
- const encoder = this.#device.createCommandEncoder();
453
- // ... dispatchWorkgroups ...
454
- this.#device.queue.submit([encoder.finish()]); // synchronous; done
455
- }
456
- }
457
- ```
458
-
459
- Included to make the point explicit: for the fire-and-forget half of WebGPU, the current `System` contract is already sufficient.
460
-
461
- ### 6.4 Heavy domain mirror (physics-in-worker) — the pattern, not a commitment
462
-
463
- For domains too big to snapshot per tick, the worker holds a **persistent mirror** and the protocol becomes *commands in, state out* — the same bet the network package already made (action-log replication, not snapshot diffs), with the same serialization discipline (`Replicator`-style per-component binary adapters, object-v2 for schema-ful payloads):
464
-
465
- - `link`/`unlink` and component events accumulate a command list (spawn body, remove body, set gravity…).
466
- - `collect` = drain the command list + the current `fixedStepTick` horizon; `process` = `channel.request('step', commands, transfers)`; the worker steps its world and returns packed transforms (Tier 1 ping-pong buffer, or Tier 2 SAB).
467
- - `apply` = bulk-write `Transform`s for live entities.
468
-
469
- **Flagged constraint:** the physics determinism gate (`determinism.spec.js`) and the network lockstep design mean a worker-side physics step may not ship on the default latency-tolerant pump — it needs the fixed-latency contract from §7. That is why this is an illustration of scale, not a v1 target.
470
-
471
- ---
472
-
473
- ## 7. Determinism
474
-
475
- The default `AsyncSystem` is **latency-tolerant**: results apply at whatever tick they drain on, which depends on frame timing. That is correct for the large class of cosmetic/asset/derived-data workloads — terrain tiles, GI/probe updates, impostor and LOD bakes, occlusion results, path *suggestions*, audio probe refreshes — and wrong for anything inside the deterministic simulation envelope (lockstep-replicated state).
476
-
477
- For sim-affecting async work, the contract has to be **fixed latency**: a request launched at fixed tick `T` applies at exactly tick `T + K`, and if the result hasn't arrived when the fixed loop reaches `T + K`, the simulation *waits* (the same shape as the network layer waiting on remote inputs; GGPO-style frame delay). That requires a small `EntityManager` hook (the fixed loop asking "may I advance past tick X?") and is deliberately **out of scope for v1** — but v1 is forward-compatible with it: requests already carry launch-tick stamps, and delivery is already launch-ordered. It becomes a `capacity`/`deadline` policy on the pump, not a redesign.
478
-
479
- ---
480
-
481
- ## 8. What does not change
482
-
483
- - `System`, `EntityManager`, `EntityComponentDataset`: untouched. `simulate` stays synchronous; the noop-gate, try/catch, and runaway guard are unaffected.
484
- - **Execution-order scheduling stays truthful.** Because an async system's writes happen inside its own `update` slot, its `components_used` declarations (e.g. `PathPlan: Create`) describe exactly when downstream systems see the data — the scheduler's scoring keeps working without knowing async exists.
485
- - The `ConcurrentExecutor`/`Task` world is unaffected and remains the right tool for *main-thread* chunked work (lightmap bakes). `promiseTask` remains the bridge for awaiting a pump-less one-shot inside a task graph.
486
- - Existing worker consumers keep running on `WorkerProxy` until individually migrated.
487
-
488
- ---
489
-
490
- ## 9. Testing plan
491
-
492
- All black-box, no mocks beyond genuine platform boundaries:
493
-
494
- - **`AsyncPump.spec.js`** — real Promises: launch-order delivery with out-of-order settlement, capacity gating, epoch invalidation routing to discard, rejection routing, `settle()` semantics, re-launch after settle refused.
495
- - **`WorkerChannel.spec.js`** — channel over a real `MessageChannel` port pair (global in Node ≥ 15, available under Jest's node environment), `WorkerHost.serve` on the far port: request/response, transferable round trip (`ArrayBuffer.byteLength === 0` after transfer proves the move), error serialization, `on_dead` rejection sweep, ready handshake. Spawning a *real* `Worker` is covered by a browser harness prototype (`prototypeAsyncSystem.js` webpack entry), not Jest.
496
- - **`AsyncSystem.spec.js`** — integration with a **real** `EntityManager` + `EntityComponentDataset`: results only ever apply during `simulate` (assert dataset untouched between simulate calls while promises settle), generation-check drops on entity removal, epoch drop on `detachDataset`, drain-then-launch ordering visible to a downstream system in the same pass, clean `settle` on `removeSystem`.
497
- - **`GpuReadbackRing`** — behavioral spec over a stub device (WebGPU is a genuine platform boundary, like `MockAudioContext`); real-GPU verification via the browser harness.
498
-
499
- ---
500
-
501
- ## 10. Phasing
502
-
503
- | Phase | Contents | Size (rough) |
504
- |---|---|---|
505
- | **P0** | `AsyncPump`, `AsyncSystem`, `entity_handle_is_live`, specs | ~300 LOC + specs |
506
- | **P1** | `WorkerChannel`/`WorkerHost` (+ transferables both ways, handshake, injectable port), specs, browser harness with a real worker `AsyncSystem` | ~400 LOC + specs |
507
- | **P2** | `WebGPUDeviceSystem`, `GpuReadbackRing`, `.wgsl` sample system in the harness | ~300 LOC |
508
- | **P3** (opt) | Migrate `ThreadedImageDecoder`, then terrain, off `WorkerBuilder`; consolidate per-component terrain workers | migration |
509
- | **P4** (opt) | Deterministic fixed-latency mode (fixed-loop gate in `EntityManager`); SAB tier for a physics mirror | design + core hook |
510
-
511
- P0 is useful alone (any promise-shaped work source, including `fetch`-based systems). P1 and P2 are independent of each other.
512
-
513
- ---
514
-
515
- ## 11. Alternatives considered
516
-
517
- - **`async update()` awaited by the manager.** Rejected. `simulate` would stall on the slowest system or require overlapping passes; per-system try/catch, the runaway guard, lock-step fixed ticks, and execution-order semantics all break; frame pacing becomes hostage to worker latency. The entire value of a worker is *not* waiting for it.
518
- - **Fire-and-forget promises with `.then(apply)` (status quo).** This is what §2.5/§3 documents: uncontrolled delivery timing, bespoke staleness guards per site, no backpressure, no shutdown story. The proposal is this pattern with the sharp edges standardized away.
519
- - **Full job-graph scheduler (Unity-DOTS-style)** — systems return handles, the manager tracks per-component access and inserts sync points. Attractive long-term (and `components_used` + `ResourceAccessKind` are exactly the metadata it needs), but it is a rewrite of `simulate`'s contract, far beyond "keep the interface simple". The pump neither blocks nor anticipates it wrongly: if a job graph ever lands, pumps become leaf nodes that publish at declared slots.
520
- - **Running systems themselves inside workers (ECS-in-worker).** Moving the *dataset* across threads makes every component access a marshalling question and breaks the library's synchronous component API. The mirror pattern (§6.4) achieves the win — heavy domain off-thread — while the ECS stays single-threaded and simple.
521
- - **SharedArrayBuffer-first transport.** Requires cross-origin isolation everywhere the game ships; ping-ponged transferables get most of the win with zero deployment constraints. SAB stays an opt-in tier.
522
-
523
- ---
524
-
525
- ## 12. Open questions
526
-
527
- 1. **Naming.** `AsyncPump`/`AsyncSystem`/`WorkerChannel`/`WorkerHost` are provisional; `AsyncPipe`, `RoundTrip`, `BridgeSystem` were the runners-up.
528
- 2. **Where worker entries get bundled for library consumers** — keep the meep rollup worker-entry convention (channel takes a URL/factory, so prebuilt bundles keep working), or document webpack-native `new URL` as the only supported path for apps?
529
- 3. **Migration appetite** — is P3 (image decoder + terrain onto the channel) wanted soon, or do the old consumers ride `WorkerProxy` indefinitely?
530
- 4. **Deterministic mode priority** — does the network lockstep roadmap need the §7 fixed-latency contract early enough to pull the `EntityManager` gate hook into P0–P2?
531
- 5. **Device-loss policy for WebGPU** — signal-and-log (proposed v1) vs. automatic restart of GPU systems, mirroring the WebGL context-loss recovery work.