@woosh/meep-engine 3.1.0 → 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 (1822) hide show
  1. package/LICENSE +188 -188
  2. package/README.md +152 -152
  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/input/GAMEPAD_API_PROPOSAL.md +717 -717
  1072. package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
  1073. package/src/engine/input/devices/GamepadDevice.js +466 -466
  1074. package/src/engine/input/devices/ManualInputDevice.js +152 -152
  1075. package/src/engine/input/devices/PointerCoordinates.js +26 -26
  1076. package/src/engine/input/devices/events/GamepadEvents.js +10 -10
  1077. package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
  1078. package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
  1079. package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
  1080. package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
  1081. package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
  1082. package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
  1083. package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
  1084. package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
  1085. package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
  1086. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
  1087. package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
  1088. package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
  1089. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
  1090. package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
  1091. package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
  1092. package/src/engine/input/ecs/components/Input.js +118 -118
  1093. package/src/engine/input/ecs/components/InputController.js +78 -78
  1094. package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
  1095. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
  1096. package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
  1097. package/src/engine/input/ecs/ism/InputBinding.js +71 -71
  1098. package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
  1099. package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
  1100. package/src/engine/input/ecs/ism/InputDirection.js +33 -33
  1101. package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
  1102. package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
  1103. package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
  1104. package/src/engine/input/ecs/ism/InputSource.js +67 -67
  1105. package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
  1106. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
  1107. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
  1108. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
  1109. package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
  1110. package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
  1111. package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
  1112. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
  1113. package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
  1114. package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
  1115. package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
  1116. package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
  1117. package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
  1118. package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
  1119. package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
  1120. package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
  1121. package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
  1122. package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
  1123. package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
  1124. package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
  1125. package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
  1126. package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
  1127. package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
  1128. package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
  1129. package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
  1130. package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
  1131. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
  1132. package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
  1133. package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
  1134. package/src/engine/input/ecs/systems/InputSystem.js +162 -162
  1135. package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
  1136. package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
  1137. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
  1138. package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
  1139. package/src/engine/interpolation/Interpoland.js +49 -49
  1140. package/src/engine/interpolation/Interpolated.js +149 -149
  1141. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
  1142. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
  1143. package/src/engine/interpolation/pose_interpoland.js +27 -27
  1144. package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
  1145. package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
  1146. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
  1147. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
  1148. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
  1149. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
  1150. package/src/engine/network/CONGESTION_CONTROL.md +87 -87
  1151. package/src/engine/network/README.md +52 -52
  1152. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
  1153. package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
  1154. package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
  1155. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
  1156. package/src/engine/network/convertPathToURL.js +2 -2
  1157. package/src/engine/network/core/MalformedPacketError.js +26 -26
  1158. package/src/engine/network/core/quantize/quantize_float.js +73 -73
  1159. package/src/engine/network/core/quantize/quantize_position.js +65 -65
  1160. package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
  1161. package/src/engine/network/core/sequence/seq16.js +69 -69
  1162. package/src/engine/network/core/sequence/seq32.js +73 -73
  1163. package/src/engine/network/ecs/NetworkSystem.js +84 -84
  1164. package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
  1165. package/src/engine/network/ecs/owner_authorization.js +55 -55
  1166. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
  1167. package/src/engine/network/replication/ScopeFilter.js +71 -71
  1168. package/src/engine/network/sim/ActionLog.js +152 -152
  1169. package/src/engine/network/sim/SimAction.js +273 -273
  1170. package/src/engine/network/sim/Snapshotter.js +222 -222
  1171. package/src/engine/network/state/FrameBufferRing.js +168 -168
  1172. package/src/engine/network/state/InputRing.js +134 -134
  1173. package/src/engine/network/state/PriorityAccumulator.js +180 -180
  1174. package/src/engine/network/time/TimeDilation.js +62 -62
  1175. package/src/engine/network/transport/Channel.js +336 -336
  1176. package/src/engine/network/transport/Transport.js +203 -203
  1177. package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
  1178. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
  1179. package/src/engine/network/transport/fragments/fragment_send.js +147 -147
  1180. package/src/engine/network/transport/fragments/packet_size.js +101 -101
  1181. package/src/engine/network/xhr.js +23 -23
  1182. package/src/engine/physics/BULLET_REVIEW.md +945 -945
  1183. package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
  1184. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
  1185. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
  1186. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
  1187. package/src/engine/physics/JOLT_REVIEW.md +913 -913
  1188. package/src/engine/physics/PLAN.md +1120 -1094
  1189. package/src/engine/physics/RAPIER_REVIEW.md +934 -934
  1190. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
  1191. package/src/engine/physics/REVIEW_002.md +151 -151
  1192. package/src/engine/physics/REVIEW_003.md +166 -166
  1193. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
  1194. package/src/engine/physics/constraint/DofMode.js +35 -35
  1195. package/src/engine/physics/constraint/solve_constraints.js +1 -1
  1196. package/src/engine/physics/contact/combine_material.js +35 -35
  1197. package/src/engine/physics/ecs/BodyKind.js +29 -29
  1198. package/src/engine/physics/ecs/Collider.js +177 -177
  1199. package/src/engine/physics/ecs/ColliderFlags.js +15 -15
  1200. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
  1201. package/src/engine/physics/ecs/Joint.js +475 -475
  1202. package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
  1203. package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
  1204. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  1205. package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
  1206. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
  1207. package/src/engine/physics/ecs/SleepState.js +12 -12
  1208. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
  1209. package/src/engine/physics/fluid/FluidField.js +867 -867
  1210. package/src/engine/physics/fluid/FluidSimulator.js +832 -832
  1211. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
  1212. package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
  1213. package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
  1214. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
  1215. package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
  1216. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  1217. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
  1218. package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
  1219. package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
  1220. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
  1221. package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
  1222. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
  1223. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
  1224. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
  1225. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
  1226. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
  1227. package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
  1228. package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
  1229. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
  1230. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
  1231. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
  1232. package/src/engine/physics/fluid/prototype.js +126 -126
  1233. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
  1234. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
  1235. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
  1236. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
  1237. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
  1238. package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
  1239. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
  1240. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
  1241. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
  1242. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
  1243. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
  1244. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
  1245. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
  1246. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
  1247. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
  1248. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
  1249. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
  1250. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
  1251. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
  1252. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
  1253. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
  1254. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
  1255. package/src/engine/physics/integration/integrate_position.js +97 -97
  1256. package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
  1257. package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
  1258. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
  1259. package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
  1260. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
  1261. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
  1262. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  1263. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
  1264. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
  1265. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
  1266. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  1267. package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
  1268. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
  1269. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
  1270. package/src/engine/physics/persistence/solver_caches.js +309 -309
  1271. package/src/engine/physics/queries/overlap_shape.js +187 -187
  1272. package/src/engine/physics/queries/raycast.d.ts +7 -4
  1273. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  1274. package/src/engine/physics/queries/raycast.js +11 -5
  1275. package/src/engine/physics/queries/shape_cast.js +417 -417
  1276. package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
  1277. package/src/engine/physics/solver/friction_cone.js +37 -37
  1278. package/src/engine/physics/solver/solve_contacts.js +1 -1
  1279. package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
  1280. package/src/engine/platform/WebEnginePlatform.js +1 -2
  1281. package/src/engine/scene/SerializedScene.d.ts +2 -2
  1282. package/src/engine/scene/SerializedScene.d.ts.map +1 -1
  1283. package/src/engine/scene/transitionToScene.js +121 -121
  1284. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  1285. package/src/engine/simulation/Ticker.js +33 -0
  1286. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
  1287. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
  1288. package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
  1289. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
  1290. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
  1291. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
  1292. package/src/engine/sound/simulation/README.md +277 -277
  1293. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
  1294. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
  1295. package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
  1296. package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
  1297. package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
  1298. package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
  1299. package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
  1300. package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
  1301. package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
  1302. package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
  1303. package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
  1304. package/src/engine/sound/simulation/core/RayHit.js +50 -50
  1305. package/src/engine/sound/simulation/core/VolumeField.js +149 -149
  1306. package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
  1307. package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
  1308. package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
  1309. package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
  1310. package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
  1311. package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
  1312. package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
  1313. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
  1314. package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
  1315. package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
  1316. package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
  1317. package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
  1318. package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
  1319. package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
  1320. package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
  1321. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
  1322. package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
  1323. package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
  1324. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
  1325. package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
  1326. package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
  1327. package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
  1328. package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
  1329. package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
  1330. package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
  1331. package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
  1332. package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
  1333. package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
  1334. package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
  1335. package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
  1336. package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
  1337. package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
  1338. package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
  1339. package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
  1340. package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
  1341. package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
  1342. package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
  1343. package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
  1344. package/src/engine/sound/sopra/README.md +643 -643
  1345. package/src/engine/sound/sopra/SopraEngine.js +432 -432
  1346. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
  1347. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
  1348. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
  1349. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
  1350. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
  1351. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
  1352. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
  1353. package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
  1354. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
  1355. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
  1356. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
  1357. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
  1358. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
  1359. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
  1360. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
  1361. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
  1362. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
  1363. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
  1364. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
  1365. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
  1366. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
  1367. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
  1368. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
  1369. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
  1370. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
  1371. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
  1372. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
  1373. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  1374. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
  1375. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
  1376. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
  1377. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
  1378. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
  1379. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
  1380. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
  1381. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
  1382. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
  1383. package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
  1384. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
  1385. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
  1386. package/src/engine/sound/sopra/runtime/Voice.js +145 -145
  1387. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
  1388. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
  1389. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
  1390. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
  1391. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
  1392. package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
  1393. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
  1394. package/src/engine/ui/DraggableAspect.js +115 -115
  1395. package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
  1396. package/src/generation/COORDINATES.md +54 -54
  1397. package/src/generation/GridGenerationResource.js +24 -24
  1398. package/src/generation/GridTaskGroup.js +214 -214
  1399. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
  1400. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
  1401. package/src/generation/automata/CellularAutomata.js +22 -22
  1402. package/src/generation/compute_writes_union.js +25 -25
  1403. package/src/generation/filtering/CellFilter.js +117 -117
  1404. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
  1405. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
  1406. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
  1407. package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
  1408. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
  1409. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
  1410. package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
  1411. package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
  1412. package/src/generation/grid/GridData.js +233 -233
  1413. package/src/generation/grid/coords/grid_to_texel.js +10 -10
  1414. package/src/generation/grid/coords/texel_to_grid.js +10 -10
  1415. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
  1416. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
  1417. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
  1418. package/src/generation/grid/generation/GridTaskSequence.js +58 -58
  1419. package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
  1420. package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
  1421. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
  1422. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
  1423. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
  1424. package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
  1425. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
  1426. package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
  1427. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
  1428. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
  1429. package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
  1430. package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
  1431. package/src/generation/placement/action/GridCellAction.js +45 -45
  1432. package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
  1433. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
  1434. package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
  1435. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
  1436. package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
  1437. package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
  1438. package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
  1439. package/src/generation/theme/ThemeEngine.js +666 -666
  1440. package/src/shade/RENDERER_CONTRACT.md +568 -568
  1441. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
  1442. package/src/shade/device/ShadeDeviceFailure.js +104 -104
  1443. package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
  1444. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
  1445. package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
  1446. package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
  1447. package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
  1448. package/src/shade/device/mock/SoftwareGPUError.js +63 -63
  1449. package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
  1450. package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
  1451. package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
  1452. package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
  1453. package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
  1454. package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
  1455. package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
  1456. package/src/shade/device/mock/texture_copy.js +287 -287
  1457. package/src/shade/device/timing/GPUTimer.js +285 -285
  1458. package/src/shade/device/timing/GPUTimerArray.js +205 -205
  1459. package/src/shade/playground/add_random_lights.js +49 -49
  1460. package/src/shade/playground/add_random_meshes.js +83 -83
  1461. package/src/shade/playground/animation_prototype.js +508 -508
  1462. package/src/shade/playground/animation_prototype_skinning.js +647 -647
  1463. package/src/shade/playground/animation_prototype_stress.js +765 -765
  1464. package/src/shade/playground/enable_picker.js +59 -59
  1465. package/src/shade/playground/gltf_skinned_prototype.js +560 -560
  1466. package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
  1467. package/src/shade/playground/gltf_viewer.js +282 -282
  1468. package/src/shade/playground/load_default_environment.js +19 -19
  1469. package/src/shade/playground/main.js +2727 -2727
  1470. package/src/shade/playground/make_animated_instances.js +117 -117
  1471. package/src/shade/playground/prototype_virtual_texture.js +274 -274
  1472. package/src/shade/playground/prototype_volumetrics_01.js +206 -206
  1473. package/src/shade/playground/sample.js +234 -234
  1474. package/src/shade/playground/sample_load_gltf.js +102 -102
  1475. package/src/shade/playground/spread_node3d_grid.js +19 -19
  1476. package/src/shade/renderer/Renderer.js +2436 -2436
  1477. package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
  1478. package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
  1479. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
  1480. package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
  1481. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
  1482. package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
  1483. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
  1484. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
  1485. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
  1486. package/src/shade/renderer/binding/BindGroupManager.js +282 -282
  1487. package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
  1488. package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
  1489. package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
  1490. package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
  1491. package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
  1492. package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
  1493. package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
  1494. package/src/shade/renderer/camera/Camera.js +281 -281
  1495. package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
  1496. package/src/shade/renderer/camera/orbital/README.md +84 -84
  1497. package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
  1498. package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
  1499. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
  1500. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
  1501. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
  1502. package/src/shade/renderer/extension/FrameContext.js +263 -263
  1503. package/src/shade/renderer/extension/FramePhase.js +104 -104
  1504. package/src/shade/renderer/extension/FrameRecord.js +126 -126
  1505. package/src/shade/renderer/extension/GBufferTextures.js +83 -83
  1506. package/src/shade/renderer/extension/PresentTarget.js +34 -34
  1507. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
  1508. package/src/shade/renderer/extension/RenderExtension.js +69 -69
  1509. package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
  1510. package/src/shade/renderer/extension/SceneColor.js +40 -40
  1511. package/src/shade/renderer/extension/ViewTextures.js +78 -78
  1512. package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
  1513. package/src/shade/renderer/geometry/Geometry.js +608 -608
  1514. package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
  1515. package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
  1516. package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
  1517. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
  1518. package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
  1519. package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
  1520. package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
  1521. package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
  1522. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
  1523. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
  1524. package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
  1525. package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
  1526. package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
  1527. package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
  1528. package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
  1529. package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
  1530. package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
  1531. package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
  1532. package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
  1533. package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
  1534. package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
  1535. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
  1536. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
  1537. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
  1538. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
  1539. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
  1540. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
  1541. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
  1542. package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
  1543. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
  1544. package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
  1545. package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
  1546. package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
  1547. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
  1548. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
  1549. package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
  1550. package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
  1551. package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
  1552. package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
  1553. package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
  1554. package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
  1555. package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
  1556. package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
  1557. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
  1558. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
  1559. package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
  1560. package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
  1561. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
  1562. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
  1563. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
  1564. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
  1565. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
  1566. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
  1567. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
  1568. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
  1569. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
  1570. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
  1571. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
  1572. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
  1573. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
  1574. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
  1575. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
  1576. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
  1577. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
  1578. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
  1579. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
  1580. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
  1581. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
  1582. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
  1583. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
  1584. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
  1585. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
  1586. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
  1587. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
  1588. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
  1589. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
  1590. package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
  1591. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
  1592. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
  1593. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
  1594. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
  1595. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
  1596. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
  1597. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
  1598. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
  1599. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
  1600. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
  1601. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
  1602. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
  1603. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
  1604. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
  1605. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
  1606. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
  1607. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
  1608. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
  1609. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
  1610. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
  1611. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
  1612. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
  1613. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
  1614. package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
  1615. package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
  1616. package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
  1617. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
  1618. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
  1619. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
  1620. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
  1621. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
  1622. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
  1623. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
  1624. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
  1625. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
  1626. package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
  1627. package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
  1628. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
  1629. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
  1630. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
  1631. package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
  1632. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
  1633. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
  1634. package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
  1635. package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
  1636. package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
  1637. package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
  1638. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
  1639. package/src/shade/renderer/light/make_sunlight.js +35 -35
  1640. package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
  1641. package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
  1642. package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
  1643. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
  1644. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
  1645. package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
  1646. package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
  1647. package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
  1648. package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
  1649. package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
  1650. package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
  1651. package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
  1652. package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
  1653. package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
  1654. package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
  1655. package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
  1656. package/src/shade/renderer/loader/usd/UsdError.js +33 -33
  1657. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
  1658. package/src/shade/renderer/loader/usd/load_usd.js +226 -226
  1659. package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
  1660. package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
  1661. package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
  1662. package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
  1663. package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
  1664. package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
  1665. package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
  1666. package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
  1667. package/src/shade/renderer/material/ShadeMaterial.js +2 -2
  1668. package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
  1669. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  1670. package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
  1671. package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
  1672. package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
  1673. package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
  1674. package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
  1675. package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
  1676. package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
  1677. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
  1678. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
  1679. package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
  1680. package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
  1681. package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
  1682. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
  1683. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
  1684. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
  1685. package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
  1686. package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
  1687. package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
  1688. package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
  1689. package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
  1690. package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
  1691. package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
  1692. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
  1693. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
  1694. package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
  1695. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
  1696. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
  1697. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
  1698. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
  1699. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
  1700. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
  1701. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
  1702. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
  1703. package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
  1704. package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
  1705. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
  1706. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
  1707. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
  1708. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
  1709. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
  1710. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
  1711. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
  1712. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
  1713. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
  1714. package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
  1715. package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
  1716. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
  1717. package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
  1718. package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
  1719. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
  1720. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
  1721. package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
  1722. package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
  1723. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
  1724. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
  1725. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
  1726. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
  1727. package/src/shade/renderer/scene/DynamicMesh.js +70 -70
  1728. package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
  1729. package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
  1730. package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
  1731. package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
  1732. package/src/shade/renderer/scene/TransformAuthority.js +33 -33
  1733. package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
  1734. package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
  1735. package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
  1736. package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
  1737. package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
  1738. package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
  1739. package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
  1740. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
  1741. package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
  1742. package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
  1743. package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
  1744. package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
  1745. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
  1746. package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
  1747. package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
  1748. package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
  1749. package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
  1750. package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
  1751. package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
  1752. package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
  1753. package/src/shade/renderer/texture/virtual/README.md +276 -276
  1754. package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
  1755. package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
  1756. package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
  1757. package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
  1758. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
  1759. package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
  1760. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
  1761. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
  1762. package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
  1763. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
  1764. package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
  1765. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
  1766. package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
  1767. package/src/shade/renderer/water/shader_water.js +246 -246
  1768. package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
  1769. package/src/shade/vitest.setup.mjs +89 -89
  1770. package/src/shade/wgsl/validate_wgsl_source.js +316 -316
  1771. package/src/view/controller/controls/NumericIntervalControl.js +90 -90
  1772. package/src/view/controller/controls/Vector2Control.js +83 -83
  1773. package/src/view/controller/controls/Vector3Control.js +90 -90
  1774. package/src/view/elements/Group.js +56 -56
  1775. package/src/view/elements/drawCheckers.js +33 -33
  1776. package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
  1777. package/src/view/graphics/WebGLContextFailureView.js +76 -76
  1778. package/src/view/layout/DockPanel.js +51 -51
  1779. package/src/view/layout/SplitView.js +365 -365
  1780. package/src/view/layout/show_popup_menu.js +81 -81
  1781. package/src/view/minimap/MinimapCanvasView.js +411 -411
  1782. package/src/view/minimap/MinimapImageCache.js +88 -88
  1783. package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
  1784. package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
  1785. package/src/view/minimap/MinimapMarkerCollection.js +180 -180
  1786. package/src/view/minimap/dom/MinimapCameraView.js +206 -206
  1787. package/src/view/minimap/minimap_focus_area.js +97 -97
  1788. package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
  1789. package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
  1790. package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
  1791. package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
  1792. package/src/engine/ecs/attachment/Attachment.js +0 -57
  1793. package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
  1794. package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
  1795. package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
  1796. package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
  1797. package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
  1798. package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
  1799. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
  1800. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
  1801. package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
  1802. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
  1803. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
  1804. package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
  1805. package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
  1806. package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
  1807. package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
  1808. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
  1809. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
  1810. package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
  1811. package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
  1812. package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
  1813. package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
  1814. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
  1815. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
  1816. package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
  1817. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
  1818. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
  1819. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
  1820. package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
  1821. package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
  1822. 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`) |