@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,446 +0,0 @@
1
- # Predicate applicability in `MultiPredicateEvaluator`
2
-
3
- **Status:** implemented — revised after adversarial review (§9) and after implementation (§10)
4
- **Date:** 2026-08-22
5
- **Area:** `core/model/reactive/evaluation`, `engine/ecs/dynamic_actions`
6
-
7
- ---
8
-
9
- ## 1. Symptom
10
-
11
- Entering combat produces a cascade of four errors:
12
-
13
- ```
14
- CombatScene.js:886 Failed to build unit entity
15
- Error: value 'scope.request' is not found is undefined
16
- at ReactiveReference.evaluate (ReactiveReference.js:136)
17
- at MultiPredicateEvaluator.__resolve_node
18
- at MultiPredicateEvaluator.next
19
- at DynamicActorSystem.matchRule
20
- at DynamicActorSystem.match
21
-
22
- CombatScene.js:906 Failed to initialize state management
23
- Error: expected entity_id to be a number, instead was 'undefined'
24
- at EntityComponentDataset.getComponent
25
- at manageCombatState (UnitStateManager.js:410)
26
-
27
- dispatchViaProxy.js:24 Failed to dispatch handler __handleUnitStateEntered
28
- Error: expected entity_id to be a number, instead was 'undefined'
29
- at HighlightCurrentTurnCellCombatProcess.processStateEntry
30
-
31
- EntityManager.js:576 Failed during update of system 'DynamicActorSystem'
32
- Error: value 'scope.Team.value' is not found is undefined
33
- ```
34
-
35
- Only the first is a cause. The rest are consequences.
36
-
37
- ## 2. Minimal repro
38
-
39
- No combat, no ECS, no game code — four lines against the evaluator:
40
-
41
- ```js
42
- const ev = new MultiPredicateEvaluator(e => e.computeTreeSize());
43
- ev.build([compileReactiveExpression('a > 5'), compileReactiveExpression('b < 3')]);
44
- ev.initialize({ b: 1 }); // 'a' deliberately unbound
45
- ev.next();
46
- ```
47
-
48
- Measured:
49
-
50
- | build | result for `{ b: 1 }` |
51
- |---|---|
52
- | dev (asserts live) | **throws** `value 'scope.a' is not found`, zero matches |
53
- | prod (`unassert` strips `assert.defined`) | `[ "( b < 3 )" ]` |
54
-
55
- Two things follow. First, dev and prod compute **different rule sets** from the same database and
56
- the same context. Second, one unbound name anywhere in the database destroys the *entire* match in
57
- dev: `b < 3` is satisfiable and is never reached.
58
-
59
- Production is behaving as intended here. Development is not.
60
-
61
- ## 3. Causal chain in combat
62
-
63
- 1. `Entity.build(ecd)` adds components one at a time. `addComponentToEntityByIndex` synchronously
64
- runs observers and dispatches `ComponentAdded` per component
65
- (`EntityComponentDataset.js:1196`).
66
- 2. `DynamicActor` is added at `UnitMaker.js:204`; `Team` is appended last at `CombatScene.js:878`.
67
- The moment `DynamicActor` lands, `AbstractContextSystem` links a `Context` whose `link()`
68
- registers an **any-event** listener.
69
- 3. Every subsequent `ComponentAdded` therefore runs a full rule match against an entity that does
70
- not yet have `Team`.
71
- 4. `DynamicActorSystem.matchRule` walks the shared predicate database. Predicates referencing
72
- `request` (never bound outside a `SendRequest` context) and `Team.value` (not bound on this
73
- entity yet) reach `ReactiveReference.evaluate`, which **asserts**.
74
- 5. The throw escapes `Entity.build` mid-loop. The entity is left in the dataset with a **partial
75
- component set**, permanently missing `Team` and everything after it — which is why error #4
76
- keeps firing on that entity forever.
77
- 6. `CombatScene.handleUnitAdded` catches and continues with `entity === undefined`, then writes
78
- that `undefined` into `cell.entity` for every occupied cell.
79
-
80
- Step 6 is the open REVIEW_03 item *"`CombatCell.entity` is `undefined` for occupied cells"*. This
81
- is its repro.
82
-
83
- Note what is **not** wrong here. Incremental component addition is `EntityComponentDataset`'s
84
- contract and `Entity` is syntactic sugar over it. A listener that expects a particular component
85
- tuple to be present when some *other* component is added is the thing in error, and that
86
- expectation belongs to the listener.
87
-
88
- ## 4. Root cause
89
-
90
- Two callers disagree about what "this name is not bound in this scope" means.
91
-
92
- `ReactiveReference.evaluate` (`ReactiveReference.js:136`) treats it as a programmer error:
93
-
94
- ```js
95
- evaluate(scope) {
96
- const v = scope[this.name];
97
- assert.defined(v, `value 'scope.${this.name}' is not found`);
98
- return v;
99
- }
100
- ```
101
-
102
- Its only production caller, `MultiPredicateEvaluator.__resolve_node` (line 358), treats it as
103
- routine:
104
-
105
- ```js
106
- value = expression.evaluate(this.__context);
107
-
108
- if (value === undefined) {
109
- // value not found
110
- this.__invalidate_node(node);
111
- return false;
112
- }
113
- ```
114
-
115
- The evaluator's own field documentation agrees with the evaluator (line 126): *"Marks nodes that
116
- have failed to be evaluated, typically due to unresolved references."*
117
-
118
- The assert throws before the caller can ever observe the `undefined` it explicitly checks for.
119
- **The `if (value === undefined)` branch is dead code in development.** It executes only in
120
- production, where `rollup-plugin-unassert` (`rollup.config.js:41`) strips the assert.
121
-
122
- That dev/prod divergence is why this defect resisted four instrumented runs: instrumentation runs
123
- unstripped, so the assert masks the very path being observed.
124
-
125
- ### The contradiction was already noticed once
126
-
127
- `MultiPredicateEvaluator.spec.js` carries this helper:
128
-
129
- ```js
130
- /**
131
- * Build a reference that, when evaluated against the test context, yields a defined value whose
132
- * runtime type does NOT match the reference's declared dataType. Such a reference cannot be
133
- * resolved by the evaluator (type mismatch -> invalidated) and therefore stands in for an
134
- * "unresolvable operand" without relying on a missing scope value (which would trip an assert).
135
- */
136
- function unresolvableNumberRef(name) { ... }
137
- ```
138
-
139
- A previous author needed an unresolvable operand, found that the natural way to express one trips
140
- the assert, and invented a type-mismatch stand-in rather than resolve the contradiction. The
141
- conflict is documented in the test suite; it just was never fixed.
142
-
143
- ### Both failing names are legitimately unbound
144
-
145
- - **`request`** — `data.json:83` and friends are *response* rules
146
- (`"request == 'combat-turn-start.archetype.Fighter' && ..."`). `request` enters scope only via
147
- `SendRequestActionDescription.execute` → `requestBestActors`. For a `ComponentAdded` event it is
148
- correctly absent.
149
- - **`Team.value`** — `EntityProxyScope.resolvePath` yields nothing when the entity has no `Team`.
150
- True for context/non-unit `DynamicActor` entities, and for the unit itself mid-build.
151
-
152
- ### Evidence of intended semantics
153
-
154
- `DynamicRuleDescriptionTable` gates on binding before evaluating, at `matchBest` (line 100) and
155
- `match` (line 137):
156
-
157
- ```js
158
- const references = rule.references;
159
-
160
- for (let i = 0; i < n; i++) {
161
- if (context[references[i].name] === undefined) {
162
- continue main; // rule does not apply to this scope
163
- }
164
- }
165
-
166
- const isMatch = rule.condition.evaluate(context); // only now
167
- ```
168
-
169
- and its spec pins it: `test("match skips rules when context is missing needed property for
170
- evaluation", ...)`, `expect(table.matchBest({})).toBeUndefined()`.
171
-
172
- This is evidence of *intent*, not settled precedent — neither `match` nor `matchBest` has a
173
- production caller (see §9, F1). The load-bearing conclusion is narrower and does not depend on
174
- them: **an expression must not be evaluated against a scope that does not bind its references**,
175
- and the check belongs before `evaluate`, not inside it.
176
-
177
- ## 5. Proposal
178
-
179
- ### R1 — Gate on binding inside `__resolve_node`, before `evaluate`
180
-
181
- Add a binding query to `ReactiveReference` and consult it first:
182
-
183
- ```js
184
- // ReactiveReference
185
- /**
186
- * Whether this reference resolves to a value in the given scope. Asking is how a caller avoids
187
- * evaluating an expression that does not apply to the scope it is being matched against;
188
- * `evaluate` remains strict and treats an unbound name as a broken precondition.
189
- * @param {Object} scope
190
- * @returns {boolean}
191
- */
192
- isBound(scope) {
193
- return scope[this.name] !== undefined;
194
- }
195
- ```
196
-
197
- ```js
198
- // MultiPredicateEvaluator.__resolve_node
199
- if (expression.isReference) {
200
-
201
- if (!expression.isBound(this.__context)) {
202
- // reference is not bound in this scope - the node cannot apply here
203
- this.__invalidate_node(node);
204
-
205
- return false;
206
- }
207
-
208
- value = expression.evaluate(this.__context);
209
- // ... type check unchanged
210
- }
211
- ```
212
-
213
- `undefined` never enters the expression algebra, `ReactiveReference.evaluate` keeps its assert
214
- verbatim, and the assert becomes meaningful: it now fires only when a caller evaluated without
215
- gating, which is a real bug.
216
-
217
- This is **exactly behaviour-preserving against production**. Production reaches
218
- `__invalidate_node` precisely when `scope[name]` is `undefined`; so does R1, by construction — the
219
- two read the same scope through the same accessor.
220
-
221
- ### R2 — *(withdrawn during implementation — see §10)*
222
-
223
- An earlier revision routed the binding query through `name in scope` and therefore required `has`
224
- traps on `DataScope`, `EntityProxyScope` and `make_blackboard_proxy`, none of which answer `in`
225
- correctly today. Implementing R1 showed the traps are not merely extra work but actively wrong for
226
- this purpose: `in` and `!== undefined` disagree on a frame that binds a name *to* `undefined`, and
227
- on that input an `in`-based gate would let `evaluate` be called on an unbound name — reintroducing
228
- the crash it exists to prevent. The value probe above has no such gap and needs no new protocol.
229
-
230
- ### R3 — Make the evaluator self-correcting across matches
231
-
232
- `initialize()` resets `evaluated` and the cursor; only `finalize()` resets `invalidated`
233
- (line 484), and `matchRule` calls it unguarded:
234
-
235
- ```js
236
- evaluator.initialize(context);
237
- for (;;) { const predicate = evaluator.next(); ... }
238
- evaluator.finalize(); // skipped when next() throws
239
- ```
240
-
241
- A throw escaping `next()` leaves stale invalidations that silently suppress later matches.
242
- **Demonstrated**, not theorised:
243
-
244
- ```
245
- run 1: type-mismatched 'bad' invalidates p1; a throwing scope getter aborts the loop
246
- run 2: clean context { bad: 50, boom: 5 } - both predicates are TRUE
247
- matches: ["( boom > 0 )"] <-- p1 silently dropped
248
- control (after finalize() ran):
249
- matches: ["( ( bad > 1 ) && ( bad < 100 ) )", "( boom > 0 )"]
250
- ```
251
-
252
- Wrong answers, no error, persisting for the life of the evaluator. Fix: `initialize()` establishes
253
- the complete starting state, resetting `invalidated` there rather than depending on `finalize()`
254
- having run. `finalize()` keeps a real job — releasing the scope, which is commonly a proxy over a
255
- live frame that a finished evaluator must not keep reachable.
256
-
257
- No `try/finally` in `matchRule`. Once `initialize()` owns the reset there is nothing left for it to
258
- protect: a skipped `finalize()` retains one scope reference until the next match and cannot corrupt
259
- a result. Guarding that would be guarding a condition that can no longer happen.
260
-
261
- This is independent of R1 and stands on its own — it is a silent-wrong-answer bug and a one-line
262
- fix. R1 removes the throw that triggers it today; R3 removes the class.
263
-
264
- ### R4 — Stop `CombatScene.handleUnitAdded` laundering failures into a corrupt board
265
-
266
- Three `try`/`catch` blocks each swallow and continue with the state the failure produced:
267
-
268
- - around `makeUnit` — leaves `entityBuilder` `undefined`, so the next line throws anyway, with a
269
- worse message than the error that was caught;
270
- - around `entityBuilder.build(ecd)` — leaves `entity` `undefined`, which then reaches
271
- `manageCombatState` **and** `cell.entity`;
272
- - around `manageCombatState` — hides a genuine wiring failure.
273
-
274
- All three go. `Signal.send4` already isolates a throwing handler (`Signal.js:615`), so the failure
275
- is reported there and the rest of the dispatch survives; what changes is that `handleUnitAdded`
276
- stops running on past the failure and writing a non-entity into the board. `cells.forEach` also
277
- moves ahead of `manageCombatState`, so the cells are populated as soon as there is a real entity to
278
- put in them.
279
-
280
- This does not make a failed unit fail the *scene* — `Signal` catches by design and that design is
281
- not this change's business. It makes a failed unit stop corrupting the board.
282
-
283
- ## 6. Tests
284
-
285
- - `MultiPredicateEvaluator.spec.js` — the §2 repro as a test: a predicate over an unbound name does
286
- not match and does not throw, while a co-resident satisfiable predicate still matches; the same
287
- name matches in scopes that do bind it. Plus the R3 leak repro: an exception escaping `next()`
288
- must not change the result of the following match.
289
- - `ReactiveReference.spec.js` — `isBound` reports absence, treats `0`/`false`/`''` as bound, and
290
- reads through a scope that resolves names dynamically rather than as own properties.
291
- - `EntityProxyScope.spec.js` — a component path is unbound until the entity has the component, and
292
- resolves through the component's getter once it does.
293
- - `DataScope.spec.js` — the combat failure end to end: a rule database matched against a real
294
- entity scope skips the rules the scope cannot supply, and picks up the `Team` rule the moment the
295
- component lands.
296
-
297
- No `evaluate`-throws-on-unbound test: `assert` is stripped from production builds, so a rejection
298
- test would pin behaviour that does not exist in the shipped artifact.
299
-
300
- The existing short-circuit suite in `MultiPredicateEvaluator.spec.js` must pass unchanged. It pins
301
- node-level resolvability semantics, which R1 deliberately does not touch.
302
-
303
- ## 7. Deferred
304
-
305
- - **Unifying `match` / `matchBest` with the evaluator.** They duplicate gate-then-evaluate and can
306
- drift from it, but neither has a production caller and meep is a library, so they are public
307
- surface to keep rather than churn. Worth doing as its own change with its own justification; it
308
- is not part of fixing this crash.
309
- - **Predicate-level applicability gating** (skip a whole predicate when any of its references is
310
- unbound, per `rule.references`). See §9, F3: it changes matching semantics, is not needed for the
311
- crash, and is behaviourally inert on the current rule data.
312
-
313
- ## 8. Explicitly rejected
314
-
315
- - **Making `ReactiveReference.evaluate` return `undefined`.** This turns `undefined` into a value in
316
- the expression algebra: every operator must learn to propagate it, every consumer must handle it,
317
- and the assert dies. It opens the floodgate instead of containing the problem. R1 achieves the
318
- same outcome with the containment intact — the question "is this bound?" is asked and answered
319
- before evaluation, and never becomes a value.
320
- - **Making `Entity.build` atomic w.r.t. observers.** `Entity` is syntactic sugar over
321
- `EntityComponentDataset`; the incremental add flow is correct. Expecting a given component tuple
322
- to be present when some other component is added is the error, and it belongs to the consumer that
323
- expects it — not to `Entity`.
324
-
325
- ---
326
-
327
- ## 9. Adversarial review
328
-
329
- Reviewed against three questions: is it principled, does it damage the engine, is it the simplest
330
- thing that works. The proposal that entered review is preserved here as **P1–P4**; §5 is the
331
- revised outcome.
332
-
333
- > **P1** intern reference names, give each predicate a `BitSet` of required names, gate whole
334
- > predicates in `next()` with a per-match probe cache.
335
- > **P2** delete the `value === undefined` branch as unreachable.
336
- > **P3** adopt `match`/`matchBest`'s all-references-must-be-bound semantics across the evaluator.
337
- > **P4** collapse the three matching paths onto one.
338
-
339
- ### Is it a principled fix?
340
-
341
- **F1 — "the contract already exists, twice" was an appeal to incumbency.** *Confirmed.* `match` and
342
- `matchBest` have no production callers; grep finds only `DynamicRuleDescriptionTable.spec.js`. The
343
- original draft leaned on them as settled precedent. They are evidence of *intent* — reinforced by
344
- their specs — but they are not a shipped contract, and "be consistent with two unused functions"
345
- cannot by itself justify changing a hot path. §4 now states this explicitly and rests the argument
346
- on the narrower claim instead.
347
-
348
- **F2 — stronger evidence existed and was missed.** The `unresolvableNumberRef` helper in
349
- `MultiPredicateEvaluator.spec.js` documents a previous author hitting this exact contradiction and
350
- routing around it. That is far better evidence than the incumbency argument. Added to §4.
351
-
352
- **F3 — P3 was a semantics change dressed as a fix.** *Upheld; P3 dropped.* It is not required to
353
- fix the crash. On the actual data it buys nothing: of 89 rule conditions only 5 contain `||`, all
354
- of the shape `(count == 1 && Team.value == 0) || (count == 1 && Team.value == 1)`, where both
355
- branches reference `Team.value` — so predicate-level gating and short-circuiting agree on every one
356
- of them. It would have been a hard-to-reason-about behaviour change with no demonstrated benefit.
357
-
358
- **Diagnosis survived.** The dev/prod divergence is now measured (§2) rather than argued, and the
359
- repro is four lines with no game code in it.
360
-
361
- ### Does it damage the engine?
362
-
363
- **F4 — P1 pushed consumer policy into a core class.** *Partly upheld.* Reference-set interning and
364
- per-predicate requirement bitsets are `dynamic_actions` shaped, added permanently to
365
- `core/model/reactive` for one consumer. R1's binding query is the general form of the same idea
366
- ("do not evaluate an expression against a scope that does not bind its references") and adds one
367
- predicate-free method to `ReactiveReference`.
368
-
369
- **F5 — P2 was the most damaging item in the document.** *Upheld; P2 dropped.* The
370
- `value === undefined` branch is only unreachable if *every* caller gates. `MultiPredicateEvaluator`
371
- is public library surface and its own spec calls `initialize()` with plain objects; deleting the
372
- branch converts a handled condition into a crash for any ungated caller. R1 does not delete it — it
373
- **re-roots** it, reaching the same `__invalidate_node` via a binding query instead of by inspecting
374
- `evaluate`'s return value. Same handling, sound trigger.
375
-
376
- **F6 — the proposal was not implementable as written.** *Upheld.* Every variant needs to ask a scope
377
- whether a name is bound, and no scope can answer today. Measured: `'a' in dataScope.proxy` is
378
- `false` for a bound name, `'stack' in dataScope.proxy` is `true`, `'contains' in blackboardProxy` is
379
- `true`. P1 would have silently gated on the host object's shape. The review's conclusion — add the
380
- missing `has` traps — was itself superseded during implementation: the right answer is not to use
381
- `in` at all. See §10.
382
-
383
- **F7 — P4 churned public API for no defect.** *Upheld; moved to §7 Deferred.*
384
-
385
- ### KISS
386
-
387
- **F8 — a materially smaller fix does the same job.** *Upheld; this is now R1.* Gating at the node
388
- level inside `__resolve_node` is roughly four lines and subsumes P1, P2 and P3:
389
-
390
- | | P1–P4 | R1 |
391
- |---|---|---|
392
- | name interning + per-predicate bitsets | yes | no |
393
- | per-match probe cache | yes | no |
394
- | semantics decision required (F3) | yes | no |
395
- | existing short-circuit tests | need re-reasoning | pass unchanged |
396
- | `match` / `matchBest` touched | yes | no |
397
- | behaviour vs production | changes it | provably identical |
398
-
399
- The one thing P1 had that R1 does not is skipping a predicate's DAG walk wholesale. That is a
400
- performance argument, and see F9.
401
-
402
- **F9 — the probe cache was premature optimisation.** *Upheld; dropped.* 89 rules, 84 distinct
403
- conditions, and the evaluator already memoises per node through `evaluated`/`invalidated`. No
404
- measurement was offered for the claim that gating is "cheaper than today". Work, right, fast — in
405
- that order.
406
-
407
- ### Net effect of the review
408
-
409
- Four of the five original items were dropped or deferred. What survives is: gate before evaluating
410
- (R1), make the scopes answerable (R2), stop the evaluator carrying state across matches (R3), stop
411
- the combat scene laundering failures (R4). R3 gained a working repro during review and is
412
- independent of the rest.
413
-
414
- ---
415
-
416
- ## 10. Implementation record
417
-
418
- Landed as R1, R3 and R4. R2 was withdrawn.
419
-
420
- **R2 did not survive contact with the code.** The review concluded that a binding query needs `has`
421
- traps because `in` is broken on all three scope proxies. Writing R1 showed the premise was wrong:
422
- `in` is the incorrect operator here regardless of whether the traps exist. `in` and `!== undefined`
423
- diverge on a frame that binds a name *to* `undefined`, and on that input an `in`-based gate reports
424
- "bound" and hands `evaluate` an unbound name — the exact crash the gate exists to prevent. The value
425
- probe has no such gap, needs no new protocol, and matches what `DynamicRuleDescriptionTable`'s gate
426
- has always done. Three files of new trap code went away and the fix got smaller.
427
-
428
- The broken `in` behaviour on `DataScope`, `EntityProxyScope` and `make_blackboard_proxy` is real and
429
- was measured, but nothing uses `in` on those proxies. Fixing it is speculative work on an unused
430
- path and is deliberately not part of this change.
431
-
432
- **No `try`/`finally` in `matchRule`.** Once `initialize()` establishes complete state, a skipped
433
- `finalize()` cannot affect a result — see R3.
434
-
435
- **Verified.** The two gate tests were written first and failed with the reported production error
436
- (`value 'scope.a' is not found`) before R1 existed. The `DataScope` end-to-end test was confirmed
437
- non-vacuous by neutralising `isBound` and observing the original failure return verbatim:
438
- `value 'scope.request' is not found`. Full regression over `engine/ecs`, `core/model` and
439
- `core/lang`: 130 files, 884 tests, green — including the pre-existing short-circuit suite, which R1
440
- does not touch.
441
-
442
- **Not done, still open:** the deferred items in §7, and the residual noted in §3 — rule matching
443
- still runs on every `ComponentAdded` during `Entity.build`, several times per unit. That is wasted
444
- work and it lets a rule fire against a partially-populated entity. R1 makes it harmless rather than
445
- fatal; whether actors should be matching at all mid-build is a separate design question about
446
- `DynamicActorSystem`, not about `Entity`.