@woosh/meep-engine 3.0.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1823) hide show
  1. package/LICENSE +188 -188
  2. package/README.md +152 -145
  3. package/editor/Editor.js +598 -598
  4. package/editor/EditorCameraNavigation.js +205 -205
  5. package/editor/EditorKeyMap.js +182 -182
  6. package/editor/SelectionVisualizer.js +127 -127
  7. package/editor/actions/concrete/ActionInvalidateSampler.js +27 -27
  8. package/editor/actions/concrete/PatchTerrainHeightAction.js +85 -85
  9. package/editor/camera/camera_framing.js +104 -104
  10. package/editor/clipboard.js +184 -184
  11. package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -22
  12. package/editor/ecs/component/editors/ObservedStringEditor.js +34 -34
  13. package/editor/ecs/component/editors/common/two_way_sync.js +50 -50
  14. package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +19 -19
  15. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -35
  16. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
  17. package/editor/ecs/component/editors/ecs/SGMeshEditor.js +24 -24
  18. package/editor/ecs/component/editors/ecs/ShadedGeometryEditor.js +33 -33
  19. package/editor/ecs/component/editors/ecs/Trail3DEditor.js +45 -45
  20. package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -19
  21. package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +72 -72
  22. package/editor/ecs/component/editors/shade/MeshletGeometryEditor.js +30 -30
  23. package/editor/ecs/component/editors/shade/ShadeImageEditor.js +70 -70
  24. package/editor/ecs/component/editors/shade/ShadeMaterialEditor.js +34 -34
  25. package/editor/ecs/component/editors/shade/ShadeTextureEditor.js +46 -46
  26. package/editor/ecs/component/editors/shade/StandardShadeMaterialEditor.js +24 -24
  27. package/editor/ecs/component/prototypeObjectEditor.js +265 -265
  28. package/editor/ecs/component/registerBasicTypeEditors.js +100 -100
  29. package/editor/ecs/component/registerEngineComponentEditors.js +95 -95
  30. package/editor/entity_world_bounds.js +27 -27
  31. package/editor/particles/lut_ops.js +148 -148
  32. package/editor/particles/particleEditEvents.js +12 -12
  33. package/editor/particles/rebuildParticleEmitter.js +31 -31
  34. package/editor/persistence/EditorLayoutStore.js +71 -71
  35. package/editor/persistence/SceneMetadata.js +52 -52
  36. package/editor/persistence/SceneSlotManager.js +144 -144
  37. package/editor/persistence/buildNewSceneDataset.js +67 -67
  38. package/editor/persistence/files.js +68 -68
  39. package/editor/persistence/scene_import.js +143 -143
  40. package/editor/persistence/scene_serialization.js +79 -79
  41. package/editor/persistence/string_buffer_codec.js +41 -41
  42. package/editor/process/GridDisplayProcess.js +125 -125
  43. package/editor/process/SymbolicDisplayProcess.js +106 -108
  44. package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
  45. package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
  46. package/editor/process/symbolic/PathSymbolicDisplay.js +118 -118
  47. package/editor/process/symbolic/PositionedMarkerSymbolicDisplay.js +79 -79
  48. package/editor/process/symbolic/SoundEmitterSymbolicDisplay.js +57 -57
  49. package/editor/process/symbolic/SymbolicDisplay.js +62 -62
  50. package/editor/process/symbolic/gizmo_draw_wire_box_oriented.js +85 -85
  51. package/editor/process/symbolic/gizmo_draw_wire_circle.js +53 -53
  52. package/editor/prototypeEditorShell.js +156 -156
  53. package/editor/selection/editor_pick.js +130 -130
  54. package/editor/selection/screen_rect_frustum.js +119 -119
  55. package/editor/style/controls.scss +168 -168
  56. package/editor/style/inspector.scss +223 -223
  57. package/editor/templates/entity_templates.js +167 -167
  58. package/editor/tools/GridPaintTool.js +273 -273
  59. package/editor/tools/SelectionTool.js +307 -307
  60. package/editor/tools/TransformToolV2.js +378 -378
  61. package/editor/tools/engine/ToolEngine.js +188 -188
  62. package/editor/tools/paint/TerrainTexturePaintTool.js +250 -250
  63. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +501 -501
  64. package/editor/tools/v2/TransformControls.js +971 -971
  65. package/editor/tools/v2/handle/gizmo_drag_plane.js +141 -141
  66. package/editor/tools/v2/handle/gizmo_draw.js +399 -399
  67. package/editor/tools/v2/handle/gizmo_handles.js +364 -364
  68. package/editor/tools/v2/handle/gizmo_ray_shapes.js +239 -239
  69. package/editor/view/ecs/HierarchicalEntityListView.js +339 -339
  70. package/editor/view/ecs/components/DatGuiController.js +2 -2
  71. package/editor/view/ecs/components/common/AutoCanvasView.js +100 -100
  72. package/editor/view/ecs/components/common/TextController.js +59 -59
  73. package/editor/view/library/MeshLibraryView.js +175 -175
  74. package/editor/view/library/mesh_library_drag.js +63 -63
  75. package/editor/view/library/model_thumbnail_source.js +86 -86
  76. package/editor/view/makeEntityDecorators.js +125 -125
  77. package/editor/view/node-graph/NodeGraphSelection.js +89 -89
  78. package/editor/view/node-graph/NodeView.js +211 -211
  79. package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -36
  80. package/editor/view/node-graph/actions/NodesMoveAction.js +41 -41
  81. package/editor/view/node-graph/connection_wire_geometry.js +107 -107
  82. package/editor/view/particles/ColorLutGradientView.js +291 -291
  83. package/editor/view/particles/ScalarLutCurveView.js +279 -279
  84. package/editor/view/prepareMeshLibrary.js +188 -188
  85. package/editor/view/shell/SceneTreePanelView.js +119 -119
  86. package/editor/view/v2/SplitView.js +2 -2
  87. package/package.json +94 -94
  88. package/samples/generation/generators/interactive/mir_generator_place_buff_objects.js +362 -362
  89. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -738
  90. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -79
  91. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -93
  92. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -80
  93. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -164
  94. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -69
  95. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -102
  96. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -129
  97. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -72
  98. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -74
  99. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -169
  100. package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -254
  101. package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -250
  102. package/src/REVIEW_2026_08_06/adv-core.md +511 -511
  103. package/src/REVIEW_2026_08_06/adv-gfx.md +480 -480
  104. package/src/REVIEW_2026_08_06/adv-view.md +446 -446
  105. package/src/REVIEW_2026_08_06/api-core.md +296 -296
  106. package/src/REVIEW_2026_08_06/api-engine.md +195 -195
  107. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -507
  108. package/src/REVIEW_2026_08_06/core-binary.md +348 -348
  109. package/src/REVIEW_2026_08_06/core-collection.md +376 -376
  110. package/src/REVIEW_2026_08_06/core-math.md +221 -221
  111. package/src/REVIEW_2026_08_06/core-process.md +266 -266
  112. package/src/REVIEW_2026_08_06/core-science.md +223 -223
  113. package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -279
  114. package/src/REVIEW_2026_08_06/engine-animation.md +167 -167
  115. package/src/REVIEW_2026_08_06/engine-asset.md +166 -166
  116. package/src/REVIEW_2026_08_06/engine-ecs.md +920 -920
  117. package/src/REVIEW_2026_08_06/engine-input.md +197 -197
  118. package/src/REVIEW_2026_08_06/engine-misc.md +248 -248
  119. package/src/REVIEW_2026_08_06/engine-terrain.md +52 -52
  120. package/src/REVIEW_2026_08_06/generation.md +176 -176
  121. package/src/REVIEW_2026_08_06/geom-2d.md +434 -434
  122. package/src/REVIEW_2026_08_06/geom-3d.md +117 -117
  123. package/src/REVIEW_2026_08_06/gfx-gi.md +143 -143
  124. package/src/REVIEW_2026_08_06/gfx-render.md +740 -740
  125. package/src/REVIEW_2026_08_06/gfx-texture.md +690 -690
  126. package/src/REVIEW_2026_08_06/prior-criticals.md +49 -49
  127. package/src/REVIEW_2026_08_06/prior-highs.md +138 -138
  128. package/src/REVIEW_2026_08_06/test-gaps.md +364 -364
  129. package/src/REVIEW_2026_08_06/view.md +667 -667
  130. package/src/REVIEW_2026_08_06.md +610 -610
  131. package/src/avif/codec/dec/avif_dec.cpp +233 -233
  132. package/src/avif/codec/dec/avif_dec.js +16 -16
  133. package/src/core/binary/BitImage2.js +246 -246
  134. package/src/core/binary/align_32.js +21 -21
  135. package/src/core/binary/data_view/DataType2DataViewReaders.js +23 -23
  136. package/src/core/binary/data_view/DataType2DataViewWriters.js +25 -25
  137. package/src/core/binary/hash/XXH128Hash.js +55 -55
  138. package/src/core/binary/hash/XXH3_SECRET.js +25 -25
  139. package/src/core/binary/hash/xxh3_128.js +341 -341
  140. package/src/core/binary/hash/xxh3_64.js +241 -241
  141. package/src/core/binary/hash/xxh3_common.js +476 -476
  142. package/src/core/binary/lz4/LZ4_MAX_INPUT_SIZE.js +9 -9
  143. package/src/core/binary/lz4/lz4_compress_block.js +385 -385
  144. package/src/core/binary/lz4/lz4_compress_bound.js +25 -25
  145. package/src/core/binary/lz4/lz4_decompress_block.js +139 -139
  146. package/src/core/binary/lz4/lz4_decompress_bound.js +30 -30
  147. package/src/core/binary/meshopt/MESHOPT_GROUP_SIZE.js +18 -18
  148. package/src/core/binary/meshopt/MeshoptCompressionFilter.js +30 -30
  149. package/src/core/binary/meshopt/MeshoptCompressionMode.js +21 -21
  150. package/src/core/binary/meshopt/__meshopt_test_streams.js +237 -237
  151. package/src/core/binary/meshopt/meshopt_decode.js +73 -73
  152. package/src/core/binary/meshopt/meshopt_decode_byte_group.js +67 -67
  153. package/src/core/binary/meshopt/meshopt_decode_data_block.js +59 -59
  154. package/src/core/binary/meshopt/meshopt_decode_filter_color.js +69 -69
  155. package/src/core/binary/meshopt/meshopt_decode_filter_exponential.js +43 -43
  156. package/src/core/binary/meshopt/meshopt_decode_filter_octahedral.js +60 -60
  157. package/src/core/binary/meshopt/meshopt_decode_filter_quaternion.js +71 -71
  158. package/src/core/binary/meshopt/meshopt_decode_index_buffer.js +294 -294
  159. package/src/core/binary/meshopt/meshopt_decode_index_sequence.js +115 -115
  160. package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +381 -381
  161. package/src/core/binary/meshopt/meshopt_read_uint_var.js +59 -59
  162. package/src/core/binary/reinterpret_float32_as_uint32.js +13 -13
  163. package/src/core/binary/reinterpret_uint32_as_float32.js +14 -14
  164. package/src/core/binary/type/DataType2TypedArrayConstructorMapping.js +35 -35
  165. package/src/core/bvh2/bvh3/ebvh_geometry_query_any_triangle_ray.js +149 -149
  166. package/src/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +96 -96
  167. package/src/core/bvh2/bvh3/query/bvh_query_depth_range_in_frustum.js +101 -101
  168. package/src/core/bvh2/bvh3/query/bvh_query_leaves_generic.js +77 -77
  169. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +90 -90
  170. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js +94 -94
  171. package/src/core/bvh2/bvh3/query/bvh_query_user_data_generic.js +86 -86
  172. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js +101 -101
  173. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +92 -92
  174. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray.js +96 -96
  175. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray_segment.js +100 -100
  176. package/src/core/bvh2/visual/BVHGeometry.js +131 -131
  177. package/src/core/bvh8/BVH8.js +436 -436
  178. package/src/core/bvh8/aabb3_round_to_float32.js +22 -22
  179. package/src/core/bvh8/build/BVH8Converter.js +588 -588
  180. package/src/core/bvh8/build/TriangleCluster.js +122 -122
  181. package/src/core/bvh8/build/aabb3_compute_merge_cost.js +28 -28
  182. package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +17 -17
  183. package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -303
  184. package/src/core/bvh8/build/bvh8_from_proxy.js +256 -256
  185. package/src/core/bvh8/build/byte.js +9 -9
  186. package/src/core/bvh8/build/encode_bounds_e.js +11 -11
  187. package/src/core/bvh8/bvh8_convert_to_dot.js +132 -132
  188. package/src/core/bvh8/bvh8_count_primitives.js +98 -98
  189. package/src/core/bvh8/bvh8_geometry_validate.js +138 -138
  190. package/src/core/bvh8/bvh8_geometry_validate_indirect.js +166 -166
  191. package/src/core/bvh8/bvh8_get_node_bounds.js +34 -34
  192. package/src/core/bvh8/bvh8_get_node_child_bounds.js +52 -52
  193. package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -18
  194. package/src/core/bvh8/bvh8_node_count_triangles.js +27 -27
  195. package/src/core/bvh8/bvh8_quality.js +72 -72
  196. package/src/core/bvh8/bvh8_validate_structure.js +87 -87
  197. package/src/core/clipboard/obtainClipBoard.js +29 -29
  198. package/src/core/clipboard/safeClipboardReadText.js +55 -55
  199. package/src/core/clipboard/safeClipboardWriteText.js +23 -23
  200. package/src/core/collection/array/typed/typed_array_value_denormalize.js +52 -52
  201. package/src/core/collection/array/typed/typed_array_value_normalize.js +51 -51
  202. package/src/core/collection/union-find/union_find.js +79 -79
  203. package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -20
  204. package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -17
  205. package/src/core/color/colormap/MAGMA_LUT.js +26 -26
  206. package/src/core/color/illuminant/D65_TRISTIMULUS_XYZ.js +19 -19
  207. package/src/core/color/lab/lab_apply_hunt_adjustment.js +27 -27
  208. package/src/core/color/lab/lab_distance_hyab.js +23 -23
  209. package/src/core/color/lab/xyz_to_lab.js +62 -62
  210. package/src/core/color/operations/color_darken.js +24 -24
  211. package/src/core/color/operations/color_desaturate.js +24 -24
  212. package/src/core/color/operations/color_lighten.js +24 -24
  213. package/src/core/color/operations/color_saturate.js +24 -24
  214. package/src/core/color/operations/color_scale_okhsv_channel.js +45 -45
  215. package/src/core/color/tonemap/tonemap_aces.js +41 -41
  216. package/src/core/color/ycxcz/xyz_to_ycxcz.js +28 -28
  217. package/src/core/color/ycxcz/ycxcz_to_xyz.js +19 -19
  218. package/src/core/dom/isImageBitmap.js +12 -12
  219. package/src/core/function/frameThrottle.js +23 -23
  220. package/src/core/geom/2d/aabb/aabb2_array_combine.js +7 -7
  221. package/src/core/geom/2d/aabb/aabb2_compute_area.js +7 -7
  222. package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +7 -7
  223. package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +7 -7
  224. package/src/core/geom/2d/aabb/aabb2_from_v2_array.js +7 -7
  225. package/src/core/geom/2d/aabb/aabb2_intersects_point.js +7 -7
  226. package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +7 -7
  227. package/src/core/geom/2d/circle/circle_compute_circle_intersection.js +7 -7
  228. package/src/core/geom/2d/circle/circle_compute_circle_penetration.js +7 -7
  229. package/src/core/geom/2d/circle/circle_intersects_point.js +7 -7
  230. package/src/core/geom/2d/compute_polygon_area_2d.js +7 -7
  231. package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +7 -7
  232. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +7 -7
  233. package/src/core/geom/2d/convex-hull/orientation3_array.js +7 -7
  234. package/src/core/geom/2d/intersect_ray_2d.js +7 -7
  235. package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +7 -7
  236. package/src/core/geom/2d/line/line2_segment_intersection_fraction_2d.js +44 -44
  237. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +7 -7
  238. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_array_2d.js +7 -7
  239. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_2d.js +7 -7
  240. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_array_2d.js +7 -7
  241. package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +7 -7
  242. package/src/core/geom/2d/line/line_segment_line_segment_intersection_exists_2d.js +7 -7
  243. package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -25
  244. package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -46
  245. package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -52
  246. package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -69
  247. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -433
  248. package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.js +51 -51
  249. package/src/core/geom/2d/polygon/polygon2_clip_halfplane.js +50 -50
  250. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -84
  251. package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -52
  252. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -86
  253. package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -1280
  254. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -77
  255. package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -39
  256. package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -101
  257. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -7
  258. package/src/core/geom/2d/rectangle_to_aabb.js +7 -7
  259. package/src/core/geom/2d/spline_bezier2_2d.js +7 -7
  260. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +99 -99
  261. package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -35
  262. package/src/core/geom/2d/triangle2d_compute_area.js +7 -7
  263. package/src/core/geom/2d/triangle2d_contains_point.js +7 -7
  264. package/src/core/geom/2d/triangle2d_get_barycentric.js +7 -7
  265. package/src/core/geom/2d/v2_morton_encode.js +23 -23
  266. package/src/core/geom/3d/aabb/aabb3_array_intersects_frustum_degree.js +7 -7
  267. package/src/core/geom/3d/aabb/aabb3_box_surface_area_2.js +7 -7
  268. package/src/core/geom/3d/aabb/aabb3_build_frustum.js +5 -5
  269. package/src/core/geom/3d/aabb/aabb3_closest_point_on_surface.js +57 -57
  270. package/src/core/geom/3d/aabb/aabb3_combined_surface_area.js +7 -7
  271. package/src/core/geom/3d/aabb/aabb3_compute_half_surface_area.js +7 -7
  272. package/src/core/geom/3d/aabb/aabb3_compute_projected_area.js +7 -7
  273. package/src/core/geom/3d/aabb/aabb3_compute_surface_area.js +7 -7
  274. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +7 -7
  275. package/src/core/geom/3d/aabb/aabb3_estimate_projected_area2.js +7 -7
  276. package/src/core/geom/3d/aabb/aabb3_from_threejs_geometry.js +7 -7
  277. package/src/core/geom/3d/aabb/aabb3_from_triangle_group.js +46 -46
  278. package/src/core/geom/3d/aabb/aabb3_intersects_frustum_degree.js +7 -7
  279. package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +7 -7
  280. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +7 -7
  281. package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +7 -7
  282. package/src/core/geom/3d/aabb/aabb3_ray_segment_entry_distance.js +95 -95
  283. package/src/core/geom/3d/aabb/aabb3_raycast.js +7 -7
  284. package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -93
  285. package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.js +82 -82
  286. package/src/core/geom/3d/aabb/compute_aabb_from_points.js +7 -7
  287. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +7 -7
  288. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -486
  289. package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -174
  290. package/src/core/geom/3d/atlas/Chart.js +77 -77
  291. package/src/core/geom/3d/atlas/REVIEW_02.md +392 -392
  292. package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -315
  293. package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -16
  294. package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -188
  295. package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -107
  296. package/src/core/geom/3d/atlas/atlas_generate.js +50 -50
  297. package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -266
  298. package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -36
  299. package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -62
  300. package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -33
  301. package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -50
  302. package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -39
  303. package/src/core/geom/3d/atlas/io/bt_loop_uv.js +35 -35
  304. package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -46
  305. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +269 -269
  306. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +209 -209
  307. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -38
  308. package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -86
  309. package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -183
  310. package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -66
  311. package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -40
  312. package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -136
  313. package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -30
  314. package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -77
  315. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +116 -116
  316. package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +271 -271
  317. package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +191 -191
  318. package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -35
  319. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -67
  320. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -137
  321. package/src/core/geom/3d/compute_circle_bounding_box.js +7 -7
  322. package/src/core/geom/3d/cone/computeConeBoundingBox.js +7 -7
  323. package/src/core/geom/3d/cone/computeConePlaneSide.js +7 -7
  324. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +7 -7
  325. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts +38 -0
  326. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts.map +1 -0
  327. package/src/core/geom/3d/cylinder/cylinder3_raycast.js +105 -0
  328. package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -18
  329. package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -24
  330. package/src/core/geom/3d/frustum/clipping_volume_matrix4_project.js +7 -7
  331. package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +7 -7
  332. package/src/core/geom/3d/frustum/frustum3_compute_closest_point.js +117 -117
  333. package/src/core/geom/3d/frustum/frustum_matrix4_project.js +7 -7
  334. package/src/core/geom/3d/frustum/hexahedron_from_aabb.js +7 -7
  335. package/src/core/geom/3d/frustum/read_frustum_corner.js +17 -17
  336. package/src/core/geom/3d/frustum/read_three_planes_to_array.js +7 -7
  337. package/src/core/geom/3d/frustum/slice_frustum_linear_to_points.js +7 -7
  338. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -146
  339. package/src/core/geom/3d/gjk/gjk.js +430 -430
  340. package/src/core/geom/3d/gjk/gjk_epa_penetration.js +520 -520
  341. package/src/core/geom/3d/gjk/minkowski_support.js +71 -71
  342. package/src/core/geom/3d/gjk/mpr.js +368 -368
  343. package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +7 -7
  344. package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.js +7 -7
  345. package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -64
  346. package/src/core/geom/3d/line/line3_compute_segment_closest_point_to_aabb3_t.js +153 -153
  347. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +7 -7
  348. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js +7 -7
  349. package/src/core/geom/3d/line/line3_compute_segment_segment_closest_points.js +88 -88
  350. package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -71
  351. package/src/core/geom/3d/line/segment3_point_distance_to_segments.js +25 -25
  352. package/src/core/geom/3d/mat4/allocate_m4.js +7 -7
  353. package/src/core/geom/3d/mat4/apply_mat4_transform_to_direction_v3_array.js +7 -7
  354. package/src/core/geom/3d/mat4/apply_mat4_transform_to_v3_array.js +7 -7
  355. package/src/core/geom/3d/mat4/compose_matrix4_array.js +7 -7
  356. package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +7 -7
  357. package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +7 -7
  358. package/src/core/geom/3d/mat4/m4_compose.js +22 -22
  359. package/src/core/geom/3d/mat4/m4_compute_normal_matrix3.js +7 -7
  360. package/src/core/geom/3d/mat4/m4_decompose.js +64 -64
  361. package/src/core/geom/3d/mat4/m4_decompose_array.js +157 -157
  362. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +32 -32
  363. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -67
  364. package/src/core/geom/3d/mat4/m4_linear_matrix3.js +29 -29
  365. package/src/core/geom/3d/mat4/m4_look_at.js +96 -96
  366. package/src/core/geom/3d/mat4/m4_make_rotation_x.js +47 -47
  367. package/src/core/geom/3d/mat4/m4_make_rotation_y.js +47 -47
  368. package/src/core/geom/3d/mat4/m4_make_rotation_z.js +47 -47
  369. package/src/core/geom/3d/mat4/m4_normal_matrix3.js +39 -39
  370. package/src/core/geom/3d/mat4/m4_perspective.js +66 -66
  371. package/src/core/geom/3d/mat4/m4_transpose.js +48 -48
  372. package/src/core/geom/3d/morton/mortonEncode_LUT.js +7 -7
  373. package/src/core/geom/3d/morton/mortonEncode_magicbits.js +7 -7
  374. package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -5
  375. package/src/core/geom/3d/normal/hemioct/encode_unit_to_hemioct.js +28 -28
  376. package/src/core/geom/3d/normal/lambert_azimuth/decode_azimuth_to_unit.js +30 -30
  377. package/src/core/geom/3d/normal/lambert_azimuth/encode_unit_to_azimuth.js +19 -19
  378. package/src/core/geom/3d/normal/spherical/decode_spheremap_to_unit.js +38 -38
  379. package/src/core/geom/3d/normal/spherical/encode_unit_to_spheremap.js +30 -30
  380. package/src/core/geom/3d/plane/is_point_within_planes.js +7 -7
  381. package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -47
  382. package/src/core/geom/3d/plane/plane3_compute_convex_3_plane_intersection.js +7 -7
  383. package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +7 -7
  384. package/src/core/geom/3d/plane/plane3_intersect_plane.js +7 -7
  385. package/src/core/geom/3d/plane/plane3_three_compute_convex_3_plane_intersection.js +7 -7
  386. package/src/core/geom/3d/point/point3_raycast.d.ts +33 -0
  387. package/src/core/geom/3d/point/point3_raycast.d.ts.map +1 -0
  388. package/src/core/geom/3d/point/point3_raycast.js +47 -0
  389. package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -60
  390. package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -94
  391. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts +40 -0
  392. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts.map +1 -0
  393. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.js +94 -0
  394. package/src/core/geom/3d/quadric/QUADRIC3_SIZE.js +14 -14
  395. package/src/core/geom/3d/quadric/Quadric3.js +282 -282
  396. package/src/core/geom/3d/quadric/quadric3_add.js +33 -33
  397. package/src/core/geom/3d/quadric/quadric3_clear.js +25 -25
  398. package/src/core/geom/3d/quadric/quadric3_copy.js +27 -27
  399. package/src/core/geom/3d/quadric/quadric3_distance.js +36 -36
  400. package/src/core/geom/3d/quadric/quadric3_evaluate.js +25 -25
  401. package/src/core/geom/3d/quadric/quadric3_optimize.js +45 -45
  402. package/src/core/geom/3d/quadric/quadric3_scale.js +32 -32
  403. package/src/core/geom/3d/quadric/quadric3_set_from_plane_through_point.js +30 -30
  404. package/src/core/geom/3d/quadric/quadric3_set_from_vector4.js +35 -35
  405. package/src/core/geom/3d/quadric/quadric3_sub.js +36 -36
  406. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3.js +28 -28
  407. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3_inverse.js +56 -56
  408. package/src/core/geom/3d/quadric/quadric3_to_vector3.js +19 -19
  409. package/src/core/geom/3d/quaternion/quat3_createFromAxisAngle.js +7 -7
  410. package/src/core/geom/3d/quaternion/quat3_integrate.js +62 -62
  411. package/src/core/geom/3d/quaternion/quat3_multiply.js +31 -31
  412. package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -72
  413. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -69
  414. package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +7 -7
  415. package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +7 -7
  416. package/src/core/geom/3d/ray/ray3_array_compose.js +7 -7
  417. package/src/core/geom/3d/ray/ray3_compute_nearest_point_to_point.js +7 -7
  418. package/src/core/geom/3d/ray/ray3_distance_to_point.js +7 -7
  419. package/src/core/geom/3d/ray/ray3_shift_origin_along_direction.js +7 -7
  420. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +17 -0
  421. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  422. package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -0
  423. package/src/core/geom/3d/shape/CylinderShape3D.d.ts +1 -0
  424. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  425. package/src/core/geom/3d/shape/CylinderShape3D.js +21 -0
  426. package/src/core/geom/3d/shape/PointShape3D.d.ts +7 -0
  427. package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
  428. package/src/core/geom/3d/shape/PointShape3D.js +88 -63
  429. package/src/core/geom/3d/shape/PosedShape3D.d.ts +6 -0
  430. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  431. package/src/core/geom/3d/shape/PosedShape3D.js +27 -0
  432. package/src/core/geom/3d/shape/TransformedShape3D.d.ts +10 -0
  433. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  434. package/src/core/geom/3d/shape/TransformedShape3D.js +13 -0
  435. package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -0
  436. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
  437. package/src/core/geom/3d/shape/Triangle3D.js +26 -0
  438. package/src/core/geom/3d/shape/UnionShape3D.d.ts +6 -0
  439. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  440. package/src/core/geom/3d/shape/UnionShape3D.js +7 -0
  441. package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -45
  442. package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -44
  443. package/src/core/geom/3d/shape/shape3_mesh_from_geometry.js +64 -64
  444. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +7 -7
  445. package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -51
  446. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +7 -7
  447. package/src/core/geom/3d/sphere/compute_bounding_sphere_of_2_spheres.js +7 -7
  448. package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -53
  449. package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -30
  450. package/src/core/geom/3d/sphere/harmonics/sh3_rotate_bl.js +7 -7
  451. package/src/core/geom/3d/sphere/harmonics/sh_compute_K.js +7 -7
  452. package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +7 -7
  453. package/src/core/geom/3d/sphere/sphere_array_intersects_ray.js +7 -7
  454. package/src/core/geom/3d/sphere/sphere_intersects_point.js +7 -7
  455. package/src/core/geom/3d/sphere/sphere_matrix4_transform.js +5 -5
  456. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +7 -7
  457. package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +7 -7
  458. package/src/core/geom/3d/sphere/spherical_from_cartesian.js +7 -7
  459. package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +174 -174
  460. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +7 -7
  461. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_volume.js +7 -7
  462. package/src/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +7 -7
  463. package/src/core/geom/3d/tetrahedra/graphics/build_tetrahedral_mesh_geometry.js +39 -39
  464. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -48
  465. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +173 -173
  466. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_compute_tet_quality.js +22 -22
  467. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -146
  468. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -232
  469. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -255
  470. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -387
  471. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -140
  472. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +7 -7
  473. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -97
  474. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -66
  475. package/src/core/geom/3d/tetrahedra/triangle/triangle_decode_depth.js +59 -59
  476. package/src/core/geom/3d/tetrahedra/triangle/triangle_rasterized_depth_sample_index.js +54 -54
  477. package/src/core/geom/3d/tetrahedra/validate_neighbour.js +70 -70
  478. package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +314 -314
  479. package/src/core/geom/3d/topology/struct/binary/BinaryDataLayer.js +364 -364
  480. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +567 -567
  481. package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +1085 -1085
  482. package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -73
  483. package/src/core/geom/3d/topology/struct/binary/io/bt_loop_kill.js +35 -35
  484. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_append.js +119 -119
  485. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +84 -84
  486. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +172 -172
  487. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -56
  488. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +7 -7
  489. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +227 -227
  490. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +161 -161
  491. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +490 -490
  492. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +120 -120
  493. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +120 -120
  494. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -52
  495. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +115 -115
  496. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_flip.js +142 -142
  497. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -21
  498. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +92 -92
  499. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_split.js +179 -179
  500. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -28
  501. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_kill_only_edge.js +8 -8
  502. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -83
  503. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +89 -89
  504. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_kill.js +25 -25
  505. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +127 -127
  506. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -268
  507. package/src/core/geom/3d/topology/struct/binary/io/face/bt_kill_only_face.js +8 -8
  508. package/src/core/geom/3d/topology/struct/binary/io/face/bt_mesh_kill_degenerate_faces.js +72 -72
  509. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_kill_only_vert.js +7 -7
  510. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js +7 -7
  511. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js +48 -48
  512. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_split_pinched_vertices.js +256 -256
  513. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js +77 -77
  514. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +7 -7
  515. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_kill.js +7 -7
  516. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +148 -148
  517. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_kill_only.js +7 -7
  518. package/src/core/geom/3d/topology/struct/binary/query/bt_collect_boundary_segments.js +29 -29
  519. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_breaks_link_condition.js +107 -107
  520. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_is_legal.js +41 -41
  521. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_would_fold.js +120 -120
  522. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -42
  523. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_is_boundary.js +20 -20
  524. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_read_diamond.js +96 -96
  525. package/src/core/geom/3d/topology/struct/binary/query/bt_face_compute_newell_normal.js +72 -72
  526. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_attached_faces.js +7 -7
  527. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces.js +39 -39
  528. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces_weighted.js +93 -93
  529. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_neighbour_faces.js +7 -7
  530. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_shared_loop.js +48 -48
  531. package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -45
  532. package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -66
  533. package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +7 -7
  534. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -44
  535. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +32 -32
  536. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -53
  537. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -703
  538. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +181 -181
  539. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +133 -133
  540. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -82
  541. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -599
  542. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -108
  543. package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +7 -7
  544. package/src/core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js +7 -7
  545. package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +7 -7
  546. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +7 -7
  547. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +7 -7
  548. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricGeometry.js +7 -7
  549. package/src/core/geom/3d/triangle/compute_triangle_area_3d.js +7 -7
  550. package/src/core/geom/3d/triangle/compute_triangle_normal.js +7 -7
  551. package/src/core/geom/3d/triangle/rayTriangleIntersection.js +7 -7
  552. package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -65
  553. package/src/core/geom/3d/triangle/tri3_closest_point_barycentric.js +171 -171
  554. package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -79
  555. package/src/core/geom/3d/triangle/tri3_ray_intersection.js +160 -160
  556. package/src/core/geom/3d/triangle/tri3_ray_intersection_barycentric.js +96 -96
  557. package/src/core/geom/3d/triangle/tri3_raycast.d.ts +42 -0
  558. package/src/core/geom/3d/triangle/tri3_raycast.d.ts.map +1 -0
  559. package/src/core/geom/3d/triangle/tri3_raycast.js +94 -0
  560. package/src/core/geom/3d/triangle/triangle3_incenter.js +7 -7
  561. package/src/core/geom/3d/triangle/triangle_compute_plane_side.js +7 -7
  562. package/src/core/geom/3d/triangle/triangle_compute_signed_volume.js +7 -7
  563. package/src/core/geom/3d/triangle/triangle_intersects_clipping_volume.js +7 -7
  564. package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +7 -7
  565. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +7 -7
  566. package/src/core/geom/3d/util/make_justified_point_grid.js +7 -7
  567. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +381 -381
  568. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +684 -684
  569. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -554
  570. package/src/core/geom/mat3/m3_cm_from_translation.js +7 -7
  571. package/src/core/geom/mat3/m3_cm_invert.js +7 -7
  572. package/src/core/geom/mat3/m3_make_rotation.js +5 -5
  573. package/src/core/geom/mat3/m3_make_rotation_TBN.js +31 -31
  574. package/src/core/geom/mat3/m3_multiply_vec3.js +7 -7
  575. package/src/core/geom/packing/computeBoundingSphereOfSpheres.js +7 -7
  576. package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +7 -7
  577. package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +7 -7
  578. package/src/core/geom/packing/max-rect/cutArea.js +7 -7
  579. package/src/core/geom/packing/max-rect/findBestContainer.js +7 -7
  580. package/src/core/geom/packing/max-rect/packMaxRectangles.js +7 -7
  581. package/src/core/geom/packing/max-rect/packOneBox.js +7 -7
  582. package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +7 -7
  583. package/src/core/geom/packing/max-rect/removeRedundantBoxesArray.js +7 -7
  584. package/src/core/geom/random/randomPointInBox.js +7 -7
  585. package/src/core/geom/random/randomPointInCircle.js +7 -7
  586. package/src/core/geom/random/randomPointInPoint.js +7 -7
  587. package/src/core/geom/random/randomPointInSphere.js +7 -7
  588. package/src/core/geom/random/randomPointOnBox.js +7 -7
  589. package/src/core/geom/random/randomPointOnSphere.js +7 -7
  590. package/src/core/geom/random/random_orthonormal_basis.js +31 -31
  591. package/src/core/geom/vec/vector_axpy.js +15 -15
  592. package/src/core/geom/vec/vector_axpy_offset.js +20 -20
  593. package/src/core/geom/vec/vector_copy.js +7 -7
  594. package/src/core/geom/vec/vector_dot_offset.js +25 -25
  595. package/src/core/geom/vec/vector_scale_array.js +7 -7
  596. package/src/core/geom/vec2/v2_rotate.js +21 -21
  597. package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -21
  598. package/src/core/geom/vec3/v3_angle_atan2_between.js +7 -7
  599. package/src/core/geom/vec3/v3_angle_between.js +7 -7
  600. package/src/core/geom/vec3/v3_angle_cos_between.js +7 -7
  601. package/src/core/geom/vec3/v3_array_add_in_place.js +7 -7
  602. package/src/core/geom/vec3/v3_array_centroid.js +7 -7
  603. package/src/core/geom/vec3/v3_array_copy.js +7 -7
  604. package/src/core/geom/vec3/v3_array_displace_in_direction.js +7 -7
  605. package/src/core/geom/vec3/v3_array_equals.js +7 -7
  606. package/src/core/geom/vec3/v3_array_immediate_add.js +7 -7
  607. package/src/core/geom/vec3/v3_array_matrix4_rotate.js +7 -7
  608. package/src/core/geom/vec3/v3_array_normalize.js +7 -7
  609. package/src/core/geom/vec3/v3_array_scale.js +7 -7
  610. package/src/core/geom/vec3/v3_compute_interior_angle.js +7 -7
  611. package/src/core/geom/vec3/v3_cotangent.js +33 -33
  612. package/src/core/geom/vec3/v3_dot_array_array.js +7 -7
  613. package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -30
  614. package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -49
  615. package/src/core/geom/vec3/v3_matrix4_rotate.js +7 -7
  616. package/src/core/geom/vec3/v3_multiply.js +23 -23
  617. package/src/core/geom/vec3/v3_multiply_scalar.js +7 -7
  618. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +49 -49
  619. package/src/core/geom/vec3/v3_quat3_apply.js +7 -7
  620. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +7 -7
  621. package/src/core/geom/vec3/v3_quaternion_apply.js +39 -39
  622. package/src/core/geom/vec3/v3_quaternion_apply_inverse.js +41 -41
  623. package/src/core/geom/vec3/v3_reflect.js +24 -24
  624. package/src/core/geom/vec3/v3_scale_array.js +18 -18
  625. package/src/core/geom/vec3/v3_triple_cross.js +45 -45
  626. package/src/core/geom/vec3/v3_triple_cross_product.js +7 -7
  627. package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -21
  628. package/src/core/geom/vec4/v4_multiply_mat4.js +7 -7
  629. package/src/core/graph/csr/csr_graph_build_distance_field.js +112 -112
  630. package/src/core/graph/csr/csr_graph_find_edge.js +25 -25
  631. package/src/core/graph/csr/csr_graph_find_shortest_path.js +105 -105
  632. package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -94
  633. package/src/core/graph/graph_k_means_cluster.js +250 -250
  634. package/src/core/graph/layout/CircleLayout.js +1 -1
  635. package/src/core/graph/layout/box/BoxLayouter.js +1 -1
  636. package/src/core/graph/layout/graph_peel_topological_layer.js +45 -45
  637. package/src/core/graph/metis/BinaryTopologyFaceGraph.js +49 -49
  638. package/src/core/graph/metis/bt_mesh_build_face_graph.js +111 -111
  639. package/src/core/graph/metis/cluster_mesh_metis.js +137 -137
  640. package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -100
  641. package/src/core/graph/metis/native/bisection/compute_2way_params.js +86 -86
  642. package/src/core/graph/metis/native/bisection/fm_2way.js +297 -297
  643. package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -137
  644. package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -119
  645. package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -94
  646. package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -158
  647. package/src/core/graph/metis/native/coarsen/match_shem.js +175 -175
  648. package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -122
  649. package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -170
  650. package/src/core/graph/metis/native/metis_partition_kway.js +126 -126
  651. package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -53
  652. package/src/core/graph/metis/native/refine/fm_kway.js +487 -487
  653. package/src/core/graph/metis/native/refine/project_kway.js +43 -43
  654. package/src/core/graph/metis/native/refine/refine_kway.js +43 -43
  655. package/src/core/lang/reactive/pegjs/parser.js +3458 -3458
  656. package/src/core/math/complex/complex_horner_eval.js +31 -31
  657. package/src/core/math/computeWholeDivisorLow.js +33 -33
  658. package/src/core/math/f32_next_down.js +24 -24
  659. package/src/core/math/f32_next_up.js +27 -27
  660. package/src/core/math/hash/hash_mix2.js +22 -22
  661. package/src/core/math/hash/hash_mix3.js +19 -19
  662. package/src/core/math/linalg/cg/cg_solve.js +106 -106
  663. package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -59
  664. package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -43
  665. package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -122
  666. package/src/core/math/linalg/eigen/matrix_qr_in_place.js +190 -190
  667. package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -117
  668. package/src/core/math/linalg/givens/givens_apply_rows.js +28 -28
  669. package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -30
  670. package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -39
  671. package/src/core/math/linalg/lu_factor_linear_system.js +108 -108
  672. package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
  673. package/src/core/math/linalg/polynomial_add_into.js +29 -29
  674. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +159 -159
  675. package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -16
  676. package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -19
  677. package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -14
  678. package/src/core/math/linalg/polynomial_multiply.js +41 -41
  679. package/src/core/math/linalg/polynomial_real_roots_in_interval.js +211 -211
  680. package/src/core/math/linalg/polynomial_root_bound_cauchy.js +30 -30
  681. package/src/core/math/linalg/polynomial_scale_into.js +17 -17
  682. package/src/core/math/linalg/polynomial_sub_into.js +29 -29
  683. package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -96
  684. package/src/core/math/linalg/sor_optimal_omega.js +42 -42
  685. package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -88
  686. package/src/core/math/linalg/sparse/spm_matvec.js +22 -22
  687. package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -29
  688. package/src/core/math/lookup/LUT_HEATMAP.js +30 -30
  689. package/src/core/math/lookup/ParameterLookupTable.js +495 -495
  690. package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -6
  691. package/src/core/math/noise/curl_noise_3d.js +18 -18
  692. package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -28
  693. package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -48
  694. package/src/core/math/physics/kinematics/computeInterceptPoint.js +79 -79
  695. package/src/core/math/random/randomSeed.js +16 -16
  696. package/src/core/math/random/random_pick_weighted_index.js +26 -26
  697. package/src/core/math/round_half_away_from_zero.js +13 -13
  698. package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -107
  699. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +125 -125
  700. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +50 -50
  701. package/src/core/math/spline/spline3_hermite_reverse.js +49 -49
  702. package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -37
  703. package/src/core/math/statistics/hammersley_sequence.js +53 -53
  704. package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
  705. package/src/core/path/convertPathToURL.js +107 -107
  706. package/src/core/process/delay.js +16 -16
  707. package/src/core/science/units/MEASUREMENT_UNITS_SCIENTIFIC.js +87 -87
  708. package/src/core/science/units/MEASUREMENT_UNITS_SI.js +17 -17
  709. package/src/core/science/units/NamedUnit.js +48 -48
  710. package/src/core/science/units/UNIT_DIMENSION_MAPPING.js +29 -29
  711. package/src/core/science/units/UnitDimension.js +28 -28
  712. package/src/core/science/units/format_quantity.js +74 -74
  713. package/src/core/science/units/magnitude_prefix.js +127 -127
  714. package/src/core/science/units/si/AMPERE.js +13 -13
  715. package/src/core/science/units/si/CANDELA.js +13 -13
  716. package/src/core/science/units/si/DIMENSIONLESS.js +14 -14
  717. package/src/core/science/units/si/KELVIN.js +13 -13
  718. package/src/core/science/units/si/KILOGRAM.js +13 -13
  719. package/src/core/science/units/si/METER.js +13 -13
  720. package/src/core/science/units/si/MOLE.js +13 -13
  721. package/src/core/science/units/si/SECOND.js +13 -13
  722. package/src/core/science/units/si/derived/BECQUEREL.js +16 -16
  723. package/src/core/science/units/si/derived/COULOMB.js +13 -13
  724. package/src/core/science/units/si/derived/FARAD.js +13 -13
  725. package/src/core/science/units/si/derived/GRAY.js +16 -16
  726. package/src/core/science/units/si/derived/HENRY.js +13 -13
  727. package/src/core/science/units/si/derived/HERTZ.js +16 -16
  728. package/src/core/science/units/si/derived/JOULE.js +13 -13
  729. package/src/core/science/units/si/derived/KATAL.js +13 -13
  730. package/src/core/science/units/si/derived/LUMEN.js +14 -14
  731. package/src/core/science/units/si/derived/LUX.js +13 -13
  732. package/src/core/science/units/si/derived/NEWTON.js +13 -13
  733. package/src/core/science/units/si/derived/OHM.js +13 -13
  734. package/src/core/science/units/si/derived/PASCAL.js +13 -13
  735. package/src/core/science/units/si/derived/SIEMENS.js +13 -13
  736. package/src/core/science/units/si/derived/SIEVERT.js +16 -16
  737. package/src/core/science/units/si/derived/TESLA.js +13 -13
  738. package/src/core/science/units/si/derived/VOLT.js +13 -13
  739. package/src/core/science/units/si/derived/WATT.js +13 -13
  740. package/src/core/science/units/si/derived/WEBER.js +13 -13
  741. package/src/core/science/units/unit_matrix_to_string.js +403 -403
  742. package/src/engine/Engine.js +726 -726
  743. package/src/engine/EngineHarness.js +590 -590
  744. package/src/engine/REVIEW_2026_07_03.md +211 -211
  745. package/src/engine/animation/AnimatedActions.js +81 -81
  746. package/src/engine/animation/AnimationUtils.js +252 -252
  747. package/src/engine/animation/async/TimeSeries.js +300 -300
  748. package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -60
  749. package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -22
  750. package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -38
  751. package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -22
  752. package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -22
  753. package/src/engine/asset/GameAssetType.d.ts +0 -1
  754. package/src/engine/asset/GameAssetType.js +0 -1
  755. package/src/engine/asset/load_model_scene_bundle.js +27 -27
  756. package/src/engine/asset/loaders/font/FontAsset.js +21 -21
  757. package/src/engine/asset/loaders/font/FontAssetLoader.js +20 -20
  758. package/src/engine/asset/loaders/gltf_test_fixtures.js +358 -358
  759. package/src/engine/asset/loaders/image/ImageBitmapAssetLoader.js +52 -52
  760. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +94 -94
  761. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -59
  762. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -55
  763. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -74
  764. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -34
  765. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -46
  766. package/src/engine/control/first-person/DESIGN.md +640 -640
  767. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -563
  768. package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -134
  769. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2045 -2045
  770. package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -30
  771. package/src/engine/control/first-person/math/Spring.js +71 -71
  772. package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -23
  773. package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -41
  774. package/src/engine/control/first-person/math/computeMassRatios.js +44 -44
  775. package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -34
  776. package/src/engine/control/first-person/math/dampedSpringStep.js +72 -72
  777. package/src/engine/control/first-person/math/jumpDynamics.js +108 -108
  778. package/src/engine/control/first-person/math/stepTowards.js +20 -20
  779. package/src/engine/control/first-person/pose/FirstPersonPose.js +137 -137
  780. package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -27
  781. package/src/engine/control/first-person/test/buildTestPlayer.js +36 -36
  782. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -531
  783. package/src/engine/ecs/EntityManager.d.ts +7 -3
  784. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  785. package/src/engine/ecs/EntityManager.js +10 -5
  786. package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
  787. package/src/engine/ecs/async/SystemWorkerHost.js +329 -329
  788. package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -143
  789. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -246
  790. package/src/engine/ecs/async/WorkerSystem.js +702 -702
  791. package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -150
  792. package/src/engine/ecs/fow/FogOfWar.js +707 -707
  793. package/src/engine/ecs/fow/FogOfWarEdgeMode.js +40 -40
  794. package/src/engine/ecs/fow/fog_of_war_mask_world_bounds.js +24 -24
  795. package/src/engine/ecs/fow/fog_of_war_uv_transform.js +30 -30
  796. package/src/engine/ecs/gui/hud/testHudEdgeStick.js +132 -134
  797. package/src/engine/ecs/hierarchy/EntityChildIndex.js +140 -140
  798. package/src/engine/ecs/ik/IKProblem.js +42 -42
  799. package/src/engine/ecs/ik/InverseKinematicsSystem.js +193 -193
  800. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +134 -134
  801. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +97 -97
  802. package/src/engine/ecs/ik/ik_bone_chain.js +85 -85
  803. package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -119
  804. package/src/engine/ecs/parent/EntityNode.d.ts +1 -1
  805. package/src/engine/ecs/parent/EntityNode.js +2 -2
  806. package/src/engine/ecs/parent/ParentEntitySystem.js +255 -255
  807. package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
  808. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  809. package/src/engine/ecs/speaker/VoiceSystem.js +13 -7
  810. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +269 -269
  811. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -315
  812. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -331
  813. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -199
  814. package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -57
  815. package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -56
  816. package/src/engine/ecs/storage/binary/object-v2/README.md +110 -110
  817. package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -48
  818. package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -44
  819. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +265 -265
  820. package/src/engine/ecs/terrain/TerrainClouds.js +97 -97
  821. package/src/engine/ecs/terrain/ecs/StubTerrainBuildWorker.js +65 -65
  822. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +4 -0
  823. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  824. package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
  825. package/src/engine/ecs/terrain/ecs/TerrainSystem.js +209 -209
  826. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +316 -316
  827. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +416 -416
  828. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +525 -525
  829. package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +302 -302
  830. package/src/engine/ecs/terrain/tiles/TerrainTile.js +561 -561
  831. package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +896 -896
  832. package/src/engine/ecs/terrain/tiles/tile_geometry_to_meshlets.js +36 -36
  833. package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
  834. package/src/engine/ecs/transform/Transform64.js +814 -814
  835. package/src/engine/ecs/transform/t64_copy_from_transform.js +35 -35
  836. package/src/engine/ecs/transform/t64_equals_transform.js +55 -55
  837. package/src/engine/ecs/transform/t64_look_rotation.js +36 -36
  838. package/src/engine/ecs/transform/t64_transform_direction.js +25 -25
  839. package/src/engine/ecs/transform/transform_copy_from_t64.js +28 -28
  840. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +345 -345
  841. package/src/engine/ecs/transform-attachment/transform_attachment_find_descendant_by_name.js +65 -65
  842. package/src/engine/ecs/transform-attachment/transform_attachment_parent_of.js +24 -24
  843. package/src/engine/ecs/util/hideEntityGracefully.js +208 -208
  844. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -446
  845. package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -14
  846. package/src/engine/graphics/FrameThrottle.js +2 -2
  847. package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +207 -207
  848. package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +151 -151
  849. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -22
  850. package/src/engine/graphics/ecs/camera/Camera.js +165 -165
  851. package/src/engine/graphics/ecs/camera/camera_active_frustum.js +25 -25
  852. package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
  853. package/src/engine/graphics/ecs/camera/camera_traverse_active.js +18 -18
  854. package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +170 -170
  855. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -68
  856. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +296 -296
  857. package/src/engine/graphics/ecs/decal/v2/Decal.js +220 -220
  858. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +56 -56
  859. package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_0_1.js +38 -38
  860. package/src/engine/graphics/ecs/light/shadow/compute_view_frustum_aabb_in_space.js +62 -62
  861. package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +77 -77
  862. package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -33
  863. package/src/engine/graphics/ecs/mesh-v2/DeferredBoundsQueue.js +88 -88
  864. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
  865. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +303 -303
  866. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +1 -1
  867. package/src/engine/graphics/ecs/mesh-v2/aggregate/serialization/SGMeshSerializationAdapter.js +39 -39
  868. package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +36 -36
  869. package/src/engine/graphics/ecs/path/tube/build/GeometryStream.js +94 -94
  870. package/src/engine/graphics/ecs/path/tube/build/build_stream_catmullrom.js +310 -310
  871. package/src/engine/graphics/ecs/path/tube/build/build_stream_linear.js +73 -73
  872. package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +199 -199
  873. package/src/engine/graphics/ecs/path/tube/build/makeTubeStream.js +217 -217
  874. package/src/engine/graphics/ecs/path/tube/build/make_cap.js +340 -340
  875. package/src/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -40
  876. package/src/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +153 -153
  877. package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -64
  878. package/src/engine/graphics/ecs/trail3d/Trail3D.js +483 -483
  879. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +26 -26
  880. package/src/engine/graphics/ecs/trail3d/advance_trail.js +42 -42
  881. package/src/engine/graphics/ecs/trail3d/make_gradient_stroke.js +58 -58
  882. package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +90 -90
  883. package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +75 -75
  884. package/src/engine/graphics/ecs/trail3d/trail_head_position.js +19 -19
  885. package/src/engine/graphics/ecs/water/Water.js +104 -104
  886. package/src/engine/graphics/geometry/AttributeData.js +84 -84
  887. package/src/engine/graphics/geometry/buffered/geometry_compute_vertex_normals_indexed.js +49 -49
  888. package/src/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +288 -288
  889. package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -87
  890. package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -102
  891. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_buffered_geometry.js +52 -52
  892. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_indexed_geometry.js +20 -20
  893. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_unindexed_geometry.js +19 -19
  894. package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -135
  895. package/src/engine/graphics/impostors/voxel/README.md +149 -149
  896. package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -141
  897. package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -20
  898. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +2 -0
  899. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
  900. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +2 -0
  901. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
  902. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +2 -0
  903. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
  904. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts +1 -2
  905. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts.map +1 -1
  906. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +2 -0
  907. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
  908. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +2 -0
  909. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
  910. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +2 -0
  911. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
  912. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +2 -0
  913. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
  914. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +2 -0
  915. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
  916. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +2 -0
  917. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
  918. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +2 -0
  919. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
  920. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +2 -0
  921. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
  922. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +2 -0
  923. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
  924. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +2 -0
  925. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
  926. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +2 -0
  927. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
  928. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +2 -0
  929. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
  930. package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -45
  931. package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +229 -229
  932. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -10
  933. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
  934. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1149 -1149
  935. package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +472 -472
  936. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -2
  937. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -2
  938. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
  939. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +3 -5
  940. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +135 -135
  941. package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -168
  942. package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -18
  943. package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -2
  944. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +219 -219
  945. package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
  946. package/src/engine/graphics/render/frame_graph/FrameGraphScope.js +72 -72
  947. package/src/engine/graphics/render/frame_graph/FrameGraphState.js +39 -39
  948. package/src/engine/graphics/render/frame_graph/ResourceEntry.js +96 -96
  949. package/src/engine/graphics/render/gizmo/GizmoShapeBuffer.js +105 -105
  950. package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +162 -162
  951. package/src/engine/graphics/render/layers/RenderLayer.js +95 -95
  952. package/src/engine/graphics/sh3/build_injected_shader.js +56 -56
  953. package/src/engine/graphics/sh3/grid_dilate_average.js +175 -175
  954. package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +506 -506
  955. package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +161 -161
  956. package/src/engine/graphics/sh3/path_tracer/PathTracedRenderer.js +205 -205
  957. package/src/engine/graphics/sh3/path_tracer/PathTracedScene.js +698 -698
  958. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +490 -490
  959. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -2
  960. package/src/engine/graphics/sh3/path_tracer/make_sky_hosek.js +45 -45
  961. package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +73 -73
  962. package/src/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +32 -32
  963. package/src/engine/graphics/sh3/path_tracer/texture/apply_texture_clamping_to_coordinate.js +28 -28
  964. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +142 -142
  965. package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -70
  966. package/src/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +388 -388
  967. package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -277
  968. package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -57
  969. package/src/engine/graphics/shadows/vsm.glsl.js +43 -43
  970. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -81
  971. package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -15
  972. package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -26
  973. package/src/engine/graphics/texture/isImageBitmap.js +2 -2
  974. package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -189
  975. package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -51
  976. package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
  977. package/src/engine/graphics/texture/sampler/Sampler2DArray.js +172 -172
  978. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +466 -466
  979. package/src/engine/graphics/texture/sampler/compare/flip/FlipParameters.js +18 -18
  980. package/src/engine/graphics/texture/sampler/compare/flip/FlipPooling.js +33 -33
  981. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_feature_kernel.js +147 -147
  982. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_spatial_kernel.js +177 -177
  983. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_color_difference.js +104 -104
  984. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_feature_difference.js +76 -76
  985. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_pixels_per_degree.js +34 -34
  986. package/src/engine/graphics/texture/sampler/compare/flip/flip_constants.js +77 -77
  987. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_feature.js +48 -48
  988. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_spatial.js +86 -86
  989. package/src/engine/graphics/texture/sampler/compare/flip/flip_pool.js +71 -71
  990. package/src/engine/graphics/texture/sampler/compare/flip/flip_srgb_to_ycxcz.js +55 -55
  991. package/src/engine/graphics/texture/sampler/compare/flip/sampler2d_flip_ldr.js +80 -80
  992. package/src/engine/graphics/texture/sampler/filter/SeparableKernel.js +82 -82
  993. package/src/engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js +122 -122
  994. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +148 -148
  995. package/src/engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js +82 -82
  996. package/src/engine/graphics/texture/sampler/sampler2d_combine.js +67 -67
  997. package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -54
  998. package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -63
  999. package/src/engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js +33 -33
  1000. package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -68
  1001. package/src/engine/graphics/texture/shade_image_preview.js +103 -103
  1002. package/src/engine/graphics/trail/TrailSpawnMode.js +20 -20
  1003. package/src/engine/graphics/trail/tube/TubeX.js +573 -573
  1004. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +58 -58
  1005. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +34 -34
  1006. package/src/engine/graphics/trail/tube/tube_attributes_spec.js +69 -69
  1007. package/src/engine/graphics/trail/tube/tube_frame.js +83 -83
  1008. package/src/engine/graphics3/AnimationGraphSystem3.js +329 -329
  1009. package/src/engine/graphics3/AnimationSystem3.js +352 -352
  1010. package/src/engine/graphics3/CameraSystem3.js +170 -170
  1011. package/src/engine/graphics3/DecalSystem3.js +441 -441
  1012. package/src/engine/graphics3/FogOfWarSystem3.js +265 -265
  1013. package/src/engine/graphics3/GraphicsEngine3.js +776 -776
  1014. package/src/engine/graphics3/HighlightSystem3.js +241 -241
  1015. package/src/engine/graphics3/LightSystem3.js +211 -211
  1016. package/src/engine/graphics3/MeshSystem3.js +646 -646
  1017. package/src/engine/graphics3/ParticleEmitterSystem3.js +289 -289
  1018. package/src/engine/graphics3/PathDisplaySystem3.js +309 -309
  1019. package/src/engine/graphics3/PickingQuery.js +48 -48
  1020. package/src/engine/graphics3/PickingResult.js +47 -47
  1021. package/src/engine/graphics3/PickingSystem3.js +273 -273
  1022. package/src/engine/graphics3/ShadeCameraAdapter.js +55 -55
  1023. package/src/engine/graphics3/ShadedGeometrySystem3.js +169 -169
  1024. package/src/engine/graphics3/TerrainSystem3.js +566 -566
  1025. package/src/engine/graphics3/TooltipComponentSystem3.js +328 -328
  1026. package/src/engine/graphics3/Trail3DSystem3.js +221 -221
  1027. package/src/engine/graphics3/WaterSystem3.js +293 -293
  1028. package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
  1029. package/src/engine/graphics3/animation/wrap_clip_time.js +23 -23
  1030. package/src/engine/graphics3/animation/write_graph_playback.js +48 -48
  1031. package/src/engine/graphics3/camera_sync_from_transform.js +29 -29
  1032. package/src/engine/graphics3/debug/gizmo_view_from_camera.js +48 -48
  1033. package/src/engine/graphics3/decal/DECAL_CLUSTER.js +61 -61
  1034. package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +117 -117
  1035. package/src/engine/graphics3/decal/DecalAtlas.js +194 -194
  1036. package/src/engine/graphics3/decal/DecalAtlasFill.js +32 -32
  1037. package/src/engine/graphics3/decal/GPUDecalRenderer.js +360 -360
  1038. package/src/engine/graphics3/decal/chunk_decal_intersects_frustum.js +59 -59
  1039. package/src/engine/graphics3/decal/chunk_decal_sampling_info.js +57 -57
  1040. package/src/engine/graphics3/decal/chunk_decal_surface_frame.js +54 -54
  1041. package/src/engine/graphics3/decal/decal_atlas_border.js +69 -69
  1042. package/src/engine/graphics3/decal/decal_gpu_records.js +120 -120
  1043. package/src/engine/graphics3/decal/decal_material_samplers.js +109 -109
  1044. package/src/engine/graphics3/decal/graph_build_decal_clusters.js +212 -212
  1045. package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +207 -207
  1046. package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +108 -108
  1047. package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +151 -151
  1048. package/src/engine/graphics3/highlight/HIGHLIGHT_OUTLINE.js +23 -23
  1049. package/src/engine/graphics3/highlight/chunk_highlight_lookup.js +53 -53
  1050. package/src/engine/graphics3/highlight/pack_highlight_color.js +44 -44
  1051. package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
  1052. package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
  1053. package/src/engine/graphics3/load_cube_environment.js +68 -68
  1054. package/src/engine/graphics3/make_default_environment.js +107 -107
  1055. package/src/engine/graphics3/particles/particle_gpu_records.js +221 -221
  1056. package/src/engine/graphics3/path/build_tube_path_streams.js +98 -98
  1057. package/src/engine/graphics3/path/tube_stream_write_geometry.js +90 -90
  1058. package/src/engine/graphics3/preview/ModelPreviewFraming.js +35 -35
  1059. package/src/engine/graphics3/preview/make_model_preview_scene.js +154 -154
  1060. package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
  1061. package/src/engine/graphics3/preview/model_preview_framing.js +70 -70
  1062. package/src/engine/graphics3/shade_bundle_bounds.js +55 -55
  1063. package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
  1064. package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
  1065. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.js +481 -481
  1066. package/src/engine/graphics3/terrain/chunk_terrain_grid_overlay.js +61 -61
  1067. package/src/engine/graphics3/terrain/chunk_terrain_splat_mix.js +66 -66
  1068. package/src/engine/graphics3/terrain/pack_terrain_row_table.js +53 -53
  1069. package/src/engine/graphics3/terrain/shader_terrain_splat.js +215 -215
  1070. package/src/engine/graphics3/trail/trail_tube_write_geometry.js +158 -158
  1071. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +2 -2
  1072. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  1073. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -717
  1074. package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
  1075. package/src/engine/input/devices/GamepadDevice.js +466 -466
  1076. package/src/engine/input/devices/ManualInputDevice.js +152 -152
  1077. package/src/engine/input/devices/PointerCoordinates.js +26 -26
  1078. package/src/engine/input/devices/events/GamepadEvents.js +10 -10
  1079. package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
  1080. package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
  1081. package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
  1082. package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
  1083. package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
  1084. package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
  1085. package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
  1086. package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
  1087. package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
  1088. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
  1089. package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
  1090. package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
  1091. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
  1092. package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
  1093. package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
  1094. package/src/engine/input/ecs/components/Input.js +118 -118
  1095. package/src/engine/input/ecs/components/InputController.js +78 -78
  1096. package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
  1097. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
  1098. package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
  1099. package/src/engine/input/ecs/ism/InputBinding.js +71 -71
  1100. package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
  1101. package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
  1102. package/src/engine/input/ecs/ism/InputDirection.js +33 -33
  1103. package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
  1104. package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
  1105. package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
  1106. package/src/engine/input/ecs/ism/InputSource.js +67 -67
  1107. package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
  1108. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
  1109. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
  1110. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
  1111. package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
  1112. package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
  1113. package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
  1114. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
  1115. package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
  1116. package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
  1117. package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
  1118. package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
  1119. package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
  1120. package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
  1121. package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
  1122. package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
  1123. package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
  1124. package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
  1125. package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
  1126. package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
  1127. package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
  1128. package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
  1129. package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
  1130. package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
  1131. package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
  1132. package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
  1133. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
  1134. package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
  1135. package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
  1136. package/src/engine/input/ecs/systems/InputSystem.js +162 -162
  1137. package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
  1138. package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
  1139. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
  1140. package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
  1141. package/src/engine/interpolation/Interpoland.js +49 -49
  1142. package/src/engine/interpolation/Interpolated.js +149 -149
  1143. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
  1144. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
  1145. package/src/engine/interpolation/pose_interpoland.js +27 -27
  1146. package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
  1147. package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
  1148. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
  1149. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
  1150. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
  1151. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
  1152. package/src/engine/network/CONGESTION_CONTROL.md +87 -87
  1153. package/src/engine/network/README.md +52 -52
  1154. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
  1155. package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
  1156. package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
  1157. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
  1158. package/src/engine/network/convertPathToURL.js +2 -2
  1159. package/src/engine/network/core/MalformedPacketError.js +26 -26
  1160. package/src/engine/network/core/quantize/quantize_float.js +73 -73
  1161. package/src/engine/network/core/quantize/quantize_position.js +65 -65
  1162. package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
  1163. package/src/engine/network/core/sequence/seq16.js +69 -69
  1164. package/src/engine/network/core/sequence/seq32.js +73 -73
  1165. package/src/engine/network/ecs/NetworkSystem.js +84 -84
  1166. package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
  1167. package/src/engine/network/ecs/owner_authorization.js +55 -55
  1168. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
  1169. package/src/engine/network/replication/ScopeFilter.js +71 -71
  1170. package/src/engine/network/sim/ActionLog.js +152 -152
  1171. package/src/engine/network/sim/SimAction.js +273 -273
  1172. package/src/engine/network/sim/Snapshotter.js +222 -222
  1173. package/src/engine/network/state/FrameBufferRing.js +168 -168
  1174. package/src/engine/network/state/InputRing.js +134 -134
  1175. package/src/engine/network/state/PriorityAccumulator.js +180 -180
  1176. package/src/engine/network/time/TimeDilation.js +62 -62
  1177. package/src/engine/network/transport/Channel.js +336 -336
  1178. package/src/engine/network/transport/Transport.js +203 -203
  1179. package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
  1180. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
  1181. package/src/engine/network/transport/fragments/fragment_send.js +147 -147
  1182. package/src/engine/network/transport/fragments/packet_size.js +101 -101
  1183. package/src/engine/network/xhr.js +23 -23
  1184. package/src/engine/physics/BULLET_REVIEW.md +945 -945
  1185. package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
  1186. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
  1187. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
  1188. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
  1189. package/src/engine/physics/JOLT_REVIEW.md +913 -913
  1190. package/src/engine/physics/PLAN.md +1120 -1094
  1191. package/src/engine/physics/RAPIER_REVIEW.md +934 -934
  1192. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
  1193. package/src/engine/physics/REVIEW_002.md +151 -151
  1194. package/src/engine/physics/REVIEW_003.md +166 -166
  1195. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
  1196. package/src/engine/physics/constraint/DofMode.js +35 -35
  1197. package/src/engine/physics/constraint/solve_constraints.js +1 -1
  1198. package/src/engine/physics/contact/combine_material.js +35 -35
  1199. package/src/engine/physics/ecs/BodyKind.js +29 -29
  1200. package/src/engine/physics/ecs/Collider.js +177 -177
  1201. package/src/engine/physics/ecs/ColliderFlags.js +15 -15
  1202. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
  1203. package/src/engine/physics/ecs/Joint.js +475 -475
  1204. package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
  1205. package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
  1206. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  1207. package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
  1208. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
  1209. package/src/engine/physics/ecs/SleepState.js +12 -12
  1210. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
  1211. package/src/engine/physics/fluid/FluidField.js +867 -867
  1212. package/src/engine/physics/fluid/FluidSimulator.js +832 -832
  1213. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
  1214. package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
  1215. package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
  1216. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
  1217. package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
  1218. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
  1219. package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
  1220. package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
  1221. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
  1222. package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
  1223. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
  1224. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
  1225. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
  1226. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
  1227. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
  1228. package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
  1229. package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
  1230. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
  1231. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
  1232. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
  1233. package/src/engine/physics/fluid/prototype.js +126 -126
  1234. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
  1235. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
  1236. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
  1237. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
  1238. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
  1239. package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
  1240. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
  1241. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
  1242. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
  1243. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
  1244. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
  1245. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
  1246. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
  1247. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
  1248. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
  1249. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
  1250. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
  1251. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
  1252. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
  1253. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
  1254. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
  1255. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
  1256. package/src/engine/physics/integration/integrate_position.js +97 -97
  1257. package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
  1258. package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
  1259. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
  1260. package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
  1261. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
  1262. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
  1263. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  1264. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
  1265. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
  1266. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
  1267. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  1268. package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
  1269. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
  1270. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
  1271. package/src/engine/physics/persistence/solver_caches.js +309 -309
  1272. package/src/engine/physics/queries/overlap_shape.js +187 -187
  1273. package/src/engine/physics/queries/raycast.d.ts +7 -4
  1274. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  1275. package/src/engine/physics/queries/raycast.js +11 -5
  1276. package/src/engine/physics/queries/shape_cast.js +417 -417
  1277. package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
  1278. package/src/engine/physics/solver/friction_cone.js +37 -37
  1279. package/src/engine/physics/solver/solve_contacts.js +1 -1
  1280. package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
  1281. package/src/engine/platform/WebEnginePlatform.js +1 -2
  1282. package/src/engine/scene/SerializedScene.d.ts +2 -2
  1283. package/src/engine/scene/SerializedScene.d.ts.map +1 -1
  1284. package/src/engine/scene/transitionToScene.js +121 -121
  1285. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  1286. package/src/engine/simulation/Ticker.js +33 -0
  1287. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
  1288. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
  1289. package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
  1290. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
  1291. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
  1292. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
  1293. package/src/engine/sound/simulation/README.md +277 -277
  1294. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
  1295. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
  1296. package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
  1297. package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
  1298. package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
  1299. package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
  1300. package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
  1301. package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
  1302. package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
  1303. package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
  1304. package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
  1305. package/src/engine/sound/simulation/core/RayHit.js +50 -50
  1306. package/src/engine/sound/simulation/core/VolumeField.js +149 -149
  1307. package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
  1308. package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
  1309. package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
  1310. package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
  1311. package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
  1312. package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
  1313. package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
  1314. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
  1315. package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
  1316. package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
  1317. package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
  1318. package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
  1319. package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
  1320. package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
  1321. package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
  1322. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
  1323. package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
  1324. package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
  1325. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
  1326. package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
  1327. package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
  1328. package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
  1329. package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
  1330. package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
  1331. package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
  1332. package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
  1333. package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
  1334. package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
  1335. package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
  1336. package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
  1337. package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
  1338. package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
  1339. package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
  1340. package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
  1341. package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
  1342. package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
  1343. package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
  1344. package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
  1345. package/src/engine/sound/sopra/README.md +643 -643
  1346. package/src/engine/sound/sopra/SopraEngine.js +432 -432
  1347. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
  1348. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
  1349. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
  1350. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
  1351. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
  1352. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
  1353. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
  1354. package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
  1355. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
  1356. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
  1357. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
  1358. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
  1359. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
  1360. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
  1361. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
  1362. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
  1363. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
  1364. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
  1365. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
  1366. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
  1367. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
  1368. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
  1369. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
  1370. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
  1371. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
  1372. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
  1373. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
  1374. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  1375. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
  1376. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
  1377. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
  1378. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
  1379. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
  1380. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
  1381. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
  1382. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
  1383. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
  1384. package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
  1385. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
  1386. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
  1387. package/src/engine/sound/sopra/runtime/Voice.js +145 -145
  1388. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
  1389. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
  1390. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
  1391. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
  1392. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
  1393. package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
  1394. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
  1395. package/src/engine/ui/DraggableAspect.js +115 -115
  1396. package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
  1397. package/src/generation/COORDINATES.md +54 -54
  1398. package/src/generation/GridGenerationResource.js +24 -24
  1399. package/src/generation/GridTaskGroup.js +214 -214
  1400. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
  1401. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
  1402. package/src/generation/automata/CellularAutomata.js +22 -22
  1403. package/src/generation/compute_writes_union.js +25 -25
  1404. package/src/generation/filtering/CellFilter.js +117 -117
  1405. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
  1406. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
  1407. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
  1408. package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
  1409. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
  1410. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
  1411. package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
  1412. package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
  1413. package/src/generation/grid/GridData.js +233 -233
  1414. package/src/generation/grid/coords/grid_to_texel.js +10 -10
  1415. package/src/generation/grid/coords/texel_to_grid.js +10 -10
  1416. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
  1417. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
  1418. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
  1419. package/src/generation/grid/generation/GridTaskSequence.js +58 -58
  1420. package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
  1421. package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
  1422. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
  1423. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
  1424. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
  1425. package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
  1426. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
  1427. package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
  1428. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
  1429. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
  1430. package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
  1431. package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
  1432. package/src/generation/placement/action/GridCellAction.js +45 -45
  1433. package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
  1434. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
  1435. package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
  1436. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
  1437. package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
  1438. package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
  1439. package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
  1440. package/src/generation/theme/ThemeEngine.js +666 -666
  1441. package/src/shade/RENDERER_CONTRACT.md +568 -0
  1442. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
  1443. package/src/shade/device/ShadeDeviceFailure.js +104 -104
  1444. package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
  1445. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
  1446. package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
  1447. package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
  1448. package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
  1449. package/src/shade/device/mock/SoftwareGPUError.js +63 -63
  1450. package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
  1451. package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
  1452. package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
  1453. package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
  1454. package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
  1455. package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
  1456. package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
  1457. package/src/shade/device/mock/texture_copy.js +287 -287
  1458. package/src/shade/device/timing/GPUTimer.js +285 -285
  1459. package/src/shade/device/timing/GPUTimerArray.js +205 -205
  1460. package/src/shade/playground/add_random_lights.js +49 -49
  1461. package/src/shade/playground/add_random_meshes.js +83 -83
  1462. package/src/shade/playground/animation_prototype.js +508 -508
  1463. package/src/shade/playground/animation_prototype_skinning.js +647 -647
  1464. package/src/shade/playground/animation_prototype_stress.js +765 -765
  1465. package/src/shade/playground/enable_picker.js +59 -59
  1466. package/src/shade/playground/gltf_skinned_prototype.js +560 -560
  1467. package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
  1468. package/src/shade/playground/gltf_viewer.js +282 -282
  1469. package/src/shade/playground/load_default_environment.js +19 -19
  1470. package/src/shade/playground/main.js +2727 -2727
  1471. package/src/shade/playground/make_animated_instances.js +117 -117
  1472. package/src/shade/playground/prototype_virtual_texture.js +274 -274
  1473. package/src/shade/playground/prototype_volumetrics_01.js +206 -206
  1474. package/src/shade/playground/sample.js +234 -234
  1475. package/src/shade/playground/sample_load_gltf.js +102 -102
  1476. package/src/shade/playground/spread_node3d_grid.js +19 -19
  1477. package/src/shade/renderer/Renderer.js +2436 -2436
  1478. package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
  1479. package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
  1480. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
  1481. package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
  1482. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
  1483. package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
  1484. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
  1485. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
  1486. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
  1487. package/src/shade/renderer/binding/BindGroupManager.js +282 -282
  1488. package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
  1489. package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
  1490. package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
  1491. package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
  1492. package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
  1493. package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
  1494. package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
  1495. package/src/shade/renderer/camera/Camera.js +281 -281
  1496. package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
  1497. package/src/shade/renderer/camera/orbital/README.md +84 -84
  1498. package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
  1499. package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
  1500. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
  1501. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
  1502. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
  1503. package/src/shade/renderer/extension/FrameContext.js +263 -263
  1504. package/src/shade/renderer/extension/FramePhase.js +104 -104
  1505. package/src/shade/renderer/extension/FrameRecord.js +126 -126
  1506. package/src/shade/renderer/extension/GBufferTextures.js +83 -83
  1507. package/src/shade/renderer/extension/PresentTarget.js +34 -34
  1508. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
  1509. package/src/shade/renderer/extension/RenderExtension.js +69 -69
  1510. package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
  1511. package/src/shade/renderer/extension/SceneColor.js +40 -40
  1512. package/src/shade/renderer/extension/ViewTextures.js +78 -78
  1513. package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
  1514. package/src/shade/renderer/geometry/Geometry.js +608 -608
  1515. package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
  1516. package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
  1517. package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
  1518. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
  1519. package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
  1520. package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
  1521. package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
  1522. package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
  1523. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
  1524. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
  1525. package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
  1526. package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
  1527. package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
  1528. package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
  1529. package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
  1530. package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
  1531. package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
  1532. package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
  1533. package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
  1534. package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
  1535. package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
  1536. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
  1537. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
  1538. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
  1539. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
  1540. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
  1541. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
  1542. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
  1543. package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
  1544. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
  1545. package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
  1546. package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
  1547. package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
  1548. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
  1549. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
  1550. package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
  1551. package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
  1552. package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
  1553. package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
  1554. package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
  1555. package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
  1556. package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
  1557. package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
  1558. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
  1559. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
  1560. package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
  1561. package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
  1562. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
  1563. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
  1564. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
  1565. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
  1566. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
  1567. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
  1568. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
  1569. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
  1570. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
  1571. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
  1572. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
  1573. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
  1574. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
  1575. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
  1576. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
  1577. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
  1578. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
  1579. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
  1580. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
  1581. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
  1582. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
  1583. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
  1584. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
  1585. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
  1586. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
  1587. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
  1588. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
  1589. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
  1590. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
  1591. package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
  1592. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
  1593. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
  1594. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
  1595. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
  1596. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
  1597. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
  1598. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
  1599. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
  1600. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
  1601. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
  1602. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
  1603. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
  1604. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
  1605. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
  1606. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
  1607. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
  1608. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
  1609. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
  1610. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
  1611. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
  1612. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
  1613. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
  1614. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
  1615. package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
  1616. package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
  1617. package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
  1618. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
  1619. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
  1620. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
  1621. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
  1622. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
  1623. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
  1624. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
  1625. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
  1626. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
  1627. package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
  1628. package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
  1629. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
  1630. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
  1631. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
  1632. package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
  1633. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
  1634. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
  1635. package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
  1636. package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
  1637. package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
  1638. package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
  1639. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
  1640. package/src/shade/renderer/light/make_sunlight.js +35 -35
  1641. package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
  1642. package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
  1643. package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
  1644. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
  1645. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
  1646. package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
  1647. package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
  1648. package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
  1649. package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
  1650. package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
  1651. package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
  1652. package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
  1653. package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
  1654. package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
  1655. package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
  1656. package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
  1657. package/src/shade/renderer/loader/usd/UsdError.js +33 -33
  1658. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
  1659. package/src/shade/renderer/loader/usd/load_usd.js +226 -226
  1660. package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
  1661. package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
  1662. package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
  1663. package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
  1664. package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
  1665. package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
  1666. package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
  1667. package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
  1668. package/src/shade/renderer/material/ShadeMaterial.js +2 -2
  1669. package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
  1670. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  1671. package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
  1672. package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
  1673. package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
  1674. package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
  1675. package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
  1676. package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
  1677. package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
  1678. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
  1679. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
  1680. package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
  1681. package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
  1682. package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
  1683. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
  1684. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
  1685. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
  1686. package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
  1687. package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
  1688. package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
  1689. package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
  1690. package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
  1691. package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
  1692. package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
  1693. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
  1694. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
  1695. package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
  1696. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
  1697. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
  1698. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
  1699. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
  1700. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
  1701. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
  1702. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
  1703. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
  1704. package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
  1705. package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
  1706. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
  1707. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
  1708. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
  1709. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
  1710. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
  1711. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
  1712. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
  1713. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
  1714. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
  1715. package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
  1716. package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
  1717. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
  1718. package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
  1719. package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
  1720. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
  1721. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
  1722. package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
  1723. package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
  1724. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
  1725. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
  1726. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
  1727. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
  1728. package/src/shade/renderer/scene/DynamicMesh.js +70 -70
  1729. package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
  1730. package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
  1731. package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
  1732. package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
  1733. package/src/shade/renderer/scene/TransformAuthority.js +33 -33
  1734. package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
  1735. package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
  1736. package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
  1737. package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
  1738. package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
  1739. package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
  1740. package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
  1741. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
  1742. package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
  1743. package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
  1744. package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
  1745. package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
  1746. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
  1747. package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
  1748. package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
  1749. package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
  1750. package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
  1751. package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
  1752. package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
  1753. package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
  1754. package/src/shade/renderer/texture/virtual/README.md +276 -276
  1755. package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
  1756. package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
  1757. package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
  1758. package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
  1759. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
  1760. package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
  1761. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
  1762. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
  1763. package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
  1764. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
  1765. package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
  1766. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
  1767. package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
  1768. package/src/shade/renderer/water/shader_water.js +246 -246
  1769. package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
  1770. package/src/shade/vitest.setup.mjs +89 -89
  1771. package/src/shade/wgsl/validate_wgsl_source.js +316 -316
  1772. package/src/view/controller/controls/NumericIntervalControl.js +90 -90
  1773. package/src/view/controller/controls/Vector2Control.js +83 -83
  1774. package/src/view/controller/controls/Vector3Control.js +90 -90
  1775. package/src/view/elements/Group.js +56 -56
  1776. package/src/view/elements/drawCheckers.js +33 -33
  1777. package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
  1778. package/src/view/graphics/WebGLContextFailureView.js +76 -76
  1779. package/src/view/layout/DockPanel.js +51 -51
  1780. package/src/view/layout/SplitView.js +365 -365
  1781. package/src/view/layout/show_popup_menu.js +81 -81
  1782. package/src/view/minimap/MinimapCanvasView.js +411 -411
  1783. package/src/view/minimap/MinimapImageCache.js +88 -88
  1784. package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
  1785. package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
  1786. package/src/view/minimap/MinimapMarkerCollection.js +180 -180
  1787. package/src/view/minimap/dom/MinimapCameraView.js +206 -206
  1788. package/src/view/minimap/minimap_focus_area.js +97 -97
  1789. package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
  1790. package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
  1791. package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
  1792. package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
  1793. package/src/engine/ecs/attachment/Attachment.js +0 -57
  1794. package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
  1795. package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
  1796. package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
  1797. package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
  1798. package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
  1799. package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
  1800. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
  1801. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
  1802. package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
  1803. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
  1804. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
  1805. package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
  1806. package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
  1807. package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
  1808. package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
  1809. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
  1810. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
  1811. package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
  1812. package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
  1813. package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
  1814. package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
  1815. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
  1816. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
  1817. package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
  1818. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
  1819. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
  1820. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
  1821. package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
  1822. package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
  1823. package/src/shade/renderer/animation/_dump_resolve.mjs +0 -5
@@ -1,69 +1,69 @@
1
- # Domain E — engine/physics
2
-
3
- Scope: `engine/physics/**` (+ `engine/ecs/ik/*` where the task named it). Every finding below was verified by opening the cited files. `*.spec.js`, `prototype*.js`, `test*.js` excluded.
4
-
5
- The mechanical brief was thin and its three "A. token-identical" hits turned out to be the *least* interesting things in the domain (two are explicitly, correctly justified). The real duplication here is semantic re-derivation, and it clusters in four places: (1) the fluid solver's collocated-vs-MAC twin kernel families, (2) the narrowphase contact-emission epilogues, (3) the two solvers (contacts / constraints) which independently implement the same impulse application, (4) core primitives that return *parameters* where every single caller wants *points*.
6
-
7
- ## Findings
8
-
9
- | # | Severity | Concept | Sites | Evidence | Verdict | Proposed common path | Effort/Risk |
10
- |---|---|---|---|---|---|---|---|
11
- | 1 | HIGH | **Solver impulse application written twice, verbatim.** `Δv = P·invM; Δω = Iw⁻¹·(r×P)` with the identical `invM===0` guard, identical sign folding, identical `world_inverse_inertia_apply_raw` argument order and identical AV write-back. | `engine/physics/constraint/solve_constraints.js:250` `apply_impulse(ss, base, invM, rx,ry,rz, Px,Py,Pz, sign)` ⟷ `engine/physics/solver/solve_contacts.js:401` `apply_impulse_to_body(ss, base, invM, rx,ry,rz, Px,Py,Pz, sign, scratch_inertia)` | Bodies are line-for-line identical (15 statements each). The *only* difference is that the contacts version takes `scratch_inertia` as an 11th parameter where the constraints version closes over a module-static one. Both call the same kernel with the same operand order. | UNJUSTIFIED | Promote one copy to `engine/physics/solver/apply_impulse_to_body.js` (next to `friction_cone.js`) with the explicit `scratch` parameter; both solvers import it. Keep the parameter form — it is already the more explicit of the two. | Low effort. **Determinism: LOW risk** — identical operand order means bit-identical output; `determinism.spec.js` is the gate. This is the single most load-bearing routine in the step (per-row × per-iteration), so re-measure the rain/KEVA benches: a cross-module call here must stay inlined. |
12
- | 2 | HIGH | **SAT minimum-overlap magnitude computed three ways, one of which diverges.** Same invariant ("how far along this axis must I push to separate?"), three independent expressions. | `engine/physics/narrowphase/box_box_manifold.js:135` `overlap = (rA + rB) - dist`; `engine/physics/narrowphase/box_triangle_contact.js:209-211` `overlap = min(bmax - tmin, tmax - bmin)`; `engine/physics/narrowphase/convex_convex_manifold.js:261` `overlap = Math.min(maxA,maxB) - Math.max(minA,minB)` | The first two are the minimum-translation (push-out) distance and agree. The third is the *interval intersection length*, which is smaller whenever one projection interval contains the other (A=[0,10], B=[4,5] → push-out 5, intersection 1). SAT then picks the axis by minimum overlap, so an understated value makes a containment axis *more* likely to win and be reported as the depth. `convex_convex_manifold` is documented as being for "the SMALL convex pieces of a decomposed mesh" — exactly where a thin piece is contained in a fat one. The three sites also carry three different degeneracy tests (`PARALLEL_EPS_SQR` absolute in two files, `PARALLEL_EPS_REL * ref2` relative in the third) and only two of three have the `TIE_REL/TIE_ABS` deadband. | UNJUSTIFIED (and needs a correctness decision before unification) | Extract `sat_track_min_overlap(state, ux,uy,uz, minA,maxA, minB,maxB, kind, sign_ref)` covering: normalise-and-reject, separation test, push-out overlap, deadband tie-break, centre-delta sign orientation. Each caller keeps its own *projection* (box half-extent vs 3 vertices vs vertex loop) — that part is legitimately shape-specific. | Medium effort. **Determinism: HIGH risk** and behaviour-changing: fixing `convex_convex`'s formula changes chosen axes on containment configs. Do it test-first against `convex_convex_manifold`'s own suite, then the determinism gate. |
13
- | 3 | HIGH | **Whole collocated advection family shadows the MAC family and has zero callers.** `FluidSimulator` uses only the `v3_mac_*` kernels (imports at `engine/physics/fluid/FluidSimulator.js:6-11`). | Zero non-spec importers: `v3_grid_advect_sl_velocity.js`, `v3_grid_apply_advection_forward.js`, `v3_grid_apply_scalar_advection.js`, `v3_grid_advect_maccormack_scalar.js`, `v3_grid_advect_maccormack_velocity.js` (≈555 LOC) | Verified by importer count across `engine/` and `app/` (spec files excluded). Each is a collocated-grid re-implementation of an algorithm the MAC file next to it also implements (SL trace, MacCormack forward/backward/correct, Selle limiter, solid pass-through). A fix in the MAC limiter or the solid handling silently misses five files. | UNJUSTIFIED (as duplication) — but **NOT dead code**: meep is a library, these are public surface. | Do not delete. Collapse the *internal* redundancy first (findings 4 and 5), then leave the collocated family as thin, correct, exported kernels. Add a one-line "the simulator uses the MAC family; this is the collocated variant" note to each so the next reviewer does not re-derive this. | Low effort for 4/5; the family itself stays. Determinism: none (unreferenced by the sim). |
14
- | 4 | MEDIUM | **MacCormack pass 1 re-implements the SL kernel that sits in the same directory.** The MAC file does this correctly (calls the SL kernel); the grid file does not. | `engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js:66-82` (pass 1) ⟷ `v3_grid_apply_scalar_advection.js:38-58`; `v3_grid_advect_maccormack_velocity.js:73-94` (pass 1) ⟷ `v3_grid_advect_sl_velocity.js:69-96` | Both pairs are statement-for-statement identical: same solid pass-through, same `s = x - vel[c]*dt`, same sampler call. Contrast `v3_mac_advect_scalar.js:146`, which *does* call `v3_mac_advect_sl_scalar(fwd, ...)` for its forward pass — proving the delegation is viable and this is an oversight, not a design choice. | UNJUSTIFIED | Replace each pass-1 loop with the call to its SL sibling, exactly as the MAC scalar file already does. | Low effort. **Determinism: LOW** — identical arithmetic and identical loop order; the only change is a function boundary. |
15
- | 5 | MEDIUM | **`v3_grid_apply_advection_forward` is a slower copy of `v3_grid_advect_sl_velocity`, and says so in its own docstring.** | `engine/physics/fluid/solver/v3_grid_apply_advection_forward.js:32` ⟷ `v3_grid_advect_sl_velocity.js:38` | The docstring at `v3_grid_apply_advection_forward.js:16` states: it "is exactly `v3_grid_advect_sl_velocity(outputs, inputs, inputs...)`". The body differs only by issuing three separate `scs3d_sample_linear` calls instead of one fused `scs3d_sample_linear3` — i.e. it recomputes the trilinear corner indices and weights three times per cell. MinHash flagged the pair at 0.813. | UNJUSTIFIED | Keep the export (public surface, meaningful name); make the body a one-line delegation to `v3_grid_advect_sl_velocity`. | Low effort. **Determinism: MEDIUM** — the fused 3-channel sampler may associate weights differently from three scalar samples. Diff numerically before/after; `quality.spec.js` is the fluid gate. |
16
- | 6 | MEDIUM | **Selle monotone limiter written four times, twice under the same name `limit8` with different signatures in the same directory.** | `engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js:155` `limit8(value, data, c0..c7)`; `v3_mac_advect_maccormack_velocity.js:293` `limit8(value, data, lx,ly,lz, dim_x,dim_y,dim_z, row, slice)`; hand-inlined at `v3_grid_advect_maccormack_scalar.js:107-132`; hand-inlined **with solid masking** at `v3_mac_advect_scalar.js:191-232` | The two named `limit8`s are different functions with the same name two files apart — a genuine reader hazard, and invisible to the mechanical pass because neither is exported (brief §G is empty). The grid-scalar inline is a *byte-level* match for the MAC-velocity `limit8` signature (`limit8(corrected, source, x-vx, y-vy, z-vz, res_x,res_y,res_z, res_x, slice_size)` reproduces it exactly). The MAC-scalar inline is a genuine superset (skips solid corners, `Infinity` init, fallback to the forward value). | UNJUSTIFIED for 3 of 4 | Extract two files: `v3_limit8_lattice.js` (position-form, absorbs the MAC-velocity version, the grid-scalar inline and — via a precomputed-index overload or by the caller passing the position — the grid-velocity version) and `v3_limit8_lattice_masked.js` for the solid-aware variant. Do **not** merge masked and unmasked behind a flag (uniform-control-flow rule). | Low-medium effort. **Determinism: LOW** for the unmasked merge (identical comparison order). These are per-cell kernels over the whole grid, so verify the extracted call stays inlined — the bodies are branchy but loop-free, which is the inlinable shape. |
17
- | 7 | MEDIUM | **Vorticity confinement: the MAC kernel collocates to cell centres and then re-derives the collocated kernel's two passes verbatim.** | `engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js:78-130` (pass 1 curl+\|ω\|, pass 2 N×ω) ⟷ `v3_mac_apply_vorticity_confinement.js:98-164` (pass 2, pass 3) | The MAC file's pass 1 (lines 70-90) averages faces into `scratch_cu/cv/cw` cell-centred arrays; its passes 2 and 3 are then character-identical to the grid file's passes 1 and 2 — same central-difference curl expressions, same `scratch_wmag` fill, same `magnitude_floor = 1e-10`, same `s = epsilon_dt / g_len`, same `N × ω` cross. Only the final deposit differs (cell write vs `0.5 *` scatter to two faces per axis). ≈60 lines. | UNJUSTIFIED | Extract `v3_grid_compute_vorticity(scratch_wx,wy,wz,wmag, vel_x,vel_y,vel_z, res, solid)` and `v3_grid_compute_confinement_force(out3, scratch_*, c, res_x, cell_slice, epsilon_dt)`; the MAC kernel calls both and keeps only its collocate-in / scatter-out ends. | Medium effort. **Determinism: LOW** (same expressions, same order). Per-cell whole-grid kernel — measure; the force helper writing 3 floats to a module-static may cost vs the current fully-inlined form. |
18
- | 8 | MEDIUM | **Two-bone and one-bone IK solvers share a ~75-line terrain-probe + influence prologue verbatim.** | `engine/ecs/ik/TwoBoneInverseKinematicsSolver.js:76-151` (`solve`) ⟷ `engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js:74-151` (`solve`) | Identical: the ray direction from parent→bone, the primary `terrain.raycastFirstSync`, the early return, the secondary cast back along the contact normal from the bone + normal, the second early return, `constraint.offset * <scale>` → `v3_displace_in_direction`, the `delta_contact_c_*` triple, `v3_dot` side test, `v3_length`, the `dot_contact_side < 0 ? 1 : 1 - clamp01(constraint.distance.normalizeValue(inverseLerp(0, <scale>, d)))` influence, and the `influence <= 0` bail. Only two things differ and both are plain inputs: the ray origin (parent bone world position vs root bone A world position) and the length scale (`boneWorldScale.length() * 1/√3` vs limb length A→B→C). The mechanical brief missed this entirely because its path filter was `^engine/physics/`. | UNJUSTIFIED | Extract `ik_probe_surface_influence(contact_out, terrain, origin_xyz, direction_xyz, effector_xyz, length_scale, constraint) -> number` returning influence (0 = no contact / no influence) and leaving the contact in the supplied `SurfacePoint3`. Both solvers keep only their own post-influence rotation math, which is genuinely different. | Low-medium effort. No determinism exposure (animation-side, not the physics step). Note: the task hypothesis "two competing two-bone IK solvers" is **false** — `TwoBoneInverseKinematicsSolver` *imports* `two_joint_ik` (line 11). The real duplication is this sibling prologue. |
19
- | 9 | MEDIUM | **Closest-point-pair → contact epilogue written five times.** Given two witness points and two radii, produce (unit normal B→A, depth, witness on A, witness on B) with a degenerate-distance tie-break. | `engine/physics/narrowphase/sphere_sphere_contact.js:27-51`; `sphere_triangle_contact.js:99-141`; `capsule_contacts.js:89-116` (`capsule_sphere_contact`); `capsule_contacts.js:158-197` (`capsule_capsule_contact`); `capsule_contacts.js:293-324` (`capsule_capsule_multi_contacts` inner loop) | All five: `d = pa - pb`, `dist_sqr`, `sum = ra + rb`, `if (dist_sqr >= sum*sum) return false`, `dist = sqrt`, `if (dist > 0) normalize else <tie-break>`, `out[3] = sum - dist`, `out[4..6] = pa - n*ra`, `out[7..9] = pb + n*rb`. The tie-break direction is the only real variation: `(1,0,0)` at two sites, segment-centre delta at one, body-centre delta at two — i.e. it is an *input*, not a mode. | UNJUSTIFIED | `narrowphase/emit_pair_contact.js`: `emit_pair_contact(out, pax,pay,paz, pbx,pby,pbz, ra, rb, fbx,fby,fbz) -> boolean`, fallback direction always supplied by the caller (no null-sentinel). `sphere_sphere_contact` writes only lanes 0-3 today and would need to keep that shorter contract or gain witness lanes — check its callers first. | Low-medium effort. Per-contact hot path but not per-cell; a leaf function of this size is a normal V8 inline candidate. **Determinism: LOW** (identical order), but the tie-break unification changes `capsule_sphere_contact`'s `(1,0,0)` if you "improve" it — don't; pass `(1,0,0)` explicitly. |
20
- | 10 | MEDIUM | **Cap-endpoint contact append + spatial dedup written twice, with the same magic constant.** | `engine/physics/narrowphase/capsule_contacts.js:582-619` (endpoint loop inside `capsule_box_multi_contacts`) ⟷ `capsule_triangle_contact.js:360-395` (`try_endpoint_contact`) | Both: run a sphere-vs-X query at a cap centre, unpack the 10-float `(n, depth, A, B)` scratch, loop `k < count` comparing the capsule-side world position against every already-emitted contact, reject below `DEDUPE_DIST_SQR`, then write the `(A, B, n, depth)` stride-10 record. `DEDUPE_DIST_SQR = 1e-6` is declared independently in both files (`capsule_contacts.js:510`, `capsule_triangle_contact.js:64`) with near-identical justifying comments, and both files independently declare a stride-10 constant (`CAPSULE_BOX_CONTACT_STRIDE`, `CAPSULE_TRIANGLE_CONTACT_STRIDE`). | UNJUSTIFIED | `narrowphase/append_deduped_contact.js`: `append_deduped_contact(out, count, scratch10) -> number`, with `DEDUPE_DIST_SQR` and the stride-10 candidate layout defined once (the layout is already shared by three files — put it next to `decomposition/triangle_buffer_layout.js`). | Low effort. **Determinism: LOW.** Real payoff is the single definition of the tolerance: today a tuning change to one leaves the other. |
21
- | 11 | MEDIUM | **Reference-face manifold emission epilogue written three times.** Project each clipped point, reject non-penetrating, push `(x,y,z,depth)` candidates, `reduce_manifold_contacts`, then per kept point project onto the reference plane and swap A/B by `ref_is_A`. | `engine/physics/narrowphase/convex_convex_manifold.js:596-622` (`emit_face_manifold`); `box_box_manifold.js:578-670`; `box_triangle_contact.js:455-497` (`emit_box_face_manifold`) | All three build a stride-4 (or 7) candidate array, call `reduce_manifold_contacts(cand, n, stride, MAX_CONTACTS, depth_lane)`, then run the identical "ref-side witness = point − ref_out · dist_to_plane" projection and the identical `if (ref_is_A) {A=proj,B=pt} else {A=pt,B=proj}` swap into `4 + k*CONTACT_STRIDE`. A previous review already lifted the *reduction* out of these three (see the "Each previously carried its own byte-identical copy" note at `reduce_manifold_contacts.js:14-18`) — this is the un-lifted residue of the same block. | UNJUSTIFIED | Finish the extraction the previous review started: `emit_reference_face_manifold(out, cand, n, stride, ref_is_A, r0x,r0y,r0z, rmx,rmy,rmz)` doing reduce + project + swap + write. The *clipping* stays per-shape. | Medium effort. **Determinism: LOW** if the projection expression is copied verbatim. The three sites use different candidate strides (4 and 7), so the stride stays a parameter — `reduce_manifold_contacts` already set that precedent. |
22
- | 12 | MEDIUM | **A general 2D half-plane Sutherland–Hodgman clipper exists in core, unused, while narrowphase ships its own.** | `core/geom/2d/polygon/polygon2_clip_halfplane.js:20` (exported, spec'd, **zero consumers**) ⟷ `engine/physics/narrowphase/box_triangle_contact.js:110` `clip_against_half_plane_uv` | Same algorithm: walk edges, evaluate a signed side function at both endpoints, emit the start point if inside, emit the crossing at `t = sa/(sa-sb)`. The core version parameterises the line by point+direction and tests `cross(dir, P-e)`; the physics version by point+normal and tests `(P-e)·n` — algebraically the same half-plane with `n = perp(dir)`. Note `box_box_manifold.js:3` and `box_triangle_contact.js:3` *already* import the sibling `polygon2_clip_axis_halfplane` from core, so the boundary crossing is established practice. | UNJUSTIFIED | `box_triangle_contact` imports `polygon2_clip_halfplane` and converts its normal to a direction (or add a normal-form export next to it). | Low effort. **Determinism: MEDIUM** — the core version carries `POINT_EPS = 1e-9` inside/outside slack; the physics version uses exact `<= 0`. That changes the surviving vertex count on boundary-coincident points, hence the manifold. Diff on the box-triangle suite before adopting. |
23
- | 13 | MEDIUM | **Effector face-lattice splat scaffolding written six times (3 axes × 2 effectors).** | `engine/physics/fluid/effector/ImpulseFluidEffector.js:105-132, 134-161, 163-191` ⟷ `WakeFluidEffector.js:249-273, 275-298, 300-~330` | Each block: derive the per-lattice index range as `max(0, floor(box_min [+0.5 on own axis]))` … `min(res_axis [−1 off-axis], ceil(box_max [+0.5]))`, triple loop, compute the lattice offset (`sx*res_y` / `res_x*(res_y+1)` / `res_x*res_y`), evaluate a falloff weight at the face position (offset −0.5 on the own axis), `continue` on zero weight, `continue` on `pin[f] !== 0`, accumulate `v[f] += impulse_axis * w`. The only per-block variation is the weight function — and `WakeFluidEffector` already proves this can be a callback (`tube_weight` closure at line 230). | UNJUSTIFIED | `fluid/effector/mac_splat_axis.js`: `mac_splat_axis(velocity, pin, axis, res_x,res_y,res_z, box6, deposit, weight_fn)`. The two effectors then differ only in their weight function and box, which is the actual content. | Medium effort. **Determinism: LOW.** Perf: this is a *bounded* splat (influence radius), not a whole-grid kernel, and the Wake path already pays a closure call per face — so the reuse-over-micro-opt rule applies cleanly here. |
24
- | 14 | PROMOTION | **`tri3_closest_point_barycentric` returns barycentrics; all 12 callers immediately reconstruct the world point.** The missing primitive is `tri3_closest_point`. | Physics: `sphere_triangle_contact.js:89-96`; `capsule_triangle_contact.js:186-191`, `:210-215`, `:234-239`; `compute_penetration.js:331-335`. Core/graphics (context): `core/geom/3d/shape/Triangle3D.js:169-175` and `:212-…`; `core/geom/3d/topology/struct/binary/query/bt_mesh_find_nearest_face.js:131-137`; `core/geom/3d/shape/MeshShape3D.js:439`, `:491`; `core/geom/3d/shape/ConvexHullShape3D.js:254`, `:285`; `engine/graphics/geometry/buffered/query/bvh32_geometry_nearest.js:203`; `.../query_bvh_geometry_nearest.js:164` | Every site writes the same four lines: `const c = 1 - a - b; qx = a*ax + b*bx + c*cx; qy = …; qz = …`. Several then compute the same `d = p - q; d_sqr` follow-up. This is the block the brief flagged as shared between `Triangle3D.js:162`, `capsule_triangle_contact.js:181`, `bt_mesh_find_nearest_face.js` and the two BVH nearest queries. | PROMOTION | Add `core/geom/3d/triangle/tri3_closest_point.js` — `tri3_closest_point(out, out_offset, px,py,pz, ax..cz)` writing the world point (wrapping the barycentric version) — and `tri3_point_distance_sqr(...)` for the distance-only callers. Keep the barycentric export (public surface; a few callers genuinely want the weights). | Low effort per site, 12 sites. **Determinism: LOW** if the reconstruction expression is copied verbatim (`a*ax + b*bx + c*cx` in that order — `bt_mesh_find_nearest_face.js:135` writes `ax*wa + bx*wb + cx*wc`, which is the same order of operands; check each before converting). |
25
- | 15 | PROMOTION | **`line3_compute_segment_segment_closest_points` returns `(s, t)`; all four physics callers immediately lerp both segments.** | `engine/physics/narrowphase/capsule_contacts.js:142-156`; `capsule_triangle_contact.js:105-117` (`test_edge`); `convex_convex_manifold.js:501-516` (`emit_edge_pair_contact`); `box_triangle_contact.js:874-885` (`emit_edge_cross_contact`) | All four: call the core function into a length-2 scratch, then six `p = a0 + (a1 - a0) * s` lines. This is the bulk of the brief's 39 `inline_lerp` hits in narrowphase. `core/geom/3d/line/` already contains a differently-named `line3_closest_points_segment_segment.js` — worth checking whether *it* is the point-returning variant that was never adopted. | PROMOTION | Add `line3_segment_segment_closest_point_pair(out6, a0x..a1z, b0x..b1z)` writing both world points (and returning the squared distance, which three of the four sites compute next anyway). | Low effort. **Determinism: LOW** — the lerp form `a + (b-a)*t` is used identically at all four sites; preserve it exactly (do not "improve" to `(1-t)*a + t*b`). |
26
- | 16 | LOW/MEDIUM | **Two hash-combine conventions coexist inside `engine/physics`, producing different values for the same inputs.** | Hand-rolled `h = (h*31 + x) | 0`, seeded with the first field: `engine/physics/ecs/Collider.js:157`, `RigidBody.js:279`, `Joint.js:442`. Canonical `combine_hash(...)`: `engine/physics/fluid/effector/GlobalFluidEffector.js:182`, `ImpulseFluidEffector.js:228`. A third variant `h = ((h<<5) - h) + v; h \|= 0` seeded with `n`: `engine/physics/fluid/ecs/FluidEffectorsComponent.js:77` | `core/collection/array/combine_hash.js` → `computeIntegerArrayHash` → `computeStridedIntegerArrayHash` is the canonical fold: `hash = length`, then `hash = ((hash<<5) - hash) + (v >>> 0)`, returning **uint32**. `(h<<5)-h` and `h*31` are the same multiply; the divergences are the seed (`length` vs first field) and the output signedness (`>>> 0` vs `\| 0`). Three conventions in one package. | UNJUSTIFIED as a *convention* split; the hand-rolled fold itself is JUSTIFIED (`combine_hash(...args)` allocates a rest-array per call — `RigidBody.hash()` folds 12 fields and runs during dirty-checking) | Do **not** route the ECS components through `combine_hash`. Instead add a non-allocating `hash_combine_step(h, v)`-style primitive next to `computeStridedIntegerArrayHash` and have all three physics families use it, so the seed and signedness are decided once. Then `FluidEffectorsComponent` and the two effectors stop being a third and fourth answer. | Low effort. No determinism exposure (hashes are change-detection, not simulation state) — but do check nothing persists these values across versions before changing seed/signedness. |
27
- | 17 | LOW/MEDIUM | **`solve_constraints` re-derives `angular_effective_mass_raw`, which was extracted for exactly this purpose.** | `engine/physics/constraint/solve_constraints.js:191-204` `axis_effective_mass` ⟷ `engine/physics/inertia/world_inverse_inertia.js:102` `angular_effective_mass_raw` | The core of `axis_effective_mass` is `cx,cy,cz = r × d; world_inverse_inertia_apply_raw(scratch, …, cx,cy,cz); k += cx*s[0] + cy*s[1] + cz*s[2]` — character-for-character the body of `angular_effective_mass_raw`. That kernel's own docstring (lines 80-84) says it is "The shared kernel behind the contact/joint solver's SoA path and the raycast-vehicle's component path — each previously inlined the cross product + apply_raw + dot themselves." `solve_contacts.js:526` and `RaycastVehicle.js:54` adopted it; `solve_constraints.js` was missed. | UNJUSTIFIED (incomplete prior extraction) | `axis_effective_mass` becomes `invM === 0 ? 0 : invM + angular_effective_mass_raw(iix,iiy,iiz, qx,qy,qz,qw, rx,ry,rz, dx,dy,dz, scratch_inertia)`, keeping the existing zero-inertia short-circuit. | Low effort. **Determinism: LOW** — identical operand order by construction. Note `angular_axis_effective_mass` at `solve_constraints.js:146` is genuinely different (`a·Iw⁻¹·a`, no lever arm) and should be left alone. |
28
- | 18 | LOW/MEDIUM | **EPA→MPR fallback + MTV sign-validation + normalise written three times.** | `engine/physics/narrowphase/narrowphase_step.js:1128-1138` and `:1245-1255` (concave-triangle path and general convex fallback, same file) ⟷ `engine/physics/queries/shape_cast.js:394-408` (MPR-only variant) | The two `narrowphase_step` copies are identical: `gjk_epa_penetration` → `pen_depth > 0 && Number.isFinite(pen_depth)` → scale unit axis by depth, else `mpr()` → `depth = Math.sqrt(ex*ex+ey*ey+ez*ez)` → `!(depth > 0) \|\| !Number.isFinite(depth)` bail. All three then dot the MTV against a centre-delta reference, flip on negative, and emit `-e * (1/depth)` as the B→A normal (`shape_cast.js:401-407` even says "same pattern as `narrowphase_step`'s EPA path"). | UNJUSTIFIED for the two same-file copies | `narrowphase/resolve_penetration_mtv.js`: `resolve_penetration_mtv(out4, posed_a, posed_b, ref_x, ref_y, ref_z) -> boolean` writing unit normal + depth. `shape_cast` keeps its MPR-only path (it deliberately skips EPA — documented at `shape_cast.js:361-366` — so folding it in would need a mode flag; leave it). | Low effort for the two `narrowphase_step` copies. **Determinism: LOW.** Do not unify the third site — that would need the sentinel-parameter shape the house rules forbid. |
29
- | 19 | LOW | **The `(p + 0.5) \| 0` + clamp + solid-lookup convention is written six times.** | `engine/physics/fluid/solver/v3_mac_clip_trace.js:83` `point_in_solid`; `v3_mac_advect_scalar.js:23` `end_in_solid`; `v3_mac_advect_maccormack_velocity.js:23` `end_in_solid`; inline ×3 at `v3_mac_advect_sl_velocity.js:112-118`, `:155-161`, `:200-206` | The two `end_in_solid` copies are byte-identical (brief §A, 120 tokens ×3 with `point_in_solid`). | **JUSTIFIED — with one exception.** `v3_mac_clip_trace.js:25-29` carries an explicit, measured performance contract: the loop in `v3_mac_clip_trace` blocks V8 inlining and "a per-face call costs several times the entire surrounding advection body (measured 8.6× on the SL kernel)", so hot kernels must inline the fluid test. The three inline copies in `v3_mac_advect_sl_velocity` are unambiguously correct. **But** the two `end_in_solid` *functions* are already function calls — the perf argument that justifies inlining does not justify a private copy of a loop-free leaf. | Export `point_in_solid` from `v3_mac_clip_trace.js` and have the two advect kernels import it instead of redeclaring it; leave the three genuine inline expansions alone. | Low effort, **but measure first** — that is the whole point of the contract comment. If the imported leaf does not inline as well as the module-local one, revert and instead cross-reference the copies in a comment. Determinism: none (integer indexing). |
30
- | 20 | LOW | **Unnormalised triangle face normal (`(B−A) × (C−A)`) inline-derived at five physics sites.** | `engine/physics/narrowphase/sphere_triangle_contact.js:73-78`; `capsule_triangle_contact.js:162-166` and `:320-325`; `refine_ray_concave.js:142-146`; `compute_penetration.js:263-271` | Same three cross-product lines each time, three of the five followed by the same `mag = sqrt(...)`, degeneracy bail, `1/mag` normalise. Context: `core/geom/3d/triangle/` already offers **three** names for this — `compute_triangle_normal.js`, `tri3_compute_normal.js`, `v3_compute_triangle_normal.js` — which is likely why nobody reaches for one. | UNJUSTIFIED, but low value | Pick the canonical core name (a domain-A decision) and use it. Worth doing only as part of that consolidation, not on its own. | Low effort. **Determinism: LOW** if operand order matches. Per-triangle in a concave dispatch loop — inlining is defensible here; this is a readability finding, not a perf one. |
31
- | 21 | LOW | **"Perpendicular axis pair from an axis index" written three times.** | `engine/physics/narrowphase/box_box_manifold.js:417-432`; `box_triangle_contact.js:835-842`; and again in `box_triangle_contact.js`'s `emit_box_face_manifold` (half-extent selection at `:441-442` region) | Identical `if (idx===0){u=1;v=2}else if(idx===1){u=2;v=0}else{u=0;v=1}` plus the identical `half_u = u===0 ? hx : (u===1 ? hy : hz)` ternary chain. | UNJUSTIFIED, low value | Two module-static `Uint8Array` lookup tables (`PERP_U = [1,2,0]`, `PERP_V = [2,0,1]`) in a shared `narrowphase/box_axis_tables.js`, plus a 3-float half-extent read. Typed constants, no string keys. | Trivial effort, **determinism: none** (integer selection only). |
32
- | 22 | LOW | **World→grid direction transform and uniform-scale extraction re-derived across the fluid effectors.** | Direction transform `m[0]*x + m[4]*y + m[8]*z` (etc.): `GlobalFluidEffector.js:99-101` and `:117-119`, `ImpulseFluidEffector.js:75-77`. Uniform scale `sqrt(m[0]²+m[1]²+m[2]²)`: `ImpulseFluidEffector.js:88`, `WakeFluidEffector.js:172` | `core/geom/vec3/` has `v3_matrix4_multiply` (point, with perspective divide) and `v3_matrix4_rotate_unit` (direction, **but it normalises the output**) — neither fits, because the effectors need the *unnormalised* linear part so world units/s² become grid units/s². So the re-derivation is caused by a genuine gap in the core surface. `v3_matrix4_rotate.js` is just a deprecated re-export of `v3_matrix4_rotate_unit`. | PROMOTION | Add `core/geom/vec3/v3_matrix4_transform_direction.js` (linear part, no normalise, no divide) and `m4_uniform_scale.js`. Three and two sites respectively. | Trivial effort. **Determinism: LOW.** Cold code (once per effector per frame), so this is a pure clarity/gap-closing win. |
33
-
34
- ## Dismissed leads
35
-
36
- - **"Two competing two-bone IK solvers"** (`engine/ecs/ik/TwoBoneInverseKinematicsSolver.js` vs `engine/physics/inverse_kinematics/two_joint_ik.js`) — not duplicates. The former *imports and calls* the latter at `TwoBoneInverseKinematicsSolver.js:11,157`; it is the ECS/terrain wrapper, `two_joint_ik` is the pure analytic core. Correctly factored. The real finding in that pair of files is #8.
37
- - **FABRIK vs `two_joint_ik`** — `engine/physics/inverse_kinematics/fabrik/fabrik3d_solve_primitive.js` is an iterative N-joint positional solver; `two_joint_ik` is an analytic 2-joint rotational solver on a different data model (Vector3/Quaternion objects vs flat Float32Array). No shared body. FABRIK has no internal callers, but meep is a library.
38
- - **`GlobalFluidEffector.equals`/`hash` vs `ImpulseFluidEffector.equals`/`hash`** (brief §A, 118 and 71 tokens) — same *shape*, different *fields* (`drag`/`force`/`wind` vs `radius`/`position`/`force`). Unifying needs a field-list registry, i.e. exactly the string-keyed metaprogramming the house rules forbid. Correctly written out per class.
39
- - **`v3_grid_patch_edges_uniform` vs `v3_grid_patch_edges_constant`** (brief §C, 0.75) — genuinely different operations (replicate the adjacent valid slab vs write a constant), and the constant version exploits `fill` contiguity the replication version cannot. Shared surface is a ~10-line dispatch preamble whose `|shift| >= res` branches *differ* (return vs full fill). Not worth unifying.
40
- - **`v3_grid_compute_divergence` vs `v3_mac_compute_divergence`** — different discretisations (central differences with one-sided boundary handling on collocated cells vs exact face differences on the staggered lattice). Different algorithms, not a copy. Same for the two `subtract_pressure_gradient` kernels.
41
- - **`Collider.setFlag/clearFlag/getFlag/writeFlag` vs `RigidBody.*`** (brief §E) — the same four one-line bodies appear in **32** engine classes. A codebase-wide idiom over a `this.flags` int, no shared state, one line each; a base class would impose an inheritance constraint on unrelated component hierarchies. Not a physics finding.
42
- - **`reduce_manifold_contacts`** — already extracted by a previous review; `reduce_manifold_contacts.js:14-18` documents that box_box, convex_convex, box_triangle and the dispatcher "each previously carried its own byte-identical copy". `box_triangle_contact.js:146 reduce_contacts` is a 1-line binding, not a copy. Do not re-flag.
43
- - **`mls-mpm` vs `fluid`** — `MLS_MPM.js` is 2D with quadratic B-spline P2G/G2P weights; the fluid stack is 3D trilinear on a MAC lattice. No shared indexing or weight code. It is also plainly a prototype (`Math.random()` at `:388-389`, per-step array allocation in `advance`, a `testMLS_MPM.js` harness beside it). No reuse opportunity.
44
- - **"Second broadphase / AABB tree inside physics"** — none. `engine/physics/broadphase/generate_pairs.js:1` queries `core/bvh2`, and `compute_fat_world_aabb.js:1` uses `core/geom/3d/aabb/aabb3_transform_oriented`. Clean.
45
- - **`solve_contacts.js:458 apply_impulse_to_pseudo` vs `:401 apply_impulse_to_body`** — same math, but the destination is a different buffer with a different stride and split lin/ang lanes. Unifying needs `(dst, lin_offset, ang_offset)` threading through the hottest routine in the solver for one caller's benefit. Borderline; folded into finding #1 only if that extraction happens anyway.
46
- - **`SBS_AV_X/SBS_INV_I_X` block shared by `solve_constraints.js` and `solve_contacts.js`** (brief §E) — that is an import list of shared lane constants from `body/SolverBodyState.js`. Correct reuse, not duplication.
47
- - **`convex_convex_manifold.js:243` vs `box_box_manifold.js:117` vs `box_triangle_contact.js:182` — the *projection* halves of `test_axis`** — legitimately shape-specific (symmetric half-extent projection vs three explicit vertex dots vs a vertex-buffer min/max loop). Only the tracking half is duplicated; that is finding #2.
48
- - **The `g_bb_*` / `g_bt_*` / `g_ct_*` module-static accumulator idiom** appearing in three narrowphase files — each carries a comment explaining it replaced a per-call closure allocation ("D3"). Same *technique*, different state. Correct as-is.
49
-
50
- ## Notes
51
-
52
- **Why duplication accumulates here, structurally:**
53
-
54
- 1. **Twin kernel families.** The fluid solver carries a full collocated (`v3_grid_*`) and a full staggered-MAC (`v3_mac_*`) implementation of advection. The MAC family superseded the collocated one (memory: "MAC staggered, exact projection; MacCormack velocity default") but the collocated family stayed as library surface — so every algorithm in it (SL trace, MacCormack, Selle limiter, solid pass-through) now exists twice, and the two copies have already begun to drift (findings 4, 5, 6, 7). This is the largest single source of duplication in the domain by line count.
55
-
56
- 2. **Core primitives that return parameters, not points.** `tri3_closest_point_barycentric` returns `(α, β)` and `line3_compute_segment_segment_closest_points` returns `(s, t)`. Sixteen call sites across physics, core and graphics then write the same reconstruction arithmetic. This is the highest-leverage class of finding (#14, #15) because the fix is additive — two new core functions, no behaviour change, and the existing exports stay.
57
-
58
- 3. **Partial prior extractions.** Three separate extractions were done and each missed one site: `reduce_manifold_contacts` left the surrounding emit epilogue behind (#11), `angular_effective_mass_raw` was adopted by `solve_contacts` and `RaycastVehicle` but not `solve_constraints` (#17), and `polygon2_clip_halfplane` was written in core but never consumed (#12). Grepping for "each previously carried its own copy"-style comments is a reliable way to find the residue.
59
-
60
- 4. **Axis triplication.** The MAC kernels and the effectors write x/y/z as three near-identical blocks with different strides. In the *kernels* (`v3_mac_advect_sl_velocity.js`, `v3_mac_advect_maccormack_velocity.js`) this is legitimate — see below. In the *effectors* it is not (#13), because those loops are radius-bounded, not grid-bounded.
61
-
62
- **Where inlining is legitimately correct (do not "fix" these):**
63
-
64
- - `v3_mac_clip_trace.js:25-29` is an explicit measured performance contract (8.6× on the SL kernel) and the three inline solid tests in `v3_mac_advect_sl_velocity.js` honour it. These run per *face* over the whole lattice — millions of evaluations per step. Finding #19 targets only the two redundant *function declarations*, and even that is gated on measurement.
65
- - The x/y/z block triplication inside `v3_mac_advect_sl_velocity.js` and `v3_mac_advect_maccormack_velocity.js`: each block has different index strides (`sx*res_y`, `res_x*sy`, `cell_slice`) and different loop bounds (`<= res_x` on the own axis). Parameterising would put stride variables in the innermost loop of a whole-grid kernel. Correct as written; I did not flag it.
66
- - `capsule_contacts.js` / `capsule_triangle_contact.js` / the manifold builders are **per-contact**, not per-cell — a few thousand calls per frame, not millions. Extracting 20-line leaf helpers there (#9, #10, #11) is squarely on the reuse side of the line, and the codebase already does exactly this (`reduce_manifold_contacts`, `angular_effective_mass_raw`).
67
- - `solve_contacts` / `solve_constraints` inner rows are per-contact-row × per-iteration — the hottest code in the engine. Finding #1 is still worth doing because it is a *verbatim* duplicate of the most correctness-critical routine present, but it must be benchmarked (rain and KEVA-mini) and must preserve operand order exactly for `determinism.spec.js`.
68
-
69
- **Determinism summary:** findings 2, 5 and 12 can change simulation output and need before/after numerical diffs. Findings 1, 4, 6, 7, 9, 10, 11, 14, 15, 17, 18 are order-preserving by construction *provided* the extracted body is copied verbatim rather than rewritten — that is the whole risk. Findings 16, 20, 21, 22 have no simulation-state exposure.
1
+ # Domain E — engine/physics
2
+
3
+ Scope: `engine/physics/**` (+ `engine/ecs/ik/*` where the task named it). Every finding below was verified by opening the cited files. `*.spec.js`, `prototype*.js`, `test*.js` excluded.
4
+
5
+ The mechanical brief was thin and its three "A. token-identical" hits turned out to be the *least* interesting things in the domain (two are explicitly, correctly justified). The real duplication here is semantic re-derivation, and it clusters in four places: (1) the fluid solver's collocated-vs-MAC twin kernel families, (2) the narrowphase contact-emission epilogues, (3) the two solvers (contacts / constraints) which independently implement the same impulse application, (4) core primitives that return *parameters* where every single caller wants *points*.
6
+
7
+ ## Findings
8
+
9
+ | # | Severity | Concept | Sites | Evidence | Verdict | Proposed common path | Effort/Risk |
10
+ |---|---|---|---|---|---|---|---|
11
+ | 1 | HIGH | **Solver impulse application written twice, verbatim.** `Δv = P·invM; Δω = Iw⁻¹·(r×P)` with the identical `invM===0` guard, identical sign folding, identical `world_inverse_inertia_apply_raw` argument order and identical AV write-back. | `engine/physics/constraint/solve_constraints.js:250` `apply_impulse(ss, base, invM, rx,ry,rz, Px,Py,Pz, sign)` ⟷ `engine/physics/solver/solve_contacts.js:401` `apply_impulse_to_body(ss, base, invM, rx,ry,rz, Px,Py,Pz, sign, scratch_inertia)` | Bodies are line-for-line identical (15 statements each). The *only* difference is that the contacts version takes `scratch_inertia` as an 11th parameter where the constraints version closes over a module-static one. Both call the same kernel with the same operand order. | UNJUSTIFIED | Promote one copy to `engine/physics/solver/apply_impulse_to_body.js` (next to `friction_cone.js`) with the explicit `scratch` parameter; both solvers import it. Keep the parameter form — it is already the more explicit of the two. | Low effort. **Determinism: LOW risk** — identical operand order means bit-identical output; `determinism.spec.js` is the gate. This is the single most load-bearing routine in the step (per-row × per-iteration), so re-measure the rain/KEVA benches: a cross-module call here must stay inlined. |
12
+ | 2 | HIGH | **SAT minimum-overlap magnitude computed three ways, one of which diverges.** Same invariant ("how far along this axis must I push to separate?"), three independent expressions. | `engine/physics/narrowphase/box_box_manifold.js:135` `overlap = (rA + rB) - dist`; `engine/physics/narrowphase/box_triangle_contact.js:209-211` `overlap = min(bmax - tmin, tmax - bmin)`; `engine/physics/narrowphase/convex_convex_manifold.js:261` `overlap = Math.min(maxA,maxB) - Math.max(minA,minB)` | The first two are the minimum-translation (push-out) distance and agree. The third is the *interval intersection length*, which is smaller whenever one projection interval contains the other (A=[0,10], B=[4,5] → push-out 5, intersection 1). SAT then picks the axis by minimum overlap, so an understated value makes a containment axis *more* likely to win and be reported as the depth. `convex_convex_manifold` is documented as being for "the SMALL convex pieces of a decomposed mesh" — exactly where a thin piece is contained in a fat one. The three sites also carry three different degeneracy tests (`PARALLEL_EPS_SQR` absolute in two files, `PARALLEL_EPS_REL * ref2` relative in the third) and only two of three have the `TIE_REL/TIE_ABS` deadband. | UNJUSTIFIED (and needs a correctness decision before unification) | Extract `sat_track_min_overlap(state, ux,uy,uz, minA,maxA, minB,maxB, kind, sign_ref)` covering: normalise-and-reject, separation test, push-out overlap, deadband tie-break, centre-delta sign orientation. Each caller keeps its own *projection* (box half-extent vs 3 vertices vs vertex loop) — that part is legitimately shape-specific. | Medium effort. **Determinism: HIGH risk** and behaviour-changing: fixing `convex_convex`'s formula changes chosen axes on containment configs. Do it test-first against `convex_convex_manifold`'s own suite, then the determinism gate. |
13
+ | 3 | HIGH | **Whole collocated advection family shadows the MAC family and has zero callers.** `FluidSimulator` uses only the `v3_mac_*` kernels (imports at `engine/physics/fluid/FluidSimulator.js:6-11`). | Zero non-spec importers: `v3_grid_advect_sl_velocity.js`, `v3_grid_apply_advection_forward.js`, `v3_grid_apply_scalar_advection.js`, `v3_grid_advect_maccormack_scalar.js`, `v3_grid_advect_maccormack_velocity.js` (≈555 LOC) | Verified by importer count across `engine/` and `app/` (spec files excluded). Each is a collocated-grid re-implementation of an algorithm the MAC file next to it also implements (SL trace, MacCormack forward/backward/correct, Selle limiter, solid pass-through). A fix in the MAC limiter or the solid handling silently misses five files. | UNJUSTIFIED (as duplication) — but **NOT dead code**: meep is a library, these are public surface. | Do not delete. Collapse the *internal* redundancy first (findings 4 and 5), then leave the collocated family as thin, correct, exported kernels. Add a one-line "the simulator uses the MAC family; this is the collocated variant" note to each so the next reviewer does not re-derive this. | Low effort for 4/5; the family itself stays. Determinism: none (unreferenced by the sim). |
14
+ | 4 | MEDIUM | **MacCormack pass 1 re-implements the SL kernel that sits in the same directory.** The MAC file does this correctly (calls the SL kernel); the grid file does not. | `engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js:66-82` (pass 1) ⟷ `v3_grid_apply_scalar_advection.js:38-58`; `v3_grid_advect_maccormack_velocity.js:73-94` (pass 1) ⟷ `v3_grid_advect_sl_velocity.js:69-96` | Both pairs are statement-for-statement identical: same solid pass-through, same `s = x - vel[c]*dt`, same sampler call. Contrast `v3_mac_advect_scalar.js:146`, which *does* call `v3_mac_advect_sl_scalar(fwd, ...)` for its forward pass — proving the delegation is viable and this is an oversight, not a design choice. | UNJUSTIFIED | Replace each pass-1 loop with the call to its SL sibling, exactly as the MAC scalar file already does. | Low effort. **Determinism: LOW** — identical arithmetic and identical loop order; the only change is a function boundary. |
15
+ | 5 | MEDIUM | **`v3_grid_apply_advection_forward` is a slower copy of `v3_grid_advect_sl_velocity`, and says so in its own docstring.** | `engine/physics/fluid/solver/v3_grid_apply_advection_forward.js:32` ⟷ `v3_grid_advect_sl_velocity.js:38` | The docstring at `v3_grid_apply_advection_forward.js:16` states: it "is exactly `v3_grid_advect_sl_velocity(outputs, inputs, inputs...)`". The body differs only by issuing three separate `scs3d_sample_linear` calls instead of one fused `scs3d_sample_linear3` — i.e. it recomputes the trilinear corner indices and weights three times per cell. MinHash flagged the pair at 0.813. | UNJUSTIFIED | Keep the export (public surface, meaningful name); make the body a one-line delegation to `v3_grid_advect_sl_velocity`. | Low effort. **Determinism: MEDIUM** — the fused 3-channel sampler may associate weights differently from three scalar samples. Diff numerically before/after; `quality.spec.js` is the fluid gate. |
16
+ | 6 | MEDIUM | **Selle monotone limiter written four times, twice under the same name `limit8` with different signatures in the same directory.** | `engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js:155` `limit8(value, data, c0..c7)`; `v3_mac_advect_maccormack_velocity.js:293` `limit8(value, data, lx,ly,lz, dim_x,dim_y,dim_z, row, slice)`; hand-inlined at `v3_grid_advect_maccormack_scalar.js:107-132`; hand-inlined **with solid masking** at `v3_mac_advect_scalar.js:191-232` | The two named `limit8`s are different functions with the same name two files apart — a genuine reader hazard, and invisible to the mechanical pass because neither is exported (brief §G is empty). The grid-scalar inline is a *byte-level* match for the MAC-velocity `limit8` signature (`limit8(corrected, source, x-vx, y-vy, z-vz, res_x,res_y,res_z, res_x, slice_size)` reproduces it exactly). The MAC-scalar inline is a genuine superset (skips solid corners, `Infinity` init, fallback to the forward value). | UNJUSTIFIED for 3 of 4 | Extract two files: `v3_limit8_lattice.js` (position-form, absorbs the MAC-velocity version, the grid-scalar inline and — via a precomputed-index overload or by the caller passing the position — the grid-velocity version) and `v3_limit8_lattice_masked.js` for the solid-aware variant. Do **not** merge masked and unmasked behind a flag (uniform-control-flow rule). | Low-medium effort. **Determinism: LOW** for the unmasked merge (identical comparison order). These are per-cell kernels over the whole grid, so verify the extracted call stays inlined — the bodies are branchy but loop-free, which is the inlinable shape. |
17
+ | 7 | MEDIUM | **Vorticity confinement: the MAC kernel collocates to cell centres and then re-derives the collocated kernel's two passes verbatim.** | `engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js:78-130` (pass 1 curl+\|ω\|, pass 2 N×ω) ⟷ `v3_mac_apply_vorticity_confinement.js:98-164` (pass 2, pass 3) | The MAC file's pass 1 (lines 70-90) averages faces into `scratch_cu/cv/cw` cell-centred arrays; its passes 2 and 3 are then character-identical to the grid file's passes 1 and 2 — same central-difference curl expressions, same `scratch_wmag` fill, same `magnitude_floor = 1e-10`, same `s = epsilon_dt / g_len`, same `N × ω` cross. Only the final deposit differs (cell write vs `0.5 *` scatter to two faces per axis). ≈60 lines. | UNJUSTIFIED | Extract `v3_grid_compute_vorticity(scratch_wx,wy,wz,wmag, vel_x,vel_y,vel_z, res, solid)` and `v3_grid_compute_confinement_force(out3, scratch_*, c, res_x, cell_slice, epsilon_dt)`; the MAC kernel calls both and keeps only its collocate-in / scatter-out ends. | Medium effort. **Determinism: LOW** (same expressions, same order). Per-cell whole-grid kernel — measure; the force helper writing 3 floats to a module-static may cost vs the current fully-inlined form. |
18
+ | 8 | MEDIUM | **Two-bone and one-bone IK solvers share a ~75-line terrain-probe + influence prologue verbatim.** | `engine/ecs/ik/TwoBoneInverseKinematicsSolver.js:76-151` (`solve`) ⟷ `engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js:74-151` (`solve`) | Identical: the ray direction from parent→bone, the primary `terrain.raycastFirstSync`, the early return, the secondary cast back along the contact normal from the bone + normal, the second early return, `constraint.offset * <scale>` → `v3_displace_in_direction`, the `delta_contact_c_*` triple, `v3_dot` side test, `v3_length`, the `dot_contact_side < 0 ? 1 : 1 - clamp01(constraint.distance.normalizeValue(inverseLerp(0, <scale>, d)))` influence, and the `influence <= 0` bail. Only two things differ and both are plain inputs: the ray origin (parent bone world position vs root bone A world position) and the length scale (`boneWorldScale.length() * 1/√3` vs limb length A→B→C). The mechanical brief missed this entirely because its path filter was `^engine/physics/`. | UNJUSTIFIED | Extract `ik_probe_surface_influence(contact_out, terrain, origin_xyz, direction_xyz, effector_xyz, length_scale, constraint) -> number` returning influence (0 = no contact / no influence) and leaving the contact in the supplied `SurfacePoint3`. Both solvers keep only their own post-influence rotation math, which is genuinely different. | Low-medium effort. No determinism exposure (animation-side, not the physics step). Note: the task hypothesis "two competing two-bone IK solvers" is **false** — `TwoBoneInverseKinematicsSolver` *imports* `two_joint_ik` (line 11). The real duplication is this sibling prologue. |
19
+ | 9 | MEDIUM | **Closest-point-pair → contact epilogue written five times.** Given two witness points and two radii, produce (unit normal B→A, depth, witness on A, witness on B) with a degenerate-distance tie-break. | `engine/physics/narrowphase/sphere_sphere_contact.js:27-51`; `sphere_triangle_contact.js:99-141`; `capsule_contacts.js:89-116` (`capsule_sphere_contact`); `capsule_contacts.js:158-197` (`capsule_capsule_contact`); `capsule_contacts.js:293-324` (`capsule_capsule_multi_contacts` inner loop) | All five: `d = pa - pb`, `dist_sqr`, `sum = ra + rb`, `if (dist_sqr >= sum*sum) return false`, `dist = sqrt`, `if (dist > 0) normalize else <tie-break>`, `out[3] = sum - dist`, `out[4..6] = pa - n*ra`, `out[7..9] = pb + n*rb`. The tie-break direction is the only real variation: `(1,0,0)` at two sites, segment-centre delta at one, body-centre delta at two — i.e. it is an *input*, not a mode. | UNJUSTIFIED | `narrowphase/emit_pair_contact.js`: `emit_pair_contact(out, pax,pay,paz, pbx,pby,pbz, ra, rb, fbx,fby,fbz) -> boolean`, fallback direction always supplied by the caller (no null-sentinel). `sphere_sphere_contact` writes only lanes 0-3 today and would need to keep that shorter contract or gain witness lanes — check its callers first. | Low-medium effort. Per-contact hot path but not per-cell; a leaf function of this size is a normal V8 inline candidate. **Determinism: LOW** (identical order), but the tie-break unification changes `capsule_sphere_contact`'s `(1,0,0)` if you "improve" it — don't; pass `(1,0,0)` explicitly. |
20
+ | 10 | MEDIUM | **Cap-endpoint contact append + spatial dedup written twice, with the same magic constant.** | `engine/physics/narrowphase/capsule_contacts.js:582-619` (endpoint loop inside `capsule_box_multi_contacts`) ⟷ `capsule_triangle_contact.js:360-395` (`try_endpoint_contact`) | Both: run a sphere-vs-X query at a cap centre, unpack the 10-float `(n, depth, A, B)` scratch, loop `k < count` comparing the capsule-side world position against every already-emitted contact, reject below `DEDUPE_DIST_SQR`, then write the `(A, B, n, depth)` stride-10 record. `DEDUPE_DIST_SQR = 1e-6` is declared independently in both files (`capsule_contacts.js:510`, `capsule_triangle_contact.js:64`) with near-identical justifying comments, and both files independently declare a stride-10 constant (`CAPSULE_BOX_CONTACT_STRIDE`, `CAPSULE_TRIANGLE_CONTACT_STRIDE`). | UNJUSTIFIED | `narrowphase/append_deduped_contact.js`: `append_deduped_contact(out, count, scratch10) -> number`, with `DEDUPE_DIST_SQR` and the stride-10 candidate layout defined once (the layout is already shared by three files — put it next to `decomposition/triangle_buffer_layout.js`). | Low effort. **Determinism: LOW.** Real payoff is the single definition of the tolerance: today a tuning change to one leaves the other. |
21
+ | 11 | MEDIUM | **Reference-face manifold emission epilogue written three times.** Project each clipped point, reject non-penetrating, push `(x,y,z,depth)` candidates, `reduce_manifold_contacts`, then per kept point project onto the reference plane and swap A/B by `ref_is_A`. | `engine/physics/narrowphase/convex_convex_manifold.js:596-622` (`emit_face_manifold`); `box_box_manifold.js:578-670`; `box_triangle_contact.js:455-497` (`emit_box_face_manifold`) | All three build a stride-4 (or 7) candidate array, call `reduce_manifold_contacts(cand, n, stride, MAX_CONTACTS, depth_lane)`, then run the identical "ref-side witness = point − ref_out · dist_to_plane" projection and the identical `if (ref_is_A) {A=proj,B=pt} else {A=pt,B=proj}` swap into `4 + k*CONTACT_STRIDE`. A previous review already lifted the *reduction* out of these three (see the "Each previously carried its own byte-identical copy" note at `reduce_manifold_contacts.js:14-18`) — this is the un-lifted residue of the same block. | UNJUSTIFIED | Finish the extraction the previous review started: `emit_reference_face_manifold(out, cand, n, stride, ref_is_A, r0x,r0y,r0z, rmx,rmy,rmz)` doing reduce + project + swap + write. The *clipping* stays per-shape. | Medium effort. **Determinism: LOW** if the projection expression is copied verbatim. The three sites use different candidate strides (4 and 7), so the stride stays a parameter — `reduce_manifold_contacts` already set that precedent. |
22
+ | 12 | MEDIUM | **A general 2D half-plane Sutherland–Hodgman clipper exists in core, unused, while narrowphase ships its own.** | `core/geom/2d/polygon/polygon2_clip_halfplane.js:20` (exported, spec'd, **zero consumers**) ⟷ `engine/physics/narrowphase/box_triangle_contact.js:110` `clip_against_half_plane_uv` | Same algorithm: walk edges, evaluate a signed side function at both endpoints, emit the start point if inside, emit the crossing at `t = sa/(sa-sb)`. The core version parameterises the line by point+direction and tests `cross(dir, P-e)`; the physics version by point+normal and tests `(P-e)·n` — algebraically the same half-plane with `n = perp(dir)`. Note `box_box_manifold.js:3` and `box_triangle_contact.js:3` *already* import the sibling `polygon2_clip_axis_halfplane` from core, so the boundary crossing is established practice. | UNJUSTIFIED | `box_triangle_contact` imports `polygon2_clip_halfplane` and converts its normal to a direction (or add a normal-form export next to it). | Low effort. **Determinism: MEDIUM** — the core version carries `POINT_EPS = 1e-9` inside/outside slack; the physics version uses exact `<= 0`. That changes the surviving vertex count on boundary-coincident points, hence the manifold. Diff on the box-triangle suite before adopting. |
23
+ | 13 | MEDIUM | **Effector face-lattice splat scaffolding written six times (3 axes × 2 effectors).** | `engine/physics/fluid/effector/ImpulseFluidEffector.js:105-132, 134-161, 163-191` ⟷ `WakeFluidEffector.js:249-273, 275-298, 300-~330` | Each block: derive the per-lattice index range as `max(0, floor(box_min [+0.5 on own axis]))` … `min(res_axis [−1 off-axis], ceil(box_max [+0.5]))`, triple loop, compute the lattice offset (`sx*res_y` / `res_x*(res_y+1)` / `res_x*res_y`), evaluate a falloff weight at the face position (offset −0.5 on the own axis), `continue` on zero weight, `continue` on `pin[f] !== 0`, accumulate `v[f] += impulse_axis * w`. The only per-block variation is the weight function — and `WakeFluidEffector` already proves this can be a callback (`tube_weight` closure at line 230). | UNJUSTIFIED | `fluid/effector/mac_splat_axis.js`: `mac_splat_axis(velocity, pin, axis, res_x,res_y,res_z, box6, deposit, weight_fn)`. The two effectors then differ only in their weight function and box, which is the actual content. | Medium effort. **Determinism: LOW.** Perf: this is a *bounded* splat (influence radius), not a whole-grid kernel, and the Wake path already pays a closure call per face — so the reuse-over-micro-opt rule applies cleanly here. |
24
+ | 14 | PROMOTION | **`tri3_closest_point_barycentric` returns barycentrics; all 12 callers immediately reconstruct the world point.** The missing primitive is `tri3_closest_point`. | Physics: `sphere_triangle_contact.js:89-96`; `capsule_triangle_contact.js:186-191`, `:210-215`, `:234-239`; `compute_penetration.js:331-335`. Core/graphics (context): `core/geom/3d/shape/Triangle3D.js:169-175` and `:212-…`; `core/geom/3d/topology/struct/binary/query/bt_mesh_find_nearest_face.js:131-137`; `core/geom/3d/shape/MeshShape3D.js:439`, `:491`; `core/geom/3d/shape/ConvexHullShape3D.js:254`, `:285`; `engine/graphics/geometry/buffered/query/bvh32_geometry_nearest.js:203`; `.../query_bvh_geometry_nearest.js:164` | Every site writes the same four lines: `const c = 1 - a - b; qx = a*ax + b*bx + c*cx; qy = …; qz = …`. Several then compute the same `d = p - q; d_sqr` follow-up. This is the block the brief flagged as shared between `Triangle3D.js:162`, `capsule_triangle_contact.js:181`, `bt_mesh_find_nearest_face.js` and the two BVH nearest queries. | PROMOTION | Add `core/geom/3d/triangle/tri3_closest_point.js` — `tri3_closest_point(out, out_offset, px,py,pz, ax..cz)` writing the world point (wrapping the barycentric version) — and `tri3_point_distance_sqr(...)` for the distance-only callers. Keep the barycentric export (public surface; a few callers genuinely want the weights). | Low effort per site, 12 sites. **Determinism: LOW** if the reconstruction expression is copied verbatim (`a*ax + b*bx + c*cx` in that order — `bt_mesh_find_nearest_face.js:135` writes `ax*wa + bx*wb + cx*wc`, which is the same order of operands; check each before converting). |
25
+ | 15 | PROMOTION | **`line3_compute_segment_segment_closest_points` returns `(s, t)`; all four physics callers immediately lerp both segments.** | `engine/physics/narrowphase/capsule_contacts.js:142-156`; `capsule_triangle_contact.js:105-117` (`test_edge`); `convex_convex_manifold.js:501-516` (`emit_edge_pair_contact`); `box_triangle_contact.js:874-885` (`emit_edge_cross_contact`) | All four: call the core function into a length-2 scratch, then six `p = a0 + (a1 - a0) * s` lines. This is the bulk of the brief's 39 `inline_lerp` hits in narrowphase. `core/geom/3d/line/` already contains a differently-named `line3_closest_points_segment_segment.js` — worth checking whether *it* is the point-returning variant that was never adopted. | PROMOTION | Add `line3_segment_segment_closest_point_pair(out6, a0x..a1z, b0x..b1z)` writing both world points (and returning the squared distance, which three of the four sites compute next anyway). | Low effort. **Determinism: LOW** — the lerp form `a + (b-a)*t` is used identically at all four sites; preserve it exactly (do not "improve" to `(1-t)*a + t*b`). |
26
+ | 16 | LOW/MEDIUM | **Two hash-combine conventions coexist inside `engine/physics`, producing different values for the same inputs.** | Hand-rolled `h = (h*31 + x) | 0`, seeded with the first field: `engine/physics/ecs/Collider.js:157`, `RigidBody.js:279`, `Joint.js:442`. Canonical `combine_hash(...)`: `engine/physics/fluid/effector/GlobalFluidEffector.js:182`, `ImpulseFluidEffector.js:228`. A third variant `h = ((h<<5) - h) + v; h \|= 0` seeded with `n`: `engine/physics/fluid/ecs/FluidEffectorsComponent.js:77` | `core/collection/array/combine_hash.js` → `computeIntegerArrayHash` → `computeStridedIntegerArrayHash` is the canonical fold: `hash = length`, then `hash = ((hash<<5) - hash) + (v >>> 0)`, returning **uint32**. `(h<<5)-h` and `h*31` are the same multiply; the divergences are the seed (`length` vs first field) and the output signedness (`>>> 0` vs `\| 0`). Three conventions in one package. | UNJUSTIFIED as a *convention* split; the hand-rolled fold itself is JUSTIFIED (`combine_hash(...args)` allocates a rest-array per call — `RigidBody.hash()` folds 12 fields and runs during dirty-checking) | Do **not** route the ECS components through `combine_hash`. Instead add a non-allocating `hash_combine_step(h, v)`-style primitive next to `computeStridedIntegerArrayHash` and have all three physics families use it, so the seed and signedness are decided once. Then `FluidEffectorsComponent` and the two effectors stop being a third and fourth answer. | Low effort. No determinism exposure (hashes are change-detection, not simulation state) — but do check nothing persists these values across versions before changing seed/signedness. |
27
+ | 17 | LOW/MEDIUM | **`solve_constraints` re-derives `angular_effective_mass_raw`, which was extracted for exactly this purpose.** | `engine/physics/constraint/solve_constraints.js:191-204` `axis_effective_mass` ⟷ `engine/physics/inertia/world_inverse_inertia.js:102` `angular_effective_mass_raw` | The core of `axis_effective_mass` is `cx,cy,cz = r × d; world_inverse_inertia_apply_raw(scratch, …, cx,cy,cz); k += cx*s[0] + cy*s[1] + cz*s[2]` — character-for-character the body of `angular_effective_mass_raw`. That kernel's own docstring (lines 80-84) says it is "The shared kernel behind the contact/joint solver's SoA path and the raycast-vehicle's component path — each previously inlined the cross product + apply_raw + dot themselves." `solve_contacts.js:526` and `RaycastVehicle.js:54` adopted it; `solve_constraints.js` was missed. | UNJUSTIFIED (incomplete prior extraction) | `axis_effective_mass` becomes `invM === 0 ? 0 : invM + angular_effective_mass_raw(iix,iiy,iiz, qx,qy,qz,qw, rx,ry,rz, dx,dy,dz, scratch_inertia)`, keeping the existing zero-inertia short-circuit. | Low effort. **Determinism: LOW** — identical operand order by construction. Note `angular_axis_effective_mass` at `solve_constraints.js:146` is genuinely different (`a·Iw⁻¹·a`, no lever arm) and should be left alone. |
28
+ | 18 | LOW/MEDIUM | **EPA→MPR fallback + MTV sign-validation + normalise written three times.** | `engine/physics/narrowphase/narrowphase_step.js:1128-1138` and `:1245-1255` (concave-triangle path and general convex fallback, same file) ⟷ `engine/physics/queries/shape_cast.js:394-408` (MPR-only variant) | The two `narrowphase_step` copies are identical: `gjk_epa_penetration` → `pen_depth > 0 && Number.isFinite(pen_depth)` → scale unit axis by depth, else `mpr()` → `depth = Math.sqrt(ex*ex+ey*ey+ez*ez)` → `!(depth > 0) \|\| !Number.isFinite(depth)` bail. All three then dot the MTV against a centre-delta reference, flip on negative, and emit `-e * (1/depth)` as the B→A normal (`shape_cast.js:401-407` even says "same pattern as `narrowphase_step`'s EPA path"). | UNJUSTIFIED for the two same-file copies | `narrowphase/resolve_penetration_mtv.js`: `resolve_penetration_mtv(out4, posed_a, posed_b, ref_x, ref_y, ref_z) -> boolean` writing unit normal + depth. `shape_cast` keeps its MPR-only path (it deliberately skips EPA — documented at `shape_cast.js:361-366` — so folding it in would need a mode flag; leave it). | Low effort for the two `narrowphase_step` copies. **Determinism: LOW.** Do not unify the third site — that would need the sentinel-parameter shape the house rules forbid. |
29
+ | 19 | LOW | **The `(p + 0.5) \| 0` + clamp + solid-lookup convention is written six times.** | `engine/physics/fluid/solver/v3_mac_clip_trace.js:83` `point_in_solid`; `v3_mac_advect_scalar.js:23` `end_in_solid`; `v3_mac_advect_maccormack_velocity.js:23` `end_in_solid`; inline ×3 at `v3_mac_advect_sl_velocity.js:112-118`, `:155-161`, `:200-206` | The two `end_in_solid` copies are byte-identical (brief §A, 120 tokens ×3 with `point_in_solid`). | **JUSTIFIED — with one exception.** `v3_mac_clip_trace.js:25-29` carries an explicit, measured performance contract: the loop in `v3_mac_clip_trace` blocks V8 inlining and "a per-face call costs several times the entire surrounding advection body (measured 8.6× on the SL kernel)", so hot kernels must inline the fluid test. The three inline copies in `v3_mac_advect_sl_velocity` are unambiguously correct. **But** the two `end_in_solid` *functions* are already function calls — the perf argument that justifies inlining does not justify a private copy of a loop-free leaf. | Export `point_in_solid` from `v3_mac_clip_trace.js` and have the two advect kernels import it instead of redeclaring it; leave the three genuine inline expansions alone. | Low effort, **but measure first** — that is the whole point of the contract comment. If the imported leaf does not inline as well as the module-local one, revert and instead cross-reference the copies in a comment. Determinism: none (integer indexing). |
30
+ | 20 | LOW | **Unnormalised triangle face normal (`(B−A) × (C−A)`) inline-derived at five physics sites.** | `engine/physics/narrowphase/sphere_triangle_contact.js:73-78`; `capsule_triangle_contact.js:162-166` and `:320-325`; `refine_ray_concave.js:142-146`; `compute_penetration.js:263-271` | Same three cross-product lines each time, three of the five followed by the same `mag = sqrt(...)`, degeneracy bail, `1/mag` normalise. Context: `core/geom/3d/triangle/` already offers **three** names for this — `compute_triangle_normal.js`, `tri3_compute_normal.js`, `v3_compute_triangle_normal.js` — which is likely why nobody reaches for one. | UNJUSTIFIED, but low value | Pick the canonical core name (a domain-A decision) and use it. Worth doing only as part of that consolidation, not on its own. | Low effort. **Determinism: LOW** if operand order matches. Per-triangle in a concave dispatch loop — inlining is defensible here; this is a readability finding, not a perf one. |
31
+ | 21 | LOW | **"Perpendicular axis pair from an axis index" written three times.** | `engine/physics/narrowphase/box_box_manifold.js:417-432`; `box_triangle_contact.js:835-842`; and again in `box_triangle_contact.js`'s `emit_box_face_manifold` (half-extent selection at `:441-442` region) | Identical `if (idx===0){u=1;v=2}else if(idx===1){u=2;v=0}else{u=0;v=1}` plus the identical `half_u = u===0 ? hx : (u===1 ? hy : hz)` ternary chain. | UNJUSTIFIED, low value | Two module-static `Uint8Array` lookup tables (`PERP_U = [1,2,0]`, `PERP_V = [2,0,1]`) in a shared `narrowphase/box_axis_tables.js`, plus a 3-float half-extent read. Typed constants, no string keys. | Trivial effort, **determinism: none** (integer selection only). |
32
+ | 22 | LOW | **World→grid direction transform and uniform-scale extraction re-derived across the fluid effectors.** | Direction transform `m[0]*x + m[4]*y + m[8]*z` (etc.): `GlobalFluidEffector.js:99-101` and `:117-119`, `ImpulseFluidEffector.js:75-77`. Uniform scale `sqrt(m[0]²+m[1]²+m[2]²)`: `ImpulseFluidEffector.js:88`, `WakeFluidEffector.js:172` | `core/geom/vec3/` has `v3_matrix4_multiply` (point, with perspective divide) and `v3_matrix4_rotate_unit` (direction, **but it normalises the output**) — neither fits, because the effectors need the *unnormalised* linear part so world units/s² become grid units/s². So the re-derivation is caused by a genuine gap in the core surface. `v3_matrix4_rotate.js` is just a deprecated re-export of `v3_matrix4_rotate_unit`. | PROMOTION | Add `core/geom/vec3/v3_matrix4_transform_direction.js` (linear part, no normalise, no divide) and `m4_uniform_scale.js`. Three and two sites respectively. | Trivial effort. **Determinism: LOW.** Cold code (once per effector per frame), so this is a pure clarity/gap-closing win. |
33
+
34
+ ## Dismissed leads
35
+
36
+ - **"Two competing two-bone IK solvers"** (`engine/ecs/ik/TwoBoneInverseKinematicsSolver.js` vs `engine/physics/inverse_kinematics/two_joint_ik.js`) — not duplicates. The former *imports and calls* the latter at `TwoBoneInverseKinematicsSolver.js:11,157`; it is the ECS/terrain wrapper, `two_joint_ik` is the pure analytic core. Correctly factored. The real finding in that pair of files is #8.
37
+ - **FABRIK vs `two_joint_ik`** — `engine/physics/inverse_kinematics/fabrik/fabrik3d_solve_primitive.js` is an iterative N-joint positional solver; `two_joint_ik` is an analytic 2-joint rotational solver on a different data model (Vector3/Quaternion objects vs flat Float32Array). No shared body. FABRIK has no internal callers, but meep is a library.
38
+ - **`GlobalFluidEffector.equals`/`hash` vs `ImpulseFluidEffector.equals`/`hash`** (brief §A, 118 and 71 tokens) — same *shape*, different *fields* (`drag`/`force`/`wind` vs `radius`/`position`/`force`). Unifying needs a field-list registry, i.e. exactly the string-keyed metaprogramming the house rules forbid. Correctly written out per class.
39
+ - **`v3_grid_patch_edges_uniform` vs `v3_grid_patch_edges_constant`** (brief §C, 0.75) — genuinely different operations (replicate the adjacent valid slab vs write a constant), and the constant version exploits `fill` contiguity the replication version cannot. Shared surface is a ~10-line dispatch preamble whose `|shift| >= res` branches *differ* (return vs full fill). Not worth unifying.
40
+ - **`v3_grid_compute_divergence` vs `v3_mac_compute_divergence`** — different discretisations (central differences with one-sided boundary handling on collocated cells vs exact face differences on the staggered lattice). Different algorithms, not a copy. Same for the two `subtract_pressure_gradient` kernels.
41
+ - **`Collider.setFlag/clearFlag/getFlag/writeFlag` vs `RigidBody.*`** (brief §E) — the same four one-line bodies appear in **32** engine classes. A codebase-wide idiom over a `this.flags` int, no shared state, one line each; a base class would impose an inheritance constraint on unrelated component hierarchies. Not a physics finding.
42
+ - **`reduce_manifold_contacts`** — already extracted by a previous review; `reduce_manifold_contacts.js:14-18` documents that box_box, convex_convex, box_triangle and the dispatcher "each previously carried its own byte-identical copy". `box_triangle_contact.js:146 reduce_contacts` is a 1-line binding, not a copy. Do not re-flag.
43
+ - **`mls-mpm` vs `fluid`** — `MLS_MPM.js` is 2D with quadratic B-spline P2G/G2P weights; the fluid stack is 3D trilinear on a MAC lattice. No shared indexing or weight code. It is also plainly a prototype (`Math.random()` at `:388-389`, per-step array allocation in `advance`, a `testMLS_MPM.js` harness beside it). No reuse opportunity.
44
+ - **"Second broadphase / AABB tree inside physics"** — none. `engine/physics/broadphase/generate_pairs.js:1` queries `core/bvh2`, and `compute_fat_world_aabb.js:1` uses `core/geom/3d/aabb/aabb3_transform_oriented`. Clean.
45
+ - **`solve_contacts.js:458 apply_impulse_to_pseudo` vs `:401 apply_impulse_to_body`** — same math, but the destination is a different buffer with a different stride and split lin/ang lanes. Unifying needs `(dst, lin_offset, ang_offset)` threading through the hottest routine in the solver for one caller's benefit. Borderline; folded into finding #1 only if that extraction happens anyway.
46
+ - **`SBS_AV_X/SBS_INV_I_X` block shared by `solve_constraints.js` and `solve_contacts.js`** (brief §E) — that is an import list of shared lane constants from `body/SolverBodyState.js`. Correct reuse, not duplication.
47
+ - **`convex_convex_manifold.js:243` vs `box_box_manifold.js:117` vs `box_triangle_contact.js:182` — the *projection* halves of `test_axis`** — legitimately shape-specific (symmetric half-extent projection vs three explicit vertex dots vs a vertex-buffer min/max loop). Only the tracking half is duplicated; that is finding #2.
48
+ - **The `g_bb_*` / `g_bt_*` / `g_ct_*` module-static accumulator idiom** appearing in three narrowphase files — each carries a comment explaining it replaced a per-call closure allocation ("D3"). Same *technique*, different state. Correct as-is.
49
+
50
+ ## Notes
51
+
52
+ **Why duplication accumulates here, structurally:**
53
+
54
+ 1. **Twin kernel families.** The fluid solver carries a full collocated (`v3_grid_*`) and a full staggered-MAC (`v3_mac_*`) implementation of advection. The MAC family superseded the collocated one (memory: "MAC staggered, exact projection; MacCormack velocity default") but the collocated family stayed as library surface — so every algorithm in it (SL trace, MacCormack, Selle limiter, solid pass-through) now exists twice, and the two copies have already begun to drift (findings 4, 5, 6, 7). This is the largest single source of duplication in the domain by line count.
55
+
56
+ 2. **Core primitives that return parameters, not points.** `tri3_closest_point_barycentric` returns `(α, β)` and `line3_compute_segment_segment_closest_points` returns `(s, t)`. Sixteen call sites across physics, core and graphics then write the same reconstruction arithmetic. This is the highest-leverage class of finding (#14, #15) because the fix is additive — two new core functions, no behaviour change, and the existing exports stay.
57
+
58
+ 3. **Partial prior extractions.** Three separate extractions were done and each missed one site: `reduce_manifold_contacts` left the surrounding emit epilogue behind (#11), `angular_effective_mass_raw` was adopted by `solve_contacts` and `RaycastVehicle` but not `solve_constraints` (#17), and `polygon2_clip_halfplane` was written in core but never consumed (#12). Grepping for "each previously carried its own copy"-style comments is a reliable way to find the residue.
59
+
60
+ 4. **Axis triplication.** The MAC kernels and the effectors write x/y/z as three near-identical blocks with different strides. In the *kernels* (`v3_mac_advect_sl_velocity.js`, `v3_mac_advect_maccormack_velocity.js`) this is legitimate — see below. In the *effectors* it is not (#13), because those loops are radius-bounded, not grid-bounded.
61
+
62
+ **Where inlining is legitimately correct (do not "fix" these):**
63
+
64
+ - `v3_mac_clip_trace.js:25-29` is an explicit measured performance contract (8.6× on the SL kernel) and the three inline solid tests in `v3_mac_advect_sl_velocity.js` honour it. These run per *face* over the whole lattice — millions of evaluations per step. Finding #19 targets only the two redundant *function declarations*, and even that is gated on measurement.
65
+ - The x/y/z block triplication inside `v3_mac_advect_sl_velocity.js` and `v3_mac_advect_maccormack_velocity.js`: each block has different index strides (`sx*res_y`, `res_x*sy`, `cell_slice`) and different loop bounds (`<= res_x` on the own axis). Parameterising would put stride variables in the innermost loop of a whole-grid kernel. Correct as written; I did not flag it.
66
+ - `capsule_contacts.js` / `capsule_triangle_contact.js` / the manifold builders are **per-contact**, not per-cell — a few thousand calls per frame, not millions. Extracting 20-line leaf helpers there (#9, #10, #11) is squarely on the reuse side of the line, and the codebase already does exactly this (`reduce_manifold_contacts`, `angular_effective_mass_raw`).
67
+ - `solve_contacts` / `solve_constraints` inner rows are per-contact-row × per-iteration — the hottest code in the engine. Finding #1 is still worth doing because it is a *verbatim* duplicate of the most correctness-critical routine present, but it must be benchmarked (rain and KEVA-mini) and must preserve operand order exactly for `determinism.spec.js`.
68
+
69
+ **Determinism summary:** findings 2, 5 and 12 can change simulation output and need before/after numerical diffs. Findings 1, 4, 6, 7, 9, 10, 11, 14, 15, 17, 18 are order-preserving by construction *provided* the extracted body is copied verbatim rather than rewritten — that is the whole risk. Findings 16, 20, 21, 22 have no simulation-state exposure.
@@ -1,102 +1,102 @@
1
- # Domain F — engine/sound (legacy + sopra + acoustic simulation)
2
-
3
- All paths relative to `H:/git/moh/app/src/mir-engine/meep/src/`. Every finding below was verified by
4
- opening the cited files.
5
-
6
- ## Findings
7
-
8
- | # | Severity | Concept | Sites | Evidence | Verdict | Proposed common path | Effort/Risk |
9
- |---|---|---|---|---|---|---|---|
10
- | 1 | HIGH | "Is this segment clear of occluders?" shadow-ray test written **3×**, each with its own module-static `Ray3` | `engine/sound/simulation/probe/probe_edge_visible.js:20` `probe_edge_visible`; `engine/sound/simulation/probe/probe_refine_connectivity.js:168` `segment_clear`; `engine/sound/simulation/probe/AcousticProbeField.js:328-345` (filter arrow in `nearestVisibleIndex`) | Brief C sim=0.75. All three do: zero-length ⇒ visible; `inv = 1/d`; `setOrigin/setDirection/tMax = d`; `!index.anyHit(RAY)`. `probe_refine_connectivity.js:167` comment literally says *"mirrors probe_edge_visible"*. `probe_edge_visible.js:8-9` claims it is *"the one visibility test shared by the offline bake and the runtime recheck, so they agree exactly"* — a claim the other two sites falsify | UNJUSTIFIED | `engine/sound/simulation/core/segment_clear.js` — `segment_clear(index, ax,ay,az, bx,by,bz)` with ONE scratch ray; `probe_edge_visible` becomes a 4-line probe-index wrapper; `nearestVisibleIndex`'s filter calls it directly | S / low |
11
- | 2 | HIGH | Undirected edge-list → CSR (degree count → prefix sum → fill via write cursor) written twice; plus a hand-rolled sorted-row `isNeighbour` binary search | `engine/sound/simulation/probe/bakeVisibility.js:109-141` (weighted, → `CSRGraph.fromArrays`); `engine/sound/simulation/probe/probe_visibility_augment.js:50-70` (unweighted + per-row `.sort()`), with `isNeighbour` at `:167-182` | Both consume the same `{a,b}` edge arrays produced by stages A–D of the same bake. `core/graph/csr/` has `CSRGraph`, `csr_graph_find_edge`, `csr_graph_build_distance_field`, `csr_graph_find_shortest_path` — but **no builder**, so every caller re-derives one | PROMOTION | `core/graph/csr/csr_graph_from_undirected_edge_list.js(vertex_count, a, b, weights?, {sorted_rows})`; `isNeighbour` → `csr_graph_find_edge !== -1` (give the core one a sorted-row fast path) | M / low |
12
- | 3 | HIGH | Listener resolution + `sopra.update(currentTime)` duplicated in two ECS systems that share ONE `SopraEngine` | `engine/sound/ecs/emitter/SoundEmitterSystem.js:137-158` `update`; `engine/sound/ecs/audio/AudioEmitterSystem.js:246-315` `update` | Both do `ecd.getAnyComponent(SoundListener)` → `getComponent(entity, Transform)` → `sopra.setListener(pos)` → `sopra.update(this.webAudioContext.currentTime)`. The engine is shared (`SoundEngine.obtainSopra`, `SoundEngine.js:82-88`), and BOTH class docs state coexistence is the design (`SoundEngine.js:76-78`, `AudioEmitterSystem.js:35-36`). Co-registering them double-runs `SopraEngine.update` (`sopra/SopraEngine.js:340-351`: snapshot + iterate all active instances + `#updateDuckers`). Harmless-but-wasted today because `EventInstance.#evaluate` guards on `entry.voice === null`; the copy-pasted "missing Transform" tolerance comment has already diverged (AudioEmitterSystem additionally skips `liveSet.refresh`) | UNJUSTIFIED | One owner for the tick: a `SopraTickSystem` (or fold listener-resolve + tick into `SoundListenerSystem`, which already owns the `SoundListener`+`Transform` pair). Emitter systems then only push emitter state | M / medium |
13
- | 4 | HIGH | The container child-hash fold is copy-pasted into all 4 concrete containers instead of living on the base that owns `children` — and has already diverged | `engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js:26-35`; `RandomContainerAudioClip.js:130-144`; `SwitchContainerAudioClip.js:83-96`; `BlendContainerAudioClip.js:126-139` (all `hash()`) | Brief A [70×2] + E. `ContainerAudioClip` owns `children` and DOES implement the parallel `equals()` fold (`ContainerAudioClip.js:80-99`) but has no `hash()`. Divergence already present: `BlendContainerAudioClip.hash()` folds `parameter`+`defaultValue` but **not** `blends`, though `equals()` compares them (`:117-121`) and the adapter serializes them — two blend containers differing only in curves hash identically. `EventDescription` polyphony buckets are keyed by event content, so clip hashes are load-bearing | UNJUSTIFIED | `ContainerAudioClip.hash()` = `super.hash()` folded with each child's `hash()`; each subclass folds only its own fields on top (and Blend adds `blends`) | S / low |
14
- | 5 | MEDIUM | `AnimationCurve` binary keyframe codec hand-written twice | `engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js:24-33` + `:55-64`; `engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js:20-33` + `:52-66` | Both carry the same excuse comment: *"AnimationCurve has no binary adapter of its own"*. Identical wire format (`uintVar` count, then `time/value/inTangent/outTangent` float32s). A payload change (wrap modes, tangent packing) must land in both or one silently mis-reads | PROMOTION | `engine/animation/curve/AnimationCurveSerializationAdapter.js` (or a `serialize_animation_curve` / `deserialize_animation_curve` pair beside `AnimationCurve.js`), registered so `this.objectAdapter` handles it | S / low |
15
- | 6 | MEDIUM | 3-band decaying-noise IR synthesis kernel duplicated | `engine/sound/simulation/render/reverbImpulseResponse.js:26` `reverbImpulseResponse` (kernel `:33-72`); `engine/sound/simulation/render/foaReverbImpulseResponse.js:33` `foaReverbImpulseResponse` (kernel `:41-96`) | Brief E (4-line window). Identical: bin-length derivation from `decayBands × sampleRate`, one-pole coefficients `a = 1 − e^(−2πf/fs)`, the energy-preserving `low/mid/high` split, and the three `Math.pow(1 − i/len, decayPower)` envelopes. Only the write-out differs (omni sum vs direction-weighted B-format). Retuning the crossover or envelope must land in both | UNJUSTIFIED (the shared part is a per-buffer producer, not a per-sample call) | A `write_band_tails(outLow, outMid, outHigh, length, lenLow, lenMid, lenHigh, decayPower, sampleRate, random)` both consume; scalar sums, FOA weights by direction | M / medium (numeric output must stay bit-identical — the specs pin it) |
16
- | 7 | MEDIUM | A **third** decaying-noise IR generator, unseeded | `engine/sound/sopra/definition/effect/ReverbEffect.js:45-66` `build` | Brief F `raw_math_random`: `data[i] = (Math.random() * 2 - 1) * Math.pow(1 - t, this.decayPower)` — the single-band case of finding 6. `reverbImpulseResponse.js:18` documents *"with three equal bands this reduces to the plain scalar tail"*, i.e. it already subsumes this. Divergence with teeth: the acoustic path is explicitly seeded (`ProbeReverbRenderer.js:97`, `FoaProbeReverbRenderer.js:161` → `seededRandom(1)`), so a sopra bus reverb is the only non-reproducible IR in the engine | UNJUSTIFIED | `ReverbEffect.build` → `reverbImpulseResponse(ctx, [d,d,d], this.decayPower, this.#random)` with a seeded RNG field | S / low |
17
- | 8 | MEDIUM | Probe-reverb renderer scaffolding duplicated, with a divergent re-bake trigger | `engine/sound/simulation/render/ProbeReverbRenderer.js:108-136` (ctor) + `:192-230` `setBands`; `engine/sound/simulation/render/FoaProbeReverbRenderer.js:172-236` (ctor) + `:296-333` `#rebake` | Same double-buffered convolver bank, same `#active` flip, same `rampGain(..., FADE_EPSILON, crossfadeSeconds, ctx)` crossfade, same `max(bands) >= minDecay` audibility gate, same `setProbeField`/`input`/`output` surface. But the scalar re-bakes on per-band hysteresis (`changeThreshold`, `:198-208`) while the FOA re-bakes on nearest-probe-index change plus a `#dirty` force-flag (`:123-131`, `:257-260`) that the scalar **lacks** — a `setProbeField` swap between two fields whose bands agree at the listener leaves the scalar renderer's IR stale | UNJUSTIFIED for the crossfade/gate scaffolding (JUSTIFIED for the differing WebAudio graph shape) | Extract an N-channel `ConvolverCrossfadeBank` (build, `swapTo(buffers[])`, `mute()`) both renderers own; give both the same `#dirty` semantics | M / medium |
18
- | 9 | MEDIUM | "Randomly orient a Fibonacci sample set" written twice, bypassing `random_point_on_sphere` | `engine/sound/simulation/core/OcclusionSolver.js:43-51` (in `solve`); `engine/sound/simulation/probe/bakeReverbBands.js:69-74` | Identical four lines: `z = 2·random()−1`, `azimuth = 2π·random()`, `ring = sqrt(max(0, 1−z²))`, then `v3_orthonormal_matrix_from_normal(ROTATION, 0, ring·cos, ring·sin, z)`. `core/geom/random/random_point_on_sphere.js` already produces the uniform direction | UNJUSTIFIED | `core/geom/random/random_orthonormal_basis(out9, out_offset, random)` beside `v3_orthonormal_matrix_from_normal`; at minimum call `random_point_on_sphere` for the direction | S / low |
19
- | 10 | MEDIUM | Canonical undirected edge key `min·n + max` written **4×** (and decoded in a 5th place) | `engine/sound/simulation/probe/ProbeVisibilityRecheck.js:72-75` `#edgeId` (decode at `:112-113`); `engine/sound/simulation/probe/probe_densify_portals.js:141-143` `edgeKey`; `engine/sound/simulation/probe/probe_visibility_bridge_components.js:84-86` (inline `rlo·n + rhi`); `engine/sound/simulation/probe/probe_visibility_augment.js:147` (inline `u·n + v`, correctness relies on the caller's `u<v` canonicalisation at `:96`) | A bug-prone invariant (must use the probe count as the radix AND canonicalise the pair) replicated across four modules of one bake pipeline; changing the radix (e.g. for >65k probes packing) silently breaks the ones you miss | UNJUSTIFIED | `engine/sound/simulation/probe/probe_edge_key.js` — `probe_edge_key(a, b, n)` + `probe_edge_decode(key, n, out2)` | S / low |
20
- | 11 | MEDIUM | `SoundControllerSystem` ≡ `AnimationControllerSystem` — the whole "component.rules → per-entity event-listener registration + teardown" ECS lifecycle | `engine/sound/ecs/SoundControllerSystem.js:49-101` `link` / `:103-113` `unlink`; `engine/graphics/ecs/animation/AnimationControllerSystem.js:18-62` `link` / `:64-72` `unlink` | Brief A [59×2] (`unlink` is token-identical) + E. Both hold `this.removers = []`, build `callbacks = this.removers[entity] = []`, register `{name, handler}` pairs for `rule.startEvent`/`rule.stopEvent` via `dataset.addEntityEventListener`, and tear down identically. Only the two handler bodies differ | UNJUSTIFIED (cross-domain, reported here per instructions) | A typed `EntityEventRuleBinding` value (owns the `{name, handler}` list, `bind(dataset, entity, name, handler)` / `release(dataset, entity)`) that both systems compose — no string-keyed registry involved | S / low |
21
- | 11b | LOW | `randomFromArray` re-derived inline | `engine/sound/ecs/SoundControllerSystem.js:13-24` `pickTackFromRule` | Brief F `raw_math_random`: `Math.floor(Math.random() * tracks.length)`. `core/math/random/randomFromArray.js` exists and is used correctly two directories away in `engine/sound/material/concrete/RandomSoundMaterial.js:17` | UNJUSTIFIED | `randomFromArray(Math.random, rule.tracks)` | XS / none |
22
- | 12 | MEDIUM | Emitter → `sopra.playEvent(...)` option mapping duplicated; drives a 9-positional-argument API | `engine/sound/ecs/audio/AudioEmitterSystem.js:209-233` `#play`; `engine/sound/ecs/audio/LiveEmitterSet.js:281-323` `#promote` (fed by `LiveEmitterSet.add` at `:158-163`, called with 9 positional args at `AudioEmitterSystem.js:139`) | Both derive `oneShot = !event.rootClip.loops()`, build the same `{busId, position, oneShot, acoustic, pathing, sourceRadius, orientation, directivity}` bag, then re-apply `setGainDb`. `LiveEmitterSet`'s record type (`:77`) is a hand-mirrored copy of that bag. Every new per-source acoustic property (`orientation`, `directivity` were the last two) must be threaded through the record, the 9-arg `add` signature, `#promote`, AND `#play` | UNJUSTIFIED | One typed `AudioEmitterPlaySpec` built from `(emitter, transform, acousticSim)`; `LiveEmitterSet.add(entity, spec)` stores it, `#promote` and `#play` both hand it to `playEvent` | M / low |
23
- | 13 | MEDIUM | Three hand-written "dynamic BVH of handle-addressed objects" wrappers inside one domain | `engine/sound/simulation/core/AcousticOccluderIndex.js:40-122`; `engine/sound/simulation/core/AcousticRegionIndex.js:23-134`; `engine/sound/ecs/audio/SpatialAudioIndex.js:24-138` | Identical skeleton in all three: `#bvh = new BVH()`, a parallel `BvhClient` store, retire-a-slot-by-null, a `#fitLeaf(client, x)` that ends in `client.resize(x0…z1)`, a reused `#candidates` gather buffer, and a query that maps returned handles back to user objects. Only the leaf-bounds policy and the refit trigger (reactive `onChanged` / per-frame `refit()` / explicit `setTransform`) differ | UNJUSTIFIED | `core/bvh2/bvh3/BvhObjectIndex.js` — typed container holding `T[]` + clients, with `computeBounds(out, item)` supplied by the owner; the three sound indices become thin policy classes | M / medium |
24
- | 13b | LOW | AABB union re-derived | `engine/sound/simulation/core/AcousticOccluderIndex.js:252-279` `computeWorldBounds` | Hand-written 6-component min/max fold; `core/geom/3d/aabb/aabb3_array_combine.js` does exactly this | UNJUSTIFIED | `aabb3_array_combine` | XS / none |
25
- | 14 | MEDIUM | Trilinear sampling of a Float32Array 3D scalar grid re-implemented | `engine/sound/simulation/probe/probe_sdf_grid.js:104-122` `grid_sample_trilinear` (+ `clampInt` at `:181-183`) | `engine/graphics/texture/3d/scs3d_sample_linear.js:15` is the same function: same `z·(rx·ry) + y·rx + x` layout, same edge clamping, same trilinear blend. `SingleChannelSampler3D` is the matching container. `probe_sdf_grid.js:1` already imports `clamp` yet still hand-rolls `clampInt` | UNJUSTIFIED | Back `SdfGrid` with `SingleChannelSampler3D` and call `scs3d_sample_linear`; delete `clampInt` | S / low |
26
- | 15 | LOW | `{type: ctor.typeName, data: x.toJSON()}` wrapper written 3× with two different `constructor` lookups | `engine/sound/material/concrete/json/serializeSoundMaterialToJSON.js:1-6` (`material.constructor.typeName`); `engine/sound/sopra/serialization/sopraJSON.js:22-27` `serializeAudioClipToJSON` and `:68-73` `serializeAudioEffectToJSON` (`Object.getPrototypeOf(x).constructor.typeName`) | Brief A [70×2], C sim=1. The **switch-based deserializers** (`deserializeSoundMaterialFromJSON.js:12`, `sopraJSON.js:33`, `sopraJSON.js:79`) are **JUSTIFIED** — the alternative in core (`core/json/abstractJSONDeserializer.js`) is precisely the string-keyed `Map<string, fn>` registry the house rules reject, and `sopraJSON.js:11-16` documents deliberate call-time class binding to survive an import cycle. Only the three one-line serializers are gratuitous, and they disagree on how to reach the constructor | UNJUSTIFIED (serializers only) | One `type_tagged_to_json(value)` helper (typed, no registry); pick one constructor lookup | XS / none |
27
- | 16 | LOW | `dB2Volume` re-derived inline in a file that already imports it | `engine/sound/sopra/runtime/EventInstance.js:33-35` `dbTiltToGain` → `Math.pow(10, -excessDb / 20)`; `dB2Volume` imported at `:2` | Called 4× per acoustic instance per frame (`:542-543`, `:561-562`) — not per-sample, so no inlining justification | UNJUSTIFIED | `excessDb <= 0 ? 1 : dB2Volume(-excessDb)` | XS / none |
28
- | 17 | LOW | `BusGraph` repeats the same lookup-or-throw preamble 6× | `engine/sound/sopra/runtime/BusGraph.js:143` `getInput`, `:159` `getOutput`, `:173` `getVolume`, `:187` `setVolume`, `:214` `rampVolume`, `:232` `approachVolume` | Six copies of `const bus = this.buses.get(id); if (bus === undefined) { throw new Error(\`sopra: no bus '${id}'\`); }` — ~30 lines | UNJUSTIFIED | A `#bus(id)` private that does the lookup + throw | XS / none |
29
- | 18 | LOW / PROMOTION | The `setFlag`/`clearFlag`/`writeFlag`/`getFlag` quad, byte-identical in **35 files** | In this domain: `engine/sound/ecs/emitter/SoundEmitter.js:156-189`; `engine/sound/ecs/emitter/SoundTrack.js:138-171`; `engine/sound/asset/SoundAssetPlaybackSpec.js`; `engine/ecs/speaker/Voice.js:13-46` | Brief E (30-file window). `grep -l "clearFlag(flag) {"` returns 35 files spanning core/, engine/ecs, graphics, physics, sound, view | PROMOTION (codebase-wide, not sound-specific — flagged for the cross-domain roll-up, not for a sound-local fix) | A `FlagCarrier` base (or a shared `flags_set/clear/write/read` quartet on the flat `flags` word); JS single inheritance makes the base awkward where a class already extends, so the function quartet is the safer shape | L / medium |
30
- | 19 | LOW | Well-conditioned perpendicular-axis construction re-derived | `engine/sound/simulation/probe/probe_detect_portals.js:170-184` `throatIsAperture` | Hand-rolls "cross with world-up, swap reference if `|ey| > 0.9`, normalize". `core/geom/vec3/v3_orthonormal_matrix_from_normal.js` builds exactly this stable basis and is already used twice in this same package (`OcclusionSolver.js:48`, `bakeReverbBands.js:74`) | UNJUSTIFIED | `v3_orthonormal_matrix_from_normal` and read the tangent column | XS / low |
31
- | 20 | LOW | Vector normalize written by hand, inconsistently with the same file | `engine/sound/simulation/AcousticSimulator.js:598-621` `#directivityGainToward` (`Math.sqrt(dx*dx+dy*dy+dz*dz)` at `:605`) vs `:454, :455, :459, :460` in `#applyPath` (`Math.hypot`) | Brief F `inline_length3`. Cold path (once per non-omni source per frame). `v3_length` / `v3_normalize_array` exist; the file already imports `line3_compute_segment_closest_point_t` from the same family | UNJUSTIFIED (cold) | `v3_length`, or at least pick one of `Math.hypot`/`sqrt` within the file | XS / none |
32
- | 21 | LOW / PROMOTION | Union-find connected components over probe connectivity written twice | `engine/sound/simulation/probe/probe_visibility_bridge_components.js:35-39` (from an `{a,b}` edge list); `engine/sound/simulation/probe/probe_refine_connectivity.js:50-56` (from a `CSRGraph`) | Both then use `uf_find(parent, i) === uf_find(parent, j)` as the same-component predicate. `core/graph/graph_compute_disconnected_clusters.js` exists but only for the object-graph `Graph`, not `CSRGraph` | PROMOTION | `core/graph/csr/csr_graph_connected_components(parent, graph)` beside `csr_graph_build_distance_field`; the edge-list variant folds into finding 2's builder | S / low |
33
-
34
- ## Strangler-migration debt
35
-
36
- Context, not a defect list. Legacy-vs-sopra parallelism is the deliberate migration shape; what matters is
37
- where the seam has stalled and where a fix would have to land twice.
38
-
39
- | Legacy | sopra / native counterpart | Is legacy still wired? | Note |
40
- |---|---|---|---|
41
- | `ecs/emitter/SoundEmitterSystem` + `SoundEmitterComponentContext` | `ecs/audio/AudioEmitterSystem` + `AudioEmitter` + `LiveEmitterSet` | **YES** — `mir-engine/model/game/makeMirEngineConfig.js:182` | The seam is genuinely healthy here: legacy is a *translator* (`sopra/legacy/soundEmitterToEventDescription.js`) onto sopra, not a second renderer. Conversely `AudioEmitter*` has **zero** consumers outside `engine/sound/` — the new path is unused in the game |
42
- | `ecs/SoundControllerSystem` | none — its own JSDoc names a *"forthcoming `AudioEventTrigger`"* (`SoundControllerSystem.js:27-30`) | **YES** — `makeMirEngineConfig.js:183` | `@deprecated` and still registered with no replacement in the tree. Findings 11/11b live here |
43
- | `material/**` (`AbstractSoundMaterialDefinition`, `Single`/`Random`/`Composition`, `json/`) | sopra clips — `SwitchContainerAudioClip.js:11` *"Supersedes the dormant material/ surface-switch concept"*; `BlendContainerAudioClip.js:13` *"Supersedes the dormant material/ composition concept"* | **NO** — grep finds zero references outside `engine/sound/material/` (only a doc mention in `sopraJSON.js:13`) | Dead but public surface (meep is a library). Origin of the JSON copies in finding 15 |
44
- | `SoundEmitterChannels` (`effects`/`music`/`ambient`) | sopra `SopraDefaultBus` + `BusDefinition` | **YES** — 7 app files import it (UnitMaker, StoryManager, WorldEventManager, …) | Already proxied 1:1: `SoundEmitterSystem.get/setChannelVolume` (`:91-102`) forwards to `sopra.get/setBusVolume` |
45
- | `ecs/SoundListenerSystem` (WebAudio `AudioListener` pose) | `sopra.setListener(position)` inside the emitter systems | **YES** — `makeMirEngineConfig.js:184` | THREE readers of the same `SoundListener`→`Transform` relation. sopra's listener is position-only; the WebAudio listener also carries orientation. See finding 3 |
46
- | `ProbeReverbRenderer` (scalar) | `FoaProbeReverbRenderer` (directional/B-format) | scalar is what `configureAcousticSimulation.js:31` documents; FOA is referenced only by `prototypeAcousticSolver.js:515` + its spec | Neither is deprecated; both live. Findings 6/8 mean an IR or crossfade fix must land in both |
47
- | `ecs/emitter/SoundPanningModelType` | `sopra/definition/SopraPanningModel` | **YES** — translated by `soundEmitterToEventDescription.js:57-61` | Identical 2-value enums; translation is one ternary. Fine |
48
- | `SoundAttenuationFunction` + `core/math/physics/irradiance/interpolate_irradiance_*` | authored `AnimationCurve` via `sopra/util/buildAttenuationCurve.js` | **YES** | This is the *good* seam: the legacy falloffs are REUSED to sample the new curve rather than reimplemented. Called out as a model, not debt |
49
-
50
- ## Dismissed leads
51
-
52
- - Brief A/C `makePhysicsGroundResolver` / `recomputeMove` / `bindMoveKey` / `buildInputBindings` / `logHelp` (`engine/control/first-person/prototype_first_person_controller.js` ⟷ `engine/sound/simulation/prototypeAcousticSolver.js`) — both sites are `prototype*.js` harnesses, out of scope. **One line as instructed:** a real shared debug fly/walk camera *with* a physics ground resolver would be worth promoting to `engine/control/`, since it is now copied verbatim for every new subsystem demo.
53
- - `DuckingRuleSerializationAdapter` / `CompressorEffectSerializationAdapter` / `ParameterDefinitionSerializationAdapter` `serialize`+`deserialize` (brief A [50×3] and [45×3]) — read all three: different field names and types (`writeUTF8String`+3×float32+float32 / 5×float32 / string+3×float32+uint8), zero shared logic. Exactly the per-class shape boilerplate the house rules bless.
54
- - `SequenceContainerAudioClipSerializationAdapter.deserialize` ⟷ `SwitchContainerAudioClipSerializationAdapter.deserialize` (brief C sim=0.781) — same verdict; Switch additionally reads `parameter`/`defaultValue`.
55
- - The container adapters' repeated `gain`/`pitch` writes and children loop (brief E) — wire order is per-class protocol; hoisting it into a base couples four independent binary formats. Deliberate.
56
- - `MixerSnapshotSerializationAdapter` bus-entry codec vs `BusDefinitionSerializationAdapter` sends codec — structurally identical (`string` + `float32` pairs), semantically distinct record types. Shape only.
57
- - `engine/ecs/speaker/Voice.js` vs `engine/sound/sopra/runtime/Voice.js` (brief G) — pure name collision. The first is a speech-bubble ECS component (flags only, `Voice.serializable = false`); the second is a WebAudio `AudioBufferSourceNode` holder. No shared logic beyond the flag quad (finding 18). The `speaker/` package has no importers, but meep is a library — not a defect.
58
- - `echogram_band_rt60.js:49` `10 * Math.log10(edc / total)` — **energy** dB, deliberately not `volume2dB`'s amplitude `20·log10`. Correct as written.
59
- - Brief F `inline_lerp` in `probe_sdf_grid.js:118-121` — the innermost trilinear blend; folds into finding 14 anyway. `AcousticSimulator.js:453, :458` and `probe_detect_portals.js:94, :109` are 3-component point-on-segment evaluations, not scalar `lerp` calls.
60
- - Brief F `inline_length3`, the remaining ~14 hits — hot per-ray / per-edge / per-candidate-pair loops (`OcclusionSolver.js:79` per ray, `bakeVisibility.js:106` per graph edge, `probe_visibility_augment.js:92,108,111` per 2-ring chord). Inlining is the right call there; only the cold `AcousticSimulator` site (finding 20) is worth changing.
61
- - Brief F `epsilon_compare` `ProbeReverbRenderer.js:198-200` — a tunable re-bake hysteresis (`changeThreshold = 0.1 s`), not float-tolerance equality. `epsilonEquals` would be semantically wrong.
62
- - Brief F `binary_search_inline` `probe_visibility_augment.js:171` — folded into finding 2.
63
- - `EventInstance.js:47-92` `build3BandCrossover` / `set3BandGains` / `disconnect3Band` — already correctly factored and shared by BOTH the direct and corner-leak paths (`:457`, `:476`). No duplication; a model for finding 8.
64
- - `ProbeFieldPathingSolution.js:62` correctly delegates to `csr_graph_build_distance_field`, and `AcousticProbeField.shortestPath:519` to `csr_graph_find_shortest_path`. **No Dijkstra / graph-search duplication in `simulation/`** — the probe pathing is a clean `core/graph` consumer.
65
- - No hand-rolled biquad coefficient math, equal-power panning law, or crossfade curve anywhere in the domain — WebAudio nodes (`createBiquadFilter`, `PannerNode`, `linearRampToValueAtTime`) do all of it. The one filter-adjacent formula, the one-pole coefficient `1 − e^(−2πf/fs)`, is finding 6.
66
- - The 3-band value classes (`AcousticMedium`, `AcousticMaterial`, `AcousticSolution`, `AcousticSourceState`) unroll their 3-element copies by hand in `equals`/`clone`/`hash`/`toJSON` — typed, explicit, per-field. Consistent with "typed over magic"; not duplication of *logic*.
67
-
68
- ## Notes — why duplication accumulates here
69
-
70
- 1. **Three generations coexist in one package.** `material/` (dead, superseded), `ecs/emitter/` (legacy, wired,
71
- now a translator), and `sopra/` + `ecs/audio/` (new, unwired outside the engine). Each generation
72
- re-solved the same problems — polymorphic type-tagged JSON, weighted random selection, composition
73
- with per-child weights — because the previous one was not being deleted. The three JSON dispatchers
74
- (finding 15) and the material-vs-clip parallel hierarchy are the visible residue.
75
-
76
- 2. **The migration seam stalled at the game boundary, not inside the engine.** Inside `engine/sound/`
77
- the strangler is genuinely done — sopra is the single renderer and legacy is a translator. But
78
- `makeMirEngineConfig.js` still registers the three legacy systems and nothing registers
79
- `AudioEmitterSystem`, so the new path's code has no production caller keeping it honest. That is
80
- what let finding 3 (two systems each owning the sopra tick) and finding 12 (two copies of the
81
- play-options mapping) survive: only one of each pair actually runs.
82
-
83
- 3. **`core/graph/csr/` ships consumers but no producer.** It has Dijkstra, distance fields, and edge
84
- lookup, but no `from_edge_list` builder — so the probe bake writes its own twice (finding 2), and the
85
- companions it also needs (connected components, canonical edge key) got written locally 2–4× each
86
- (findings 10, 21). One missing primitive generated four findings.
87
-
88
- 4. **Ray-scratch locality beats reuse.** Every module that casts a shadow ray declares its own
89
- module-static `Ray3` and inlines the normalize-and-set sequence (finding 1). The pattern is
90
- allocation-free by construction, which makes the copy feel cheap, but it means the "one shared
91
- visibility test" the code documents does not actually exist.
92
-
93
- 5. **Per-sample DSP genuinely justifies inlining — but the justification got over-applied.** The IR
94
- synthesis kernels (findings 6, 7) share far more than the inner loop: window sizing, filter
95
- coefficients, and envelope shape are all per-*bake* work that could be shared without a per-sample
96
- call. The `EventInstance` crossover helpers show the team already knows how to draw that line
97
- correctly; the reverb bakers just predate it.
98
-
99
- 6. **The base class stops one level short.** `AbstractAudioClip` folds `gain`/`pitch` into `hash()`, and
100
- `ContainerAudioClip` folds `children` into `equals()` — but not into `hash()`. Four subclasses filled
101
- the gap by copy-paste, and one of them (Blend) has already drifted (finding 4). The same shape
102
- appears in the container adapters, where the base clip fields are re-written per subclass.
1
+ # Domain F — engine/sound (legacy + sopra + acoustic simulation)
2
+
3
+ All paths relative to `H:/git/moh/app/src/mir-engine/meep/src/`. Every finding below was verified by
4
+ opening the cited files.
5
+
6
+ ## Findings
7
+
8
+ | # | Severity | Concept | Sites | Evidence | Verdict | Proposed common path | Effort/Risk |
9
+ |---|---|---|---|---|---|---|---|
10
+ | 1 | HIGH | "Is this segment clear of occluders?" shadow-ray test written **3×**, each with its own module-static `Ray3` | `engine/sound/simulation/probe/probe_edge_visible.js:20` `probe_edge_visible`; `engine/sound/simulation/probe/probe_refine_connectivity.js:168` `segment_clear`; `engine/sound/simulation/probe/AcousticProbeField.js:328-345` (filter arrow in `nearestVisibleIndex`) | Brief C sim=0.75. All three do: zero-length ⇒ visible; `inv = 1/d`; `setOrigin/setDirection/tMax = d`; `!index.anyHit(RAY)`. `probe_refine_connectivity.js:167` comment literally says *"mirrors probe_edge_visible"*. `probe_edge_visible.js:8-9` claims it is *"the one visibility test shared by the offline bake and the runtime recheck, so they agree exactly"* — a claim the other two sites falsify | UNJUSTIFIED | `engine/sound/simulation/core/segment_clear.js` — `segment_clear(index, ax,ay,az, bx,by,bz)` with ONE scratch ray; `probe_edge_visible` becomes a 4-line probe-index wrapper; `nearestVisibleIndex`'s filter calls it directly | S / low |
11
+ | 2 | HIGH | Undirected edge-list → CSR (degree count → prefix sum → fill via write cursor) written twice; plus a hand-rolled sorted-row `isNeighbour` binary search | `engine/sound/simulation/probe/bakeVisibility.js:109-141` (weighted, → `CSRGraph.fromArrays`); `engine/sound/simulation/probe/probe_visibility_augment.js:50-70` (unweighted + per-row `.sort()`), with `isNeighbour` at `:167-182` | Both consume the same `{a,b}` edge arrays produced by stages A–D of the same bake. `core/graph/csr/` has `CSRGraph`, `csr_graph_find_edge`, `csr_graph_build_distance_field`, `csr_graph_find_shortest_path` — but **no builder**, so every caller re-derives one | PROMOTION | `core/graph/csr/csr_graph_from_undirected_edge_list.js(vertex_count, a, b, weights?, {sorted_rows})`; `isNeighbour` → `csr_graph_find_edge !== -1` (give the core one a sorted-row fast path) | M / low |
12
+ | 3 | HIGH | Listener resolution + `sopra.update(currentTime)` duplicated in two ECS systems that share ONE `SopraEngine` | `engine/sound/ecs/emitter/SoundEmitterSystem.js:137-158` `update`; `engine/sound/ecs/audio/AudioEmitterSystem.js:246-315` `update` | Both do `ecd.getAnyComponent(SoundListener)` → `getComponent(entity, Transform)` → `sopra.setListener(pos)` → `sopra.update(this.webAudioContext.currentTime)`. The engine is shared (`SoundEngine.obtainSopra`, `SoundEngine.js:82-88`), and BOTH class docs state coexistence is the design (`SoundEngine.js:76-78`, `AudioEmitterSystem.js:35-36`). Co-registering them double-runs `SopraEngine.update` (`sopra/SopraEngine.js:340-351`: snapshot + iterate all active instances + `#updateDuckers`). Harmless-but-wasted today because `EventInstance.#evaluate` guards on `entry.voice === null`; the copy-pasted "missing Transform" tolerance comment has already diverged (AudioEmitterSystem additionally skips `liveSet.refresh`) | UNJUSTIFIED | One owner for the tick: a `SopraTickSystem` (or fold listener-resolve + tick into `SoundListenerSystem`, which already owns the `SoundListener`+`Transform` pair). Emitter systems then only push emitter state | M / medium |
13
+ | 4 | HIGH | The container child-hash fold is copy-pasted into all 4 concrete containers instead of living on the base that owns `children` — and has already diverged | `engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js:26-35`; `RandomContainerAudioClip.js:130-144`; `SwitchContainerAudioClip.js:83-96`; `BlendContainerAudioClip.js:126-139` (all `hash()`) | Brief A [70×2] + E. `ContainerAudioClip` owns `children` and DOES implement the parallel `equals()` fold (`ContainerAudioClip.js:80-99`) but has no `hash()`. Divergence already present: `BlendContainerAudioClip.hash()` folds `parameter`+`defaultValue` but **not** `blends`, though `equals()` compares them (`:117-121`) and the adapter serializes them — two blend containers differing only in curves hash identically. `EventDescription` polyphony buckets are keyed by event content, so clip hashes are load-bearing | UNJUSTIFIED | `ContainerAudioClip.hash()` = `super.hash()` folded with each child's `hash()`; each subclass folds only its own fields on top (and Blend adds `blends`) | S / low |
14
+ | 5 | MEDIUM | `AnimationCurve` binary keyframe codec hand-written twice | `engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js:24-33` + `:55-64`; `engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js:20-33` + `:52-66` | Both carry the same excuse comment: *"AnimationCurve has no binary adapter of its own"*. Identical wire format (`uintVar` count, then `time/value/inTangent/outTangent` float32s). A payload change (wrap modes, tangent packing) must land in both or one silently mis-reads | PROMOTION | `engine/animation/curve/AnimationCurveSerializationAdapter.js` (or a `serialize_animation_curve` / `deserialize_animation_curve` pair beside `AnimationCurve.js`), registered so `this.objectAdapter` handles it | S / low |
15
+ | 6 | MEDIUM | 3-band decaying-noise IR synthesis kernel duplicated | `engine/sound/simulation/render/reverbImpulseResponse.js:26` `reverbImpulseResponse` (kernel `:33-72`); `engine/sound/simulation/render/foaReverbImpulseResponse.js:33` `foaReverbImpulseResponse` (kernel `:41-96`) | Brief E (4-line window). Identical: bin-length derivation from `decayBands × sampleRate`, one-pole coefficients `a = 1 − e^(−2πf/fs)`, the energy-preserving `low/mid/high` split, and the three `Math.pow(1 − i/len, decayPower)` envelopes. Only the write-out differs (omni sum vs direction-weighted B-format). Retuning the crossover or envelope must land in both | UNJUSTIFIED (the shared part is a per-buffer producer, not a per-sample call) | A `write_band_tails(outLow, outMid, outHigh, length, lenLow, lenMid, lenHigh, decayPower, sampleRate, random)` both consume; scalar sums, FOA weights by direction | M / medium (numeric output must stay bit-identical — the specs pin it) |
16
+ | 7 | MEDIUM | A **third** decaying-noise IR generator, unseeded | `engine/sound/sopra/definition/effect/ReverbEffect.js:45-66` `build` | Brief F `raw_math_random`: `data[i] = (Math.random() * 2 - 1) * Math.pow(1 - t, this.decayPower)` — the single-band case of finding 6. `reverbImpulseResponse.js:18` documents *"with three equal bands this reduces to the plain scalar tail"*, i.e. it already subsumes this. Divergence with teeth: the acoustic path is explicitly seeded (`ProbeReverbRenderer.js:97`, `FoaProbeReverbRenderer.js:161` → `seededRandom(1)`), so a sopra bus reverb is the only non-reproducible IR in the engine | UNJUSTIFIED | `ReverbEffect.build` → `reverbImpulseResponse(ctx, [d,d,d], this.decayPower, this.#random)` with a seeded RNG field | S / low |
17
+ | 8 | MEDIUM | Probe-reverb renderer scaffolding duplicated, with a divergent re-bake trigger | `engine/sound/simulation/render/ProbeReverbRenderer.js:108-136` (ctor) + `:192-230` `setBands`; `engine/sound/simulation/render/FoaProbeReverbRenderer.js:172-236` (ctor) + `:296-333` `#rebake` | Same double-buffered convolver bank, same `#active` flip, same `rampGain(..., FADE_EPSILON, crossfadeSeconds, ctx)` crossfade, same `max(bands) >= minDecay` audibility gate, same `setProbeField`/`input`/`output` surface. But the scalar re-bakes on per-band hysteresis (`changeThreshold`, `:198-208`) while the FOA re-bakes on nearest-probe-index change plus a `#dirty` force-flag (`:123-131`, `:257-260`) that the scalar **lacks** — a `setProbeField` swap between two fields whose bands agree at the listener leaves the scalar renderer's IR stale | UNJUSTIFIED for the crossfade/gate scaffolding (JUSTIFIED for the differing WebAudio graph shape) | Extract an N-channel `ConvolverCrossfadeBank` (build, `swapTo(buffers[])`, `mute()`) both renderers own; give both the same `#dirty` semantics | M / medium |
18
+ | 9 | MEDIUM | "Randomly orient a Fibonacci sample set" written twice, bypassing `random_point_on_sphere` | `engine/sound/simulation/core/OcclusionSolver.js:43-51` (in `solve`); `engine/sound/simulation/probe/bakeReverbBands.js:69-74` | Identical four lines: `z = 2·random()−1`, `azimuth = 2π·random()`, `ring = sqrt(max(0, 1−z²))`, then `v3_orthonormal_matrix_from_normal(ROTATION, 0, ring·cos, ring·sin, z)`. `core/geom/random/random_point_on_sphere.js` already produces the uniform direction | UNJUSTIFIED | `core/geom/random/random_orthonormal_basis(out9, out_offset, random)` beside `v3_orthonormal_matrix_from_normal`; at minimum call `random_point_on_sphere` for the direction | S / low |
19
+ | 10 | MEDIUM | Canonical undirected edge key `min·n + max` written **4×** (and decoded in a 5th place) | `engine/sound/simulation/probe/ProbeVisibilityRecheck.js:72-75` `#edgeId` (decode at `:112-113`); `engine/sound/simulation/probe/probe_densify_portals.js:141-143` `edgeKey`; `engine/sound/simulation/probe/probe_visibility_bridge_components.js:84-86` (inline `rlo·n + rhi`); `engine/sound/simulation/probe/probe_visibility_augment.js:147` (inline `u·n + v`, correctness relies on the caller's `u<v` canonicalisation at `:96`) | A bug-prone invariant (must use the probe count as the radix AND canonicalise the pair) replicated across four modules of one bake pipeline; changing the radix (e.g. for >65k probes packing) silently breaks the ones you miss | UNJUSTIFIED | `engine/sound/simulation/probe/probe_edge_key.js` — `probe_edge_key(a, b, n)` + `probe_edge_decode(key, n, out2)` | S / low |
20
+ | 11 | MEDIUM | `SoundControllerSystem` ≡ `AnimationControllerSystem` — the whole "component.rules → per-entity event-listener registration + teardown" ECS lifecycle | `engine/sound/ecs/SoundControllerSystem.js:49-101` `link` / `:103-113` `unlink`; `engine/graphics/ecs/animation/AnimationControllerSystem.js:18-62` `link` / `:64-72` `unlink` | Brief A [59×2] (`unlink` is token-identical) + E. Both hold `this.removers = []`, build `callbacks = this.removers[entity] = []`, register `{name, handler}` pairs for `rule.startEvent`/`rule.stopEvent` via `dataset.addEntityEventListener`, and tear down identically. Only the two handler bodies differ | UNJUSTIFIED (cross-domain, reported here per instructions) | A typed `EntityEventRuleBinding` value (owns the `{name, handler}` list, `bind(dataset, entity, name, handler)` / `release(dataset, entity)`) that both systems compose — no string-keyed registry involved | S / low |
21
+ | 11b | LOW | `randomFromArray` re-derived inline | `engine/sound/ecs/SoundControllerSystem.js:13-24` `pickTackFromRule` | Brief F `raw_math_random`: `Math.floor(Math.random() * tracks.length)`. `core/math/random/randomFromArray.js` exists and is used correctly two directories away in `engine/sound/material/concrete/RandomSoundMaterial.js:17` | UNJUSTIFIED | `randomFromArray(Math.random, rule.tracks)` | XS / none |
22
+ | 12 | MEDIUM | Emitter → `sopra.playEvent(...)` option mapping duplicated; drives a 9-positional-argument API | `engine/sound/ecs/audio/AudioEmitterSystem.js:209-233` `#play`; `engine/sound/ecs/audio/LiveEmitterSet.js:281-323` `#promote` (fed by `LiveEmitterSet.add` at `:158-163`, called with 9 positional args at `AudioEmitterSystem.js:139`) | Both derive `oneShot = !event.rootClip.loops()`, build the same `{busId, position, oneShot, acoustic, pathing, sourceRadius, orientation, directivity}` bag, then re-apply `setGainDb`. `LiveEmitterSet`'s record type (`:77`) is a hand-mirrored copy of that bag. Every new per-source acoustic property (`orientation`, `directivity` were the last two) must be threaded through the record, the 9-arg `add` signature, `#promote`, AND `#play` | UNJUSTIFIED | One typed `AudioEmitterPlaySpec` built from `(emitter, transform, acousticSim)`; `LiveEmitterSet.add(entity, spec)` stores it, `#promote` and `#play` both hand it to `playEvent` | M / low |
23
+ | 13 | MEDIUM | Three hand-written "dynamic BVH of handle-addressed objects" wrappers inside one domain | `engine/sound/simulation/core/AcousticOccluderIndex.js:40-122`; `engine/sound/simulation/core/AcousticRegionIndex.js:23-134`; `engine/sound/ecs/audio/SpatialAudioIndex.js:24-138` | Identical skeleton in all three: `#bvh = new BVH()`, a parallel `BvhClient` store, retire-a-slot-by-null, a `#fitLeaf(client, x)` that ends in `client.resize(x0…z1)`, a reused `#candidates` gather buffer, and a query that maps returned handles back to user objects. Only the leaf-bounds policy and the refit trigger (reactive `onChanged` / per-frame `refit()` / explicit `setTransform`) differ | UNJUSTIFIED | `core/bvh2/bvh3/BvhObjectIndex.js` — typed container holding `T[]` + clients, with `computeBounds(out, item)` supplied by the owner; the three sound indices become thin policy classes | M / medium |
24
+ | 13b | LOW | AABB union re-derived | `engine/sound/simulation/core/AcousticOccluderIndex.js:252-279` `computeWorldBounds` | Hand-written 6-component min/max fold; `core/geom/3d/aabb/aabb3_array_combine.js` does exactly this | UNJUSTIFIED | `aabb3_array_combine` | XS / none |
25
+ | 14 | MEDIUM | Trilinear sampling of a Float32Array 3D scalar grid re-implemented | `engine/sound/simulation/probe/probe_sdf_grid.js:104-122` `grid_sample_trilinear` (+ `clampInt` at `:181-183`) | `engine/graphics/texture/3d/scs3d_sample_linear.js:15` is the same function: same `z·(rx·ry) + y·rx + x` layout, same edge clamping, same trilinear blend. `SingleChannelSampler3D` is the matching container. `probe_sdf_grid.js:1` already imports `clamp` yet still hand-rolls `clampInt` | UNJUSTIFIED | Back `SdfGrid` with `SingleChannelSampler3D` and call `scs3d_sample_linear`; delete `clampInt` | S / low |
26
+ | 15 | LOW | `{type: ctor.typeName, data: x.toJSON()}` wrapper written 3× with two different `constructor` lookups | `engine/sound/material/concrete/json/serializeSoundMaterialToJSON.js:1-6` (`material.constructor.typeName`); `engine/sound/sopra/serialization/sopraJSON.js:22-27` `serializeAudioClipToJSON` and `:68-73` `serializeAudioEffectToJSON` (`Object.getPrototypeOf(x).constructor.typeName`) | Brief A [70×2], C sim=1. The **switch-based deserializers** (`deserializeSoundMaterialFromJSON.js:12`, `sopraJSON.js:33`, `sopraJSON.js:79`) are **JUSTIFIED** — the alternative in core (`core/json/abstractJSONDeserializer.js`) is precisely the string-keyed `Map<string, fn>` registry the house rules reject, and `sopraJSON.js:11-16` documents deliberate call-time class binding to survive an import cycle. Only the three one-line serializers are gratuitous, and they disagree on how to reach the constructor | UNJUSTIFIED (serializers only) | One `type_tagged_to_json(value)` helper (typed, no registry); pick one constructor lookup | XS / none |
27
+ | 16 | LOW | `dB2Volume` re-derived inline in a file that already imports it | `engine/sound/sopra/runtime/EventInstance.js:33-35` `dbTiltToGain` → `Math.pow(10, -excessDb / 20)`; `dB2Volume` imported at `:2` | Called 4× per acoustic instance per frame (`:542-543`, `:561-562`) — not per-sample, so no inlining justification | UNJUSTIFIED | `excessDb <= 0 ? 1 : dB2Volume(-excessDb)` | XS / none |
28
+ | 17 | LOW | `BusGraph` repeats the same lookup-or-throw preamble 6× | `engine/sound/sopra/runtime/BusGraph.js:143` `getInput`, `:159` `getOutput`, `:173` `getVolume`, `:187` `setVolume`, `:214` `rampVolume`, `:232` `approachVolume` | Six copies of `const bus = this.buses.get(id); if (bus === undefined) { throw new Error(\`sopra: no bus '${id}'\`); }` — ~30 lines | UNJUSTIFIED | A `#bus(id)` private that does the lookup + throw | XS / none |
29
+ | 18 | LOW / PROMOTION | The `setFlag`/`clearFlag`/`writeFlag`/`getFlag` quad, byte-identical in **35 files** | In this domain: `engine/sound/ecs/emitter/SoundEmitter.js:156-189`; `engine/sound/ecs/emitter/SoundTrack.js:138-171`; `engine/sound/asset/SoundAssetPlaybackSpec.js`; `engine/ecs/speaker/Voice.js:13-46` | Brief E (30-file window). `grep -l "clearFlag(flag) {"` returns 35 files spanning core/, engine/ecs, graphics, physics, sound, view | PROMOTION (codebase-wide, not sound-specific — flagged for the cross-domain roll-up, not for a sound-local fix) | A `FlagCarrier` base (or a shared `flags_set/clear/write/read` quartet on the flat `flags` word); JS single inheritance makes the base awkward where a class already extends, so the function quartet is the safer shape | L / medium |
30
+ | 19 | LOW | Well-conditioned perpendicular-axis construction re-derived | `engine/sound/simulation/probe/probe_detect_portals.js:170-184` `throatIsAperture` | Hand-rolls "cross with world-up, swap reference if `|ey| > 0.9`, normalize". `core/geom/vec3/v3_orthonormal_matrix_from_normal.js` builds exactly this stable basis and is already used twice in this same package (`OcclusionSolver.js:48`, `bakeReverbBands.js:74`) | UNJUSTIFIED | `v3_orthonormal_matrix_from_normal` and read the tangent column | XS / low |
31
+ | 20 | LOW | Vector normalize written by hand, inconsistently with the same file | `engine/sound/simulation/AcousticSimulator.js:598-621` `#directivityGainToward` (`Math.sqrt(dx*dx+dy*dy+dz*dz)` at `:605`) vs `:454, :455, :459, :460` in `#applyPath` (`Math.hypot`) | Brief F `inline_length3`. Cold path (once per non-omni source per frame). `v3_length` / `v3_normalize_array` exist; the file already imports `line3_compute_segment_closest_point_t` from the same family | UNJUSTIFIED (cold) | `v3_length`, or at least pick one of `Math.hypot`/`sqrt` within the file | XS / none |
32
+ | 21 | LOW / PROMOTION | Union-find connected components over probe connectivity written twice | `engine/sound/simulation/probe/probe_visibility_bridge_components.js:35-39` (from an `{a,b}` edge list); `engine/sound/simulation/probe/probe_refine_connectivity.js:50-56` (from a `CSRGraph`) | Both then use `uf_find(parent, i) === uf_find(parent, j)` as the same-component predicate. `core/graph/graph_compute_disconnected_clusters.js` exists but only for the object-graph `Graph`, not `CSRGraph` | PROMOTION | `core/graph/csr/csr_graph_connected_components(parent, graph)` beside `csr_graph_build_distance_field`; the edge-list variant folds into finding 2's builder | S / low |
33
+
34
+ ## Strangler-migration debt
35
+
36
+ Context, not a defect list. Legacy-vs-sopra parallelism is the deliberate migration shape; what matters is
37
+ where the seam has stalled and where a fix would have to land twice.
38
+
39
+ | Legacy | sopra / native counterpart | Is legacy still wired? | Note |
40
+ |---|---|---|---|
41
+ | `ecs/emitter/SoundEmitterSystem` + `SoundEmitterComponentContext` | `ecs/audio/AudioEmitterSystem` + `AudioEmitter` + `LiveEmitterSet` | **YES** — `mir-engine/model/game/makeMirEngineConfig.js:182` | The seam is genuinely healthy here: legacy is a *translator* (`sopra/legacy/soundEmitterToEventDescription.js`) onto sopra, not a second renderer. Conversely `AudioEmitter*` has **zero** consumers outside `engine/sound/` — the new path is unused in the game |
42
+ | `ecs/SoundControllerSystem` | none — its own JSDoc names a *"forthcoming `AudioEventTrigger`"* (`SoundControllerSystem.js:27-30`) | **YES** — `makeMirEngineConfig.js:183` | `@deprecated` and still registered with no replacement in the tree. Findings 11/11b live here |
43
+ | `material/**` (`AbstractSoundMaterialDefinition`, `Single`/`Random`/`Composition`, `json/`) | sopra clips — `SwitchContainerAudioClip.js:11` *"Supersedes the dormant material/ surface-switch concept"*; `BlendContainerAudioClip.js:13` *"Supersedes the dormant material/ composition concept"* | **NO** — grep finds zero references outside `engine/sound/material/` (only a doc mention in `sopraJSON.js:13`) | Dead but public surface (meep is a library). Origin of the JSON copies in finding 15 |
44
+ | `SoundEmitterChannels` (`effects`/`music`/`ambient`) | sopra `SopraDefaultBus` + `BusDefinition` | **YES** — 7 app files import it (UnitMaker, StoryManager, WorldEventManager, …) | Already proxied 1:1: `SoundEmitterSystem.get/setChannelVolume` (`:91-102`) forwards to `sopra.get/setBusVolume` |
45
+ | `ecs/SoundListenerSystem` (WebAudio `AudioListener` pose) | `sopra.setListener(position)` inside the emitter systems | **YES** — `makeMirEngineConfig.js:184` | THREE readers of the same `SoundListener`→`Transform` relation. sopra's listener is position-only; the WebAudio listener also carries orientation. See finding 3 |
46
+ | `ProbeReverbRenderer` (scalar) | `FoaProbeReverbRenderer` (directional/B-format) | scalar is what `configureAcousticSimulation.js:31` documents; FOA is referenced only by `prototypeAcousticSolver.js:515` + its spec | Neither is deprecated; both live. Findings 6/8 mean an IR or crossfade fix must land in both |
47
+ | `ecs/emitter/SoundPanningModelType` | `sopra/definition/SopraPanningModel` | **YES** — translated by `soundEmitterToEventDescription.js:57-61` | Identical 2-value enums; translation is one ternary. Fine |
48
+ | `SoundAttenuationFunction` + `core/math/physics/irradiance/interpolate_irradiance_*` | authored `AnimationCurve` via `sopra/util/buildAttenuationCurve.js` | **YES** | This is the *good* seam: the legacy falloffs are REUSED to sample the new curve rather than reimplemented. Called out as a model, not debt |
49
+
50
+ ## Dismissed leads
51
+
52
+ - Brief A/C `makePhysicsGroundResolver` / `recomputeMove` / `bindMoveKey` / `buildInputBindings` / `logHelp` (`engine/control/first-person/prototype_first_person_controller.js` ⟷ `engine/sound/simulation/prototypeAcousticSolver.js`) — both sites are `prototype*.js` harnesses, out of scope. **One line as instructed:** a real shared debug fly/walk camera *with* a physics ground resolver would be worth promoting to `engine/control/`, since it is now copied verbatim for every new subsystem demo.
53
+ - `DuckingRuleSerializationAdapter` / `CompressorEffectSerializationAdapter` / `ParameterDefinitionSerializationAdapter` `serialize`+`deserialize` (brief A [50×3] and [45×3]) — read all three: different field names and types (`writeUTF8String`+3×float32+float32 / 5×float32 / string+3×float32+uint8), zero shared logic. Exactly the per-class shape boilerplate the house rules bless.
54
+ - `SequenceContainerAudioClipSerializationAdapter.deserialize` ⟷ `SwitchContainerAudioClipSerializationAdapter.deserialize` (brief C sim=0.781) — same verdict; Switch additionally reads `parameter`/`defaultValue`.
55
+ - The container adapters' repeated `gain`/`pitch` writes and children loop (brief E) — wire order is per-class protocol; hoisting it into a base couples four independent binary formats. Deliberate.
56
+ - `MixerSnapshotSerializationAdapter` bus-entry codec vs `BusDefinitionSerializationAdapter` sends codec — structurally identical (`string` + `float32` pairs), semantically distinct record types. Shape only.
57
+ - `engine/ecs/speaker/Voice.js` vs `engine/sound/sopra/runtime/Voice.js` (brief G) — pure name collision. The first is a speech-bubble ECS component (flags only, `Voice.serializable = false`); the second is a WebAudio `AudioBufferSourceNode` holder. No shared logic beyond the flag quad (finding 18). The `speaker/` package has no importers, but meep is a library — not a defect.
58
+ - `echogram_band_rt60.js:49` `10 * Math.log10(edc / total)` — **energy** dB, deliberately not `volume2dB`'s amplitude `20·log10`. Correct as written.
59
+ - Brief F `inline_lerp` in `probe_sdf_grid.js:118-121` — the innermost trilinear blend; folds into finding 14 anyway. `AcousticSimulator.js:453, :458` and `probe_detect_portals.js:94, :109` are 3-component point-on-segment evaluations, not scalar `lerp` calls.
60
+ - Brief F `inline_length3`, the remaining ~14 hits — hot per-ray / per-edge / per-candidate-pair loops (`OcclusionSolver.js:79` per ray, `bakeVisibility.js:106` per graph edge, `probe_visibility_augment.js:92,108,111` per 2-ring chord). Inlining is the right call there; only the cold `AcousticSimulator` site (finding 20) is worth changing.
61
+ - Brief F `epsilon_compare` `ProbeReverbRenderer.js:198-200` — a tunable re-bake hysteresis (`changeThreshold = 0.1 s`), not float-tolerance equality. `epsilonEquals` would be semantically wrong.
62
+ - Brief F `binary_search_inline` `probe_visibility_augment.js:171` — folded into finding 2.
63
+ - `EventInstance.js:47-92` `build3BandCrossover` / `set3BandGains` / `disconnect3Band` — already correctly factored and shared by BOTH the direct and corner-leak paths (`:457`, `:476`). No duplication; a model for finding 8.
64
+ - `ProbeFieldPathingSolution.js:62` correctly delegates to `csr_graph_build_distance_field`, and `AcousticProbeField.shortestPath:519` to `csr_graph_find_shortest_path`. **No Dijkstra / graph-search duplication in `simulation/`** — the probe pathing is a clean `core/graph` consumer.
65
+ - No hand-rolled biquad coefficient math, equal-power panning law, or crossfade curve anywhere in the domain — WebAudio nodes (`createBiquadFilter`, `PannerNode`, `linearRampToValueAtTime`) do all of it. The one filter-adjacent formula, the one-pole coefficient `1 − e^(−2πf/fs)`, is finding 6.
66
+ - The 3-band value classes (`AcousticMedium`, `AcousticMaterial`, `AcousticSolution`, `AcousticSourceState`) unroll their 3-element copies by hand in `equals`/`clone`/`hash`/`toJSON` — typed, explicit, per-field. Consistent with "typed over magic"; not duplication of *logic*.
67
+
68
+ ## Notes — why duplication accumulates here
69
+
70
+ 1. **Three generations coexist in one package.** `material/` (dead, superseded), `ecs/emitter/` (legacy, wired,
71
+ now a translator), and `sopra/` + `ecs/audio/` (new, unwired outside the engine). Each generation
72
+ re-solved the same problems — polymorphic type-tagged JSON, weighted random selection, composition
73
+ with per-child weights — because the previous one was not being deleted. The three JSON dispatchers
74
+ (finding 15) and the material-vs-clip parallel hierarchy are the visible residue.
75
+
76
+ 2. **The migration seam stalled at the game boundary, not inside the engine.** Inside `engine/sound/`
77
+ the strangler is genuinely done — sopra is the single renderer and legacy is a translator. But
78
+ `makeMirEngineConfig.js` still registers the three legacy systems and nothing registers
79
+ `AudioEmitterSystem`, so the new path's code has no production caller keeping it honest. That is
80
+ what let finding 3 (two systems each owning the sopra tick) and finding 12 (two copies of the
81
+ play-options mapping) survive: only one of each pair actually runs.
82
+
83
+ 3. **`core/graph/csr/` ships consumers but no producer.** It has Dijkstra, distance fields, and edge
84
+ lookup, but no `from_edge_list` builder — so the probe bake writes its own twice (finding 2), and the
85
+ companions it also needs (connected components, canonical edge key) got written locally 2–4× each
86
+ (findings 10, 21). One missing primitive generated four findings.
87
+
88
+ 4. **Ray-scratch locality beats reuse.** Every module that casts a shadow ray declares its own
89
+ module-static `Ray3` and inlines the normalize-and-set sequence (finding 1). The pattern is
90
+ allocation-free by construction, which makes the copy feel cheap, but it means the "one shared
91
+ visibility test" the code documents does not actually exist.
92
+
93
+ 5. **Per-sample DSP genuinely justifies inlining — but the justification got over-applied.** The IR
94
+ synthesis kernels (findings 6, 7) share far more than the inner loop: window sizing, filter
95
+ coefficients, and envelope shape are all per-*bake* work that could be shared without a per-sample
96
+ call. The `EventInstance` crossover helpers show the team already knows how to draw that line
97
+ correctly; the reverb bakers just predate it.
98
+
99
+ 6. **The base class stops one level short.** `AbstractAudioClip` folds `gain`/`pitch` into `hash()`, and
100
+ `ContainerAudioClip` folds `children` into `equals()` — but not into `hash()`. Four subclasses filled
101
+ the gap by copy-paste, and one of them (Blend) has already drifted (finding 4). The same shape
102
+ appears in the container adapters, where the base clip fields are re-written per subclass.