@woosh/meep-engine 3.0.2 → 3.2.0

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 (1823) hide show
  1. package/LICENSE +188 -188
  2. package/README.md +152 -145
  3. package/editor/Editor.js +598 -598
  4. package/editor/EditorCameraNavigation.js +205 -205
  5. package/editor/EditorKeyMap.js +182 -182
  6. package/editor/SelectionVisualizer.js +127 -127
  7. package/editor/actions/concrete/ActionInvalidateSampler.js +27 -27
  8. package/editor/actions/concrete/PatchTerrainHeightAction.js +85 -85
  9. package/editor/camera/camera_framing.js +104 -104
  10. package/editor/clipboard.js +184 -184
  11. package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -22
  12. package/editor/ecs/component/editors/ObservedStringEditor.js +34 -34
  13. package/editor/ecs/component/editors/common/two_way_sync.js +50 -50
  14. package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +19 -19
  15. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -35
  16. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
  17. package/editor/ecs/component/editors/ecs/SGMeshEditor.js +24 -24
  18. package/editor/ecs/component/editors/ecs/ShadedGeometryEditor.js +33 -33
  19. package/editor/ecs/component/editors/ecs/Trail3DEditor.js +45 -45
  20. package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -19
  21. package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +72 -72
  22. package/editor/ecs/component/editors/shade/MeshletGeometryEditor.js +30 -30
  23. package/editor/ecs/component/editors/shade/ShadeImageEditor.js +70 -70
  24. package/editor/ecs/component/editors/shade/ShadeMaterialEditor.js +34 -34
  25. package/editor/ecs/component/editors/shade/ShadeTextureEditor.js +46 -46
  26. package/editor/ecs/component/editors/shade/StandardShadeMaterialEditor.js +24 -24
  27. package/editor/ecs/component/prototypeObjectEditor.js +265 -265
  28. package/editor/ecs/component/registerBasicTypeEditors.js +100 -100
  29. package/editor/ecs/component/registerEngineComponentEditors.js +95 -95
  30. package/editor/entity_world_bounds.js +27 -27
  31. package/editor/particles/lut_ops.js +148 -148
  32. package/editor/particles/particleEditEvents.js +12 -12
  33. package/editor/particles/rebuildParticleEmitter.js +31 -31
  34. package/editor/persistence/EditorLayoutStore.js +71 -71
  35. package/editor/persistence/SceneMetadata.js +52 -52
  36. package/editor/persistence/SceneSlotManager.js +144 -144
  37. package/editor/persistence/buildNewSceneDataset.js +67 -67
  38. package/editor/persistence/files.js +68 -68
  39. package/editor/persistence/scene_import.js +143 -143
  40. package/editor/persistence/scene_serialization.js +79 -79
  41. package/editor/persistence/string_buffer_codec.js +41 -41
  42. package/editor/process/GridDisplayProcess.js +125 -125
  43. package/editor/process/SymbolicDisplayProcess.js +106 -108
  44. package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
  45. package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
  46. package/editor/process/symbolic/PathSymbolicDisplay.js +118 -118
  47. package/editor/process/symbolic/PositionedMarkerSymbolicDisplay.js +79 -79
  48. package/editor/process/symbolic/SoundEmitterSymbolicDisplay.js +57 -57
  49. package/editor/process/symbolic/SymbolicDisplay.js +62 -62
  50. package/editor/process/symbolic/gizmo_draw_wire_box_oriented.js +85 -85
  51. package/editor/process/symbolic/gizmo_draw_wire_circle.js +53 -53
  52. package/editor/prototypeEditorShell.js +156 -156
  53. package/editor/selection/editor_pick.js +130 -130
  54. package/editor/selection/screen_rect_frustum.js +119 -119
  55. package/editor/style/controls.scss +168 -168
  56. package/editor/style/inspector.scss +223 -223
  57. package/editor/templates/entity_templates.js +167 -167
  58. package/editor/tools/GridPaintTool.js +273 -273
  59. package/editor/tools/SelectionTool.js +307 -307
  60. package/editor/tools/TransformToolV2.js +378 -378
  61. package/editor/tools/engine/ToolEngine.js +188 -188
  62. package/editor/tools/paint/TerrainTexturePaintTool.js +250 -250
  63. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +501 -501
  64. package/editor/tools/v2/TransformControls.js +971 -971
  65. package/editor/tools/v2/handle/gizmo_drag_plane.js +141 -141
  66. package/editor/tools/v2/handle/gizmo_draw.js +399 -399
  67. package/editor/tools/v2/handle/gizmo_handles.js +364 -364
  68. package/editor/tools/v2/handle/gizmo_ray_shapes.js +239 -239
  69. package/editor/view/ecs/HierarchicalEntityListView.js +339 -339
  70. package/editor/view/ecs/components/DatGuiController.js +2 -2
  71. package/editor/view/ecs/components/common/AutoCanvasView.js +100 -100
  72. package/editor/view/ecs/components/common/TextController.js +59 -59
  73. package/editor/view/library/MeshLibraryView.js +175 -175
  74. package/editor/view/library/mesh_library_drag.js +63 -63
  75. package/editor/view/library/model_thumbnail_source.js +86 -86
  76. package/editor/view/makeEntityDecorators.js +125 -125
  77. package/editor/view/node-graph/NodeGraphSelection.js +89 -89
  78. package/editor/view/node-graph/NodeView.js +211 -211
  79. package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -36
  80. package/editor/view/node-graph/actions/NodesMoveAction.js +41 -41
  81. package/editor/view/node-graph/connection_wire_geometry.js +107 -107
  82. package/editor/view/particles/ColorLutGradientView.js +291 -291
  83. package/editor/view/particles/ScalarLutCurveView.js +279 -279
  84. package/editor/view/prepareMeshLibrary.js +188 -188
  85. package/editor/view/shell/SceneTreePanelView.js +119 -119
  86. package/editor/view/v2/SplitView.js +2 -2
  87. package/package.json +94 -94
  88. package/samples/generation/generators/interactive/mir_generator_place_buff_objects.js +362 -362
  89. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -738
  90. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -79
  91. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -93
  92. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -80
  93. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -164
  94. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -69
  95. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -102
  96. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -129
  97. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -72
  98. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -74
  99. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -169
  100. package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -254
  101. package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -250
  102. package/src/REVIEW_2026_08_06/adv-core.md +511 -511
  103. package/src/REVIEW_2026_08_06/adv-gfx.md +480 -480
  104. package/src/REVIEW_2026_08_06/adv-view.md +446 -446
  105. package/src/REVIEW_2026_08_06/api-core.md +296 -296
  106. package/src/REVIEW_2026_08_06/api-engine.md +195 -195
  107. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -507
  108. package/src/REVIEW_2026_08_06/core-binary.md +348 -348
  109. package/src/REVIEW_2026_08_06/core-collection.md +376 -376
  110. package/src/REVIEW_2026_08_06/core-math.md +221 -221
  111. package/src/REVIEW_2026_08_06/core-process.md +266 -266
  112. package/src/REVIEW_2026_08_06/core-science.md +223 -223
  113. package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -279
  114. package/src/REVIEW_2026_08_06/engine-animation.md +167 -167
  115. package/src/REVIEW_2026_08_06/engine-asset.md +166 -166
  116. package/src/REVIEW_2026_08_06/engine-ecs.md +920 -920
  117. package/src/REVIEW_2026_08_06/engine-input.md +197 -197
  118. package/src/REVIEW_2026_08_06/engine-misc.md +248 -248
  119. package/src/REVIEW_2026_08_06/engine-terrain.md +52 -52
  120. package/src/REVIEW_2026_08_06/generation.md +176 -176
  121. package/src/REVIEW_2026_08_06/geom-2d.md +434 -434
  122. package/src/REVIEW_2026_08_06/geom-3d.md +117 -117
  123. package/src/REVIEW_2026_08_06/gfx-gi.md +143 -143
  124. package/src/REVIEW_2026_08_06/gfx-render.md +740 -740
  125. package/src/REVIEW_2026_08_06/gfx-texture.md +690 -690
  126. package/src/REVIEW_2026_08_06/prior-criticals.md +49 -49
  127. package/src/REVIEW_2026_08_06/prior-highs.md +138 -138
  128. package/src/REVIEW_2026_08_06/test-gaps.md +364 -364
  129. package/src/REVIEW_2026_08_06/view.md +667 -667
  130. package/src/REVIEW_2026_08_06.md +610 -610
  131. package/src/avif/codec/dec/avif_dec.cpp +233 -233
  132. package/src/avif/codec/dec/avif_dec.js +16 -16
  133. package/src/core/binary/BitImage2.js +246 -246
  134. package/src/core/binary/align_32.js +21 -21
  135. package/src/core/binary/data_view/DataType2DataViewReaders.js +23 -23
  136. package/src/core/binary/data_view/DataType2DataViewWriters.js +25 -25
  137. package/src/core/binary/hash/XXH128Hash.js +55 -55
  138. package/src/core/binary/hash/XXH3_SECRET.js +25 -25
  139. package/src/core/binary/hash/xxh3_128.js +341 -341
  140. package/src/core/binary/hash/xxh3_64.js +241 -241
  141. package/src/core/binary/hash/xxh3_common.js +476 -476
  142. package/src/core/binary/lz4/LZ4_MAX_INPUT_SIZE.js +9 -9
  143. package/src/core/binary/lz4/lz4_compress_block.js +385 -385
  144. package/src/core/binary/lz4/lz4_compress_bound.js +25 -25
  145. package/src/core/binary/lz4/lz4_decompress_block.js +139 -139
  146. package/src/core/binary/lz4/lz4_decompress_bound.js +30 -30
  147. package/src/core/binary/meshopt/MESHOPT_GROUP_SIZE.js +18 -18
  148. package/src/core/binary/meshopt/MeshoptCompressionFilter.js +30 -30
  149. package/src/core/binary/meshopt/MeshoptCompressionMode.js +21 -21
  150. package/src/core/binary/meshopt/__meshopt_test_streams.js +237 -237
  151. package/src/core/binary/meshopt/meshopt_decode.js +73 -73
  152. package/src/core/binary/meshopt/meshopt_decode_byte_group.js +67 -67
  153. package/src/core/binary/meshopt/meshopt_decode_data_block.js +59 -59
  154. package/src/core/binary/meshopt/meshopt_decode_filter_color.js +69 -69
  155. package/src/core/binary/meshopt/meshopt_decode_filter_exponential.js +43 -43
  156. package/src/core/binary/meshopt/meshopt_decode_filter_octahedral.js +60 -60
  157. package/src/core/binary/meshopt/meshopt_decode_filter_quaternion.js +71 -71
  158. package/src/core/binary/meshopt/meshopt_decode_index_buffer.js +294 -294
  159. package/src/core/binary/meshopt/meshopt_decode_index_sequence.js +115 -115
  160. package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +381 -381
  161. package/src/core/binary/meshopt/meshopt_read_uint_var.js +59 -59
  162. package/src/core/binary/reinterpret_float32_as_uint32.js +13 -13
  163. package/src/core/binary/reinterpret_uint32_as_float32.js +14 -14
  164. package/src/core/binary/type/DataType2TypedArrayConstructorMapping.js +35 -35
  165. package/src/core/bvh2/bvh3/ebvh_geometry_query_any_triangle_ray.js +149 -149
  166. package/src/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +96 -96
  167. package/src/core/bvh2/bvh3/query/bvh_query_depth_range_in_frustum.js +101 -101
  168. package/src/core/bvh2/bvh3/query/bvh_query_leaves_generic.js +77 -77
  169. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +90 -90
  170. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js +94 -94
  171. package/src/core/bvh2/bvh3/query/bvh_query_user_data_generic.js +86 -86
  172. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js +101 -101
  173. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +92 -92
  174. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray.js +96 -96
  175. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray_segment.js +100 -100
  176. package/src/core/bvh2/visual/BVHGeometry.js +131 -131
  177. package/src/core/bvh8/BVH8.js +436 -436
  178. package/src/core/bvh8/aabb3_round_to_float32.js +22 -22
  179. package/src/core/bvh8/build/BVH8Converter.js +588 -588
  180. package/src/core/bvh8/build/TriangleCluster.js +122 -122
  181. package/src/core/bvh8/build/aabb3_compute_merge_cost.js +28 -28
  182. package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +17 -17
  183. package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -303
  184. package/src/core/bvh8/build/bvh8_from_proxy.js +256 -256
  185. package/src/core/bvh8/build/byte.js +9 -9
  186. package/src/core/bvh8/build/encode_bounds_e.js +11 -11
  187. package/src/core/bvh8/bvh8_convert_to_dot.js +132 -132
  188. package/src/core/bvh8/bvh8_count_primitives.js +98 -98
  189. package/src/core/bvh8/bvh8_geometry_validate.js +138 -138
  190. package/src/core/bvh8/bvh8_geometry_validate_indirect.js +166 -166
  191. package/src/core/bvh8/bvh8_get_node_bounds.js +34 -34
  192. package/src/core/bvh8/bvh8_get_node_child_bounds.js +52 -52
  193. package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -18
  194. package/src/core/bvh8/bvh8_node_count_triangles.js +27 -27
  195. package/src/core/bvh8/bvh8_quality.js +72 -72
  196. package/src/core/bvh8/bvh8_validate_structure.js +87 -87
  197. package/src/core/clipboard/obtainClipBoard.js +29 -29
  198. package/src/core/clipboard/safeClipboardReadText.js +55 -55
  199. package/src/core/clipboard/safeClipboardWriteText.js +23 -23
  200. package/src/core/collection/array/typed/typed_array_value_denormalize.js +52 -52
  201. package/src/core/collection/array/typed/typed_array_value_normalize.js +51 -51
  202. package/src/core/collection/union-find/union_find.js +79 -79
  203. package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -20
  204. package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -17
  205. package/src/core/color/colormap/MAGMA_LUT.js +26 -26
  206. package/src/core/color/illuminant/D65_TRISTIMULUS_XYZ.js +19 -19
  207. package/src/core/color/lab/lab_apply_hunt_adjustment.js +27 -27
  208. package/src/core/color/lab/lab_distance_hyab.js +23 -23
  209. package/src/core/color/lab/xyz_to_lab.js +62 -62
  210. package/src/core/color/operations/color_darken.js +24 -24
  211. package/src/core/color/operations/color_desaturate.js +24 -24
  212. package/src/core/color/operations/color_lighten.js +24 -24
  213. package/src/core/color/operations/color_saturate.js +24 -24
  214. package/src/core/color/operations/color_scale_okhsv_channel.js +45 -45
  215. package/src/core/color/tonemap/tonemap_aces.js +41 -41
  216. package/src/core/color/ycxcz/xyz_to_ycxcz.js +28 -28
  217. package/src/core/color/ycxcz/ycxcz_to_xyz.js +19 -19
  218. package/src/core/dom/isImageBitmap.js +12 -12
  219. package/src/core/function/frameThrottle.js +23 -23
  220. package/src/core/geom/2d/aabb/aabb2_array_combine.js +7 -7
  221. package/src/core/geom/2d/aabb/aabb2_compute_area.js +7 -7
  222. package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +7 -7
  223. package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +7 -7
  224. package/src/core/geom/2d/aabb/aabb2_from_v2_array.js +7 -7
  225. package/src/core/geom/2d/aabb/aabb2_intersects_point.js +7 -7
  226. package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +7 -7
  227. package/src/core/geom/2d/circle/circle_compute_circle_intersection.js +7 -7
  228. package/src/core/geom/2d/circle/circle_compute_circle_penetration.js +7 -7
  229. package/src/core/geom/2d/circle/circle_intersects_point.js +7 -7
  230. package/src/core/geom/2d/compute_polygon_area_2d.js +7 -7
  231. package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +7 -7
  232. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +7 -7
  233. package/src/core/geom/2d/convex-hull/orientation3_array.js +7 -7
  234. package/src/core/geom/2d/intersect_ray_2d.js +7 -7
  235. package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +7 -7
  236. package/src/core/geom/2d/line/line2_segment_intersection_fraction_2d.js +44 -44
  237. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +7 -7
  238. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_array_2d.js +7 -7
  239. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_2d.js +7 -7
  240. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_array_2d.js +7 -7
  241. package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +7 -7
  242. package/src/core/geom/2d/line/line_segment_line_segment_intersection_exists_2d.js +7 -7
  243. package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -25
  244. package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -46
  245. package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -52
  246. package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -69
  247. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -433
  248. package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.js +51 -51
  249. package/src/core/geom/2d/polygon/polygon2_clip_halfplane.js +50 -50
  250. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -84
  251. package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -52
  252. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -86
  253. package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -1280
  254. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -77
  255. package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -39
  256. package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -101
  257. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -7
  258. package/src/core/geom/2d/rectangle_to_aabb.js +7 -7
  259. package/src/core/geom/2d/spline_bezier2_2d.js +7 -7
  260. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +99 -99
  261. package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -35
  262. package/src/core/geom/2d/triangle2d_compute_area.js +7 -7
  263. package/src/core/geom/2d/triangle2d_contains_point.js +7 -7
  264. package/src/core/geom/2d/triangle2d_get_barycentric.js +7 -7
  265. package/src/core/geom/2d/v2_morton_encode.js +23 -23
  266. package/src/core/geom/3d/aabb/aabb3_array_intersects_frustum_degree.js +7 -7
  267. package/src/core/geom/3d/aabb/aabb3_box_surface_area_2.js +7 -7
  268. package/src/core/geom/3d/aabb/aabb3_build_frustum.js +5 -5
  269. package/src/core/geom/3d/aabb/aabb3_closest_point_on_surface.js +57 -57
  270. package/src/core/geom/3d/aabb/aabb3_combined_surface_area.js +7 -7
  271. package/src/core/geom/3d/aabb/aabb3_compute_half_surface_area.js +7 -7
  272. package/src/core/geom/3d/aabb/aabb3_compute_projected_area.js +7 -7
  273. package/src/core/geom/3d/aabb/aabb3_compute_surface_area.js +7 -7
  274. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +7 -7
  275. package/src/core/geom/3d/aabb/aabb3_estimate_projected_area2.js +7 -7
  276. package/src/core/geom/3d/aabb/aabb3_from_threejs_geometry.js +7 -7
  277. package/src/core/geom/3d/aabb/aabb3_from_triangle_group.js +46 -46
  278. package/src/core/geom/3d/aabb/aabb3_intersects_frustum_degree.js +7 -7
  279. package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +7 -7
  280. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +7 -7
  281. package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +7 -7
  282. package/src/core/geom/3d/aabb/aabb3_ray_segment_entry_distance.js +95 -95
  283. package/src/core/geom/3d/aabb/aabb3_raycast.js +7 -7
  284. package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -93
  285. package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.js +82 -82
  286. package/src/core/geom/3d/aabb/compute_aabb_from_points.js +7 -7
  287. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +7 -7
  288. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -486
  289. package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -174
  290. package/src/core/geom/3d/atlas/Chart.js +77 -77
  291. package/src/core/geom/3d/atlas/REVIEW_02.md +392 -392
  292. package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -315
  293. package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -16
  294. package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -188
  295. package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -107
  296. package/src/core/geom/3d/atlas/atlas_generate.js +50 -50
  297. package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -266
  298. package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -36
  299. package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -62
  300. package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -33
  301. package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -50
  302. package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -39
  303. package/src/core/geom/3d/atlas/io/bt_loop_uv.js +35 -35
  304. package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -46
  305. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +269 -269
  306. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +209 -209
  307. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -38
  308. package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -86
  309. package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -183
  310. package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -66
  311. package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -40
  312. package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -136
  313. package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -30
  314. package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -77
  315. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +116 -116
  316. package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +271 -271
  317. package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +191 -191
  318. package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -35
  319. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -67
  320. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -137
  321. package/src/core/geom/3d/compute_circle_bounding_box.js +7 -7
  322. package/src/core/geom/3d/cone/computeConeBoundingBox.js +7 -7
  323. package/src/core/geom/3d/cone/computeConePlaneSide.js +7 -7
  324. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +7 -7
  325. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts +38 -0
  326. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts.map +1 -0
  327. package/src/core/geom/3d/cylinder/cylinder3_raycast.js +105 -0
  328. package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -18
  329. package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -24
  330. package/src/core/geom/3d/frustum/clipping_volume_matrix4_project.js +7 -7
  331. package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +7 -7
  332. package/src/core/geom/3d/frustum/frustum3_compute_closest_point.js +117 -117
  333. package/src/core/geom/3d/frustum/frustum_matrix4_project.js +7 -7
  334. package/src/core/geom/3d/frustum/hexahedron_from_aabb.js +7 -7
  335. package/src/core/geom/3d/frustum/read_frustum_corner.js +17 -17
  336. package/src/core/geom/3d/frustum/read_three_planes_to_array.js +7 -7
  337. package/src/core/geom/3d/frustum/slice_frustum_linear_to_points.js +7 -7
  338. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -146
  339. package/src/core/geom/3d/gjk/gjk.js +430 -430
  340. package/src/core/geom/3d/gjk/gjk_epa_penetration.js +520 -520
  341. package/src/core/geom/3d/gjk/minkowski_support.js +71 -71
  342. package/src/core/geom/3d/gjk/mpr.js +368 -368
  343. package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +7 -7
  344. package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.js +7 -7
  345. package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -64
  346. package/src/core/geom/3d/line/line3_compute_segment_closest_point_to_aabb3_t.js +153 -153
  347. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +7 -7
  348. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js +7 -7
  349. package/src/core/geom/3d/line/line3_compute_segment_segment_closest_points.js +88 -88
  350. package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -71
  351. package/src/core/geom/3d/line/segment3_point_distance_to_segments.js +25 -25
  352. package/src/core/geom/3d/mat4/allocate_m4.js +7 -7
  353. package/src/core/geom/3d/mat4/apply_mat4_transform_to_direction_v3_array.js +7 -7
  354. package/src/core/geom/3d/mat4/apply_mat4_transform_to_v3_array.js +7 -7
  355. package/src/core/geom/3d/mat4/compose_matrix4_array.js +7 -7
  356. package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +7 -7
  357. package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +7 -7
  358. package/src/core/geom/3d/mat4/m4_compose.js +22 -22
  359. package/src/core/geom/3d/mat4/m4_compute_normal_matrix3.js +7 -7
  360. package/src/core/geom/3d/mat4/m4_decompose.js +64 -64
  361. package/src/core/geom/3d/mat4/m4_decompose_array.js +157 -157
  362. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +32 -32
  363. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -67
  364. package/src/core/geom/3d/mat4/m4_linear_matrix3.js +29 -29
  365. package/src/core/geom/3d/mat4/m4_look_at.js +96 -96
  366. package/src/core/geom/3d/mat4/m4_make_rotation_x.js +47 -47
  367. package/src/core/geom/3d/mat4/m4_make_rotation_y.js +47 -47
  368. package/src/core/geom/3d/mat4/m4_make_rotation_z.js +47 -47
  369. package/src/core/geom/3d/mat4/m4_normal_matrix3.js +39 -39
  370. package/src/core/geom/3d/mat4/m4_perspective.js +66 -66
  371. package/src/core/geom/3d/mat4/m4_transpose.js +48 -48
  372. package/src/core/geom/3d/morton/mortonEncode_LUT.js +7 -7
  373. package/src/core/geom/3d/morton/mortonEncode_magicbits.js +7 -7
  374. package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -5
  375. package/src/core/geom/3d/normal/hemioct/encode_unit_to_hemioct.js +28 -28
  376. package/src/core/geom/3d/normal/lambert_azimuth/decode_azimuth_to_unit.js +30 -30
  377. package/src/core/geom/3d/normal/lambert_azimuth/encode_unit_to_azimuth.js +19 -19
  378. package/src/core/geom/3d/normal/spherical/decode_spheremap_to_unit.js +38 -38
  379. package/src/core/geom/3d/normal/spherical/encode_unit_to_spheremap.js +30 -30
  380. package/src/core/geom/3d/plane/is_point_within_planes.js +7 -7
  381. package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -47
  382. package/src/core/geom/3d/plane/plane3_compute_convex_3_plane_intersection.js +7 -7
  383. package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +7 -7
  384. package/src/core/geom/3d/plane/plane3_intersect_plane.js +7 -7
  385. package/src/core/geom/3d/plane/plane3_three_compute_convex_3_plane_intersection.js +7 -7
  386. package/src/core/geom/3d/point/point3_raycast.d.ts +33 -0
  387. package/src/core/geom/3d/point/point3_raycast.d.ts.map +1 -0
  388. package/src/core/geom/3d/point/point3_raycast.js +47 -0
  389. package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -60
  390. package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -94
  391. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts +40 -0
  392. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts.map +1 -0
  393. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.js +94 -0
  394. package/src/core/geom/3d/quadric/QUADRIC3_SIZE.js +14 -14
  395. package/src/core/geom/3d/quadric/Quadric3.js +282 -282
  396. package/src/core/geom/3d/quadric/quadric3_add.js +33 -33
  397. package/src/core/geom/3d/quadric/quadric3_clear.js +25 -25
  398. package/src/core/geom/3d/quadric/quadric3_copy.js +27 -27
  399. package/src/core/geom/3d/quadric/quadric3_distance.js +36 -36
  400. package/src/core/geom/3d/quadric/quadric3_evaluate.js +25 -25
  401. package/src/core/geom/3d/quadric/quadric3_optimize.js +45 -45
  402. package/src/core/geom/3d/quadric/quadric3_scale.js +32 -32
  403. package/src/core/geom/3d/quadric/quadric3_set_from_plane_through_point.js +30 -30
  404. package/src/core/geom/3d/quadric/quadric3_set_from_vector4.js +35 -35
  405. package/src/core/geom/3d/quadric/quadric3_sub.js +36 -36
  406. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3.js +28 -28
  407. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3_inverse.js +56 -56
  408. package/src/core/geom/3d/quadric/quadric3_to_vector3.js +19 -19
  409. package/src/core/geom/3d/quaternion/quat3_createFromAxisAngle.js +7 -7
  410. package/src/core/geom/3d/quaternion/quat3_integrate.js +62 -62
  411. package/src/core/geom/3d/quaternion/quat3_multiply.js +31 -31
  412. package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -72
  413. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -69
  414. package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +7 -7
  415. package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +7 -7
  416. package/src/core/geom/3d/ray/ray3_array_compose.js +7 -7
  417. package/src/core/geom/3d/ray/ray3_compute_nearest_point_to_point.js +7 -7
  418. package/src/core/geom/3d/ray/ray3_distance_to_point.js +7 -7
  419. package/src/core/geom/3d/ray/ray3_shift_origin_along_direction.js +7 -7
  420. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +17 -0
  421. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  422. package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -0
  423. package/src/core/geom/3d/shape/CylinderShape3D.d.ts +1 -0
  424. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  425. package/src/core/geom/3d/shape/CylinderShape3D.js +21 -0
  426. package/src/core/geom/3d/shape/PointShape3D.d.ts +7 -0
  427. package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
  428. package/src/core/geom/3d/shape/PointShape3D.js +88 -63
  429. package/src/core/geom/3d/shape/PosedShape3D.d.ts +6 -0
  430. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  431. package/src/core/geom/3d/shape/PosedShape3D.js +27 -0
  432. package/src/core/geom/3d/shape/TransformedShape3D.d.ts +10 -0
  433. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  434. package/src/core/geom/3d/shape/TransformedShape3D.js +13 -0
  435. package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -0
  436. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
  437. package/src/core/geom/3d/shape/Triangle3D.js +26 -0
  438. package/src/core/geom/3d/shape/UnionShape3D.d.ts +6 -0
  439. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  440. package/src/core/geom/3d/shape/UnionShape3D.js +7 -0
  441. package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -45
  442. package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -44
  443. package/src/core/geom/3d/shape/shape3_mesh_from_geometry.js +64 -64
  444. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +7 -7
  445. package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -51
  446. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +7 -7
  447. package/src/core/geom/3d/sphere/compute_bounding_sphere_of_2_spheres.js +7 -7
  448. package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -53
  449. package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -30
  450. package/src/core/geom/3d/sphere/harmonics/sh3_rotate_bl.js +7 -7
  451. package/src/core/geom/3d/sphere/harmonics/sh_compute_K.js +7 -7
  452. package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +7 -7
  453. package/src/core/geom/3d/sphere/sphere_array_intersects_ray.js +7 -7
  454. package/src/core/geom/3d/sphere/sphere_intersects_point.js +7 -7
  455. package/src/core/geom/3d/sphere/sphere_matrix4_transform.js +5 -5
  456. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +7 -7
  457. package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +7 -7
  458. package/src/core/geom/3d/sphere/spherical_from_cartesian.js +7 -7
  459. package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +174 -174
  460. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +7 -7
  461. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_volume.js +7 -7
  462. package/src/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +7 -7
  463. package/src/core/geom/3d/tetrahedra/graphics/build_tetrahedral_mesh_geometry.js +39 -39
  464. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -48
  465. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +173 -173
  466. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_compute_tet_quality.js +22 -22
  467. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -146
  468. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -232
  469. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -255
  470. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -387
  471. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -140
  472. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +7 -7
  473. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -97
  474. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -66
  475. package/src/core/geom/3d/tetrahedra/triangle/triangle_decode_depth.js +59 -59
  476. package/src/core/geom/3d/tetrahedra/triangle/triangle_rasterized_depth_sample_index.js +54 -54
  477. package/src/core/geom/3d/tetrahedra/validate_neighbour.js +70 -70
  478. package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +314 -314
  479. package/src/core/geom/3d/topology/struct/binary/BinaryDataLayer.js +364 -364
  480. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +567 -567
  481. package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +1085 -1085
  482. package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -73
  483. package/src/core/geom/3d/topology/struct/binary/io/bt_loop_kill.js +35 -35
  484. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_append.js +119 -119
  485. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +84 -84
  486. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +172 -172
  487. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -56
  488. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +7 -7
  489. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +227 -227
  490. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +161 -161
  491. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +490 -490
  492. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +120 -120
  493. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +120 -120
  494. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -52
  495. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +115 -115
  496. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_flip.js +142 -142
  497. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -21
  498. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +92 -92
  499. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_split.js +179 -179
  500. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -28
  501. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_kill_only_edge.js +8 -8
  502. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -83
  503. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +89 -89
  504. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_kill.js +25 -25
  505. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +127 -127
  506. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -268
  507. package/src/core/geom/3d/topology/struct/binary/io/face/bt_kill_only_face.js +8 -8
  508. package/src/core/geom/3d/topology/struct/binary/io/face/bt_mesh_kill_degenerate_faces.js +72 -72
  509. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_kill_only_vert.js +7 -7
  510. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js +7 -7
  511. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js +48 -48
  512. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_split_pinched_vertices.js +256 -256
  513. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js +77 -77
  514. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +7 -7
  515. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_kill.js +7 -7
  516. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +148 -148
  517. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_kill_only.js +7 -7
  518. package/src/core/geom/3d/topology/struct/binary/query/bt_collect_boundary_segments.js +29 -29
  519. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_breaks_link_condition.js +107 -107
  520. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_is_legal.js +41 -41
  521. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_would_fold.js +120 -120
  522. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -42
  523. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_is_boundary.js +20 -20
  524. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_read_diamond.js +96 -96
  525. package/src/core/geom/3d/topology/struct/binary/query/bt_face_compute_newell_normal.js +72 -72
  526. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_attached_faces.js +7 -7
  527. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces.js +39 -39
  528. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces_weighted.js +93 -93
  529. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_neighbour_faces.js +7 -7
  530. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_shared_loop.js +48 -48
  531. package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -45
  532. package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -66
  533. package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +7 -7
  534. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -44
  535. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +32 -32
  536. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -53
  537. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -703
  538. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +181 -181
  539. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +133 -133
  540. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -82
  541. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -599
  542. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -108
  543. package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +7 -7
  544. package/src/core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js +7 -7
  545. package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +7 -7
  546. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +7 -7
  547. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +7 -7
  548. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricGeometry.js +7 -7
  549. package/src/core/geom/3d/triangle/compute_triangle_area_3d.js +7 -7
  550. package/src/core/geom/3d/triangle/compute_triangle_normal.js +7 -7
  551. package/src/core/geom/3d/triangle/rayTriangleIntersection.js +7 -7
  552. package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -65
  553. package/src/core/geom/3d/triangle/tri3_closest_point_barycentric.js +171 -171
  554. package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -79
  555. package/src/core/geom/3d/triangle/tri3_ray_intersection.js +160 -160
  556. package/src/core/geom/3d/triangle/tri3_ray_intersection_barycentric.js +96 -96
  557. package/src/core/geom/3d/triangle/tri3_raycast.d.ts +42 -0
  558. package/src/core/geom/3d/triangle/tri3_raycast.d.ts.map +1 -0
  559. package/src/core/geom/3d/triangle/tri3_raycast.js +94 -0
  560. package/src/core/geom/3d/triangle/triangle3_incenter.js +7 -7
  561. package/src/core/geom/3d/triangle/triangle_compute_plane_side.js +7 -7
  562. package/src/core/geom/3d/triangle/triangle_compute_signed_volume.js +7 -7
  563. package/src/core/geom/3d/triangle/triangle_intersects_clipping_volume.js +7 -7
  564. package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +7 -7
  565. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +7 -7
  566. package/src/core/geom/3d/util/make_justified_point_grid.js +7 -7
  567. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +381 -381
  568. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +684 -684
  569. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -554
  570. package/src/core/geom/mat3/m3_cm_from_translation.js +7 -7
  571. package/src/core/geom/mat3/m3_cm_invert.js +7 -7
  572. package/src/core/geom/mat3/m3_make_rotation.js +5 -5
  573. package/src/core/geom/mat3/m3_make_rotation_TBN.js +31 -31
  574. package/src/core/geom/mat3/m3_multiply_vec3.js +7 -7
  575. package/src/core/geom/packing/computeBoundingSphereOfSpheres.js +7 -7
  576. package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +7 -7
  577. package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +7 -7
  578. package/src/core/geom/packing/max-rect/cutArea.js +7 -7
  579. package/src/core/geom/packing/max-rect/findBestContainer.js +7 -7
  580. package/src/core/geom/packing/max-rect/packMaxRectangles.js +7 -7
  581. package/src/core/geom/packing/max-rect/packOneBox.js +7 -7
  582. package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +7 -7
  583. package/src/core/geom/packing/max-rect/removeRedundantBoxesArray.js +7 -7
  584. package/src/core/geom/random/randomPointInBox.js +7 -7
  585. package/src/core/geom/random/randomPointInCircle.js +7 -7
  586. package/src/core/geom/random/randomPointInPoint.js +7 -7
  587. package/src/core/geom/random/randomPointInSphere.js +7 -7
  588. package/src/core/geom/random/randomPointOnBox.js +7 -7
  589. package/src/core/geom/random/randomPointOnSphere.js +7 -7
  590. package/src/core/geom/random/random_orthonormal_basis.js +31 -31
  591. package/src/core/geom/vec/vector_axpy.js +15 -15
  592. package/src/core/geom/vec/vector_axpy_offset.js +20 -20
  593. package/src/core/geom/vec/vector_copy.js +7 -7
  594. package/src/core/geom/vec/vector_dot_offset.js +25 -25
  595. package/src/core/geom/vec/vector_scale_array.js +7 -7
  596. package/src/core/geom/vec2/v2_rotate.js +21 -21
  597. package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -21
  598. package/src/core/geom/vec3/v3_angle_atan2_between.js +7 -7
  599. package/src/core/geom/vec3/v3_angle_between.js +7 -7
  600. package/src/core/geom/vec3/v3_angle_cos_between.js +7 -7
  601. package/src/core/geom/vec3/v3_array_add_in_place.js +7 -7
  602. package/src/core/geom/vec3/v3_array_centroid.js +7 -7
  603. package/src/core/geom/vec3/v3_array_copy.js +7 -7
  604. package/src/core/geom/vec3/v3_array_displace_in_direction.js +7 -7
  605. package/src/core/geom/vec3/v3_array_equals.js +7 -7
  606. package/src/core/geom/vec3/v3_array_immediate_add.js +7 -7
  607. package/src/core/geom/vec3/v3_array_matrix4_rotate.js +7 -7
  608. package/src/core/geom/vec3/v3_array_normalize.js +7 -7
  609. package/src/core/geom/vec3/v3_array_scale.js +7 -7
  610. package/src/core/geom/vec3/v3_compute_interior_angle.js +7 -7
  611. package/src/core/geom/vec3/v3_cotangent.js +33 -33
  612. package/src/core/geom/vec3/v3_dot_array_array.js +7 -7
  613. package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -30
  614. package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -49
  615. package/src/core/geom/vec3/v3_matrix4_rotate.js +7 -7
  616. package/src/core/geom/vec3/v3_multiply.js +23 -23
  617. package/src/core/geom/vec3/v3_multiply_scalar.js +7 -7
  618. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +49 -49
  619. package/src/core/geom/vec3/v3_quat3_apply.js +7 -7
  620. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +7 -7
  621. package/src/core/geom/vec3/v3_quaternion_apply.js +39 -39
  622. package/src/core/geom/vec3/v3_quaternion_apply_inverse.js +41 -41
  623. package/src/core/geom/vec3/v3_reflect.js +24 -24
  624. package/src/core/geom/vec3/v3_scale_array.js +18 -18
  625. package/src/core/geom/vec3/v3_triple_cross.js +45 -45
  626. package/src/core/geom/vec3/v3_triple_cross_product.js +7 -7
  627. package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -21
  628. package/src/core/geom/vec4/v4_multiply_mat4.js +7 -7
  629. package/src/core/graph/csr/csr_graph_build_distance_field.js +112 -112
  630. package/src/core/graph/csr/csr_graph_find_edge.js +25 -25
  631. package/src/core/graph/csr/csr_graph_find_shortest_path.js +105 -105
  632. package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -94
  633. package/src/core/graph/graph_k_means_cluster.js +250 -250
  634. package/src/core/graph/layout/CircleLayout.js +1 -1
  635. package/src/core/graph/layout/box/BoxLayouter.js +1 -1
  636. package/src/core/graph/layout/graph_peel_topological_layer.js +45 -45
  637. package/src/core/graph/metis/BinaryTopologyFaceGraph.js +49 -49
  638. package/src/core/graph/metis/bt_mesh_build_face_graph.js +111 -111
  639. package/src/core/graph/metis/cluster_mesh_metis.js +137 -137
  640. package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -100
  641. package/src/core/graph/metis/native/bisection/compute_2way_params.js +86 -86
  642. package/src/core/graph/metis/native/bisection/fm_2way.js +297 -297
  643. package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -137
  644. package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -119
  645. package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -94
  646. package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -158
  647. package/src/core/graph/metis/native/coarsen/match_shem.js +175 -175
  648. package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -122
  649. package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -170
  650. package/src/core/graph/metis/native/metis_partition_kway.js +126 -126
  651. package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -53
  652. package/src/core/graph/metis/native/refine/fm_kway.js +487 -487
  653. package/src/core/graph/metis/native/refine/project_kway.js +43 -43
  654. package/src/core/graph/metis/native/refine/refine_kway.js +43 -43
  655. package/src/core/lang/reactive/pegjs/parser.js +3458 -3458
  656. package/src/core/math/complex/complex_horner_eval.js +31 -31
  657. package/src/core/math/computeWholeDivisorLow.js +33 -33
  658. package/src/core/math/f32_next_down.js +24 -24
  659. package/src/core/math/f32_next_up.js +27 -27
  660. package/src/core/math/hash/hash_mix2.js +22 -22
  661. package/src/core/math/hash/hash_mix3.js +19 -19
  662. package/src/core/math/linalg/cg/cg_solve.js +106 -106
  663. package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -59
  664. package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -43
  665. package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -122
  666. package/src/core/math/linalg/eigen/matrix_qr_in_place.js +190 -190
  667. package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -117
  668. package/src/core/math/linalg/givens/givens_apply_rows.js +28 -28
  669. package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -30
  670. package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -39
  671. package/src/core/math/linalg/lu_factor_linear_system.js +108 -108
  672. package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
  673. package/src/core/math/linalg/polynomial_add_into.js +29 -29
  674. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +159 -159
  675. package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -16
  676. package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -19
  677. package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -14
  678. package/src/core/math/linalg/polynomial_multiply.js +41 -41
  679. package/src/core/math/linalg/polynomial_real_roots_in_interval.js +211 -211
  680. package/src/core/math/linalg/polynomial_root_bound_cauchy.js +30 -30
  681. package/src/core/math/linalg/polynomial_scale_into.js +17 -17
  682. package/src/core/math/linalg/polynomial_sub_into.js +29 -29
  683. package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -96
  684. package/src/core/math/linalg/sor_optimal_omega.js +42 -42
  685. package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -88
  686. package/src/core/math/linalg/sparse/spm_matvec.js +22 -22
  687. package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -29
  688. package/src/core/math/lookup/LUT_HEATMAP.js +30 -30
  689. package/src/core/math/lookup/ParameterLookupTable.js +495 -495
  690. package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -6
  691. package/src/core/math/noise/curl_noise_3d.js +18 -18
  692. package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -28
  693. package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -48
  694. package/src/core/math/physics/kinematics/computeInterceptPoint.js +79 -79
  695. package/src/core/math/random/randomSeed.js +16 -16
  696. package/src/core/math/random/random_pick_weighted_index.js +26 -26
  697. package/src/core/math/round_half_away_from_zero.js +13 -13
  698. package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -107
  699. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +125 -125
  700. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +50 -50
  701. package/src/core/math/spline/spline3_hermite_reverse.js +49 -49
  702. package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -37
  703. package/src/core/math/statistics/hammersley_sequence.js +53 -53
  704. package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
  705. package/src/core/path/convertPathToURL.js +107 -107
  706. package/src/core/process/delay.js +16 -16
  707. package/src/core/science/units/MEASUREMENT_UNITS_SCIENTIFIC.js +87 -87
  708. package/src/core/science/units/MEASUREMENT_UNITS_SI.js +17 -17
  709. package/src/core/science/units/NamedUnit.js +48 -48
  710. package/src/core/science/units/UNIT_DIMENSION_MAPPING.js +29 -29
  711. package/src/core/science/units/UnitDimension.js +28 -28
  712. package/src/core/science/units/format_quantity.js +74 -74
  713. package/src/core/science/units/magnitude_prefix.js +127 -127
  714. package/src/core/science/units/si/AMPERE.js +13 -13
  715. package/src/core/science/units/si/CANDELA.js +13 -13
  716. package/src/core/science/units/si/DIMENSIONLESS.js +14 -14
  717. package/src/core/science/units/si/KELVIN.js +13 -13
  718. package/src/core/science/units/si/KILOGRAM.js +13 -13
  719. package/src/core/science/units/si/METER.js +13 -13
  720. package/src/core/science/units/si/MOLE.js +13 -13
  721. package/src/core/science/units/si/SECOND.js +13 -13
  722. package/src/core/science/units/si/derived/BECQUEREL.js +16 -16
  723. package/src/core/science/units/si/derived/COULOMB.js +13 -13
  724. package/src/core/science/units/si/derived/FARAD.js +13 -13
  725. package/src/core/science/units/si/derived/GRAY.js +16 -16
  726. package/src/core/science/units/si/derived/HENRY.js +13 -13
  727. package/src/core/science/units/si/derived/HERTZ.js +16 -16
  728. package/src/core/science/units/si/derived/JOULE.js +13 -13
  729. package/src/core/science/units/si/derived/KATAL.js +13 -13
  730. package/src/core/science/units/si/derived/LUMEN.js +14 -14
  731. package/src/core/science/units/si/derived/LUX.js +13 -13
  732. package/src/core/science/units/si/derived/NEWTON.js +13 -13
  733. package/src/core/science/units/si/derived/OHM.js +13 -13
  734. package/src/core/science/units/si/derived/PASCAL.js +13 -13
  735. package/src/core/science/units/si/derived/SIEMENS.js +13 -13
  736. package/src/core/science/units/si/derived/SIEVERT.js +16 -16
  737. package/src/core/science/units/si/derived/TESLA.js +13 -13
  738. package/src/core/science/units/si/derived/VOLT.js +13 -13
  739. package/src/core/science/units/si/derived/WATT.js +13 -13
  740. package/src/core/science/units/si/derived/WEBER.js +13 -13
  741. package/src/core/science/units/unit_matrix_to_string.js +403 -403
  742. package/src/engine/Engine.js +726 -726
  743. package/src/engine/EngineHarness.js +590 -590
  744. package/src/engine/REVIEW_2026_07_03.md +211 -211
  745. package/src/engine/animation/AnimatedActions.js +81 -81
  746. package/src/engine/animation/AnimationUtils.js +252 -252
  747. package/src/engine/animation/async/TimeSeries.js +300 -300
  748. package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -60
  749. package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -22
  750. package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -38
  751. package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -22
  752. package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -22
  753. package/src/engine/asset/GameAssetType.d.ts +0 -1
  754. package/src/engine/asset/GameAssetType.js +0 -1
  755. package/src/engine/asset/load_model_scene_bundle.js +27 -27
  756. package/src/engine/asset/loaders/font/FontAsset.js +21 -21
  757. package/src/engine/asset/loaders/font/FontAssetLoader.js +20 -20
  758. package/src/engine/asset/loaders/gltf_test_fixtures.js +358 -358
  759. package/src/engine/asset/loaders/image/ImageBitmapAssetLoader.js +52 -52
  760. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +94 -94
  761. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -59
  762. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -55
  763. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -74
  764. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -34
  765. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -46
  766. package/src/engine/control/first-person/DESIGN.md +640 -640
  767. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -563
  768. package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -134
  769. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2045 -2045
  770. package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -30
  771. package/src/engine/control/first-person/math/Spring.js +71 -71
  772. package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -23
  773. package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -41
  774. package/src/engine/control/first-person/math/computeMassRatios.js +44 -44
  775. package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -34
  776. package/src/engine/control/first-person/math/dampedSpringStep.js +72 -72
  777. package/src/engine/control/first-person/math/jumpDynamics.js +108 -108
  778. package/src/engine/control/first-person/math/stepTowards.js +20 -20
  779. package/src/engine/control/first-person/pose/FirstPersonPose.js +137 -137
  780. package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -27
  781. package/src/engine/control/first-person/test/buildTestPlayer.js +36 -36
  782. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -531
  783. package/src/engine/ecs/EntityManager.d.ts +7 -3
  784. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  785. package/src/engine/ecs/EntityManager.js +10 -5
  786. package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
  787. package/src/engine/ecs/async/SystemWorkerHost.js +329 -329
  788. package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -143
  789. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -246
  790. package/src/engine/ecs/async/WorkerSystem.js +702 -702
  791. package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -150
  792. package/src/engine/ecs/fow/FogOfWar.js +707 -707
  793. package/src/engine/ecs/fow/FogOfWarEdgeMode.js +40 -40
  794. package/src/engine/ecs/fow/fog_of_war_mask_world_bounds.js +24 -24
  795. package/src/engine/ecs/fow/fog_of_war_uv_transform.js +30 -30
  796. package/src/engine/ecs/gui/hud/testHudEdgeStick.js +132 -134
  797. package/src/engine/ecs/hierarchy/EntityChildIndex.js +140 -140
  798. package/src/engine/ecs/ik/IKProblem.js +42 -42
  799. package/src/engine/ecs/ik/InverseKinematicsSystem.js +193 -193
  800. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +134 -134
  801. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +97 -97
  802. package/src/engine/ecs/ik/ik_bone_chain.js +85 -85
  803. package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -119
  804. package/src/engine/ecs/parent/EntityNode.d.ts +1 -1
  805. package/src/engine/ecs/parent/EntityNode.js +2 -2
  806. package/src/engine/ecs/parent/ParentEntitySystem.js +255 -255
  807. package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
  808. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  809. package/src/engine/ecs/speaker/VoiceSystem.js +13 -7
  810. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +269 -269
  811. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -315
  812. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -331
  813. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -199
  814. package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -57
  815. package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -56
  816. package/src/engine/ecs/storage/binary/object-v2/README.md +110 -110
  817. package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -48
  818. package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -44
  819. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +265 -265
  820. package/src/engine/ecs/terrain/TerrainClouds.js +97 -97
  821. package/src/engine/ecs/terrain/ecs/StubTerrainBuildWorker.js +65 -65
  822. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +4 -0
  823. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  824. package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
  825. package/src/engine/ecs/terrain/ecs/TerrainSystem.js +209 -209
  826. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +316 -316
  827. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +416 -416
  828. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +525 -525
  829. package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +302 -302
  830. package/src/engine/ecs/terrain/tiles/TerrainTile.js +561 -561
  831. package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +896 -896
  832. package/src/engine/ecs/terrain/tiles/tile_geometry_to_meshlets.js +36 -36
  833. package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
  834. package/src/engine/ecs/transform/Transform64.js +814 -814
  835. package/src/engine/ecs/transform/t64_copy_from_transform.js +35 -35
  836. package/src/engine/ecs/transform/t64_equals_transform.js +55 -55
  837. package/src/engine/ecs/transform/t64_look_rotation.js +36 -36
  838. package/src/engine/ecs/transform/t64_transform_direction.js +25 -25
  839. package/src/engine/ecs/transform/transform_copy_from_t64.js +28 -28
  840. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +345 -345
  841. package/src/engine/ecs/transform-attachment/transform_attachment_find_descendant_by_name.js +65 -65
  842. package/src/engine/ecs/transform-attachment/transform_attachment_parent_of.js +24 -24
  843. package/src/engine/ecs/util/hideEntityGracefully.js +208 -208
  844. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -446
  845. package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -14
  846. package/src/engine/graphics/FrameThrottle.js +2 -2
  847. package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +207 -207
  848. package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +151 -151
  849. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -22
  850. package/src/engine/graphics/ecs/camera/Camera.js +165 -165
  851. package/src/engine/graphics/ecs/camera/camera_active_frustum.js +25 -25
  852. package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
  853. package/src/engine/graphics/ecs/camera/camera_traverse_active.js +18 -18
  854. package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +170 -170
  855. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -68
  856. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +296 -296
  857. package/src/engine/graphics/ecs/decal/v2/Decal.js +220 -220
  858. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +56 -56
  859. package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_0_1.js +38 -38
  860. package/src/engine/graphics/ecs/light/shadow/compute_view_frustum_aabb_in_space.js +62 -62
  861. package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +77 -77
  862. package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -33
  863. package/src/engine/graphics/ecs/mesh-v2/DeferredBoundsQueue.js +88 -88
  864. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
  865. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +303 -303
  866. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +1 -1
  867. package/src/engine/graphics/ecs/mesh-v2/aggregate/serialization/SGMeshSerializationAdapter.js +39 -39
  868. package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +36 -36
  869. package/src/engine/graphics/ecs/path/tube/build/GeometryStream.js +94 -94
  870. package/src/engine/graphics/ecs/path/tube/build/build_stream_catmullrom.js +310 -310
  871. package/src/engine/graphics/ecs/path/tube/build/build_stream_linear.js +73 -73
  872. package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +199 -199
  873. package/src/engine/graphics/ecs/path/tube/build/makeTubeStream.js +217 -217
  874. package/src/engine/graphics/ecs/path/tube/build/make_cap.js +340 -340
  875. package/src/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -40
  876. package/src/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +153 -153
  877. package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -64
  878. package/src/engine/graphics/ecs/trail3d/Trail3D.js +483 -483
  879. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +26 -26
  880. package/src/engine/graphics/ecs/trail3d/advance_trail.js +42 -42
  881. package/src/engine/graphics/ecs/trail3d/make_gradient_stroke.js +58 -58
  882. package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +90 -90
  883. package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +75 -75
  884. package/src/engine/graphics/ecs/trail3d/trail_head_position.js +19 -19
  885. package/src/engine/graphics/ecs/water/Water.js +104 -104
  886. package/src/engine/graphics/geometry/AttributeData.js +84 -84
  887. package/src/engine/graphics/geometry/buffered/geometry_compute_vertex_normals_indexed.js +49 -49
  888. package/src/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +288 -288
  889. package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -87
  890. package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -102
  891. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_buffered_geometry.js +52 -52
  892. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_indexed_geometry.js +20 -20
  893. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_unindexed_geometry.js +19 -19
  894. package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -135
  895. package/src/engine/graphics/impostors/voxel/README.md +149 -149
  896. package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -141
  897. package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -20
  898. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +2 -0
  899. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
  900. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +2 -0
  901. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
  902. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +2 -0
  903. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
  904. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts +1 -2
  905. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts.map +1 -1
  906. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +2 -0
  907. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
  908. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +2 -0
  909. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
  910. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +2 -0
  911. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
  912. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +2 -0
  913. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
  914. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +2 -0
  915. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
  916. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +2 -0
  917. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
  918. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +2 -0
  919. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
  920. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +2 -0
  921. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
  922. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +2 -0
  923. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
  924. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +2 -0
  925. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
  926. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +2 -0
  927. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
  928. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +2 -0
  929. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
  930. package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -45
  931. package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +229 -229
  932. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -10
  933. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
  934. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1149 -1149
  935. package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +472 -472
  936. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -2
  937. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -2
  938. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
  939. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +3 -5
  940. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +135 -135
  941. package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -168
  942. package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -18
  943. package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -2
  944. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +219 -219
  945. package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
  946. package/src/engine/graphics/render/frame_graph/FrameGraphScope.js +72 -72
  947. package/src/engine/graphics/render/frame_graph/FrameGraphState.js +39 -39
  948. package/src/engine/graphics/render/frame_graph/ResourceEntry.js +96 -96
  949. package/src/engine/graphics/render/gizmo/GizmoShapeBuffer.js +105 -105
  950. package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +162 -162
  951. package/src/engine/graphics/render/layers/RenderLayer.js +95 -95
  952. package/src/engine/graphics/sh3/build_injected_shader.js +56 -56
  953. package/src/engine/graphics/sh3/grid_dilate_average.js +175 -175
  954. package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +506 -506
  955. package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +161 -161
  956. package/src/engine/graphics/sh3/path_tracer/PathTracedRenderer.js +205 -205
  957. package/src/engine/graphics/sh3/path_tracer/PathTracedScene.js +698 -698
  958. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +490 -490
  959. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -2
  960. package/src/engine/graphics/sh3/path_tracer/make_sky_hosek.js +45 -45
  961. package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +73 -73
  962. package/src/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +32 -32
  963. package/src/engine/graphics/sh3/path_tracer/texture/apply_texture_clamping_to_coordinate.js +28 -28
  964. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +142 -142
  965. package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -70
  966. package/src/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +388 -388
  967. package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -277
  968. package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -57
  969. package/src/engine/graphics/shadows/vsm.glsl.js +43 -43
  970. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -81
  971. package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -15
  972. package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -26
  973. package/src/engine/graphics/texture/isImageBitmap.js +2 -2
  974. package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -189
  975. package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -51
  976. package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
  977. package/src/engine/graphics/texture/sampler/Sampler2DArray.js +172 -172
  978. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +466 -466
  979. package/src/engine/graphics/texture/sampler/compare/flip/FlipParameters.js +18 -18
  980. package/src/engine/graphics/texture/sampler/compare/flip/FlipPooling.js +33 -33
  981. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_feature_kernel.js +147 -147
  982. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_spatial_kernel.js +177 -177
  983. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_color_difference.js +104 -104
  984. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_feature_difference.js +76 -76
  985. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_pixels_per_degree.js +34 -34
  986. package/src/engine/graphics/texture/sampler/compare/flip/flip_constants.js +77 -77
  987. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_feature.js +48 -48
  988. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_spatial.js +86 -86
  989. package/src/engine/graphics/texture/sampler/compare/flip/flip_pool.js +71 -71
  990. package/src/engine/graphics/texture/sampler/compare/flip/flip_srgb_to_ycxcz.js +55 -55
  991. package/src/engine/graphics/texture/sampler/compare/flip/sampler2d_flip_ldr.js +80 -80
  992. package/src/engine/graphics/texture/sampler/filter/SeparableKernel.js +82 -82
  993. package/src/engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js +122 -122
  994. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +148 -148
  995. package/src/engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js +82 -82
  996. package/src/engine/graphics/texture/sampler/sampler2d_combine.js +67 -67
  997. package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -54
  998. package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -63
  999. package/src/engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js +33 -33
  1000. package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -68
  1001. package/src/engine/graphics/texture/shade_image_preview.js +103 -103
  1002. package/src/engine/graphics/trail/TrailSpawnMode.js +20 -20
  1003. package/src/engine/graphics/trail/tube/TubeX.js +573 -573
  1004. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +58 -58
  1005. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +34 -34
  1006. package/src/engine/graphics/trail/tube/tube_attributes_spec.js +69 -69
  1007. package/src/engine/graphics/trail/tube/tube_frame.js +83 -83
  1008. package/src/engine/graphics3/AnimationGraphSystem3.js +329 -329
  1009. package/src/engine/graphics3/AnimationSystem3.js +352 -352
  1010. package/src/engine/graphics3/CameraSystem3.js +170 -170
  1011. package/src/engine/graphics3/DecalSystem3.js +441 -441
  1012. package/src/engine/graphics3/FogOfWarSystem3.js +265 -265
  1013. package/src/engine/graphics3/GraphicsEngine3.js +776 -776
  1014. package/src/engine/graphics3/HighlightSystem3.js +241 -241
  1015. package/src/engine/graphics3/LightSystem3.js +211 -211
  1016. package/src/engine/graphics3/MeshSystem3.js +646 -646
  1017. package/src/engine/graphics3/ParticleEmitterSystem3.js +289 -289
  1018. package/src/engine/graphics3/PathDisplaySystem3.js +309 -309
  1019. package/src/engine/graphics3/PickingQuery.js +48 -48
  1020. package/src/engine/graphics3/PickingResult.js +47 -47
  1021. package/src/engine/graphics3/PickingSystem3.js +273 -273
  1022. package/src/engine/graphics3/ShadeCameraAdapter.js +55 -55
  1023. package/src/engine/graphics3/ShadedGeometrySystem3.js +169 -169
  1024. package/src/engine/graphics3/TerrainSystem3.js +566 -566
  1025. package/src/engine/graphics3/TooltipComponentSystem3.js +328 -328
  1026. package/src/engine/graphics3/Trail3DSystem3.js +221 -221
  1027. package/src/engine/graphics3/WaterSystem3.js +293 -293
  1028. package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
  1029. package/src/engine/graphics3/animation/wrap_clip_time.js +23 -23
  1030. package/src/engine/graphics3/animation/write_graph_playback.js +48 -48
  1031. package/src/engine/graphics3/camera_sync_from_transform.js +29 -29
  1032. package/src/engine/graphics3/debug/gizmo_view_from_camera.js +48 -48
  1033. package/src/engine/graphics3/decal/DECAL_CLUSTER.js +61 -61
  1034. package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +117 -117
  1035. package/src/engine/graphics3/decal/DecalAtlas.js +194 -194
  1036. package/src/engine/graphics3/decal/DecalAtlasFill.js +32 -32
  1037. package/src/engine/graphics3/decal/GPUDecalRenderer.js +360 -360
  1038. package/src/engine/graphics3/decal/chunk_decal_intersects_frustum.js +59 -59
  1039. package/src/engine/graphics3/decal/chunk_decal_sampling_info.js +57 -57
  1040. package/src/engine/graphics3/decal/chunk_decal_surface_frame.js +54 -54
  1041. package/src/engine/graphics3/decal/decal_atlas_border.js +69 -69
  1042. package/src/engine/graphics3/decal/decal_gpu_records.js +120 -120
  1043. package/src/engine/graphics3/decal/decal_material_samplers.js +109 -109
  1044. package/src/engine/graphics3/decal/graph_build_decal_clusters.js +212 -212
  1045. package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +207 -207
  1046. package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +108 -108
  1047. package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +151 -151
  1048. package/src/engine/graphics3/highlight/HIGHLIGHT_OUTLINE.js +23 -23
  1049. package/src/engine/graphics3/highlight/chunk_highlight_lookup.js +53 -53
  1050. package/src/engine/graphics3/highlight/pack_highlight_color.js +44 -44
  1051. package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
  1052. package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
  1053. package/src/engine/graphics3/load_cube_environment.js +68 -68
  1054. package/src/engine/graphics3/make_default_environment.js +107 -107
  1055. package/src/engine/graphics3/particles/particle_gpu_records.js +221 -221
  1056. package/src/engine/graphics3/path/build_tube_path_streams.js +98 -98
  1057. package/src/engine/graphics3/path/tube_stream_write_geometry.js +90 -90
  1058. package/src/engine/graphics3/preview/ModelPreviewFraming.js +35 -35
  1059. package/src/engine/graphics3/preview/make_model_preview_scene.js +154 -154
  1060. package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
  1061. package/src/engine/graphics3/preview/model_preview_framing.js +70 -70
  1062. package/src/engine/graphics3/shade_bundle_bounds.js +55 -55
  1063. package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
  1064. package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
  1065. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.js +481 -481
  1066. package/src/engine/graphics3/terrain/chunk_terrain_grid_overlay.js +61 -61
  1067. package/src/engine/graphics3/terrain/chunk_terrain_splat_mix.js +66 -66
  1068. package/src/engine/graphics3/terrain/pack_terrain_row_table.js +53 -53
  1069. package/src/engine/graphics3/terrain/shader_terrain_splat.js +215 -215
  1070. package/src/engine/graphics3/trail/trail_tube_write_geometry.js +158 -158
  1071. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +2 -2
  1072. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  1073. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -717
  1074. package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
  1075. package/src/engine/input/devices/GamepadDevice.js +466 -466
  1076. package/src/engine/input/devices/ManualInputDevice.js +152 -152
  1077. package/src/engine/input/devices/PointerCoordinates.js +26 -26
  1078. package/src/engine/input/devices/events/GamepadEvents.js +10 -10
  1079. package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
  1080. package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
  1081. package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
  1082. package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
  1083. package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
  1084. package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
  1085. package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
  1086. package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
  1087. package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
  1088. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
  1089. package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
  1090. package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
  1091. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
  1092. package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
  1093. package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
  1094. package/src/engine/input/ecs/components/Input.js +118 -118
  1095. package/src/engine/input/ecs/components/InputController.js +78 -78
  1096. package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
  1097. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
  1098. package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
  1099. package/src/engine/input/ecs/ism/InputBinding.js +71 -71
  1100. package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
  1101. package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
  1102. package/src/engine/input/ecs/ism/InputDirection.js +33 -33
  1103. package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
  1104. package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
  1105. package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
  1106. package/src/engine/input/ecs/ism/InputSource.js +67 -67
  1107. package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
  1108. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
  1109. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
  1110. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
  1111. package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
  1112. package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
  1113. package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
  1114. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
  1115. package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
  1116. package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
  1117. package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
  1118. package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
  1119. package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
  1120. package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
  1121. package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
  1122. package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
  1123. package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
  1124. package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
  1125. package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
  1126. package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
  1127. package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
  1128. package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
  1129. package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
  1130. package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
  1131. package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
  1132. package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
  1133. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
  1134. package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
  1135. package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
  1136. package/src/engine/input/ecs/systems/InputSystem.js +162 -162
  1137. package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
  1138. package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
  1139. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
  1140. package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
  1141. package/src/engine/interpolation/Interpoland.js +49 -49
  1142. package/src/engine/interpolation/Interpolated.js +149 -149
  1143. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
  1144. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
  1145. package/src/engine/interpolation/pose_interpoland.js +27 -27
  1146. package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
  1147. package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
  1148. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
  1149. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
  1150. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
  1151. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
  1152. package/src/engine/network/CONGESTION_CONTROL.md +87 -87
  1153. package/src/engine/network/README.md +52 -52
  1154. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
  1155. package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
  1156. package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
  1157. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
  1158. package/src/engine/network/convertPathToURL.js +2 -2
  1159. package/src/engine/network/core/MalformedPacketError.js +26 -26
  1160. package/src/engine/network/core/quantize/quantize_float.js +73 -73
  1161. package/src/engine/network/core/quantize/quantize_position.js +65 -65
  1162. package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
  1163. package/src/engine/network/core/sequence/seq16.js +69 -69
  1164. package/src/engine/network/core/sequence/seq32.js +73 -73
  1165. package/src/engine/network/ecs/NetworkSystem.js +84 -84
  1166. package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
  1167. package/src/engine/network/ecs/owner_authorization.js +55 -55
  1168. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
  1169. package/src/engine/network/replication/ScopeFilter.js +71 -71
  1170. package/src/engine/network/sim/ActionLog.js +152 -152
  1171. package/src/engine/network/sim/SimAction.js +273 -273
  1172. package/src/engine/network/sim/Snapshotter.js +222 -222
  1173. package/src/engine/network/state/FrameBufferRing.js +168 -168
  1174. package/src/engine/network/state/InputRing.js +134 -134
  1175. package/src/engine/network/state/PriorityAccumulator.js +180 -180
  1176. package/src/engine/network/time/TimeDilation.js +62 -62
  1177. package/src/engine/network/transport/Channel.js +336 -336
  1178. package/src/engine/network/transport/Transport.js +203 -203
  1179. package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
  1180. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
  1181. package/src/engine/network/transport/fragments/fragment_send.js +147 -147
  1182. package/src/engine/network/transport/fragments/packet_size.js +101 -101
  1183. package/src/engine/network/xhr.js +23 -23
  1184. package/src/engine/physics/BULLET_REVIEW.md +945 -945
  1185. package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
  1186. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
  1187. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
  1188. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
  1189. package/src/engine/physics/JOLT_REVIEW.md +913 -913
  1190. package/src/engine/physics/PLAN.md +1120 -1094
  1191. package/src/engine/physics/RAPIER_REVIEW.md +934 -934
  1192. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
  1193. package/src/engine/physics/REVIEW_002.md +151 -151
  1194. package/src/engine/physics/REVIEW_003.md +166 -166
  1195. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
  1196. package/src/engine/physics/constraint/DofMode.js +35 -35
  1197. package/src/engine/physics/constraint/solve_constraints.js +1 -1
  1198. package/src/engine/physics/contact/combine_material.js +35 -35
  1199. package/src/engine/physics/ecs/BodyKind.js +29 -29
  1200. package/src/engine/physics/ecs/Collider.js +177 -177
  1201. package/src/engine/physics/ecs/ColliderFlags.js +15 -15
  1202. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
  1203. package/src/engine/physics/ecs/Joint.js +475 -475
  1204. package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
  1205. package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
  1206. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  1207. package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
  1208. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
  1209. package/src/engine/physics/ecs/SleepState.js +12 -12
  1210. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
  1211. package/src/engine/physics/fluid/FluidField.js +867 -867
  1212. package/src/engine/physics/fluid/FluidSimulator.js +832 -832
  1213. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
  1214. package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
  1215. package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
  1216. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
  1217. package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
  1218. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
  1219. package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
  1220. package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
  1221. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
  1222. package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
  1223. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
  1224. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
  1225. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
  1226. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
  1227. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
  1228. package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
  1229. package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
  1230. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
  1231. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
  1232. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
  1233. package/src/engine/physics/fluid/prototype.js +126 -126
  1234. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
  1235. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
  1236. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
  1237. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
  1238. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
  1239. package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
  1240. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
  1241. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
  1242. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
  1243. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
  1244. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
  1245. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
  1246. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
  1247. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
  1248. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
  1249. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
  1250. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
  1251. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
  1252. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
  1253. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
  1254. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
  1255. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
  1256. package/src/engine/physics/integration/integrate_position.js +97 -97
  1257. package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
  1258. package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
  1259. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
  1260. package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
  1261. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
  1262. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
  1263. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  1264. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
  1265. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
  1266. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
  1267. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  1268. package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
  1269. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
  1270. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
  1271. package/src/engine/physics/persistence/solver_caches.js +309 -309
  1272. package/src/engine/physics/queries/overlap_shape.js +187 -187
  1273. package/src/engine/physics/queries/raycast.d.ts +7 -4
  1274. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  1275. package/src/engine/physics/queries/raycast.js +11 -5
  1276. package/src/engine/physics/queries/shape_cast.js +417 -417
  1277. package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
  1278. package/src/engine/physics/solver/friction_cone.js +37 -37
  1279. package/src/engine/physics/solver/solve_contacts.js +1 -1
  1280. package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
  1281. package/src/engine/platform/WebEnginePlatform.js +1 -2
  1282. package/src/engine/scene/SerializedScene.d.ts +2 -2
  1283. package/src/engine/scene/SerializedScene.d.ts.map +1 -1
  1284. package/src/engine/scene/transitionToScene.js +121 -121
  1285. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  1286. package/src/engine/simulation/Ticker.js +33 -0
  1287. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
  1288. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
  1289. package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
  1290. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
  1291. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
  1292. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
  1293. package/src/engine/sound/simulation/README.md +277 -277
  1294. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
  1295. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
  1296. package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
  1297. package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
  1298. package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
  1299. package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
  1300. package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
  1301. package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
  1302. package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
  1303. package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
  1304. package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
  1305. package/src/engine/sound/simulation/core/RayHit.js +50 -50
  1306. package/src/engine/sound/simulation/core/VolumeField.js +149 -149
  1307. package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
  1308. package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
  1309. package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
  1310. package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
  1311. package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
  1312. package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
  1313. package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
  1314. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
  1315. package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
  1316. package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
  1317. package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
  1318. package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
  1319. package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
  1320. package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
  1321. package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
  1322. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
  1323. package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
  1324. package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
  1325. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
  1326. package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
  1327. package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
  1328. package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
  1329. package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
  1330. package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
  1331. package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
  1332. package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
  1333. package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
  1334. package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
  1335. package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
  1336. package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
  1337. package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
  1338. package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
  1339. package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
  1340. package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
  1341. package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
  1342. package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
  1343. package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
  1344. package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
  1345. package/src/engine/sound/sopra/README.md +643 -643
  1346. package/src/engine/sound/sopra/SopraEngine.js +432 -432
  1347. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
  1348. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
  1349. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
  1350. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
  1351. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
  1352. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
  1353. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
  1354. package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
  1355. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
  1356. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
  1357. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
  1358. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
  1359. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
  1360. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
  1361. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
  1362. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
  1363. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
  1364. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
  1365. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
  1366. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
  1367. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
  1368. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
  1369. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
  1370. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
  1371. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
  1372. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
  1373. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
  1374. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  1375. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
  1376. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
  1377. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
  1378. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
  1379. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
  1380. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
  1381. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
  1382. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
  1383. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
  1384. package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
  1385. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
  1386. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
  1387. package/src/engine/sound/sopra/runtime/Voice.js +145 -145
  1388. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
  1389. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
  1390. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
  1391. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
  1392. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
  1393. package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
  1394. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
  1395. package/src/engine/ui/DraggableAspect.js +115 -115
  1396. package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
  1397. package/src/generation/COORDINATES.md +54 -54
  1398. package/src/generation/GridGenerationResource.js +24 -24
  1399. package/src/generation/GridTaskGroup.js +214 -214
  1400. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
  1401. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
  1402. package/src/generation/automata/CellularAutomata.js +22 -22
  1403. package/src/generation/compute_writes_union.js +25 -25
  1404. package/src/generation/filtering/CellFilter.js +117 -117
  1405. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
  1406. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
  1407. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
  1408. package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
  1409. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
  1410. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
  1411. package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
  1412. package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
  1413. package/src/generation/grid/GridData.js +233 -233
  1414. package/src/generation/grid/coords/grid_to_texel.js +10 -10
  1415. package/src/generation/grid/coords/texel_to_grid.js +10 -10
  1416. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
  1417. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
  1418. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
  1419. package/src/generation/grid/generation/GridTaskSequence.js +58 -58
  1420. package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
  1421. package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
  1422. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
  1423. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
  1424. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
  1425. package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
  1426. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
  1427. package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
  1428. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
  1429. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
  1430. package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
  1431. package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
  1432. package/src/generation/placement/action/GridCellAction.js +45 -45
  1433. package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
  1434. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
  1435. package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
  1436. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
  1437. package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
  1438. package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
  1439. package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
  1440. package/src/generation/theme/ThemeEngine.js +666 -666
  1441. package/src/shade/RENDERER_CONTRACT.md +568 -0
  1442. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
  1443. package/src/shade/device/ShadeDeviceFailure.js +104 -104
  1444. package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
  1445. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
  1446. package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
  1447. package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
  1448. package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
  1449. package/src/shade/device/mock/SoftwareGPUError.js +63 -63
  1450. package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
  1451. package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
  1452. package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
  1453. package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
  1454. package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
  1455. package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
  1456. package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
  1457. package/src/shade/device/mock/texture_copy.js +287 -287
  1458. package/src/shade/device/timing/GPUTimer.js +285 -285
  1459. package/src/shade/device/timing/GPUTimerArray.js +205 -205
  1460. package/src/shade/playground/add_random_lights.js +49 -49
  1461. package/src/shade/playground/add_random_meshes.js +83 -83
  1462. package/src/shade/playground/animation_prototype.js +508 -508
  1463. package/src/shade/playground/animation_prototype_skinning.js +647 -647
  1464. package/src/shade/playground/animation_prototype_stress.js +765 -765
  1465. package/src/shade/playground/enable_picker.js +59 -59
  1466. package/src/shade/playground/gltf_skinned_prototype.js +560 -560
  1467. package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
  1468. package/src/shade/playground/gltf_viewer.js +282 -282
  1469. package/src/shade/playground/load_default_environment.js +19 -19
  1470. package/src/shade/playground/main.js +2727 -2727
  1471. package/src/shade/playground/make_animated_instances.js +117 -117
  1472. package/src/shade/playground/prototype_virtual_texture.js +274 -274
  1473. package/src/shade/playground/prototype_volumetrics_01.js +206 -206
  1474. package/src/shade/playground/sample.js +234 -234
  1475. package/src/shade/playground/sample_load_gltf.js +102 -102
  1476. package/src/shade/playground/spread_node3d_grid.js +19 -19
  1477. package/src/shade/renderer/Renderer.js +2436 -2436
  1478. package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
  1479. package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
  1480. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
  1481. package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
  1482. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
  1483. package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
  1484. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
  1485. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
  1486. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
  1487. package/src/shade/renderer/binding/BindGroupManager.js +282 -282
  1488. package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
  1489. package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
  1490. package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
  1491. package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
  1492. package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
  1493. package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
  1494. package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
  1495. package/src/shade/renderer/camera/Camera.js +281 -281
  1496. package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
  1497. package/src/shade/renderer/camera/orbital/README.md +84 -84
  1498. package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
  1499. package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
  1500. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
  1501. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
  1502. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
  1503. package/src/shade/renderer/extension/FrameContext.js +263 -263
  1504. package/src/shade/renderer/extension/FramePhase.js +104 -104
  1505. package/src/shade/renderer/extension/FrameRecord.js +126 -126
  1506. package/src/shade/renderer/extension/GBufferTextures.js +83 -83
  1507. package/src/shade/renderer/extension/PresentTarget.js +34 -34
  1508. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
  1509. package/src/shade/renderer/extension/RenderExtension.js +69 -69
  1510. package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
  1511. package/src/shade/renderer/extension/SceneColor.js +40 -40
  1512. package/src/shade/renderer/extension/ViewTextures.js +78 -78
  1513. package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
  1514. package/src/shade/renderer/geometry/Geometry.js +608 -608
  1515. package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
  1516. package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
  1517. package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
  1518. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
  1519. package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
  1520. package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
  1521. package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
  1522. package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
  1523. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
  1524. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
  1525. package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
  1526. package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
  1527. package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
  1528. package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
  1529. package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
  1530. package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
  1531. package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
  1532. package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
  1533. package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
  1534. package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
  1535. package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
  1536. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
  1537. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
  1538. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
  1539. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
  1540. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
  1541. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
  1542. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
  1543. package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
  1544. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
  1545. package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
  1546. package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
  1547. package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
  1548. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
  1549. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
  1550. package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
  1551. package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
  1552. package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
  1553. package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
  1554. package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
  1555. package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
  1556. package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
  1557. package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
  1558. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
  1559. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
  1560. package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
  1561. package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
  1562. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
  1563. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
  1564. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
  1565. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
  1566. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
  1567. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
  1568. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
  1569. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
  1570. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
  1571. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
  1572. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
  1573. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
  1574. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
  1575. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
  1576. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
  1577. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
  1578. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
  1579. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
  1580. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
  1581. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
  1582. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
  1583. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
  1584. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
  1585. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
  1586. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
  1587. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
  1588. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
  1589. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
  1590. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
  1591. package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
  1592. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
  1593. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
  1594. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
  1595. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
  1596. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
  1597. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
  1598. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
  1599. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
  1600. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
  1601. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
  1602. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
  1603. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
  1604. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
  1605. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
  1606. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
  1607. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
  1608. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
  1609. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
  1610. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
  1611. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
  1612. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
  1613. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
  1614. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
  1615. package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
  1616. package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
  1617. package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
  1618. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
  1619. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
  1620. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
  1621. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
  1622. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
  1623. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
  1624. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
  1625. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
  1626. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
  1627. package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
  1628. package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
  1629. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
  1630. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
  1631. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
  1632. package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
  1633. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
  1634. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
  1635. package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
  1636. package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
  1637. package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
  1638. package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
  1639. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
  1640. package/src/shade/renderer/light/make_sunlight.js +35 -35
  1641. package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
  1642. package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
  1643. package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
  1644. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
  1645. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
  1646. package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
  1647. package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
  1648. package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
  1649. package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
  1650. package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
  1651. package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
  1652. package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
  1653. package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
  1654. package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
  1655. package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
  1656. package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
  1657. package/src/shade/renderer/loader/usd/UsdError.js +33 -33
  1658. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
  1659. package/src/shade/renderer/loader/usd/load_usd.js +226 -226
  1660. package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
  1661. package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
  1662. package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
  1663. package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
  1664. package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
  1665. package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
  1666. package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
  1667. package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
  1668. package/src/shade/renderer/material/ShadeMaterial.js +2 -2
  1669. package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
  1670. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  1671. package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
  1672. package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
  1673. package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
  1674. package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
  1675. package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
  1676. package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
  1677. package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
  1678. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
  1679. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
  1680. package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
  1681. package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
  1682. package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
  1683. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
  1684. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
  1685. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
  1686. package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
  1687. package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
  1688. package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
  1689. package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
  1690. package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
  1691. package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
  1692. package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
  1693. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
  1694. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
  1695. package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
  1696. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
  1697. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
  1698. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
  1699. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
  1700. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
  1701. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
  1702. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
  1703. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
  1704. package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
  1705. package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
  1706. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
  1707. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
  1708. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
  1709. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
  1710. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
  1711. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
  1712. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
  1713. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
  1714. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
  1715. package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
  1716. package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
  1717. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
  1718. package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
  1719. package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
  1720. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
  1721. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
  1722. package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
  1723. package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
  1724. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
  1725. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
  1726. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
  1727. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
  1728. package/src/shade/renderer/scene/DynamicMesh.js +70 -70
  1729. package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
  1730. package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
  1731. package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
  1732. package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
  1733. package/src/shade/renderer/scene/TransformAuthority.js +33 -33
  1734. package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
  1735. package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
  1736. package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
  1737. package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
  1738. package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
  1739. package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
  1740. package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
  1741. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
  1742. package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
  1743. package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
  1744. package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
  1745. package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
  1746. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
  1747. package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
  1748. package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
  1749. package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
  1750. package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
  1751. package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
  1752. package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
  1753. package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
  1754. package/src/shade/renderer/texture/virtual/README.md +276 -276
  1755. package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
  1756. package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
  1757. package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
  1758. package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
  1759. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
  1760. package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
  1761. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
  1762. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
  1763. package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
  1764. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
  1765. package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
  1766. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
  1767. package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
  1768. package/src/shade/renderer/water/shader_water.js +246 -246
  1769. package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
  1770. package/src/shade/vitest.setup.mjs +89 -89
  1771. package/src/shade/wgsl/validate_wgsl_source.js +316 -316
  1772. package/src/view/controller/controls/NumericIntervalControl.js +90 -90
  1773. package/src/view/controller/controls/Vector2Control.js +83 -83
  1774. package/src/view/controller/controls/Vector3Control.js +90 -90
  1775. package/src/view/elements/Group.js +56 -56
  1776. package/src/view/elements/drawCheckers.js +33 -33
  1777. package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
  1778. package/src/view/graphics/WebGLContextFailureView.js +76 -76
  1779. package/src/view/layout/DockPanel.js +51 -51
  1780. package/src/view/layout/SplitView.js +365 -365
  1781. package/src/view/layout/show_popup_menu.js +81 -81
  1782. package/src/view/minimap/MinimapCanvasView.js +411 -411
  1783. package/src/view/minimap/MinimapImageCache.js +88 -88
  1784. package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
  1785. package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
  1786. package/src/view/minimap/MinimapMarkerCollection.js +180 -180
  1787. package/src/view/minimap/dom/MinimapCameraView.js +206 -206
  1788. package/src/view/minimap/minimap_focus_area.js +97 -97
  1789. package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
  1790. package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
  1791. package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
  1792. package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
  1793. package/src/engine/ecs/attachment/Attachment.js +0 -57
  1794. package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
  1795. package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
  1796. package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
  1797. package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
  1798. package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
  1799. package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
  1800. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
  1801. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
  1802. package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
  1803. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
  1804. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
  1805. package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
  1806. package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
  1807. package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
  1808. package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
  1809. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
  1810. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
  1811. package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
  1812. package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
  1813. package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
  1814. package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
  1815. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
  1816. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
  1817. package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
  1818. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
  1819. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
  1820. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
  1821. package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
  1822. package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
  1823. package/src/shade/renderer/animation/_dump_resolve.mjs +0 -5
@@ -1,920 +1,920 @@
1
- # Correctness review — `engine/ecs`
2
-
3
- Scope: every non-spec source file under `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/`
4
- (208 source files). Reviewed 2026-08-06.
5
-
6
- Findings marked **Confirmed** were either reproduced by executing the code or read directly
7
- off the source with an unambiguous trace. `assert.*` is treated as absent (stripped in prod).
8
-
9
- ---
10
-
11
- ## Core (Entity / EntityComponentDataset / EntityManager / System)
12
-
13
- ### ECS-1 `Entity.readFromDataset` never watches destruction, so a stale `Built` flag corrupts recycled entity ids
14
- - **Severity**: CRITICAL
15
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/Entity.js:508-525`
16
- - **Defect**: `build()` registers `#handleEntityDestroyed` on `EventType.EntityRemoved` when the
17
- `WatchDestruction` flag is set (line 487-489), which is on by default. `readFromDataset` sets
18
- `EntityFlags.Built` and binds the reference but registers no such listener, so the returned
19
- `Entity` keeps reporting `isBuilt === true` forever after the underlying entity is removed.
20
- Since `EntityComponentDataset.createEntity` recycles ids (`entityOccupancy.nextClearBit(0)`),
21
- every subsequent operation on that `Entity` lands on a *different* entity.
22
- - **Failure scenario** (reproduced):
23
- ```js
24
- const ds = new EntityComponentDataset();
25
- ds.setComponentTypeMap([A, B]);
26
- const id = ds.createEntity(); // id = 0
27
- ds.addComponentToEntity(id, new A());
28
- const e = Entity.readFromDataset(id, ds);
29
- ds.removeEntity(id);
30
- const recycled = ds.createEntity(); // id = 0 again, generation 1
31
- e.isBuilt // => true (should be false)
32
- e.add(new B()); // => writes B onto `recycled`, an unrelated entity
33
- e.destroy(); // => destroys `recycled`
34
- ```
35
- Output observed: `recycled entity now has B: true`, `after e.destroy(), recycled entity exists: false`.
36
- The `EntityReference` already carries the generation needed to detect this (`e.generation === 0`
37
- vs `ds.getEntityGeneration(recycled) === 1`) but nothing consults it.
38
- - **Fix**: in `readFromDataset`, mirror `build()` — after setting the `Built` flag, do
39
- `if (r.getFlag(EntityFlags.WatchDestruction)) dataset.addEntityEventListener(entity, EventType.EntityRemoved, r.#handleEntityDestroyed, r);`
40
- (needs a small private accessor since `#handleEntityDestroyed` is instance-private).
41
- - **Confidence**: Confirmed
42
-
43
- ### ECS-2 `setComponentTypeMap` leaves the dataset permanently corrupted when observer rebuild throws
44
- - **Severity**: MAJOR
45
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityComponentDataset.js:674-731`
46
- - **Defect**: `setComponentTypeMap` mutates state in four steps — `updateComponentOccupancy` (716),
47
- `updateComponentStores` (718), `updateObservers` (720), then `componentTypeMap` / `componentTypeCount`
48
- (722, 730). `updateObservers` calls `observer.build(map)` for every observer sitting in a *surviving*
49
- component's store; `EntityObserver.build` **throws** if any of that observer's component types is
50
- absent from the new map (`EntityObserver.js:102-104`). Because occupancy and the component stores
51
- have already been re-strided at that point, the throw leaves the dataset with the new stride but the
52
- old `componentTypeMap` / `componentTypeCount` / `__type_to_index_map`.
53
- - **Failure scenario** (reproduced):
54
- ```js
55
- ds.setComponentTypeMap([A, B]);
56
- ds.addObserver(new EntityObserver([A, B], onComplete, onBroken), false);
57
- ds.unregisterComponentType(A); // A has no live instances, so the removal check passes
58
- ```
59
- Observed: throws `Component type[0] was not found in the supplied map`, then
60
- `componentTypeMap = ['A','B']`, `componentTypeCount = 2`, but `components.length = 1`.
61
- The next `ds.addComponentToEntity(e, new B())` throws
62
- `TypeError: Cannot read properties of undefined (reading '0')` — `components[1]` no longer exists.
63
- The dataset cannot be used again.
64
- - **Fix**: validate first, mutate second. Before touching occupancy/stores, iterate every registered
65
- observer and check `map` contains all of `observer.componentTypes`; throw there (dataset untouched).
66
- Observers that watch only removed types should be disconnected explicitly (`observer.dataset = null`)
67
- rather than silently dropped from the stores.
68
- - **Confidence**: Confirmed
69
-
70
- ### ECS-3 `traverseEntitiesCompactedFiltered` uses `-1` as a component index for unregistered classes
71
- - **Severity**: MAJOR
72
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityComponentDataset.js:1990-2020`
73
- - **Defect**: `componentIndices[i] = this.computeComponentTypeIndex(klass)` stores `-1` for a class
74
- that is not registered in this dataset, and nothing filters it out. The loop then does
75
- `componentOccupancy.get(entityOccupancyAddress + (-1))` — reading the *last* component bit of the
76
- *previous* entity — and, when that bit happens to be set, `this.components[-1][i]`.
77
- `this.components[-1]` is `undefined`. (In production `BitSet.get`'s `assert.isNonNegativeInteger`
78
- is stripped, so it silently returns the neighbouring entity's bit instead of throwing.)
79
- - **Failure scenario** (reproduced): dataset registered with `[A, B]`; entity 0 has both A and B;
80
- entity 1 has A. `ds.traverseEntitiesCompactedFiltered([C, A], visitor)` where `C` was never
81
- registered. With asserts on it throws `bitIndex must be >= 0, instead was -1`. With asserts stripped,
82
- entity 1 reads bit `2 - 1 = 1` (entity 0's B bit, which is set) and then dereferences
83
- `this.components[-1][1]` → `TypeError: Cannot read properties of undefined`.
84
- - **Fix**: skip unregistered classes when building `componentIndices` (build a compacted array of
85
- valid indices only), the same way `traverseEntities` returns early on `-1`.
86
- - **Confidence**: Confirmed
87
-
88
- ### ECS-4 Bare `.sort()` on component indices is lexicographic
89
- - **Severity**: MINOR
90
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityComponentDataset.js:1996`
91
- - **Defect**: `componentIndices.sort()` with no comparator sorts numbers as strings. The comment
92
- says "sort component indices array for faster traversal"; with 10 or more registered component
93
- types the array is not sorted at all, and the `components` array handed to the visitor comes out
94
- in a scrambled, size-dependent order.
95
- - **Failure scenario** (reproduced): 12 registered component types; an entity holds instances of
96
- type index 2 and type index 10. The visitor receives `[K10, K2]` instead of `[K2, K10]`. Any
97
- consumer of this "main utility … to facilitate serialization" method that depends on a stable
98
- component ordering (e.g. a byte-for-byte save comparison or a diff) sees the order flip as soon
99
- as the dataset crosses 10 component types.
100
- - **Fix**: `componentIndices.sort((a, b) => a - b);`
101
- - **Confidence**: Confirmed
102
-
103
- ### ECS-5 `Entity.getComponentSafe` throws `ReferenceError` — its helper is neither imported nor exported
104
- - **Severity**: MINOR
105
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/Entity.js:256`
106
- - **Defect**: The error path calls `computeComponentClassName(klass)`, but `Entity.js` imports only
107
- `assert`, `Signal`, `EntityFlags`, `EntityReference`, `EventType`. Worse,
108
- `computeComponentClassName.js` declares the function without any `export` statement at all, so
109
- there is nothing to import.
110
- - **Failure scenario** (reproduced): `new Entity().getComponentSafe(SomeClass)` throws
111
- `ReferenceError: computeComponentClassName is not defined` instead of the documented
112
- `Error: Component of given class 'SomeClass' not found`. The diagnostic naming the missing
113
- component — the entire point of the method over `getComponent` — is lost.
114
- - **Fix**: add `export` to `computeComponentClassName.js` and import it in `Entity.js`.
115
- - **Confidence**: Confirmed
116
-
117
- ### ECS-6 Re-`build()`ing an `Entity` whose components drifted orphans the old entity and mis-aims its destruction watcher
118
- - **Severity**: MINOR
119
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/Entity.js:442-493`
120
- - **Defect**: `build()` returns early only when `checkExistingComponents` passes. Otherwise it falls
121
- through and calls `dataset.createEntity()` **without** removing the entity it was previously bound
122
- to and without unregistering the `EntityRemoved` listener registered on the old id at line 488.
123
- - **Failure scenario** (reproduced):
124
- ```js
125
- const e = new Entity(); e.add(new A());
126
- const id1 = e.build(ds); // 0
127
- ds.removeComponentFromEntity(id1, A); // a system strips the component directly
128
- const id2 = e.build(ds); // 1 -- entity 0 is now orphaned, entityCount = 2
129
- ds.removeEntity(id1); // fires the stale watcher
130
- e.isBuilt // => false, although entity 1 is alive
131
- e.destroy() // => false; entity 1 leaks permanently
132
- ```
133
- - **Fix**: when the early-return check fails but `EntityFlags.Built` is set, call `this.destroy()`
134
- first so the previous entity and its listener are released before rebinding.
135
- - **Confidence**: Confirmed
136
-
137
- ### ECS-7 `EntityManager.removeSystem` never clears `system.entityManager`
138
- - **Severity**: MINOR
139
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityManager.js:590-631`
140
- - **Defect**: `startSystem` refuses to start a system whose `entityManager` is set to a different
141
- manager (`EntityManager.js:739-741`). Neither `removeSystem` nor `stopSystem` resets the field.
142
- - **Failure scenario**: `em1.removeSystem(s)` then `em2.addSystem(s); em2.startup()` →
143
- `startSystem` throws `System is bound to another EntityManager`, caught at line 763, so
144
- `errorCallback` fires and `em2.state` becomes `EntityManagerState.Failed`; the whole manager
145
- never reaches `Running`. Reusing a system instance across manager instances (level reload, test
146
- fixtures) is impossible.
147
- - **Fix**: set `system.entityManager = null` in `removeSystem` after `stopSystem` resolves.
148
- - **Confidence**: Confirmed
149
-
150
- ### ECS-8 `system_validate_class` duplicate-component backtrace is off by one
151
- - **Severity**: MINOR
152
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/system_validate_class.js:49`
153
- - **Defect**: `for (let j = 0; j < i - 1; j++)` never compares `components_used[i]` against
154
- `components_used[i - 1]`, so adjacent duplicate resource specifications slip through.
155
- - **Failure scenario**: `components_used = [Spec(Transform, Read), Spec(Transform, Write)]` passes
156
- validation. `System.getAccessForComponent` (`System.js:110-117`) `break`s at the first match, so
157
- the `Write` access is silently discarded, and `EntityManager.updateExecutionOrder`'s `scoreSystem`
158
- scores the system as a reader (`attached_edge_count` instead of `× 2`) — the system is scheduled
159
- after the systems that should run after it.
160
- - **Fix**: `for (let j = 0; j < i; j++)`.
161
- - **Confidence**: Confirmed
162
-
163
- ### ECS-48 `EntityManager.startup` reports `Running` while a system added during startup is still `STARTING`
164
- - **Severity**: MINOR
165
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityManager.js:795,814,567-574`
166
- - **Defect**: `startup()` captures `expectedReadyCount = systems.length` before the start loop, then
167
- `finalizeStartup()` fires when `readyCount === expectedReadyCount`. A system's own `startup()` may
168
- call `entityManager.addSystem(...)`, which pushes onto the same `systems` array the `for…of` loop is
169
- walking, so the loop does start the newcomer — but the target count is stale by one. Separately,
170
- `addSystem` only auto-starts when `this.state === EntityManagerState.Running`; during
171
- `EntityManagerState.Starting` it returns `Promise.resolve()` and never calls `startSystem`, so a
172
- system added after the loop has drained is never started at all.
173
- - **Failure scenario**: `SGMeshSystem.startup(em)` does
174
- `if (!em.hasSystem(TransformAttachmentSystem)) await em.addSystem(new TransformAttachmentSystem());`
175
- (`engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.js:94-96`). With N systems registered,
176
- `expectedReadyCount = N`; once the original N have reported ready, `finalizeStartup()` sets
177
- `state = Running` and invokes `readyCallback()` while `TransformAttachmentSystem` is still in
178
- `STARTING`. The caller begins `simulate()`; `updateExecutionOrder` skips non-`RUNNING` systems and its
179
- observer has not been added to the dataset, so attachments silently do not update for those frames.
180
- If the same `addSystem` were made from after an `await` (once the start loop has drained) the system
181
- would never start at all, while its promise still resolves.
182
- - **Fix**: read `systems.length` live at each `systemReady` (`readyCount === systems.length`) instead of
183
- a captured count, and treat `EntityManagerState.Starting` like `Running` in `addSystem`'s auto-start
184
- branch.
185
- - **Confidence**: Confirmed
186
-
187
- ---
188
-
189
- ## Fog of war
190
-
191
- ### ECS-9 `FogOfWarSystem.shutdown` removes none of the three render-loop subscriptions it made in `startup`
192
- - **Severity**: MAJOR
193
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWarSystem.js:162-219`
194
- - **Defect**: `startup` registers three anonymous closures on `graphics.on.preRender`,
195
- `graphics.on.buffersRendered` and `graphics.on.postRender`. `shutdown` only calls
196
- `this.graphics.layerComposer.remove(this.compositLayer)`. Because the handlers are inline
197
- functions with no retained reference, they can never be removed even by a caller.
198
- - **Failure scenario**: `entityManager.shutdown()` (or `removeSystem(fogOfWarSystem)`) removes the
199
- composit layer, but on the very next frame the `preRender` handler still runs
200
- `compositerLayer.enable()` on the removed layer and `depthFrameBuffer.referenceCount++`, and
201
- `buffersRendered` still runs `fogOfWarRenderer.render(renderer, camera, scene, compositerLayer.renderTarget)`
202
- into a render target that is no longer composited — burning a full-screen pass every frame for the
203
- lifetime of the `GraphicsEngine`. The closures also pin `entityManager`, `graphics` and the
204
- `FogOfWarRenderer` (its `ShaderMaterial`, `Mesh` and `Scene` are never disposed). Starting a second
205
- `FogOfWarSystem` — e.g. a level reload on a shared `GraphicsEngine` — stacks a second set of handlers,
206
- so each frame renders the fog twice.
207
- - **Fix**: hoist the three handlers into named fields (or wrap them in `SignalBinding`s) in `startup`,
208
- `remove` them in `shutdown`, and dispose `fogOfWarRenderer.material` / `fogOfWarRenderer.quad`.
209
- - **Confidence**: Confirmed
210
-
211
- ### ECS-10 A revealer on the *wrong* team still reveals fog whenever its radius changes
212
- - **Severity**: MAJOR
213
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWarRevealerSystem.js:52-97`
214
- - **Defect**: The team check lives only in `attemptToWatch`, which gates the *position* watcher.
215
- `update()` itself contains no team test, and `radiusWatcher` (line 93-97) is linked
216
- unconditionally for every linked entity regardless of team.
217
- - **Failure scenario**: `new FogOfWarRevealerSystem(/* team */ 1)`. An enemy unit with
218
- `Team = 2`, `Transform`, `FogOfWarRevealer` links: `attemptToWatch(2)` correctly leaves
219
- `positionWatcher` unlinked, but `radiusWatcher.link()` runs anyway. The enemy then receives a
220
- vision buff (`revealer.radius.set(8)`) — `revealer.radius.onChanged` fires `update()`, which maps
221
- the *enemy's* position to the grid and calls `fow.reveal(...)` on the player's fog. The player's
222
- map is permanently uncovered at the enemy's location.
223
- - **Fix**: put the team check inside `update()` (`if (team.getValue() !== visibilityTeam) return;`),
224
- or link/unlink `radiusWatcher` from `attemptToWatch` alongside `positionWatcher`.
225
- - **Confidence**: Confirmed
226
-
227
- ### ECS-11 `computeRevealedGridBoundingRectangle` returns an inverted AABB when nothing is revealed
228
- - **Severity**: MINOR
229
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWar.js:532-563`
230
- - **Defect**: `x0`/`y0` seed to `width - 1` / `height - 1` and `x1`/`y1` to `0`. If no cell differs
231
- from 255 the loop body never runs and those seeds are written out unchanged, producing
232
- `x0 > x1` and `y0 > y1`.
233
- - **Failure scenario**: a freshly resized 64×64 fog (sampler 66×66, all 255) →
234
- `result.set(65, 65, 0, 0)`. `MinimapFogOfWar.updateFocusArea`
235
- (`view/minimap/gl/MinimapFogOfWar.js:87`) then does `bounds.grow(2)`, `bounds.move(-0.5, 0.5)`
236
- and multiplies by the scale, feeding a negative-extent rectangle into `enforceAspectRatio` — the
237
- minimap focus area is garbage on the first frame of any map before the first reveal.
238
- - **Fix**: track a `found` flag and, when nothing is revealed, emit an explicitly empty rectangle
239
- (`result.set(0, 0, 0, 0)`) or leave `result` untouched and return a boolean.
240
- - **Confidence**: Confirmed
241
-
242
- ### ECS-12 `getWorldClearance` reads the distance sampler unclamped
243
- - **Severity**: MINOR
244
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWar.js:677-684`
245
- - **Defect**: Its sibling `computeAABBVisibility` clamps the sample position before
246
- `distanceSampler.readChannel` (line 628-629); `getWorldClearance` does not.
247
- `Sampler2D.readChannel` computes `(y * width + x) * itemSize + channel` with no bounds check
248
- (the `assert`s are stripped), so an x outside `[0, width)` silently indexes into an adjacent row.
249
- - **Failure scenario**: `FogOfWarVisibilityPredicate.test(x, y, z)` (line 41) is called with an
250
- object's world position. For a 64×64 fog with `scale = 1`, an object one cell east of the map at
251
- grid row 20 produces `sampleX = 65`, `sampleY = 21`, giving index `21 * 66 + 65 = 1451` — which is
252
- cell `(1, 22)`, the *west* border of the row below. The object's visibility is decided by an
253
- unrelated cell; where that cell is revealed, an off-map object that should be hidden renders.
254
- - **Fix**: clamp exactly as `computeAABBVisibility` does, using the sampler's own dimensions.
255
- - **Confidence**: Confirmed
256
-
257
- ### ECS-13 `computeAABBVisibility` clamps to `size - 1` but the distance sampler is `size + 2` wide
258
- - **Severity**: MINOR
259
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWar.js:628-629`
260
- - **Defect**: `worldToSamplePosition` emits sampler-space coordinates with the +1 border offset
261
- baked in, so the real world area occupies sampler columns `[1, size.x]`. The clamp bound is
262
- `this.size.x - 1`, two cells short of the valid maximum.
263
- - **Failure scenario**: 64×64 fog, `scale = 1`. `worldToSamplePosition` maps the world extent onto
264
- sample coordinates `[1, 64]`, but the clamp caps at `63`. An object at the far eastern edge maps to
265
- `sampleX = 64` and is read at column `63` instead — the outermost column/row of the map takes its
266
- clearance from the cell one step inward. An unexplored strip along the east/south map edge therefore
267
- reports the clearance of the explored cell beside it, and objects standing there are drawn through
268
- the fog.
269
- - **Fix**: clamp against the sampler, not the logical size: `clamp(sampleX, 0, this.sampler.width - 1)`.
270
- - **Confidence**: Confirmed
271
-
272
- ---
273
-
274
- ## Terrain
275
-
276
- ### ECS-14 `getTileByWorldPosition2D` reads the vertical axis as the grid row
277
- - **Severity**: CRITICAL
278
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/tiles/TerrainTileManager.js:461-472`
279
- - **Defect**: The point is packed as `Float32Array.of(x, 0, y)` — so the top-down Y lands at index
280
- **2** — inverse-transformed, and then passed as `getRawTileByPosition(v3[0], v3[1])`. `v3[1]` is
281
- the vertical component. Separately, `getRawTileByPosition` divides by `tileSize`, which is in grid
282
- units, while `v3` is in object units, so `this.scale` must be divided out first.
283
- - **Failure scenario**: 64×64 terrain, `gridScale = 1`, tile size 32 → a 2×2 tile grid, identity
284
- transform. `getTileByWorldPosition2D(40, 40)` → `getRawTileByPosition(40, 0)` →
285
- `tileX = 1, tileY = floor(0 / 32) = 0` → tile **(1,0)** instead of **(1,1)**. With an identity
286
- transform the row is *always* 0, so every lookup collapses onto the top row of tiles.
287
- `obtainTileAtWorldPosition2D` then builds the wrong tile.
288
- - **Fix**: `return this.getRawTileByPosition(v3[0] / this.scale.x, v3[2] / this.scale.y);`
289
- - **Confidence**: Confirmed
290
-
291
- ### ECS-15 `traverseVisibleTiles` sums per-frustum hit counts but always writes at offset 0
292
- - **Severity**: MAJOR
293
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/TerrainSystem.js:265-290`
294
- - **Defect**: `tile_count` is initialised to 0 and passed as the `result_offset` to
295
- `bvh_query_user_data_intersects_frustum` on every iteration but never advanced, so each frustum
296
- overwrites the previous frustum's results from index 0. `found_tiles` meanwhile accumulates the
297
- per-frustum counts, and the consumer loop iterates to that sum.
298
- - **Failure scenario**: two active `Camera` components (`CameraSystem.getActiveFrustums` yields one
299
- frustum per active camera). Frustum A returns 6 tiles, frustum B returns 4 → `tiles` holds 6
300
- entries but `found_tiles = 10`. Indices 6..9 are `undefined` →
301
- `tileManager.tiles[undefined] === undefined` → the callback dereferences `tile.isBuilt` /
302
- `tile.gridPosition` → `TypeError` out of `TerrainSystem.update` every frame.
303
- - **Fix**: `tile_count += bvh_query_user_data_intersects_frustum(tiles, tile_count, ...)` and iterate
304
- to `tile_count`.
305
- - **Confidence**: Confirmed
306
-
307
- ### ECS-16 `TerrainOverlay.paintSampler` passes the overlay instead of its sampler
308
- - **Severity**: MAJOR
309
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/overlay/TerrainOverlay.js:321`
310
- - **Defect**: `sampler2d_paint(this, scaled_source, ...)` supplies the `TerrainOverlay` as the
311
- destination `Sampler2D`. The pixel data lives on `this.sampler`.
312
- - **Failure scenario**: `sampler2d_paint` computes
313
- `_w = Math.min(width, source.width - 0, undefined - dx)` → `NaN`, so `for (x = 0; x < NaN; ...)`
314
- never executes. Nothing is written and no error is raised — `paintImage`/`paintSampler` are silent
315
- no-ops, while `texture.needsUpdate = true` re-uploads unchanged pixels every call.
316
- - **Fix**: `sampler2d_paint(this.sampler, scaled_source, 0, 0, dx, dy, dWidth, dHeight);`
317
- - **Confidence**: Confirmed
318
-
319
- ### ECS-17 Tile rebuild leaks the previous `BufferGeometry`
320
- - **Severity**: MAJOR
321
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/tiles/TerrainTile.js:551,586`
322
- - **Defect**: `build()` assigns `this.geometry = g` / `mesh.geometry = g` without disposing the
323
- geometry it replaces. The only disposal path, `dispose()`, early-returns on `if (!this.isBuilt) return;`
324
- — and `TerrainTileManager.rebuildTilesByUV` (`tiles/TerrainTileManager.js:300`) clears `isBuilt`
325
- while the geometry is still attached, so the old geometry can never be released.
326
- - **Failure scenario**: terrain height editing calls `rebuildTilesByUV`, clearing `isBuilt` on every
327
- overlapping tile; `TerrainSystem.update` then re-invokes `tile.build(tileData)`, installing a fresh
328
- geometry and orphaning the old VBO/IBO. For a 4-tile region at `resolution = 4` with 32×32 tiles
329
- (16 384 verts, ~590 KB of attributes each) that is ~2.4 MB of VRAM per edit, never reclaimed.
330
- `destroyTiles()` hits the same early return.
331
- - **Fix**: dispose the previous geometry at the top of `build()`, and replace the `!isBuilt` early
332
- return in `dispose()` with a `geometry !== null` check.
333
- - **Confidence**: Confirmed
334
-
335
- ### ECS-18 `TerrainTile.stitching` flags are never reset, so re-stitched seams copy instead of average
336
- - **Severity**: MAJOR
337
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/tiles/TerrainTile.js:99-110` (consumed at `:279`, `:333`)
338
- - **Defect**: Neither `build()`, `dispose()`, nor `TerrainTileManager.rebuildTilesByUV` clears the
339
- per-neighbour `stitching` state. `stitchHorizontal`/`stitchVertical` branch on those flags: when one
340
- side is already marked stitched, the other side's normals are *copied* wholesale rather than averaged.
341
- - **Failure scenario**: tiles A(0,0) and B(1,0) built and stitched, so `A.stitching.right === true`.
342
- A height edit clears `isBuilt` on both but leaves the flags. A rebuilds first — `stitchTile` sees B
343
- unbuilt and skips. B rebuilds — `stitchHorizontal(A, B)` finds the stale
344
- `left.stitching.right === true` and takes the copy branch, writing A's one-sided edge normals into B.
345
- The shared edge now carries normals computed from A's triangles only: a visible lighting crease down
346
- every rebuilt tile boundary after any terrain edit.
347
- - **Fix**: reset all eight `stitching` fields in `TerrainTile.build()` and `dispose()`, and clear the
348
- neighbours' matching flags in `rebuildTilesByUV`.
349
- - **Confidence**: Confirmed
350
-
351
- ### ECS-19 `ClingToTerrain.fromJSON` defaults `rotationSpeed` to `Infinity`; the field initialiser is `3`
352
- - **Severity**: MAJOR
353
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/cling/ClingToTerrain.js:54`
354
- - **Defect**: The class field is `rotationSpeed = 3`, and `ClingToTerrainSerializationUpgrader_0_1.js:22`
355
- explicitly writes `3`, calling it "the default value from the ClingToTerrain class". `fromJSON`
356
- destructures with `Number.POSITIVE_INFINITY` as the default.
357
- - **Failure scenario**: a blueprint carrying `{"ClingToTerrain": {"normalAlign": true}}` with no
358
- `rotationSpeed` → `rotationSpeed = Infinity`. In `ClingToTerrainSystem.processRaycastHit:272`
359
- `Number.isFinite` is false → `angularLimit = Infinity` → `alignToVector` computes
360
- `t = clamp01(Infinity / angle) = 1` → the entity snaps to the terrain normal instantly every frame
361
- instead of easing at 3 rad/s. The same entity constructed in code, or loaded from binary v0 through
362
- the upgrader, eases correctly — the behaviour differs by load path.
363
- - **Fix**: use `3` as the `fromJSON` destructuring default.
364
- - **Confidence**: Confirmed
365
-
366
- ### ECS-20 `Terrain` JSON and binary paths persist disjoint field sets, and disagree on a default
367
- - **Severity**: MAJOR
368
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/Terrain.js:930,981`;
369
- `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/serialization/TerrainSerializationAdapter.js:29,144`
370
- - **Defect**: Three fields are handled by exactly one of the two paths.
371
- `gridTransformKind` and `lightMapURL` are written into `extra` by `serialize`/`deserialize` but are
372
- absent from `toJSON`/`fromJSON`. `overlay.baseTileImage` is written by `toJSON` (as
373
- `overlayTileImage`) and restored by `fromJSON`, but is absent from the binary adapter. The two paths
374
- also disagree on the `gridTransformKind` default: `deserialize` falls back to
375
- `GridTransformKind.Legacy` (`:144`) while the class field initialiser is `GridTransformKind.Direct`
376
- (`Terrain.js:76`), which `fromJSON` leaves in place.
377
- - **Failure scenario**: a terrain with `gridTransformKind = Legacy`, `size = (64,64)`, `gridScale = 1`,
378
- saved via `toJSON` and reloaded. `fromJSON` leaves it at `Direct`, so `buildGridTransform` produces
379
- `scale = 1, offset = 0.5` instead of `scale = 64/63 ≈ 1.0159, offset = 0`. `mapPointGrid2World(63, 63)`
380
- returns `(63.5, ·, 63.5)` instead of `(64, ·, 64)` — every grid-anchored entity, marker and combat
381
- cell is displaced, growing to more than half a cell at the far edge. The same round-trip drops
382
- `lightMapURL`, so `build()` skips the `aoMap` load and the terrain renders unshadowed. Symmetrically,
383
- a custom `overlay.baseTileImage` saved to binary reloads as the default `WHITE_PIXEL_DATA_URL`.
384
- - **Fix**: add `gridTransformKind` and `lightMapURL` to `toJSON`/`fromJSON` (with the same default the
385
- binary path uses), and write `overlay.baseTileImage` into the binary adapter's `extra` blob.
386
- - **Confidence**: Confirmed
387
-
388
- ### ECS-21 `convertHeightMap2NormalMap` returns normals tilted uphill — both gradients unnegated
389
- - **Severity**: MAJOR
390
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/grid/HeightSampler2NormalSamplerSoft.js:54-56`
391
- - **Defect**: For a height field `h(x, y)` the surface normal is `normalize(-∂h/∂x, -∂h/∂y, k)`. The
392
- Sobel gradients `dX`/`dY` are computed with the correct sign but written out unnegated
393
- (`_x = dX / magnitude`, `_y = dY / magnitude`).
394
- - **Failure scenario**: a ramp `h(x, y) = x`. At an interior texel `dX = 8`, `dY = 0`,
395
- `magnitude = sqrt(64.25) ≈ 8.0156` → the function returns `(+0.998, 0, +0.0624)`. The true normal for
396
- a surface rising in `+x` leans toward `−x`: `(−0.998, 0, +0.0624)`. Every slope gets a normal pointing
397
- up-slope, so any lighting or slope test built on this map is exactly reversed on both horizontal
398
- axes. Flat regions are unaffected, which is why it is not obvious.
399
- - **Fix**: `const _x = -dX / magnitude; const _y = -dY / magnitude;`
400
- - **Confidence**: Likely (no in-repo consumer — public library surface; the maths is unambiguous)
401
-
402
- ### ECS-22 `Terrain.heightTexture` receives the `Sampler2D` object as its `height` argument
403
- - **Severity**: MINOR
404
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/Terrain.js:145`
405
- - **Defect**: `new DataTexture(this.samplerHeight.data, this.samplerHeight.width, this.samplerHeight, RedFormat, FloatType)`
406
- — the third parameter of `DataTexture(data, width, height, …)` gets the sampler instance rather than
407
- `this.samplerHeight.height`. Compare `graphics/ecs/water/Water.js:138`.
408
- - **Failure scenario**: immediately after `new Terrain()`, `terrain.heightTexture.image` is
409
- `{data: Float32Array(1), width: 1, height: <Sampler2D>}`. Any consumer that binds `heightTexture`
410
- before `build()` runs `updateHeightTexture()` (which repairs `image.width`/`image.height` at
411
- `:721-722`) uploads with a non-numeric height — `gl.texImage2D` raises `GL_INVALID_VALUE`.
412
- - **Fix**: pass `this.samplerHeight.height`.
413
- - **Confidence**: Confirmed
414
-
415
- ### ECS-23 `heightMap2AOMap` leaks the intermediate denoise source texture
416
- - **Severity**: MINOR
417
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/grid/HeightMap2AOMap.js:77`
418
- - **Defect**: `filter_result_to_texture(raw_ao.array, width, height)` creates a `DataTexture` that is
419
- uploaded by the denoise `ImageFilter` pass and then dropped without `.dispose()`. The sibling
420
- `pyramid_texture` *is* disposed at `:91`, so the omission is unintentional.
421
- - **Failure scenario**: `terrain.buildLightMap()` runs per generated map. For a 256×256 terrain at the
422
- default `quality = 4` the AO resolution is 1024×1024, so each bake orphans a 1024×1024 RGBA8 GPU
423
- texture (~4 MB VRAM) for the lifetime of the WebGL context. Each new run accumulates another.
424
- - **Fix**: hold the texture in a local and `.dispose()` it after the `smooth_ao` `ImageFilter` returns.
425
- - **Confidence**: Confirmed
426
-
427
- ### ECS-24 `loadVisibleTerrainTiles` does not reset its counters on retry, and can report success early
428
- - **Severity**: MINOR
429
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js:70-84`
430
- - **Defect**: `handleFailedTile` bumps `attempt` and re-runs `init()`, which recomputes `totalTiles` and
431
- restarts `start_time`, but `loadedTiles` and `failedTiles` carry over from the aborted attempt. The
432
- `attemptId !== attempt` guard only suppresses *stale* callbacks; it does not roll back what they
433
- already counted.
434
- - **Failure scenario**: 20 visible tiles; 12 resolve, then one rejects
435
- (`'Original tile was destroyed during build process'`). `init()` re-runs with `totalTiles = 20` and
436
- `loadedTiles = 12`. The 12 already-built tiles resolve on the next microtask → `loadedTiles = 24 ≥ 20`
437
- → `cycleFunction` returns `TaskSignal.EndSuccess` while 8 tiles are still mid-build. The loading
438
- screen closes and the scene shows 8 tiles with no geometry.
439
- - **Fix**: reset `loadedTiles = 0` and `failedTiles = 0` at the top of `init()`.
440
- - **Confidence**: Confirmed
441
-
442
- ### ECS-25 `TerrainSystem.mapPointGrid2World` hardcodes the Legacy transform and drops the offset
443
- - **Severity**: MINOR
444
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/TerrainSystem.js:81-87,186-187`
445
- - **Defect**: `link()` computes `gridScaleX = g_w * gridScale / (g_w - 1)` unconditionally — the
446
- `GridTransformKind.Legacy` formula — ignoring `component.gridTransformKind`, and
447
- `mapPointGrid2World` applies no offset. `Terrain.mapPointGrid2World` (`Terrain.js:555`) uses
448
- `gridTransform`, whose `Direct` branch (the class default) is `scale = gridScale, offset = gridScale / 2`.
449
- - **Failure scenario**: 64×64 terrain, `gridScale = 1`, default `Direct` kind.
450
- `terrain.mapPointGrid2World(0, 0, v3)` → `(0.5, ·, 0.5)`; `terrainSystem.mapPointGrid2World(0, 0, v3)`
451
- → `(0, ·, 0)`. At `x = 63`: `63.5` vs `64.0`. Two public methods documented to perform the same
452
- mapping differ by half a cell at the origin and diverge with distance. `gridScaleX/Y` are also
453
- system-global, so with two `Terrain` entities they hold whichever linked last.
454
- - **Fix**: delete `TerrainSystem.mapPointGrid2World`/`gridScaleX`/`gridScaleY` and route callers to
455
- `Terrain.mapPointGrid2World`, or read `terrain.gridTransform` directly.
456
- - **Confidence**: Confirmed
457
-
458
- ---
459
-
460
- ## Animation / dynamic actions / evaluation
461
-
462
- ### ECS-26 `AnimationOptimizer` writes its results to itself — optimization is a silent no-op
463
- - **Severity**: MAJOR
464
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/animation/AnimationOptimizer.js:109-114`
465
- - **Defect**: `optimizeTrack(track)` is a port of three.js `KeyframeTrack.prototype.optimize()`, where
466
- `this` *is* the track. Here `this` is the `AnimationOptimizer`, so the compacted arrays are assigned
467
- to `this.times` / `this.values` on the optimizer; the `track` (whose arrays were only *copied* at
468
- lines 108-109) is never modified, and the method returns `this`.
469
- - **Failure scenario**: `GLTFAssetLoader.js:426` calls `animationOptimizer.optimize(animation)` on
470
- every animation of every loaded GLTF, using the module-level singleton. Load a GLTF whose track has
471
- 1000 keyframes of which 900 are redundant: the track still has 1000 keyframes afterwards. The
472
- singleton also permanently retains `this.times`/`this.values` — full copies of the last-optimized
473
- track's typed arrays, megabytes for a skeletal clip — for the module's lifetime.
474
- - **Fix**: write back to the track (`track.times = ...; track.values = ...;`) and return `track`.
475
- - **Confidence**: Confirmed
476
-
477
- ### ECS-27 `AnimationOptimizer`: `time[0]` should be `times[0]`
478
- - **Severity**: MINOR
479
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/animation/AnimationOptimizer.js:36`
480
- - **Defect**: `if (time !== timeNext && (i !== 1 || time !== time[0]))` — `time` is a number, so
481
- `time[0]` is `undefined` and the sub-condition is always true. three.js has `times[0]`.
482
- - **Failure scenario**: a track with `times = [0, 0, 1, 2]` and distinct values at index 1. three.js
483
- drops keyframe 1 as a duplicate of the keyframe at t=0; this code keeps it, leaving two keyframes
484
- scheduled at t=0 in the "optimized" output.
485
- - **Fix**: `time !== times[0]`.
486
- - **Confidence**: Confirmed
487
-
488
- ### ECS-28 `DynamicRuleDescription.equals` calls `.equals` on a primitive string
489
- - **Severity**: MAJOR
490
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/dynamic_actions/rules/DynamicRuleDescription.js:55`
491
- - **Defect**: `id` is a primitive string (`UUID.string()` returns `UUID.v4().toString()`, and
492
- `fromJSON` assigns the JSON string directly), but `equals` does `return this.id.equals(other.id)`.
493
- `String.prototype.equals` does not exist.
494
- - **Failure scenario**:
495
- `DynamicRuleDescription.fromJSON({id:"combat.long-idle.ai", ...}).equals(otherRule)` →
496
- `TypeError: this.id.equals is not a function`. Any container relying on the `equals` protocol
497
- (a default-configured `HashMap`/`HashSet` keyed by rules) throws on the first lookup.
498
- `DynamicActorSystem` only escapes because `__global_last_used_times` supplies a custom
499
- `keyEqualityFunction`.
500
- - **Fix**: `return this.id === other.id;`
501
- - **Confidence**: Confirmed
502
-
503
- ### ECS-29 Pooled `DynamicActorSystem` context keeps the previous entity's `RuleExecution`
504
- - **Severity**: MAJOR
505
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/dynamic_actions/DynamicActorSystem.js:43,74-90`
506
- - **Defect**: `Context` instances are pooled by `AbstractContextSystem`, and the pool's reset callback
507
- is an empty `// TODO` (`system/AbstractContextSystem.js:37-42`). `Context.link()` resets
508
- `next_idle_event_time` but never resets `execution`, so a recycled context arrives carrying the
509
- previous entity's `execution.rule` and `execution.executor`. `Context.unlink()` also never terminates
510
- the still-running executor.
511
- - **Failure scenario**: unit A executes a `SpeakLine` rule, whose executor tree is
512
- `ParallelBehavior([WaitForEventBehavior(FinishedSpeakingLine on A), SendEventBehavior])`. A is killed
513
- and removed; `removeEntity` deletes A's event listeners, so `FinishedSpeakingLine` can never arrive,
514
- the parallel never completes, `DieBehavior` never runs, and the executor `Entity` keeps
515
- `EntityFlags.Built` forever. A's context is released to the pool still pointing at that immortal
516
- executor. Unit B spawns and pops the same context. On B's first event, `attemptRuleExecution`
517
- (lines 193-199) sees `execution.executor.getFlag(Built) === true` and tests
518
- `rule.priority <= execution.rule.priority`. Every rule in
519
- `app/data/database/dynamic-actions/data.json` uses the default `priority = 0`, so `0 <= 0` →
520
- `return false`: **B never executes any rule for the rest of the session.** If a higher-priority rule
521
- ever does get through, `terminateActiveExecution` destroys entity A's executor from B's context.
522
- - **Fix**: reset execution state at the context boundary — in `Context.link()` set
523
- `this.execution.rule = null; this.execution.executor = null;`, and in `Context.unlink()` destroy a
524
- still-built executor before clearing.
525
- - **Confidence**: Confirmed
526
-
527
- ### ECS-30 `match()` hands actions a live view of a shared scope stack that it unwinds before the action runs
528
- - **Severity**: MAJOR
529
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/dynamic_actions/DynamicActorSystem.js:536-551`;
530
- `.../actions/definition/WriteToBlackboardActionDescription.js:24-35`
531
- - **Defect**: `match()` passes `this.scope.proxy` — a proxy over the system-wide, mutable `DataScope` —
532
- as the action `context`, then calls `this.scope.unwind(top)` on line 551, dropping every frame it
533
- just pushed (blackboards, `{now, entity}`, the event frame, the entity proxy scope).
534
- `WriteToBlackboardActionDescription` captures that proxy in an `ActionBehavior` closure and only
535
- calls `expression.evaluate(context)` when the behavior ticks. `BehaviorSystem.link()` only calls
536
- `behavior.initialize()`; behaviors are first ticked in `BehaviorSystem.update()`, i.e. no earlier
537
- than the next frame.
538
- - **Failure scenario**: a rule with `"action": {"type":"Write", "name":"last_bark_time", "value":"now"}`.
539
- `match()` pushes `{now: 12.5, entity}`, matches, builds the executor, then unwinds. Next frame the
540
- `ActionBehavior` ticks and evaluates `now` against an empty scope → `undefined`, so
541
- `blackboard.acquire("last_bark_time", …).set(undefined)` instead of `12.5`. With two actors handling
542
- events in the same frame the deferred read can also resolve against the *other* actor's frames if
543
- those are still on the stack.
544
- - **Fix**: snapshot the scope before handing it to an action — materialise the proxy into a plain
545
- object in `executeRule` (as `SendRequestActionDescription` already does eagerly for
546
- `action_context`), or have `WriteToBlackboardActionDescription.execute` evaluate the expression
547
- eagerly and defer only the blackboard write.
548
- - **Confidence**: Confirmed
549
-
550
- ### ECS-31 `PointFitnessGaussianBlur` serializes a `quality` field that is never stored
551
- - **Severity**: MAJOR
552
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/evaluation/json/serializeToJSON_PointFitnessFunction.js:61`
553
- - **Defect**: The serializer writes `quality: value.quality`, but `PointFitnessGaussianBlur` never
554
- stores it — `from()` consumes it only to compute `samples_x/y/z = size * quality` and discards it.
555
- The deserializer (`deserializeFromJSON_PointFitnessFunction.js:45,51`) destructures `quality` with no
556
- default and forwards it to `from()`, whose own default `quality = 5` then applies.
557
- - **Failure scenario**: `PointFitnessGaussianBlur.from(src, 4, 4, 4, 1)` → `samples = 4,4,4`
558
- (64 source evaluations per point). Serialize → `{size_x:4, size_y:4, size_z:4, quality: undefined}`
559
- (and `undefined` is dropped entirely by `JSON.stringify`). Deserialize → `quality = 5` →
560
- `samples = 20,20,20`, i.e. 8000 source evaluations per point and a numerically different blur.
561
- - **Fix**: store the value in `from()` (`r.quality = quality;`) with a matching class field, or derive
562
- it on serialize as `value.samples_x / value.size_x`.
563
- - **Confidence**: Confirmed
564
-
565
- ### ECS-32 `PointFitnessGaussianBlur` kernel is offset by half a sample on every axis
566
- - **Severity**: MINOR
567
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/evaluation/complex/PointFitnessGaussianBlur.js:67-69,80,89,99`
568
- - **Defect**: `half_samples_x = samplesX / 2` with `local_x = ix - half_samples_x` gives taps at
569
- `-n/2 … n/2 - 1`, which is not centred on the sample point. A centred kernel needs `(samplesX - 1) / 2`.
570
- - **Failure scenario**: `samples_x = 5, size_x = 10`. Taps land at
571
- `local_x ∈ {-2.5,-1.5,-0.5,0.5,1.5}` → spatial offsets `{-5,-3,-1,1,3}` instead of
572
- `{-5,-2.5,0,2.5,5}`. Both the gaussian weights and the sample positions are biased by half a tap, so
573
- blurring a step edge shifts it by 1 world unit in −x on each axis, and the point being blurred is
574
- never itself sampled.
575
- - **Fix**: `const half_samples_x = (samplesX - 1) / 2;` (and likewise for y, z).
576
- - **Confidence**: Confirmed
577
-
578
- ### ECS-33 `Animation.flags` is in neither serialization path, so a paused animation resumes after load
579
- - **Severity**: MINOR
580
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/animation/Animation.js:40,153-158`;
581
- `.../animation/AnimationSerializationAdapter.js:15-28`
582
- - **Defect**: `flags` (carrying `Playing` and `MeshSizeCulling`) is written by neither `toJSON` nor
583
- `serialize`, and the field initialiser unconditionally sets `Playing | MeshSizeCulling`.
584
- `equals()`/`hash()` also ignore `flags` while including `debtTime`.
585
- - **Failure scenario**: `animation.isPlaying = false` (a supported setter, covered by
586
- `Animation.spec.js:58-62`), save via `BinaryBufferSerializer`, reload → `flags` is back to the
587
- constructor default and the animation plays again. Two `Animation` components differing only in
588
- `isPlaying` also report `equals() === true` and identical `hash()`.
589
- - **Fix**: add `flags` to the binary adapter (bumping `version`) and to `toJSON`/`fromJSON`
590
- (defaulting to `MeshSizeCulling | Playing`), and include it in `equals`/`hash`.
591
- - **Confidence**: Confirmed
592
-
593
- ### ECS-34 `AnimationSystem` re-subscribes to clip signals on every mesh change and never unsubscribes
594
- - **Severity**: MINOR
595
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/systems/AnimationSystem.js:116-117,150,221-230`
596
- - **Defect**: `updateAnimationState` unconditionally does `clips.on.added.add(initAnimationClip)` and
597
- `clips.on.removed.add(...)` with fresh closures each time. It runs from `registerAnimation` (line 150),
598
- which runs both from `link()` and from every `MeshEvents.DataSet` event. `unlink()` removes only the
599
- `MeshEvents.DataSet` entity listener; the clip-signal handlers are never removed, and `Signal.add`
600
- does not deduplicate.
601
- - **Failure scenario**: `MeshSystem.process` sets a placeholder mesh (DataSet #1) then the real asset
602
- (DataSet #2) for every async-loaded mesh with `__use_placeholder_mesh` on — together with `link()`
603
- that is 3 registrations. Every later runtime mesh swap adds another. After N mesh sets, a single
604
- `animation.clips.add(clip)` invokes `startClip` N times, and the handler list grows without bound for
605
- the component's life; unlink/relink cycles accumulate across cycles too.
606
- - **Fix**: keep references to the two handlers on the component, remove any previous pair at the top of
607
- `updateAnimationState`, and remove them in `unlink()`.
608
- - **Confidence**: Confirmed
609
-
610
- ### ECS-35 `SerializationMetadata` binary adapter truncates a documented 32-bit flag field to 8 bits
611
- - **Severity**: MINOR
612
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/components/SerializationMetadataSerializationAdapter.js:15,24`
613
- - **Defect**: `flags` is documented as a "32 bit bitfield" (`SerializationMetadata.js:19-22`) and
614
- `toJSON` writes the full number, but the binary adapter uses `writeUint8`/`readUint8`.
615
- - **Failure scenario**: a consumer defines a ninth flag `Foo = 1 << 8`. `metadata.setFlag(Foo)` →
616
- `flags === 256`. `toJSON()` preserves `256`; the binary path writes `256 & 0xFF === 0` and reads back
617
- `flags === 0`, silently losing the flag on save/load.
618
- - **Fix**: `writeUint32`/`readUint32`, bumping the adapter `version` and adding an upgrader.
619
- - **Confidence**: Confirmed
620
-
621
- ---
622
-
623
- ## Storage / serialization
624
-
625
- ### ECS-36 Schema back-reference is looked up by address in the schema-*object* map, then dereferenced
626
- - **Severity**: MAJOR
627
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter2.js:416`
628
- - **Defect**: `#deserializeObject` resolves a `HEADER_TAG_REF_SCHEMA` record with
629
- `this.#schema_table.getRecord(schema_address)`. `getRecord(schema)` looks up `#schema_map`, which is
630
- keyed by the **schema object** (`#schema_map.set(record.schema, record)`); the address-keyed map is
631
- `#address_map`, reachable only via `getSchemaAt(address)`. Passing a number can never hit, so
632
- `record` is `undefined` and line 419 (`record.typeName`) throws. The whole `HEADER_TAG_REF_SCHEMA`
633
- decode branch can only crash.
634
- - **Failure scenario**: the `schema` parameter of `serialize(buffer, object, schema)` is a documented
635
- optional argument. `const S = {v: undefined}; a2.serialize(buf, {v:1}, S); a2.serialize(buf, {v:2}, S);`
636
- — the first object writes `HEADER_TAG_INLINE_SCHEMA` and interns `S`; the second takes the `else`
637
- branch at line 375 and writes `HEADER_TAG_REF_SCHEMA` + address. On read, the first decodes fine, the
638
- second throws `TypeError: Cannot read properties of undefined (reading 'typeName')`. Same for a single
639
- object whose explicit schema reuses one sub-schema object for two fields.
640
- - **Fix**: `record = this.#schema_table.getSchemaAt(schema_address);`. Also reset `#schema_table` when
641
- switching an instance between writing and reading — write-side records are registered at write-buffer
642
- positions and never cleared, so they can shadow read-side records at the same address.
643
- - **Confidence**: Confirmed
644
-
645
- ### ECS-37 Per-component transient flag is ignored whenever the dataset has no `SerializationMetadata` type
646
- - **Severity**: MAJOR
647
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/storage/BinaryBufferSerializer.js:26-34`
648
- - **Defect**: `isEntityTransient` returns `false` on `smComponentIndex === -1` **before** checking
649
- `componentInstance[COMPONENT_SERIALIZATION_TRANSIENT_FIELD]`. The two mechanisms are independent —
650
- the magic field is a per-instance opt-out that does not require a `SerializationMetadata` component
651
- to exist — but it is gated behind one.
652
- - **Failure scenario**: `EntityComponentDataset.computeComponentTypeIndex` returns `-1` for an
653
- unregistered type, and a type is only registered once some entity actually receives one. In a dataset
654
- where nothing ever adds a `SerializationMetadata`, `smComponentIndex === -1`. Mark any component
655
- transient the way the game does (`guiElement[COMPONENT_SERIALIZATION_TRANSIENT_FIELD] = true` in
656
- `setupStrategyHeadsUpDisplays.js:98`, `sockets[...] = true` in `CombatUnitEntityDecorator.js:660`,
657
- `this.__transform2GridPosition[...] = true` in `CharacterMoveAsynchronousAction.js:236`) and it is
658
- written to the save anyway, then resurrected on load as a duplicate of a runtime-only component.
659
- - **Fix**: move the `COMPONENT_SERIALIZATION_TRANSIENT_FIELD` check above the `smComponentIndex === -1`
660
- early return.
661
- - **Confidence**: Confirmed
662
-
663
- ### ECS-38 Object adapter resolves class identity through *inherited* `typeName`
664
- - **Severity**: MAJOR
665
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.js:59-61`
666
- - **Defect**: `const typeName = Klass.typeName;` is a normal static lookup, so an ES class that does not
667
- declare its own `typeName` silently resolves to its ancestor's. The object-v2 writer explicitly
668
- guards this with `Object.prototype.hasOwnProperty.call(ctor, "typeName")`
669
- (`BinaryObjectSerializer.js:270`) and has a dedicated regression test, so the v1 adapter is the
670
- un-fixed half.
671
- - **Failure scenario**: `BehaviorComponentSerializationAdapter.serialize`
672
- (`engine/intelligence/behavior/ecs/BehaviorComponentSerializationAdapter.js:38`) routes every behavior
673
- through `this.objectAdapter.serialize(buffer, behavior)`. `Behavior.js:78` sets
674
- `Behavior.typeName = "Behavior"`, while `SelectorBehavior`, `WeightedRandomBehavior`, `BranchBehavior`
675
- and `ConditionalBehavior` declare none. Saving an entity whose behavior tree contains a
676
- `SelectorBehavior` therefore looks up adapter `"Behavior"` — not registered — and the save aborts with
677
- `No adapter found for class 'Behavior'`, naming a class that is not in the tree. If any ancestor *does*
678
- have a registered adapter the failure is worse: the subclass is written with the ancestor's payload
679
- and read back as the ancestor, dropping all subclass state.
680
- - **Fix**: require an own `typeName` —
681
- `Object.prototype.hasOwnProperty.call(Klass, "typeName") ? Klass.typeName : undefined` — and throw
682
- naming the actual constructor when absent, mirroring `BinaryObjectSerializer.writeValue`.
683
- - **Confidence**: Confirmed
684
-
685
- ### ECS-39 The last component type in a stream is never finalized on load
686
- - **Severity**: MINOR
687
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/storage/BinaryBufferDeSerializer.js:117-127`
688
- - **Defect**: `cycleFunction` finalizes the *previous* type at the top of the next loop iteration
689
- (`if (typesDecoded > 0) collectionDeSerializer.finalize();`), but the `typesLeft === 0` exit at
690
- line 117 returns `TaskSignal.EndSuccess` first. So `BinaryCollectionDeSerializer.finalize()` — and
691
- hence `adapter.finalize()` — never runs for the final type. The write path
692
- (`BinaryBufferSerializer.js:194`) finalizes every type correctly.
693
- - **Failure scenario**: load a buffer containing types `[Name, CombatUnit]`. `CombatUnit` elements are
694
- read to exhaustion, `typesLeft` is `0`, the task ends, and
695
- `CombatUnitSerializationAdapter.finalize()`
696
- (`model/game/ecs/component/unit/serialization/CombatUnitSerializationAdapter.js:51`) is never invoked,
697
- so its module-static `serialization_array` is left holding stale entries instead of being spliced
698
- empty. The deserializer is also left in `State.Ready`, so a subsequent `initialize()` on the same
699
- instance throws `IllegalStateException`. Which adapter is affected depends on which component type
700
- happens to be last, so the leak is non-deterministic across saves.
701
- - **Fix**: finalize the in-flight collection before returning `EndSuccess`, guarding against
702
- double-finalize on a re-entered cycle.
703
- - **Confidence**: Confirmed
704
-
705
- ---
706
-
707
- ## GUI / HUD / speaker
708
-
709
- ### ECS-40 `LineSetDescription.collect` walks the root's children instead of the popped node's — infinite loop
710
- - **Severity**: CRITICAL
711
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/speaker/lines/sets/LineSetDescription.js:56`
712
- - **Defect**: Inside the traversal loop the popped node is `set`, but the child-set expansion reads
713
- `const sets = this.sets;` — the *root's* children — on every iteration. The root's children are
714
- therefore re-pushed forever and `pointer` never drains.
715
- - **Failure scenario** (reproduced): a `voice-line-sets` entry with a non-empty `sets` array
716
- (schema-supported: `LineSetDescriptionTable.linkOne` reads `sets` from JSON, and `validateOne` exists
717
- purely to cycle-check that graph). Root `R{lines:[l-root], sets:[C]}`, `C{lines:[l-child]}` →
718
- `collect` writes `l-root, l-child, l-child, l-child, …` without bound; `destination` grows until OOM.
719
- `VoiceSystem.sayLineFromSet` calls `set.collect(temp_lines, 0)`, so the tab hangs on the first nested
720
- bark set anyone authors.
721
- - **Fix**: `const sets = set.sets;`
722
- - **Confidence**: Confirmed
723
-
724
- ### ECS-41 `GUIElementSystem.unlink` calls `Signal.remove` without the context it subscribed with
725
- - **Severity**: MAJOR
726
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/GUIElementSystem.js:261` (subscribe at `:248`)
727
- - **Defect**: `link` does `component.visible.onChanged.add(handleComponentVisibilityChange, {el, system, entity})`;
728
- `unlink` does `component.visible.onChanged.remove(handleComponentVisibilityChange)` with no `thisArg`.
729
- `Signal.remove` matches on `handle === h && context === thisArg`, so it matches nothing and returns
730
- `false`.
731
- - **Failure scenario** (reproduced against the real `Signal`): `add(handler, ctx)` then `remove(handler)`
732
- → returns `false`, `hasHandlers()` stays `true`, the next `send1` still runs the handler. Concretely,
733
- `ecd.removeComponentFromEntity(entity, GUIElement)` (real caller:
734
- `model/game/scenes/combat/UnitStateManager.js:437`) or `Entity.destroy()` → `Entity.build()` leaves
735
- the old subscription in place, and the next `link` adds a second. With two handlers,
736
- `component.visible.set(true)` runs `attachComponent` twice → `parent.addChild(component.view)` twice
737
- → `View.addChild` (`view/View.js:365`) pushes the same child into `parent.children` twice (the
738
- `isLinked` assert that would catch it is stripped in production). `removeChild` then drops only the
739
- first entry, so `parent.children` keeps a stale entry whose `el` is no longer in the DOM; a later
740
- `removeAllChildren` calls `this.el.removeChild(child.el)` on a non-child and throws `NotFoundError`.
741
- - **Fix**: store the context object (as `ViewportPositionSystem` does in `this.data`) and pass it to
742
- `remove`.
743
- - **Confidence**: Confirmed
744
-
745
- ### ECS-42 `ViewportPositionSystem.unlink` never unsubscribes from `el.view.size.onChanged`
746
- - **Severity**: MAJOR
747
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/position/ViewportPositionSystem.js:154` (unlink at `:167-183`)
748
- - **Defect**: `link` makes five `onChanged.add(updatePosition, eventContext)` subscriptions —
749
- `vp.position`, `vp.offset`, `vp.anchor`, `vp.enabled` and `el.view.size`. `unlink` removes only the
750
- first four.
751
- - **Failure scenario**: `GUIEngine.createModal` (`meep/src/engine/ui/GUIEngine.js:215`) does
752
- `window.removeComponent(ViewportPosition)` immediately after `openWindow` built the entity,
753
- deliberately freeing the modal from viewport positioning while the `GUIElement` and its
754
- `ui-modal-window-container` view stay live on screen. `unlink` runs, but `updatePosition` stays bound
755
- to that container's `size.onChanged` with a context holding the now-detached `vp`. Any later size
756
- change re-invokes `positionComponent(el, vp)`, overwriting `view.position` from the stale
757
- `vp.position`, overwriting `view.transformOrigin` with the stale `vp.anchor` (default `0,0`,
758
- discarding the `0.5,0.5` set at `GUIEngine.js:213`), and calling `el.visible.set(...)` — if the stale
759
- position is out of bounds and `stickToScreenEdge` is false (the default), `el.visible.set(false)`
760
- makes `GUIElementSystem` detach the modal from the DOM and the open modal vanishes.
761
- - **Fix**: add `el.view.size.onChanged.remove(updatePosition, eventContext);` inside the
762
- `eventContext !== undefined` block in `unlink`.
763
- - **Confidence**: Confirmed
764
-
765
- ### ECS-43 `HeadsUpDisplay.toJSON` writes `transformPerspectiveRotation`, `fromJSON` reads `perspectiveRotation`
766
- - **Severity**: MAJOR
767
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/hud/HeadsUpDisplay.js:86` vs `:90`
768
- - **Defect**: The JSON key emitted for the `PerspectiveRotation` flag does not match the key the reader
769
- destructures, so the reader always falls back to its `false` default.
770
- - **Failure scenario** (reproduced): a HUD with `PerspectiveRotation` set — exactly what
771
- `makeEnemyGroupHud` in `model/game/scenes/strategy/gui/setupStrategyHeadsUpDisplays.js` configures.
772
- `toJSON()` yields `{"worldOffset":…,"transformWorldOffset":true,"transformPerspectiveRotation":true}`;
773
- `HeadsUpDisplay.fromJSON(that)` yields `flags = 1` instead of `3`,
774
- `getFlag(PerspectiveRotation) === false`, and `a.equals(b) === false`. The screen-space billboard
775
- rotation is silently dropped on any JSON round-trip. The binary adapter round-trips the flag
776
- correctly, so the two persistence paths disagree.
777
- - **Fix**: emit `perspectiveRotation` in `toJSON`, or rename the `fromJSON` destructure to
778
- `transformPerspectiveRotation`. Pick one and keep both sides identical.
779
- - **Confidence**: Confirmed
780
-
781
- ### ECS-44 `GUIElementSystem.link` logs "does not have a view" and then dereferences the missing view
782
- - **Severity**: MINOR
783
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/GUIElementSystem.js:212`
784
- - **Defect**: The un-managed-with-no-view branch does `console.error(...)` with no `return`. Execution
785
- falls through to `if (component.visible.getValue()) this.attachComponent(component, entity)`
786
- (`visible` defaults to `true`), and `attachComponent` reaches `parent.addChild(component.view)` with
787
- `component.view === null`.
788
- - **Failure scenario**: `const c = new GUIElement(); c.clearFlag(GUIElementFlag.Managed);` added to an
789
- entity — precisely the state the error message describes. `View.addChild(null)` executes
790
- `child.parent = this;` (its guarding asserts are stripped in production) →
791
- `TypeError: Cannot set properties of null (setting 'parent')` propagates out of `link`, aborting the
792
- component-added observer dispatch for that entity so later systems never link.
793
- - **Fix**: `return;` after the `console.error` at `:212`.
794
- - **Confidence**: Confirmed
795
-
796
- ### ECS-45 `ButtonViewEntity.finalize` destroys the structural children created in its constructor
797
- - **Severity**: MINOR
798
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/view/ButtonViewEntity.js:104-107` (children created at `:17-23`)
799
- - **Defect**: The constructor adds the `foreground` and `background` `EmptyView`s the button's CSS is
800
- built on. `finalize()` calls `removeAllChildren()`, which removes them too — but `ViewEntity.finalize`
801
- is documented as bringing the view "to initial state ready to be destroyed **or re-used**", and
802
- `GUIElementSystem` implements exactly that re-use contract (`unlink` calls `view.finalize()` and
803
- clears `Initialized`; `attachComponent` re-calls `view.initialize(...)` when it sees
804
- `Managed && !Initialized`).
805
- - **Failure scenario**: an entity carrying a `view.Button` `GUIElement` (registered at
806
- `model/game/GameClassRegistry.js:89`) goes through link → unlink → link. On the second `initialize`
807
- the label is re-added but `.foreground`/`.background` are gone, so the button renders with no
808
- background fill and dead hover/press styling. `LocalizedLabelViewEntity` is correct precisely because
809
- its constructor adds no children.
810
- - **Fix**: create the foreground/background children in `initialize()`, or have `finalize()` remove only
811
- the label child.
812
- - **Confidence**: Confirmed
813
-
814
- ### ECS-46 `LineSetDescriptionTable.validateOne` reports a false circular dependency for a shared child set
815
- - **Severity**: MINOR
816
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/speaker/lines/sets/LineSetDescriptionTable.js:79`
817
- - **Defect**: The check treats *any* second visit to a node as a cycle
818
- (`if (visited.hasOwnProperty(set.id))`) rather than tracking the current traversal path, so a DAG with
819
- a shared child is rejected.
820
- - **Failure scenario**: `A{sets:[B,C]}`, `B{sets:[D]}`, `C{sets:[D]}` — no cycle. Traversal pops `A`,
821
- pushes `B,C`; pops `C`, pushes `D`; pops `D`, marks visited; pops `B`, pushes `D`; pops `D` again →
822
- `visited` hit → `errorConsumer("Circular dependency detected: D -> C -> A")` and `return false`. Valid
823
- voice-line-set data fails validation at load, and the reported path names the wrong parent.
824
- - **Fix**: track the recursion stack (mark on entry, unmark on exit) or colour nodes white/grey/black,
825
- and only report a cycle on re-entering a grey node.
826
- - **Confidence**: Confirmed
827
-
828
- ### ECS-47 `VoiceSystem.sayLineFromSet` dereferences `undefined` when the set yields no lines
829
- - **Severity**: MINOR
830
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/speaker/VoiceSystem.js:313-315`
831
- - **Defect**: `weightedRandomFromArray` returns `undefined` when `length === 0`
832
- (`core/collection/array/weightedRandomFromArray.js:26-28`), and the result is used unguarded as
833
- `selected_line.id`. The neighbouring "set not found" case at `:303` warns and returns, so graceful
834
- handling of bad data is the intended contract here.
835
- - **Failure scenario**: a `voice-line-sets` entry with an empty or omitted `lines` array (`linkOne`
836
- defaults `lines = []`) — a placeholder or not-yet-authored bark set.
837
- `ecd.sendEvent(entity, VoiceEvents.SpeakSetLine, {id: thatSet})` → `collect` returns `0` →
838
- `TypeError: Cannot read properties of undefined (reading 'id')` thrown out of the entity-event dispatch.
839
- - **Fix**: guard on `selected_line === undefined` after the `weightedRandomFromArray` call.
840
- - **Confidence**: Confirmed
841
-
842
- ---
843
-
844
- ## Attachment / parent / transform / IK
845
-
846
- ### ECS-49 `EntityNode`'s `parent` setter strips `TransformAttachment` and never re-adds it
847
- - **Severity**: MAJOR
848
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/parent/EntityNode.js:240-270`
849
- - **Defect**: The setter removes both components up front
850
- (`let parent_entity = this_entity.removeComponent(ParentEntity); let attachment = this_entity.removeComponent(TransformAttachment);`)
851
- and, in the `node !== null` branch, explicitly re-adds `ParentEntity` via `this_entity.add(parent_entity)`
852
- — but the attachment is only put back through `this.__safe_get_attachment()`, which runs **only when
853
- `attachment === null`**. When the node already had a `TransformAttachment`, the removed instance is
854
- mutated (`attachment.parent = parent_entity_id`) and dropped on the floor.
855
- - **Failure scenario** (reproduced): three built `EntityNode`s A, B, C each with a `Transform`.
856
- ```js
857
- A.addChild(C); // C -> ParentEntity{0}, TransformAttachment{parent:0}
858
- C.parent = B; // public setter, the documented re-parent entry point
859
- ```
860
- Observed: `ParentEntity.entity` correctly becomes `1`, but
861
- `C.entity.getComponent(TransformAttachment)` is `null` and
862
- `ds.getComponent(C.entity.id, TransformAttachment)` is `undefined`. `TransformAttachmentSystem` has
863
- unlinked C, so C's world transform is no longer driven by any parent — the node visually detaches
864
- while still being logically parented (and still gets destroyed when B is destroyed).
865
- `addChild` escapes this only because it routes through `removeChild` → `node.parent = null` first,
866
- which nulls both.
867
- - **Fix**: after `attachment.parent = parent_entity_id`, put the component back —
868
- `if (this_entity.getComponent(TransformAttachment) === null) this_entity.add(attachment);` — or
869
- simply do not remove `TransformAttachment` at the top when `node !== null`.
870
- - **Confidence**: Confirmed
871
-
872
- ### ECS-50 `InverseKinematicsSystem` declares read-only access to `Mesh` because of an undefined enum member
873
- - **Severity**: MINOR
874
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/ik/InverseKinematicsSystem.js:18`
875
- - **Defect**: `ResourceAccessSpecification.from(Mesh, ResourceAccessKind.Read | ResourceAccessSpecification.Write)`
876
- — `Write` is a member of `ResourceAccessKind`, not of `ResourceAccessSpecification`. The latter has no
877
- such static, so the expression is `1 | undefined === 1`: the system declares `Read` only, while
878
- `TwoBoneInverseKinematicsSolver.solve` actually mutates the mesh's bone quaternions
879
- (`boneA.setRotationFromQuaternion(...)`, `TwoBoneInverseKinematicsSolver.js:173-174`).
880
- - **Failure scenario**: `EntityManager.updateExecutionOrder`'s `scoreSystem` weights a `Write` component
881
- at `attached_edge_count * 2` and a `Read` at `* 1`, and `computeSystemComponentDependencyGraph` only
882
- emits edges out of a system that declares `Write`/`Create`. With the typo, `InverseKinematicsSystem`
883
- contributes no dependency edges and is scored as a pure `Mesh` reader, so it can be ordered *after*
884
- the systems that consume mesh/bone state in the same `simulate()` call — IK corrections (feet planted
885
- on terrain) are applied one frame late, producing visible foot sliding / ground penetration on the
886
- frame a character's pose changes.
887
- - **Fix**: `ResourceAccessKind.Read | ResourceAccessKind.Write`.
888
- - **Confidence**: Confirmed
889
-
890
- ### ECS-51 `OneBoneSurfaceAlignmentSolver` recovers the alignment angle with `asin`, which cannot exceed 90°
891
- - **Severity**: MINOR
892
- - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js:170-175`
893
- - **Defect**: `axis.crossVectors(v3, Vector3.forward); const betaSine = axis.length(); const beta = Math.asin(clamp(betaSine, -1, 1));`
894
- — `|a × b| = sin θ` is ambiguous between `θ` and `π − θ`, and `asin` always returns the branch in
895
- `[0, π/2]`. The angle between the contact normal and the bone axis is not recoverable this way once it
896
- exceeds 90°.
897
- - **Failure scenario**: bone axis `(0,0,1)` in bone space, contact normal in bone space
898
- `(0, 0.9, −0.436)` — 116° apart. `|cross| = 0.9` → `beta = asin(0.9) = 64.2°`. The bone is rotated
899
- 64° instead of 116°, so instead of squaring up to the surface it ends up 52° off, tilted into the
900
- surface. Any surface whose normal is more than 90° from the bone's own axis (steep slopes,
901
- overhangs, an inverted foot-bone rig) is aligned to the wrong angle rather than clamped by
902
- `constraint.limit`.
903
- - **Fix**: recover the angle unambiguously — `Math.atan2(axis.length(), v3.dot(Vector3.forward))` —
904
- before normalising `axis`.
905
- - **Confidence**: Likely (the trigonometric limitation is unambiguous; I did not exercise it against a
906
- real rig, so how often a live skeleton crosses 90° is unverified)
907
-
908
- ---
909
-
910
- ## Status of previously-known items
911
-
912
- | Item | Status |
913
- | --- | --- |
914
- | `Entity.promiseEvent` calling `removeEventListener` instead of `add` | **FIXED** (`Entity.js:326-327` now `addEventListener`) |
915
- | `EntityComponentDataset.dropData` not clearing `__entityAnyEventListeners` | **FIXED** (`EntityComponentDataset.js:1882`) |
916
- | `HeadsUpDisplaySystem` scaling world-offset Z by `scale.y` | **FIXED** (`HeadsUpDisplaySystem.js:88` uses `transform_scale.z`) |
917
- | `obtainTileAtWorldPosition2D` dereferencing an out-of-bounds tile | **STILL PRESENT** (`TerrainTileManager.js:487-489`; `getTileByWorldPosition2D` can return `undefined`, then `tile.gridPosition`) |
918
- | `FogOfWar.rebuildSampler` width/height swap | **FIXED** (`FogOfWar.js:707-713`, correct `Sampler2D(data, itemSize, width, height)` order) |
919
- | `TerrainTileManager.stitchTile` floor-vs-ceil | **STILL PRESENT** (`:566` uses `floor`; `:353`, `:405`, `:408`, `:422` all use `ceil`) |
920
- | Event-dispatch pointer restore missing `try/finally` | **STILL PRESENT** (`EntityComponentDataset.js:2138-2162` and `2180-2204`) |
1
+ # Correctness review — `engine/ecs`
2
+
3
+ Scope: every non-spec source file under `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/`
4
+ (208 source files). Reviewed 2026-08-06.
5
+
6
+ Findings marked **Confirmed** were either reproduced by executing the code or read directly
7
+ off the source with an unambiguous trace. `assert.*` is treated as absent (stripped in prod).
8
+
9
+ ---
10
+
11
+ ## Core (Entity / EntityComponentDataset / EntityManager / System)
12
+
13
+ ### ECS-1 `Entity.readFromDataset` never watches destruction, so a stale `Built` flag corrupts recycled entity ids
14
+ - **Severity**: CRITICAL
15
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/Entity.js:508-525`
16
+ - **Defect**: `build()` registers `#handleEntityDestroyed` on `EventType.EntityRemoved` when the
17
+ `WatchDestruction` flag is set (line 487-489), which is on by default. `readFromDataset` sets
18
+ `EntityFlags.Built` and binds the reference but registers no such listener, so the returned
19
+ `Entity` keeps reporting `isBuilt === true` forever after the underlying entity is removed.
20
+ Since `EntityComponentDataset.createEntity` recycles ids (`entityOccupancy.nextClearBit(0)`),
21
+ every subsequent operation on that `Entity` lands on a *different* entity.
22
+ - **Failure scenario** (reproduced):
23
+ ```js
24
+ const ds = new EntityComponentDataset();
25
+ ds.setComponentTypeMap([A, B]);
26
+ const id = ds.createEntity(); // id = 0
27
+ ds.addComponentToEntity(id, new A());
28
+ const e = Entity.readFromDataset(id, ds);
29
+ ds.removeEntity(id);
30
+ const recycled = ds.createEntity(); // id = 0 again, generation 1
31
+ e.isBuilt // => true (should be false)
32
+ e.add(new B()); // => writes B onto `recycled`, an unrelated entity
33
+ e.destroy(); // => destroys `recycled`
34
+ ```
35
+ Output observed: `recycled entity now has B: true`, `after e.destroy(), recycled entity exists: false`.
36
+ The `EntityReference` already carries the generation needed to detect this (`e.generation === 0`
37
+ vs `ds.getEntityGeneration(recycled) === 1`) but nothing consults it.
38
+ - **Fix**: in `readFromDataset`, mirror `build()` — after setting the `Built` flag, do
39
+ `if (r.getFlag(EntityFlags.WatchDestruction)) dataset.addEntityEventListener(entity, EventType.EntityRemoved, r.#handleEntityDestroyed, r);`
40
+ (needs a small private accessor since `#handleEntityDestroyed` is instance-private).
41
+ - **Confidence**: Confirmed
42
+
43
+ ### ECS-2 `setComponentTypeMap` leaves the dataset permanently corrupted when observer rebuild throws
44
+ - **Severity**: MAJOR
45
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityComponentDataset.js:674-731`
46
+ - **Defect**: `setComponentTypeMap` mutates state in four steps — `updateComponentOccupancy` (716),
47
+ `updateComponentStores` (718), `updateObservers` (720), then `componentTypeMap` / `componentTypeCount`
48
+ (722, 730). `updateObservers` calls `observer.build(map)` for every observer sitting in a *surviving*
49
+ component's store; `EntityObserver.build` **throws** if any of that observer's component types is
50
+ absent from the new map (`EntityObserver.js:102-104`). Because occupancy and the component stores
51
+ have already been re-strided at that point, the throw leaves the dataset with the new stride but the
52
+ old `componentTypeMap` / `componentTypeCount` / `__type_to_index_map`.
53
+ - **Failure scenario** (reproduced):
54
+ ```js
55
+ ds.setComponentTypeMap([A, B]);
56
+ ds.addObserver(new EntityObserver([A, B], onComplete, onBroken), false);
57
+ ds.unregisterComponentType(A); // A has no live instances, so the removal check passes
58
+ ```
59
+ Observed: throws `Component type[0] was not found in the supplied map`, then
60
+ `componentTypeMap = ['A','B']`, `componentTypeCount = 2`, but `components.length = 1`.
61
+ The next `ds.addComponentToEntity(e, new B())` throws
62
+ `TypeError: Cannot read properties of undefined (reading '0')` — `components[1]` no longer exists.
63
+ The dataset cannot be used again.
64
+ - **Fix**: validate first, mutate second. Before touching occupancy/stores, iterate every registered
65
+ observer and check `map` contains all of `observer.componentTypes`; throw there (dataset untouched).
66
+ Observers that watch only removed types should be disconnected explicitly (`observer.dataset = null`)
67
+ rather than silently dropped from the stores.
68
+ - **Confidence**: Confirmed
69
+
70
+ ### ECS-3 `traverseEntitiesCompactedFiltered` uses `-1` as a component index for unregistered classes
71
+ - **Severity**: MAJOR
72
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityComponentDataset.js:1990-2020`
73
+ - **Defect**: `componentIndices[i] = this.computeComponentTypeIndex(klass)` stores `-1` for a class
74
+ that is not registered in this dataset, and nothing filters it out. The loop then does
75
+ `componentOccupancy.get(entityOccupancyAddress + (-1))` — reading the *last* component bit of the
76
+ *previous* entity — and, when that bit happens to be set, `this.components[-1][i]`.
77
+ `this.components[-1]` is `undefined`. (In production `BitSet.get`'s `assert.isNonNegativeInteger`
78
+ is stripped, so it silently returns the neighbouring entity's bit instead of throwing.)
79
+ - **Failure scenario** (reproduced): dataset registered with `[A, B]`; entity 0 has both A and B;
80
+ entity 1 has A. `ds.traverseEntitiesCompactedFiltered([C, A], visitor)` where `C` was never
81
+ registered. With asserts on it throws `bitIndex must be >= 0, instead was -1`. With asserts stripped,
82
+ entity 1 reads bit `2 - 1 = 1` (entity 0's B bit, which is set) and then dereferences
83
+ `this.components[-1][1]` → `TypeError: Cannot read properties of undefined`.
84
+ - **Fix**: skip unregistered classes when building `componentIndices` (build a compacted array of
85
+ valid indices only), the same way `traverseEntities` returns early on `-1`.
86
+ - **Confidence**: Confirmed
87
+
88
+ ### ECS-4 Bare `.sort()` on component indices is lexicographic
89
+ - **Severity**: MINOR
90
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityComponentDataset.js:1996`
91
+ - **Defect**: `componentIndices.sort()` with no comparator sorts numbers as strings. The comment
92
+ says "sort component indices array for faster traversal"; with 10 or more registered component
93
+ types the array is not sorted at all, and the `components` array handed to the visitor comes out
94
+ in a scrambled, size-dependent order.
95
+ - **Failure scenario** (reproduced): 12 registered component types; an entity holds instances of
96
+ type index 2 and type index 10. The visitor receives `[K10, K2]` instead of `[K2, K10]`. Any
97
+ consumer of this "main utility … to facilitate serialization" method that depends on a stable
98
+ component ordering (e.g. a byte-for-byte save comparison or a diff) sees the order flip as soon
99
+ as the dataset crosses 10 component types.
100
+ - **Fix**: `componentIndices.sort((a, b) => a - b);`
101
+ - **Confidence**: Confirmed
102
+
103
+ ### ECS-5 `Entity.getComponentSafe` throws `ReferenceError` — its helper is neither imported nor exported
104
+ - **Severity**: MINOR
105
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/Entity.js:256`
106
+ - **Defect**: The error path calls `computeComponentClassName(klass)`, but `Entity.js` imports only
107
+ `assert`, `Signal`, `EntityFlags`, `EntityReference`, `EventType`. Worse,
108
+ `computeComponentClassName.js` declares the function without any `export` statement at all, so
109
+ there is nothing to import.
110
+ - **Failure scenario** (reproduced): `new Entity().getComponentSafe(SomeClass)` throws
111
+ `ReferenceError: computeComponentClassName is not defined` instead of the documented
112
+ `Error: Component of given class 'SomeClass' not found`. The diagnostic naming the missing
113
+ component — the entire point of the method over `getComponent` — is lost.
114
+ - **Fix**: add `export` to `computeComponentClassName.js` and import it in `Entity.js`.
115
+ - **Confidence**: Confirmed
116
+
117
+ ### ECS-6 Re-`build()`ing an `Entity` whose components drifted orphans the old entity and mis-aims its destruction watcher
118
+ - **Severity**: MINOR
119
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/Entity.js:442-493`
120
+ - **Defect**: `build()` returns early only when `checkExistingComponents` passes. Otherwise it falls
121
+ through and calls `dataset.createEntity()` **without** removing the entity it was previously bound
122
+ to and without unregistering the `EntityRemoved` listener registered on the old id at line 488.
123
+ - **Failure scenario** (reproduced):
124
+ ```js
125
+ const e = new Entity(); e.add(new A());
126
+ const id1 = e.build(ds); // 0
127
+ ds.removeComponentFromEntity(id1, A); // a system strips the component directly
128
+ const id2 = e.build(ds); // 1 -- entity 0 is now orphaned, entityCount = 2
129
+ ds.removeEntity(id1); // fires the stale watcher
130
+ e.isBuilt // => false, although entity 1 is alive
131
+ e.destroy() // => false; entity 1 leaks permanently
132
+ ```
133
+ - **Fix**: when the early-return check fails but `EntityFlags.Built` is set, call `this.destroy()`
134
+ first so the previous entity and its listener are released before rebinding.
135
+ - **Confidence**: Confirmed
136
+
137
+ ### ECS-7 `EntityManager.removeSystem` never clears `system.entityManager`
138
+ - **Severity**: MINOR
139
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityManager.js:590-631`
140
+ - **Defect**: `startSystem` refuses to start a system whose `entityManager` is set to a different
141
+ manager (`EntityManager.js:739-741`). Neither `removeSystem` nor `stopSystem` resets the field.
142
+ - **Failure scenario**: `em1.removeSystem(s)` then `em2.addSystem(s); em2.startup()` →
143
+ `startSystem` throws `System is bound to another EntityManager`, caught at line 763, so
144
+ `errorCallback` fires and `em2.state` becomes `EntityManagerState.Failed`; the whole manager
145
+ never reaches `Running`. Reusing a system instance across manager instances (level reload, test
146
+ fixtures) is impossible.
147
+ - **Fix**: set `system.entityManager = null` in `removeSystem` after `stopSystem` resolves.
148
+ - **Confidence**: Confirmed
149
+
150
+ ### ECS-8 `system_validate_class` duplicate-component backtrace is off by one
151
+ - **Severity**: MINOR
152
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/system_validate_class.js:49`
153
+ - **Defect**: `for (let j = 0; j < i - 1; j++)` never compares `components_used[i]` against
154
+ `components_used[i - 1]`, so adjacent duplicate resource specifications slip through.
155
+ - **Failure scenario**: `components_used = [Spec(Transform, Read), Spec(Transform, Write)]` passes
156
+ validation. `System.getAccessForComponent` (`System.js:110-117`) `break`s at the first match, so
157
+ the `Write` access is silently discarded, and `EntityManager.updateExecutionOrder`'s `scoreSystem`
158
+ scores the system as a reader (`attached_edge_count` instead of `× 2`) — the system is scheduled
159
+ after the systems that should run after it.
160
+ - **Fix**: `for (let j = 0; j < i; j++)`.
161
+ - **Confidence**: Confirmed
162
+
163
+ ### ECS-48 `EntityManager.startup` reports `Running` while a system added during startup is still `STARTING`
164
+ - **Severity**: MINOR
165
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/EntityManager.js:795,814,567-574`
166
+ - **Defect**: `startup()` captures `expectedReadyCount = systems.length` before the start loop, then
167
+ `finalizeStartup()` fires when `readyCount === expectedReadyCount`. A system's own `startup()` may
168
+ call `entityManager.addSystem(...)`, which pushes onto the same `systems` array the `for…of` loop is
169
+ walking, so the loop does start the newcomer — but the target count is stale by one. Separately,
170
+ `addSystem` only auto-starts when `this.state === EntityManagerState.Running`; during
171
+ `EntityManagerState.Starting` it returns `Promise.resolve()` and never calls `startSystem`, so a
172
+ system added after the loop has drained is never started at all.
173
+ - **Failure scenario**: `SGMeshSystem.startup(em)` does
174
+ `if (!em.hasSystem(TransformAttachmentSystem)) await em.addSystem(new TransformAttachmentSystem());`
175
+ (`engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.js:94-96`). With N systems registered,
176
+ `expectedReadyCount = N`; once the original N have reported ready, `finalizeStartup()` sets
177
+ `state = Running` and invokes `readyCallback()` while `TransformAttachmentSystem` is still in
178
+ `STARTING`. The caller begins `simulate()`; `updateExecutionOrder` skips non-`RUNNING` systems and its
179
+ observer has not been added to the dataset, so attachments silently do not update for those frames.
180
+ If the same `addSystem` were made from after an `await` (once the start loop has drained) the system
181
+ would never start at all, while its promise still resolves.
182
+ - **Fix**: read `systems.length` live at each `systemReady` (`readyCount === systems.length`) instead of
183
+ a captured count, and treat `EntityManagerState.Starting` like `Running` in `addSystem`'s auto-start
184
+ branch.
185
+ - **Confidence**: Confirmed
186
+
187
+ ---
188
+
189
+ ## Fog of war
190
+
191
+ ### ECS-9 `FogOfWarSystem.shutdown` removes none of the three render-loop subscriptions it made in `startup`
192
+ - **Severity**: MAJOR
193
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWarSystem.js:162-219`
194
+ - **Defect**: `startup` registers three anonymous closures on `graphics.on.preRender`,
195
+ `graphics.on.buffersRendered` and `graphics.on.postRender`. `shutdown` only calls
196
+ `this.graphics.layerComposer.remove(this.compositLayer)`. Because the handlers are inline
197
+ functions with no retained reference, they can never be removed even by a caller.
198
+ - **Failure scenario**: `entityManager.shutdown()` (or `removeSystem(fogOfWarSystem)`) removes the
199
+ composit layer, but on the very next frame the `preRender` handler still runs
200
+ `compositerLayer.enable()` on the removed layer and `depthFrameBuffer.referenceCount++`, and
201
+ `buffersRendered` still runs `fogOfWarRenderer.render(renderer, camera, scene, compositerLayer.renderTarget)`
202
+ into a render target that is no longer composited — burning a full-screen pass every frame for the
203
+ lifetime of the `GraphicsEngine`. The closures also pin `entityManager`, `graphics` and the
204
+ `FogOfWarRenderer` (its `ShaderMaterial`, `Mesh` and `Scene` are never disposed). Starting a second
205
+ `FogOfWarSystem` — e.g. a level reload on a shared `GraphicsEngine` — stacks a second set of handlers,
206
+ so each frame renders the fog twice.
207
+ - **Fix**: hoist the three handlers into named fields (or wrap them in `SignalBinding`s) in `startup`,
208
+ `remove` them in `shutdown`, and dispose `fogOfWarRenderer.material` / `fogOfWarRenderer.quad`.
209
+ - **Confidence**: Confirmed
210
+
211
+ ### ECS-10 A revealer on the *wrong* team still reveals fog whenever its radius changes
212
+ - **Severity**: MAJOR
213
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWarRevealerSystem.js:52-97`
214
+ - **Defect**: The team check lives only in `attemptToWatch`, which gates the *position* watcher.
215
+ `update()` itself contains no team test, and `radiusWatcher` (line 93-97) is linked
216
+ unconditionally for every linked entity regardless of team.
217
+ - **Failure scenario**: `new FogOfWarRevealerSystem(/* team */ 1)`. An enemy unit with
218
+ `Team = 2`, `Transform`, `FogOfWarRevealer` links: `attemptToWatch(2)` correctly leaves
219
+ `positionWatcher` unlinked, but `radiusWatcher.link()` runs anyway. The enemy then receives a
220
+ vision buff (`revealer.radius.set(8)`) — `revealer.radius.onChanged` fires `update()`, which maps
221
+ the *enemy's* position to the grid and calls `fow.reveal(...)` on the player's fog. The player's
222
+ map is permanently uncovered at the enemy's location.
223
+ - **Fix**: put the team check inside `update()` (`if (team.getValue() !== visibilityTeam) return;`),
224
+ or link/unlink `radiusWatcher` from `attemptToWatch` alongside `positionWatcher`.
225
+ - **Confidence**: Confirmed
226
+
227
+ ### ECS-11 `computeRevealedGridBoundingRectangle` returns an inverted AABB when nothing is revealed
228
+ - **Severity**: MINOR
229
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWar.js:532-563`
230
+ - **Defect**: `x0`/`y0` seed to `width - 1` / `height - 1` and `x1`/`y1` to `0`. If no cell differs
231
+ from 255 the loop body never runs and those seeds are written out unchanged, producing
232
+ `x0 > x1` and `y0 > y1`.
233
+ - **Failure scenario**: a freshly resized 64×64 fog (sampler 66×66, all 255) →
234
+ `result.set(65, 65, 0, 0)`. `MinimapFogOfWar.updateFocusArea`
235
+ (`view/minimap/gl/MinimapFogOfWar.js:87`) then does `bounds.grow(2)`, `bounds.move(-0.5, 0.5)`
236
+ and multiplies by the scale, feeding a negative-extent rectangle into `enforceAspectRatio` — the
237
+ minimap focus area is garbage on the first frame of any map before the first reveal.
238
+ - **Fix**: track a `found` flag and, when nothing is revealed, emit an explicitly empty rectangle
239
+ (`result.set(0, 0, 0, 0)`) or leave `result` untouched and return a boolean.
240
+ - **Confidence**: Confirmed
241
+
242
+ ### ECS-12 `getWorldClearance` reads the distance sampler unclamped
243
+ - **Severity**: MINOR
244
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWar.js:677-684`
245
+ - **Defect**: Its sibling `computeAABBVisibility` clamps the sample position before
246
+ `distanceSampler.readChannel` (line 628-629); `getWorldClearance` does not.
247
+ `Sampler2D.readChannel` computes `(y * width + x) * itemSize + channel` with no bounds check
248
+ (the `assert`s are stripped), so an x outside `[0, width)` silently indexes into an adjacent row.
249
+ - **Failure scenario**: `FogOfWarVisibilityPredicate.test(x, y, z)` (line 41) is called with an
250
+ object's world position. For a 64×64 fog with `scale = 1`, an object one cell east of the map at
251
+ grid row 20 produces `sampleX = 65`, `sampleY = 21`, giving index `21 * 66 + 65 = 1451` — which is
252
+ cell `(1, 22)`, the *west* border of the row below. The object's visibility is decided by an
253
+ unrelated cell; where that cell is revealed, an off-map object that should be hidden renders.
254
+ - **Fix**: clamp exactly as `computeAABBVisibility` does, using the sampler's own dimensions.
255
+ - **Confidence**: Confirmed
256
+
257
+ ### ECS-13 `computeAABBVisibility` clamps to `size - 1` but the distance sampler is `size + 2` wide
258
+ - **Severity**: MINOR
259
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/fow/FogOfWar.js:628-629`
260
+ - **Defect**: `worldToSamplePosition` emits sampler-space coordinates with the +1 border offset
261
+ baked in, so the real world area occupies sampler columns `[1, size.x]`. The clamp bound is
262
+ `this.size.x - 1`, two cells short of the valid maximum.
263
+ - **Failure scenario**: 64×64 fog, `scale = 1`. `worldToSamplePosition` maps the world extent onto
264
+ sample coordinates `[1, 64]`, but the clamp caps at `63`. An object at the far eastern edge maps to
265
+ `sampleX = 64` and is read at column `63` instead — the outermost column/row of the map takes its
266
+ clearance from the cell one step inward. An unexplored strip along the east/south map edge therefore
267
+ reports the clearance of the explored cell beside it, and objects standing there are drawn through
268
+ the fog.
269
+ - **Fix**: clamp against the sampler, not the logical size: `clamp(sampleX, 0, this.sampler.width - 1)`.
270
+ - **Confidence**: Confirmed
271
+
272
+ ---
273
+
274
+ ## Terrain
275
+
276
+ ### ECS-14 `getTileByWorldPosition2D` reads the vertical axis as the grid row
277
+ - **Severity**: CRITICAL
278
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/tiles/TerrainTileManager.js:461-472`
279
+ - **Defect**: The point is packed as `Float32Array.of(x, 0, y)` — so the top-down Y lands at index
280
+ **2** — inverse-transformed, and then passed as `getRawTileByPosition(v3[0], v3[1])`. `v3[1]` is
281
+ the vertical component. Separately, `getRawTileByPosition` divides by `tileSize`, which is in grid
282
+ units, while `v3` is in object units, so `this.scale` must be divided out first.
283
+ - **Failure scenario**: 64×64 terrain, `gridScale = 1`, tile size 32 → a 2×2 tile grid, identity
284
+ transform. `getTileByWorldPosition2D(40, 40)` → `getRawTileByPosition(40, 0)` →
285
+ `tileX = 1, tileY = floor(0 / 32) = 0` → tile **(1,0)** instead of **(1,1)**. With an identity
286
+ transform the row is *always* 0, so every lookup collapses onto the top row of tiles.
287
+ `obtainTileAtWorldPosition2D` then builds the wrong tile.
288
+ - **Fix**: `return this.getRawTileByPosition(v3[0] / this.scale.x, v3[2] / this.scale.y);`
289
+ - **Confidence**: Confirmed
290
+
291
+ ### ECS-15 `traverseVisibleTiles` sums per-frustum hit counts but always writes at offset 0
292
+ - **Severity**: MAJOR
293
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/TerrainSystem.js:265-290`
294
+ - **Defect**: `tile_count` is initialised to 0 and passed as the `result_offset` to
295
+ `bvh_query_user_data_intersects_frustum` on every iteration but never advanced, so each frustum
296
+ overwrites the previous frustum's results from index 0. `found_tiles` meanwhile accumulates the
297
+ per-frustum counts, and the consumer loop iterates to that sum.
298
+ - **Failure scenario**: two active `Camera` components (`CameraSystem.getActiveFrustums` yields one
299
+ frustum per active camera). Frustum A returns 6 tiles, frustum B returns 4 → `tiles` holds 6
300
+ entries but `found_tiles = 10`. Indices 6..9 are `undefined` →
301
+ `tileManager.tiles[undefined] === undefined` → the callback dereferences `tile.isBuilt` /
302
+ `tile.gridPosition` → `TypeError` out of `TerrainSystem.update` every frame.
303
+ - **Fix**: `tile_count += bvh_query_user_data_intersects_frustum(tiles, tile_count, ...)` and iterate
304
+ to `tile_count`.
305
+ - **Confidence**: Confirmed
306
+
307
+ ### ECS-16 `TerrainOverlay.paintSampler` passes the overlay instead of its sampler
308
+ - **Severity**: MAJOR
309
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/overlay/TerrainOverlay.js:321`
310
+ - **Defect**: `sampler2d_paint(this, scaled_source, ...)` supplies the `TerrainOverlay` as the
311
+ destination `Sampler2D`. The pixel data lives on `this.sampler`.
312
+ - **Failure scenario**: `sampler2d_paint` computes
313
+ `_w = Math.min(width, source.width - 0, undefined - dx)` → `NaN`, so `for (x = 0; x < NaN; ...)`
314
+ never executes. Nothing is written and no error is raised — `paintImage`/`paintSampler` are silent
315
+ no-ops, while `texture.needsUpdate = true` re-uploads unchanged pixels every call.
316
+ - **Fix**: `sampler2d_paint(this.sampler, scaled_source, 0, 0, dx, dy, dWidth, dHeight);`
317
+ - **Confidence**: Confirmed
318
+
319
+ ### ECS-17 Tile rebuild leaks the previous `BufferGeometry`
320
+ - **Severity**: MAJOR
321
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/tiles/TerrainTile.js:551,586`
322
+ - **Defect**: `build()` assigns `this.geometry = g` / `mesh.geometry = g` without disposing the
323
+ geometry it replaces. The only disposal path, `dispose()`, early-returns on `if (!this.isBuilt) return;`
324
+ — and `TerrainTileManager.rebuildTilesByUV` (`tiles/TerrainTileManager.js:300`) clears `isBuilt`
325
+ while the geometry is still attached, so the old geometry can never be released.
326
+ - **Failure scenario**: terrain height editing calls `rebuildTilesByUV`, clearing `isBuilt` on every
327
+ overlapping tile; `TerrainSystem.update` then re-invokes `tile.build(tileData)`, installing a fresh
328
+ geometry and orphaning the old VBO/IBO. For a 4-tile region at `resolution = 4` with 32×32 tiles
329
+ (16 384 verts, ~590 KB of attributes each) that is ~2.4 MB of VRAM per edit, never reclaimed.
330
+ `destroyTiles()` hits the same early return.
331
+ - **Fix**: dispose the previous geometry at the top of `build()`, and replace the `!isBuilt` early
332
+ return in `dispose()` with a `geometry !== null` check.
333
+ - **Confidence**: Confirmed
334
+
335
+ ### ECS-18 `TerrainTile.stitching` flags are never reset, so re-stitched seams copy instead of average
336
+ - **Severity**: MAJOR
337
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/tiles/TerrainTile.js:99-110` (consumed at `:279`, `:333`)
338
+ - **Defect**: Neither `build()`, `dispose()`, nor `TerrainTileManager.rebuildTilesByUV` clears the
339
+ per-neighbour `stitching` state. `stitchHorizontal`/`stitchVertical` branch on those flags: when one
340
+ side is already marked stitched, the other side's normals are *copied* wholesale rather than averaged.
341
+ - **Failure scenario**: tiles A(0,0) and B(1,0) built and stitched, so `A.stitching.right === true`.
342
+ A height edit clears `isBuilt` on both but leaves the flags. A rebuilds first — `stitchTile` sees B
343
+ unbuilt and skips. B rebuilds — `stitchHorizontal(A, B)` finds the stale
344
+ `left.stitching.right === true` and takes the copy branch, writing A's one-sided edge normals into B.
345
+ The shared edge now carries normals computed from A's triangles only: a visible lighting crease down
346
+ every rebuilt tile boundary after any terrain edit.
347
+ - **Fix**: reset all eight `stitching` fields in `TerrainTile.build()` and `dispose()`, and clear the
348
+ neighbours' matching flags in `rebuildTilesByUV`.
349
+ - **Confidence**: Confirmed
350
+
351
+ ### ECS-19 `ClingToTerrain.fromJSON` defaults `rotationSpeed` to `Infinity`; the field initialiser is `3`
352
+ - **Severity**: MAJOR
353
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/cling/ClingToTerrain.js:54`
354
+ - **Defect**: The class field is `rotationSpeed = 3`, and `ClingToTerrainSerializationUpgrader_0_1.js:22`
355
+ explicitly writes `3`, calling it "the default value from the ClingToTerrain class". `fromJSON`
356
+ destructures with `Number.POSITIVE_INFINITY` as the default.
357
+ - **Failure scenario**: a blueprint carrying `{"ClingToTerrain": {"normalAlign": true}}` with no
358
+ `rotationSpeed` → `rotationSpeed = Infinity`. In `ClingToTerrainSystem.processRaycastHit:272`
359
+ `Number.isFinite` is false → `angularLimit = Infinity` → `alignToVector` computes
360
+ `t = clamp01(Infinity / angle) = 1` → the entity snaps to the terrain normal instantly every frame
361
+ instead of easing at 3 rad/s. The same entity constructed in code, or loaded from binary v0 through
362
+ the upgrader, eases correctly — the behaviour differs by load path.
363
+ - **Fix**: use `3` as the `fromJSON` destructuring default.
364
+ - **Confidence**: Confirmed
365
+
366
+ ### ECS-20 `Terrain` JSON and binary paths persist disjoint field sets, and disagree on a default
367
+ - **Severity**: MAJOR
368
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/Terrain.js:930,981`;
369
+ `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/serialization/TerrainSerializationAdapter.js:29,144`
370
+ - **Defect**: Three fields are handled by exactly one of the two paths.
371
+ `gridTransformKind` and `lightMapURL` are written into `extra` by `serialize`/`deserialize` but are
372
+ absent from `toJSON`/`fromJSON`. `overlay.baseTileImage` is written by `toJSON` (as
373
+ `overlayTileImage`) and restored by `fromJSON`, but is absent from the binary adapter. The two paths
374
+ also disagree on the `gridTransformKind` default: `deserialize` falls back to
375
+ `GridTransformKind.Legacy` (`:144`) while the class field initialiser is `GridTransformKind.Direct`
376
+ (`Terrain.js:76`), which `fromJSON` leaves in place.
377
+ - **Failure scenario**: a terrain with `gridTransformKind = Legacy`, `size = (64,64)`, `gridScale = 1`,
378
+ saved via `toJSON` and reloaded. `fromJSON` leaves it at `Direct`, so `buildGridTransform` produces
379
+ `scale = 1, offset = 0.5` instead of `scale = 64/63 ≈ 1.0159, offset = 0`. `mapPointGrid2World(63, 63)`
380
+ returns `(63.5, ·, 63.5)` instead of `(64, ·, 64)` — every grid-anchored entity, marker and combat
381
+ cell is displaced, growing to more than half a cell at the far edge. The same round-trip drops
382
+ `lightMapURL`, so `build()` skips the `aoMap` load and the terrain renders unshadowed. Symmetrically,
383
+ a custom `overlay.baseTileImage` saved to binary reloads as the default `WHITE_PIXEL_DATA_URL`.
384
+ - **Fix**: add `gridTransformKind` and `lightMapURL` to `toJSON`/`fromJSON` (with the same default the
385
+ binary path uses), and write `overlay.baseTileImage` into the binary adapter's `extra` blob.
386
+ - **Confidence**: Confirmed
387
+
388
+ ### ECS-21 `convertHeightMap2NormalMap` returns normals tilted uphill — both gradients unnegated
389
+ - **Severity**: MAJOR
390
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/grid/HeightSampler2NormalSamplerSoft.js:54-56`
391
+ - **Defect**: For a height field `h(x, y)` the surface normal is `normalize(-∂h/∂x, -∂h/∂y, k)`. The
392
+ Sobel gradients `dX`/`dY` are computed with the correct sign but written out unnegated
393
+ (`_x = dX / magnitude`, `_y = dY / magnitude`).
394
+ - **Failure scenario**: a ramp `h(x, y) = x`. At an interior texel `dX = 8`, `dY = 0`,
395
+ `magnitude = sqrt(64.25) ≈ 8.0156` → the function returns `(+0.998, 0, +0.0624)`. The true normal for
396
+ a surface rising in `+x` leans toward `−x`: `(−0.998, 0, +0.0624)`. Every slope gets a normal pointing
397
+ up-slope, so any lighting or slope test built on this map is exactly reversed on both horizontal
398
+ axes. Flat regions are unaffected, which is why it is not obvious.
399
+ - **Fix**: `const _x = -dX / magnitude; const _y = -dY / magnitude;`
400
+ - **Confidence**: Likely (no in-repo consumer — public library surface; the maths is unambiguous)
401
+
402
+ ### ECS-22 `Terrain.heightTexture` receives the `Sampler2D` object as its `height` argument
403
+ - **Severity**: MINOR
404
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/Terrain.js:145`
405
+ - **Defect**: `new DataTexture(this.samplerHeight.data, this.samplerHeight.width, this.samplerHeight, RedFormat, FloatType)`
406
+ — the third parameter of `DataTexture(data, width, height, …)` gets the sampler instance rather than
407
+ `this.samplerHeight.height`. Compare `graphics/ecs/water/Water.js:138`.
408
+ - **Failure scenario**: immediately after `new Terrain()`, `terrain.heightTexture.image` is
409
+ `{data: Float32Array(1), width: 1, height: <Sampler2D>}`. Any consumer that binds `heightTexture`
410
+ before `build()` runs `updateHeightTexture()` (which repairs `image.width`/`image.height` at
411
+ `:721-722`) uploads with a non-numeric height — `gl.texImage2D` raises `GL_INVALID_VALUE`.
412
+ - **Fix**: pass `this.samplerHeight.height`.
413
+ - **Confidence**: Confirmed
414
+
415
+ ### ECS-23 `heightMap2AOMap` leaks the intermediate denoise source texture
416
+ - **Severity**: MINOR
417
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/grid/HeightMap2AOMap.js:77`
418
+ - **Defect**: `filter_result_to_texture(raw_ao.array, width, height)` creates a `DataTexture` that is
419
+ uploaded by the denoise `ImageFilter` pass and then dropped without `.dispose()`. The sibling
420
+ `pyramid_texture` *is* disposed at `:91`, so the omission is unintentional.
421
+ - **Failure scenario**: `terrain.buildLightMap()` runs per generated map. For a 256×256 terrain at the
422
+ default `quality = 4` the AO resolution is 1024×1024, so each bake orphans a 1024×1024 RGBA8 GPU
423
+ texture (~4 MB VRAM) for the lifetime of the WebGL context. Each new run accumulates another.
424
+ - **Fix**: hold the texture in a local and `.dispose()` it after the `smooth_ao` `ImageFilter` returns.
425
+ - **Confidence**: Confirmed
426
+
427
+ ### ECS-24 `loadVisibleTerrainTiles` does not reset its counters on retry, and can report success early
428
+ - **Severity**: MINOR
429
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js:70-84`
430
+ - **Defect**: `handleFailedTile` bumps `attempt` and re-runs `init()`, which recomputes `totalTiles` and
431
+ restarts `start_time`, but `loadedTiles` and `failedTiles` carry over from the aborted attempt. The
432
+ `attemptId !== attempt` guard only suppresses *stale* callbacks; it does not roll back what they
433
+ already counted.
434
+ - **Failure scenario**: 20 visible tiles; 12 resolve, then one rejects
435
+ (`'Original tile was destroyed during build process'`). `init()` re-runs with `totalTiles = 20` and
436
+ `loadedTiles = 12`. The 12 already-built tiles resolve on the next microtask → `loadedTiles = 24 ≥ 20`
437
+ → `cycleFunction` returns `TaskSignal.EndSuccess` while 8 tiles are still mid-build. The loading
438
+ screen closes and the scene shows 8 tiles with no geometry.
439
+ - **Fix**: reset `loadedTiles = 0` and `failedTiles = 0` at the top of `init()`.
440
+ - **Confidence**: Confirmed
441
+
442
+ ### ECS-25 `TerrainSystem.mapPointGrid2World` hardcodes the Legacy transform and drops the offset
443
+ - **Severity**: MINOR
444
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/terrain/ecs/TerrainSystem.js:81-87,186-187`
445
+ - **Defect**: `link()` computes `gridScaleX = g_w * gridScale / (g_w - 1)` unconditionally — the
446
+ `GridTransformKind.Legacy` formula — ignoring `component.gridTransformKind`, and
447
+ `mapPointGrid2World` applies no offset. `Terrain.mapPointGrid2World` (`Terrain.js:555`) uses
448
+ `gridTransform`, whose `Direct` branch (the class default) is `scale = gridScale, offset = gridScale / 2`.
449
+ - **Failure scenario**: 64×64 terrain, `gridScale = 1`, default `Direct` kind.
450
+ `terrain.mapPointGrid2World(0, 0, v3)` → `(0.5, ·, 0.5)`; `terrainSystem.mapPointGrid2World(0, 0, v3)`
451
+ → `(0, ·, 0)`. At `x = 63`: `63.5` vs `64.0`. Two public methods documented to perform the same
452
+ mapping differ by half a cell at the origin and diverge with distance. `gridScaleX/Y` are also
453
+ system-global, so with two `Terrain` entities they hold whichever linked last.
454
+ - **Fix**: delete `TerrainSystem.mapPointGrid2World`/`gridScaleX`/`gridScaleY` and route callers to
455
+ `Terrain.mapPointGrid2World`, or read `terrain.gridTransform` directly.
456
+ - **Confidence**: Confirmed
457
+
458
+ ---
459
+
460
+ ## Animation / dynamic actions / evaluation
461
+
462
+ ### ECS-26 `AnimationOptimizer` writes its results to itself — optimization is a silent no-op
463
+ - **Severity**: MAJOR
464
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/animation/AnimationOptimizer.js:109-114`
465
+ - **Defect**: `optimizeTrack(track)` is a port of three.js `KeyframeTrack.prototype.optimize()`, where
466
+ `this` *is* the track. Here `this` is the `AnimationOptimizer`, so the compacted arrays are assigned
467
+ to `this.times` / `this.values` on the optimizer; the `track` (whose arrays were only *copied* at
468
+ lines 108-109) is never modified, and the method returns `this`.
469
+ - **Failure scenario**: `GLTFAssetLoader.js:426` calls `animationOptimizer.optimize(animation)` on
470
+ every animation of every loaded GLTF, using the module-level singleton. Load a GLTF whose track has
471
+ 1000 keyframes of which 900 are redundant: the track still has 1000 keyframes afterwards. The
472
+ singleton also permanently retains `this.times`/`this.values` — full copies of the last-optimized
473
+ track's typed arrays, megabytes for a skeletal clip — for the module's lifetime.
474
+ - **Fix**: write back to the track (`track.times = ...; track.values = ...;`) and return `track`.
475
+ - **Confidence**: Confirmed
476
+
477
+ ### ECS-27 `AnimationOptimizer`: `time[0]` should be `times[0]`
478
+ - **Severity**: MINOR
479
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/animation/AnimationOptimizer.js:36`
480
+ - **Defect**: `if (time !== timeNext && (i !== 1 || time !== time[0]))` — `time` is a number, so
481
+ `time[0]` is `undefined` and the sub-condition is always true. three.js has `times[0]`.
482
+ - **Failure scenario**: a track with `times = [0, 0, 1, 2]` and distinct values at index 1. three.js
483
+ drops keyframe 1 as a duplicate of the keyframe at t=0; this code keeps it, leaving two keyframes
484
+ scheduled at t=0 in the "optimized" output.
485
+ - **Fix**: `time !== times[0]`.
486
+ - **Confidence**: Confirmed
487
+
488
+ ### ECS-28 `DynamicRuleDescription.equals` calls `.equals` on a primitive string
489
+ - **Severity**: MAJOR
490
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/dynamic_actions/rules/DynamicRuleDescription.js:55`
491
+ - **Defect**: `id` is a primitive string (`UUID.string()` returns `UUID.v4().toString()`, and
492
+ `fromJSON` assigns the JSON string directly), but `equals` does `return this.id.equals(other.id)`.
493
+ `String.prototype.equals` does not exist.
494
+ - **Failure scenario**:
495
+ `DynamicRuleDescription.fromJSON({id:"combat.long-idle.ai", ...}).equals(otherRule)` →
496
+ `TypeError: this.id.equals is not a function`. Any container relying on the `equals` protocol
497
+ (a default-configured `HashMap`/`HashSet` keyed by rules) throws on the first lookup.
498
+ `DynamicActorSystem` only escapes because `__global_last_used_times` supplies a custom
499
+ `keyEqualityFunction`.
500
+ - **Fix**: `return this.id === other.id;`
501
+ - **Confidence**: Confirmed
502
+
503
+ ### ECS-29 Pooled `DynamicActorSystem` context keeps the previous entity's `RuleExecution`
504
+ - **Severity**: MAJOR
505
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/dynamic_actions/DynamicActorSystem.js:43,74-90`
506
+ - **Defect**: `Context` instances are pooled by `AbstractContextSystem`, and the pool's reset callback
507
+ is an empty `// TODO` (`system/AbstractContextSystem.js:37-42`). `Context.link()` resets
508
+ `next_idle_event_time` but never resets `execution`, so a recycled context arrives carrying the
509
+ previous entity's `execution.rule` and `execution.executor`. `Context.unlink()` also never terminates
510
+ the still-running executor.
511
+ - **Failure scenario**: unit A executes a `SpeakLine` rule, whose executor tree is
512
+ `ParallelBehavior([WaitForEventBehavior(FinishedSpeakingLine on A), SendEventBehavior])`. A is killed
513
+ and removed; `removeEntity` deletes A's event listeners, so `FinishedSpeakingLine` can never arrive,
514
+ the parallel never completes, `DieBehavior` never runs, and the executor `Entity` keeps
515
+ `EntityFlags.Built` forever. A's context is released to the pool still pointing at that immortal
516
+ executor. Unit B spawns and pops the same context. On B's first event, `attemptRuleExecution`
517
+ (lines 193-199) sees `execution.executor.getFlag(Built) === true` and tests
518
+ `rule.priority <= execution.rule.priority`. Every rule in
519
+ `app/data/database/dynamic-actions/data.json` uses the default `priority = 0`, so `0 <= 0` →
520
+ `return false`: **B never executes any rule for the rest of the session.** If a higher-priority rule
521
+ ever does get through, `terminateActiveExecution` destroys entity A's executor from B's context.
522
+ - **Fix**: reset execution state at the context boundary — in `Context.link()` set
523
+ `this.execution.rule = null; this.execution.executor = null;`, and in `Context.unlink()` destroy a
524
+ still-built executor before clearing.
525
+ - **Confidence**: Confirmed
526
+
527
+ ### ECS-30 `match()` hands actions a live view of a shared scope stack that it unwinds before the action runs
528
+ - **Severity**: MAJOR
529
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/dynamic_actions/DynamicActorSystem.js:536-551`;
530
+ `.../actions/definition/WriteToBlackboardActionDescription.js:24-35`
531
+ - **Defect**: `match()` passes `this.scope.proxy` — a proxy over the system-wide, mutable `DataScope` —
532
+ as the action `context`, then calls `this.scope.unwind(top)` on line 551, dropping every frame it
533
+ just pushed (blackboards, `{now, entity}`, the event frame, the entity proxy scope).
534
+ `WriteToBlackboardActionDescription` captures that proxy in an `ActionBehavior` closure and only
535
+ calls `expression.evaluate(context)` when the behavior ticks. `BehaviorSystem.link()` only calls
536
+ `behavior.initialize()`; behaviors are first ticked in `BehaviorSystem.update()`, i.e. no earlier
537
+ than the next frame.
538
+ - **Failure scenario**: a rule with `"action": {"type":"Write", "name":"last_bark_time", "value":"now"}`.
539
+ `match()` pushes `{now: 12.5, entity}`, matches, builds the executor, then unwinds. Next frame the
540
+ `ActionBehavior` ticks and evaluates `now` against an empty scope → `undefined`, so
541
+ `blackboard.acquire("last_bark_time", …).set(undefined)` instead of `12.5`. With two actors handling
542
+ events in the same frame the deferred read can also resolve against the *other* actor's frames if
543
+ those are still on the stack.
544
+ - **Fix**: snapshot the scope before handing it to an action — materialise the proxy into a plain
545
+ object in `executeRule` (as `SendRequestActionDescription` already does eagerly for
546
+ `action_context`), or have `WriteToBlackboardActionDescription.execute` evaluate the expression
547
+ eagerly and defer only the blackboard write.
548
+ - **Confidence**: Confirmed
549
+
550
+ ### ECS-31 `PointFitnessGaussianBlur` serializes a `quality` field that is never stored
551
+ - **Severity**: MAJOR
552
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/evaluation/json/serializeToJSON_PointFitnessFunction.js:61`
553
+ - **Defect**: The serializer writes `quality: value.quality`, but `PointFitnessGaussianBlur` never
554
+ stores it — `from()` consumes it only to compute `samples_x/y/z = size * quality` and discards it.
555
+ The deserializer (`deserializeFromJSON_PointFitnessFunction.js:45,51`) destructures `quality` with no
556
+ default and forwards it to `from()`, whose own default `quality = 5` then applies.
557
+ - **Failure scenario**: `PointFitnessGaussianBlur.from(src, 4, 4, 4, 1)` → `samples = 4,4,4`
558
+ (64 source evaluations per point). Serialize → `{size_x:4, size_y:4, size_z:4, quality: undefined}`
559
+ (and `undefined` is dropped entirely by `JSON.stringify`). Deserialize → `quality = 5` →
560
+ `samples = 20,20,20`, i.e. 8000 source evaluations per point and a numerically different blur.
561
+ - **Fix**: store the value in `from()` (`r.quality = quality;`) with a matching class field, or derive
562
+ it on serialize as `value.samples_x / value.size_x`.
563
+ - **Confidence**: Confirmed
564
+
565
+ ### ECS-32 `PointFitnessGaussianBlur` kernel is offset by half a sample on every axis
566
+ - **Severity**: MINOR
567
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/evaluation/complex/PointFitnessGaussianBlur.js:67-69,80,89,99`
568
+ - **Defect**: `half_samples_x = samplesX / 2` with `local_x = ix - half_samples_x` gives taps at
569
+ `-n/2 … n/2 - 1`, which is not centred on the sample point. A centred kernel needs `(samplesX - 1) / 2`.
570
+ - **Failure scenario**: `samples_x = 5, size_x = 10`. Taps land at
571
+ `local_x ∈ {-2.5,-1.5,-0.5,0.5,1.5}` → spatial offsets `{-5,-3,-1,1,3}` instead of
572
+ `{-5,-2.5,0,2.5,5}`. Both the gaussian weights and the sample positions are biased by half a tap, so
573
+ blurring a step edge shifts it by 1 world unit in −x on each axis, and the point being blurred is
574
+ never itself sampled.
575
+ - **Fix**: `const half_samples_x = (samplesX - 1) / 2;` (and likewise for y, z).
576
+ - **Confidence**: Confirmed
577
+
578
+ ### ECS-33 `Animation.flags` is in neither serialization path, so a paused animation resumes after load
579
+ - **Severity**: MINOR
580
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/animation/Animation.js:40,153-158`;
581
+ `.../animation/AnimationSerializationAdapter.js:15-28`
582
+ - **Defect**: `flags` (carrying `Playing` and `MeshSizeCulling`) is written by neither `toJSON` nor
583
+ `serialize`, and the field initialiser unconditionally sets `Playing | MeshSizeCulling`.
584
+ `equals()`/`hash()` also ignore `flags` while including `debtTime`.
585
+ - **Failure scenario**: `animation.isPlaying = false` (a supported setter, covered by
586
+ `Animation.spec.js:58-62`), save via `BinaryBufferSerializer`, reload → `flags` is back to the
587
+ constructor default and the animation plays again. Two `Animation` components differing only in
588
+ `isPlaying` also report `equals() === true` and identical `hash()`.
589
+ - **Fix**: add `flags` to the binary adapter (bumping `version`) and to `toJSON`/`fromJSON`
590
+ (defaulting to `MeshSizeCulling | Playing`), and include it in `equals`/`hash`.
591
+ - **Confidence**: Confirmed
592
+
593
+ ### ECS-34 `AnimationSystem` re-subscribes to clip signals on every mesh change and never unsubscribes
594
+ - **Severity**: MINOR
595
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/systems/AnimationSystem.js:116-117,150,221-230`
596
+ - **Defect**: `updateAnimationState` unconditionally does `clips.on.added.add(initAnimationClip)` and
597
+ `clips.on.removed.add(...)` with fresh closures each time. It runs from `registerAnimation` (line 150),
598
+ which runs both from `link()` and from every `MeshEvents.DataSet` event. `unlink()` removes only the
599
+ `MeshEvents.DataSet` entity listener; the clip-signal handlers are never removed, and `Signal.add`
600
+ does not deduplicate.
601
+ - **Failure scenario**: `MeshSystem.process` sets a placeholder mesh (DataSet #1) then the real asset
602
+ (DataSet #2) for every async-loaded mesh with `__use_placeholder_mesh` on — together with `link()`
603
+ that is 3 registrations. Every later runtime mesh swap adds another. After N mesh sets, a single
604
+ `animation.clips.add(clip)` invokes `startClip` N times, and the handler list grows without bound for
605
+ the component's life; unlink/relink cycles accumulate across cycles too.
606
+ - **Fix**: keep references to the two handlers on the component, remove any previous pair at the top of
607
+ `updateAnimationState`, and remove them in `unlink()`.
608
+ - **Confidence**: Confirmed
609
+
610
+ ### ECS-35 `SerializationMetadata` binary adapter truncates a documented 32-bit flag field to 8 bits
611
+ - **Severity**: MINOR
612
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/components/SerializationMetadataSerializationAdapter.js:15,24`
613
+ - **Defect**: `flags` is documented as a "32 bit bitfield" (`SerializationMetadata.js:19-22`) and
614
+ `toJSON` writes the full number, but the binary adapter uses `writeUint8`/`readUint8`.
615
+ - **Failure scenario**: a consumer defines a ninth flag `Foo = 1 << 8`. `metadata.setFlag(Foo)` →
616
+ `flags === 256`. `toJSON()` preserves `256`; the binary path writes `256 & 0xFF === 0` and reads back
617
+ `flags === 0`, silently losing the flag on save/load.
618
+ - **Fix**: `writeUint32`/`readUint32`, bumping the adapter `version` and adding an upgrader.
619
+ - **Confidence**: Confirmed
620
+
621
+ ---
622
+
623
+ ## Storage / serialization
624
+
625
+ ### ECS-36 Schema back-reference is looked up by address in the schema-*object* map, then dereferenced
626
+ - **Severity**: MAJOR
627
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter2.js:416`
628
+ - **Defect**: `#deserializeObject` resolves a `HEADER_TAG_REF_SCHEMA` record with
629
+ `this.#schema_table.getRecord(schema_address)`. `getRecord(schema)` looks up `#schema_map`, which is
630
+ keyed by the **schema object** (`#schema_map.set(record.schema, record)`); the address-keyed map is
631
+ `#address_map`, reachable only via `getSchemaAt(address)`. Passing a number can never hit, so
632
+ `record` is `undefined` and line 419 (`record.typeName`) throws. The whole `HEADER_TAG_REF_SCHEMA`
633
+ decode branch can only crash.
634
+ - **Failure scenario**: the `schema` parameter of `serialize(buffer, object, schema)` is a documented
635
+ optional argument. `const S = {v: undefined}; a2.serialize(buf, {v:1}, S); a2.serialize(buf, {v:2}, S);`
636
+ — the first object writes `HEADER_TAG_INLINE_SCHEMA` and interns `S`; the second takes the `else`
637
+ branch at line 375 and writes `HEADER_TAG_REF_SCHEMA` + address. On read, the first decodes fine, the
638
+ second throws `TypeError: Cannot read properties of undefined (reading 'typeName')`. Same for a single
639
+ object whose explicit schema reuses one sub-schema object for two fields.
640
+ - **Fix**: `record = this.#schema_table.getSchemaAt(schema_address);`. Also reset `#schema_table` when
641
+ switching an instance between writing and reading — write-side records are registered at write-buffer
642
+ positions and never cleared, so they can shadow read-side records at the same address.
643
+ - **Confidence**: Confirmed
644
+
645
+ ### ECS-37 Per-component transient flag is ignored whenever the dataset has no `SerializationMetadata` type
646
+ - **Severity**: MAJOR
647
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/storage/BinaryBufferSerializer.js:26-34`
648
+ - **Defect**: `isEntityTransient` returns `false` on `smComponentIndex === -1` **before** checking
649
+ `componentInstance[COMPONENT_SERIALIZATION_TRANSIENT_FIELD]`. The two mechanisms are independent —
650
+ the magic field is a per-instance opt-out that does not require a `SerializationMetadata` component
651
+ to exist — but it is gated behind one.
652
+ - **Failure scenario**: `EntityComponentDataset.computeComponentTypeIndex` returns `-1` for an
653
+ unregistered type, and a type is only registered once some entity actually receives one. In a dataset
654
+ where nothing ever adds a `SerializationMetadata`, `smComponentIndex === -1`. Mark any component
655
+ transient the way the game does (`guiElement[COMPONENT_SERIALIZATION_TRANSIENT_FIELD] = true` in
656
+ `setupStrategyHeadsUpDisplays.js:98`, `sockets[...] = true` in `CombatUnitEntityDecorator.js:660`,
657
+ `this.__transform2GridPosition[...] = true` in `CharacterMoveAsynchronousAction.js:236`) and it is
658
+ written to the save anyway, then resurrected on load as a duplicate of a runtime-only component.
659
+ - **Fix**: move the `COMPONENT_SERIALIZATION_TRANSIENT_FIELD` check above the `smComponentIndex === -1`
660
+ early return.
661
+ - **Confidence**: Confirmed
662
+
663
+ ### ECS-38 Object adapter resolves class identity through *inherited* `typeName`
664
+ - **Severity**: MAJOR
665
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/storage/binary/object/BinaryObjectSerializationAdapter.js:59-61`
666
+ - **Defect**: `const typeName = Klass.typeName;` is a normal static lookup, so an ES class that does not
667
+ declare its own `typeName` silently resolves to its ancestor's. The object-v2 writer explicitly
668
+ guards this with `Object.prototype.hasOwnProperty.call(ctor, "typeName")`
669
+ (`BinaryObjectSerializer.js:270`) and has a dedicated regression test, so the v1 adapter is the
670
+ un-fixed half.
671
+ - **Failure scenario**: `BehaviorComponentSerializationAdapter.serialize`
672
+ (`engine/intelligence/behavior/ecs/BehaviorComponentSerializationAdapter.js:38`) routes every behavior
673
+ through `this.objectAdapter.serialize(buffer, behavior)`. `Behavior.js:78` sets
674
+ `Behavior.typeName = "Behavior"`, while `SelectorBehavior`, `WeightedRandomBehavior`, `BranchBehavior`
675
+ and `ConditionalBehavior` declare none. Saving an entity whose behavior tree contains a
676
+ `SelectorBehavior` therefore looks up adapter `"Behavior"` — not registered — and the save aborts with
677
+ `No adapter found for class 'Behavior'`, naming a class that is not in the tree. If any ancestor *does*
678
+ have a registered adapter the failure is worse: the subclass is written with the ancestor's payload
679
+ and read back as the ancestor, dropping all subclass state.
680
+ - **Fix**: require an own `typeName` —
681
+ `Object.prototype.hasOwnProperty.call(Klass, "typeName") ? Klass.typeName : undefined` — and throw
682
+ naming the actual constructor when absent, mirroring `BinaryObjectSerializer.writeValue`.
683
+ - **Confidence**: Confirmed
684
+
685
+ ### ECS-39 The last component type in a stream is never finalized on load
686
+ - **Severity**: MINOR
687
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/storage/BinaryBufferDeSerializer.js:117-127`
688
+ - **Defect**: `cycleFunction` finalizes the *previous* type at the top of the next loop iteration
689
+ (`if (typesDecoded > 0) collectionDeSerializer.finalize();`), but the `typesLeft === 0` exit at
690
+ line 117 returns `TaskSignal.EndSuccess` first. So `BinaryCollectionDeSerializer.finalize()` — and
691
+ hence `adapter.finalize()` — never runs for the final type. The write path
692
+ (`BinaryBufferSerializer.js:194`) finalizes every type correctly.
693
+ - **Failure scenario**: load a buffer containing types `[Name, CombatUnit]`. `CombatUnit` elements are
694
+ read to exhaustion, `typesLeft` is `0`, the task ends, and
695
+ `CombatUnitSerializationAdapter.finalize()`
696
+ (`model/game/ecs/component/unit/serialization/CombatUnitSerializationAdapter.js:51`) is never invoked,
697
+ so its module-static `serialization_array` is left holding stale entries instead of being spliced
698
+ empty. The deserializer is also left in `State.Ready`, so a subsequent `initialize()` on the same
699
+ instance throws `IllegalStateException`. Which adapter is affected depends on which component type
700
+ happens to be last, so the leak is non-deterministic across saves.
701
+ - **Fix**: finalize the in-flight collection before returning `EndSuccess`, guarding against
702
+ double-finalize on a re-entered cycle.
703
+ - **Confidence**: Confirmed
704
+
705
+ ---
706
+
707
+ ## GUI / HUD / speaker
708
+
709
+ ### ECS-40 `LineSetDescription.collect` walks the root's children instead of the popped node's — infinite loop
710
+ - **Severity**: CRITICAL
711
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/speaker/lines/sets/LineSetDescription.js:56`
712
+ - **Defect**: Inside the traversal loop the popped node is `set`, but the child-set expansion reads
713
+ `const sets = this.sets;` — the *root's* children — on every iteration. The root's children are
714
+ therefore re-pushed forever and `pointer` never drains.
715
+ - **Failure scenario** (reproduced): a `voice-line-sets` entry with a non-empty `sets` array
716
+ (schema-supported: `LineSetDescriptionTable.linkOne` reads `sets` from JSON, and `validateOne` exists
717
+ purely to cycle-check that graph). Root `R{lines:[l-root], sets:[C]}`, `C{lines:[l-child]}` →
718
+ `collect` writes `l-root, l-child, l-child, l-child, …` without bound; `destination` grows until OOM.
719
+ `VoiceSystem.sayLineFromSet` calls `set.collect(temp_lines, 0)`, so the tab hangs on the first nested
720
+ bark set anyone authors.
721
+ - **Fix**: `const sets = set.sets;`
722
+ - **Confidence**: Confirmed
723
+
724
+ ### ECS-41 `GUIElementSystem.unlink` calls `Signal.remove` without the context it subscribed with
725
+ - **Severity**: MAJOR
726
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/GUIElementSystem.js:261` (subscribe at `:248`)
727
+ - **Defect**: `link` does `component.visible.onChanged.add(handleComponentVisibilityChange, {el, system, entity})`;
728
+ `unlink` does `component.visible.onChanged.remove(handleComponentVisibilityChange)` with no `thisArg`.
729
+ `Signal.remove` matches on `handle === h && context === thisArg`, so it matches nothing and returns
730
+ `false`.
731
+ - **Failure scenario** (reproduced against the real `Signal`): `add(handler, ctx)` then `remove(handler)`
732
+ → returns `false`, `hasHandlers()` stays `true`, the next `send1` still runs the handler. Concretely,
733
+ `ecd.removeComponentFromEntity(entity, GUIElement)` (real caller:
734
+ `model/game/scenes/combat/UnitStateManager.js:437`) or `Entity.destroy()` → `Entity.build()` leaves
735
+ the old subscription in place, and the next `link` adds a second. With two handlers,
736
+ `component.visible.set(true)` runs `attachComponent` twice → `parent.addChild(component.view)` twice
737
+ → `View.addChild` (`view/View.js:365`) pushes the same child into `parent.children` twice (the
738
+ `isLinked` assert that would catch it is stripped in production). `removeChild` then drops only the
739
+ first entry, so `parent.children` keeps a stale entry whose `el` is no longer in the DOM; a later
740
+ `removeAllChildren` calls `this.el.removeChild(child.el)` on a non-child and throws `NotFoundError`.
741
+ - **Fix**: store the context object (as `ViewportPositionSystem` does in `this.data`) and pass it to
742
+ `remove`.
743
+ - **Confidence**: Confirmed
744
+
745
+ ### ECS-42 `ViewportPositionSystem.unlink` never unsubscribes from `el.view.size.onChanged`
746
+ - **Severity**: MAJOR
747
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/position/ViewportPositionSystem.js:154` (unlink at `:167-183`)
748
+ - **Defect**: `link` makes five `onChanged.add(updatePosition, eventContext)` subscriptions —
749
+ `vp.position`, `vp.offset`, `vp.anchor`, `vp.enabled` and `el.view.size`. `unlink` removes only the
750
+ first four.
751
+ - **Failure scenario**: `GUIEngine.createModal` (`meep/src/engine/ui/GUIEngine.js:215`) does
752
+ `window.removeComponent(ViewportPosition)` immediately after `openWindow` built the entity,
753
+ deliberately freeing the modal from viewport positioning while the `GUIElement` and its
754
+ `ui-modal-window-container` view stay live on screen. `unlink` runs, but `updatePosition` stays bound
755
+ to that container's `size.onChanged` with a context holding the now-detached `vp`. Any later size
756
+ change re-invokes `positionComponent(el, vp)`, overwriting `view.position` from the stale
757
+ `vp.position`, overwriting `view.transformOrigin` with the stale `vp.anchor` (default `0,0`,
758
+ discarding the `0.5,0.5` set at `GUIEngine.js:213`), and calling `el.visible.set(...)` — if the stale
759
+ position is out of bounds and `stickToScreenEdge` is false (the default), `el.visible.set(false)`
760
+ makes `GUIElementSystem` detach the modal from the DOM and the open modal vanishes.
761
+ - **Fix**: add `el.view.size.onChanged.remove(updatePosition, eventContext);` inside the
762
+ `eventContext !== undefined` block in `unlink`.
763
+ - **Confidence**: Confirmed
764
+
765
+ ### ECS-43 `HeadsUpDisplay.toJSON` writes `transformPerspectiveRotation`, `fromJSON` reads `perspectiveRotation`
766
+ - **Severity**: MAJOR
767
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/hud/HeadsUpDisplay.js:86` vs `:90`
768
+ - **Defect**: The JSON key emitted for the `PerspectiveRotation` flag does not match the key the reader
769
+ destructures, so the reader always falls back to its `false` default.
770
+ - **Failure scenario** (reproduced): a HUD with `PerspectiveRotation` set — exactly what
771
+ `makeEnemyGroupHud` in `model/game/scenes/strategy/gui/setupStrategyHeadsUpDisplays.js` configures.
772
+ `toJSON()` yields `{"worldOffset":…,"transformWorldOffset":true,"transformPerspectiveRotation":true}`;
773
+ `HeadsUpDisplay.fromJSON(that)` yields `flags = 1` instead of `3`,
774
+ `getFlag(PerspectiveRotation) === false`, and `a.equals(b) === false`. The screen-space billboard
775
+ rotation is silently dropped on any JSON round-trip. The binary adapter round-trips the flag
776
+ correctly, so the two persistence paths disagree.
777
+ - **Fix**: emit `perspectiveRotation` in `toJSON`, or rename the `fromJSON` destructure to
778
+ `transformPerspectiveRotation`. Pick one and keep both sides identical.
779
+ - **Confidence**: Confirmed
780
+
781
+ ### ECS-44 `GUIElementSystem.link` logs "does not have a view" and then dereferences the missing view
782
+ - **Severity**: MINOR
783
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/GUIElementSystem.js:212`
784
+ - **Defect**: The un-managed-with-no-view branch does `console.error(...)` with no `return`. Execution
785
+ falls through to `if (component.visible.getValue()) this.attachComponent(component, entity)`
786
+ (`visible` defaults to `true`), and `attachComponent` reaches `parent.addChild(component.view)` with
787
+ `component.view === null`.
788
+ - **Failure scenario**: `const c = new GUIElement(); c.clearFlag(GUIElementFlag.Managed);` added to an
789
+ entity — precisely the state the error message describes. `View.addChild(null)` executes
790
+ `child.parent = this;` (its guarding asserts are stripped in production) →
791
+ `TypeError: Cannot set properties of null (setting 'parent')` propagates out of `link`, aborting the
792
+ component-added observer dispatch for that entity so later systems never link.
793
+ - **Fix**: `return;` after the `console.error` at `:212`.
794
+ - **Confidence**: Confirmed
795
+
796
+ ### ECS-45 `ButtonViewEntity.finalize` destroys the structural children created in its constructor
797
+ - **Severity**: MINOR
798
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/gui/view/ButtonViewEntity.js:104-107` (children created at `:17-23`)
799
+ - **Defect**: The constructor adds the `foreground` and `background` `EmptyView`s the button's CSS is
800
+ built on. `finalize()` calls `removeAllChildren()`, which removes them too — but `ViewEntity.finalize`
801
+ is documented as bringing the view "to initial state ready to be destroyed **or re-used**", and
802
+ `GUIElementSystem` implements exactly that re-use contract (`unlink` calls `view.finalize()` and
803
+ clears `Initialized`; `attachComponent` re-calls `view.initialize(...)` when it sees
804
+ `Managed && !Initialized`).
805
+ - **Failure scenario**: an entity carrying a `view.Button` `GUIElement` (registered at
806
+ `model/game/GameClassRegistry.js:89`) goes through link → unlink → link. On the second `initialize`
807
+ the label is re-added but `.foreground`/`.background` are gone, so the button renders with no
808
+ background fill and dead hover/press styling. `LocalizedLabelViewEntity` is correct precisely because
809
+ its constructor adds no children.
810
+ - **Fix**: create the foreground/background children in `initialize()`, or have `finalize()` remove only
811
+ the label child.
812
+ - **Confidence**: Confirmed
813
+
814
+ ### ECS-46 `LineSetDescriptionTable.validateOne` reports a false circular dependency for a shared child set
815
+ - **Severity**: MINOR
816
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/speaker/lines/sets/LineSetDescriptionTable.js:79`
817
+ - **Defect**: The check treats *any* second visit to a node as a cycle
818
+ (`if (visited.hasOwnProperty(set.id))`) rather than tracking the current traversal path, so a DAG with
819
+ a shared child is rejected.
820
+ - **Failure scenario**: `A{sets:[B,C]}`, `B{sets:[D]}`, `C{sets:[D]}` — no cycle. Traversal pops `A`,
821
+ pushes `B,C`; pops `C`, pushes `D`; pops `D`, marks visited; pops `B`, pushes `D`; pops `D` again →
822
+ `visited` hit → `errorConsumer("Circular dependency detected: D -> C -> A")` and `return false`. Valid
823
+ voice-line-set data fails validation at load, and the reported path names the wrong parent.
824
+ - **Fix**: track the recursion stack (mark on entry, unmark on exit) or colour nodes white/grey/black,
825
+ and only report a cycle on re-entering a grey node.
826
+ - **Confidence**: Confirmed
827
+
828
+ ### ECS-47 `VoiceSystem.sayLineFromSet` dereferences `undefined` when the set yields no lines
829
+ - **Severity**: MINOR
830
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/speaker/VoiceSystem.js:313-315`
831
+ - **Defect**: `weightedRandomFromArray` returns `undefined` when `length === 0`
832
+ (`core/collection/array/weightedRandomFromArray.js:26-28`), and the result is used unguarded as
833
+ `selected_line.id`. The neighbouring "set not found" case at `:303` warns and returns, so graceful
834
+ handling of bad data is the intended contract here.
835
+ - **Failure scenario**: a `voice-line-sets` entry with an empty or omitted `lines` array (`linkOne`
836
+ defaults `lines = []`) — a placeholder or not-yet-authored bark set.
837
+ `ecd.sendEvent(entity, VoiceEvents.SpeakSetLine, {id: thatSet})` → `collect` returns `0` →
838
+ `TypeError: Cannot read properties of undefined (reading 'id')` thrown out of the entity-event dispatch.
839
+ - **Fix**: guard on `selected_line === undefined` after the `weightedRandomFromArray` call.
840
+ - **Confidence**: Confirmed
841
+
842
+ ---
843
+
844
+ ## Attachment / parent / transform / IK
845
+
846
+ ### ECS-49 `EntityNode`'s `parent` setter strips `TransformAttachment` and never re-adds it
847
+ - **Severity**: MAJOR
848
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/parent/EntityNode.js:240-270`
849
+ - **Defect**: The setter removes both components up front
850
+ (`let parent_entity = this_entity.removeComponent(ParentEntity); let attachment = this_entity.removeComponent(TransformAttachment);`)
851
+ and, in the `node !== null` branch, explicitly re-adds `ParentEntity` via `this_entity.add(parent_entity)`
852
+ — but the attachment is only put back through `this.__safe_get_attachment()`, which runs **only when
853
+ `attachment === null`**. When the node already had a `TransformAttachment`, the removed instance is
854
+ mutated (`attachment.parent = parent_entity_id`) and dropped on the floor.
855
+ - **Failure scenario** (reproduced): three built `EntityNode`s A, B, C each with a `Transform`.
856
+ ```js
857
+ A.addChild(C); // C -> ParentEntity{0}, TransformAttachment{parent:0}
858
+ C.parent = B; // public setter, the documented re-parent entry point
859
+ ```
860
+ Observed: `ParentEntity.entity` correctly becomes `1`, but
861
+ `C.entity.getComponent(TransformAttachment)` is `null` and
862
+ `ds.getComponent(C.entity.id, TransformAttachment)` is `undefined`. `TransformAttachmentSystem` has
863
+ unlinked C, so C's world transform is no longer driven by any parent — the node visually detaches
864
+ while still being logically parented (and still gets destroyed when B is destroyed).
865
+ `addChild` escapes this only because it routes through `removeChild` → `node.parent = null` first,
866
+ which nulls both.
867
+ - **Fix**: after `attachment.parent = parent_entity_id`, put the component back —
868
+ `if (this_entity.getComponent(TransformAttachment) === null) this_entity.add(attachment);` — or
869
+ simply do not remove `TransformAttachment` at the top when `node !== null`.
870
+ - **Confidence**: Confirmed
871
+
872
+ ### ECS-50 `InverseKinematicsSystem` declares read-only access to `Mesh` because of an undefined enum member
873
+ - **Severity**: MINOR
874
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/ik/InverseKinematicsSystem.js:18`
875
+ - **Defect**: `ResourceAccessSpecification.from(Mesh, ResourceAccessKind.Read | ResourceAccessSpecification.Write)`
876
+ — `Write` is a member of `ResourceAccessKind`, not of `ResourceAccessSpecification`. The latter has no
877
+ such static, so the expression is `1 | undefined === 1`: the system declares `Read` only, while
878
+ `TwoBoneInverseKinematicsSolver.solve` actually mutates the mesh's bone quaternions
879
+ (`boneA.setRotationFromQuaternion(...)`, `TwoBoneInverseKinematicsSolver.js:173-174`).
880
+ - **Failure scenario**: `EntityManager.updateExecutionOrder`'s `scoreSystem` weights a `Write` component
881
+ at `attached_edge_count * 2` and a `Read` at `* 1`, and `computeSystemComponentDependencyGraph` only
882
+ emits edges out of a system that declares `Write`/`Create`. With the typo, `InverseKinematicsSystem`
883
+ contributes no dependency edges and is scored as a pure `Mesh` reader, so it can be ordered *after*
884
+ the systems that consume mesh/bone state in the same `simulate()` call — IK corrections (feet planted
885
+ on terrain) are applied one frame late, producing visible foot sliding / ground penetration on the
886
+ frame a character's pose changes.
887
+ - **Fix**: `ResourceAccessKind.Read | ResourceAccessKind.Write`.
888
+ - **Confidence**: Confirmed
889
+
890
+ ### ECS-51 `OneBoneSurfaceAlignmentSolver` recovers the alignment angle with `asin`, which cannot exceed 90°
891
+ - **Severity**: MINOR
892
+ - **File**: `H:/git/moh/app/src/mir-engine/meep/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js:170-175`
893
+ - **Defect**: `axis.crossVectors(v3, Vector3.forward); const betaSine = axis.length(); const beta = Math.asin(clamp(betaSine, -1, 1));`
894
+ — `|a × b| = sin θ` is ambiguous between `θ` and `π − θ`, and `asin` always returns the branch in
895
+ `[0, π/2]`. The angle between the contact normal and the bone axis is not recoverable this way once it
896
+ exceeds 90°.
897
+ - **Failure scenario**: bone axis `(0,0,1)` in bone space, contact normal in bone space
898
+ `(0, 0.9, −0.436)` — 116° apart. `|cross| = 0.9` → `beta = asin(0.9) = 64.2°`. The bone is rotated
899
+ 64° instead of 116°, so instead of squaring up to the surface it ends up 52° off, tilted into the
900
+ surface. Any surface whose normal is more than 90° from the bone's own axis (steep slopes,
901
+ overhangs, an inverted foot-bone rig) is aligned to the wrong angle rather than clamped by
902
+ `constraint.limit`.
903
+ - **Fix**: recover the angle unambiguously — `Math.atan2(axis.length(), v3.dot(Vector3.forward))` —
904
+ before normalising `axis`.
905
+ - **Confidence**: Likely (the trigonometric limitation is unambiguous; I did not exercise it against a
906
+ real rig, so how often a live skeleton crosses 90° is unverified)
907
+
908
+ ---
909
+
910
+ ## Status of previously-known items
911
+
912
+ | Item | Status |
913
+ | --- | --- |
914
+ | `Entity.promiseEvent` calling `removeEventListener` instead of `add` | **FIXED** (`Entity.js:326-327` now `addEventListener`) |
915
+ | `EntityComponentDataset.dropData` not clearing `__entityAnyEventListeners` | **FIXED** (`EntityComponentDataset.js:1882`) |
916
+ | `HeadsUpDisplaySystem` scaling world-offset Z by `scale.y` | **FIXED** (`HeadsUpDisplaySystem.js:88` uses `transform_scale.z`) |
917
+ | `obtainTileAtWorldPosition2D` dereferencing an out-of-bounds tile | **STILL PRESENT** (`TerrainTileManager.js:487-489`; `getTileByWorldPosition2D` can return `undefined`, then `tile.gridPosition`) |
918
+ | `FogOfWar.rebuildSampler` width/height swap | **FIXED** (`FogOfWar.js:707-713`, correct `Sampler2D(data, itemSize, width, height)` order) |
919
+ | `TerrainTileManager.stitchTile` floor-vs-ceil | **STILL PRESENT** (`:566` uses `floor`; `:353`, `:405`, `:408`, `:422` all use `ceil`) |
920
+ | Event-dispatch pointer restore missing `try/finally` | **STILL PRESENT** (`EntityComponentDataset.js:2138-2162` and `2180-2204`) |