@woosh/meep-engine 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1822) hide show
  1. package/LICENSE +188 -188
  2. package/README.md +152 -152
  3. package/editor/Editor.js +598 -598
  4. package/editor/EditorCameraNavigation.js +205 -205
  5. package/editor/EditorKeyMap.js +182 -182
  6. package/editor/SelectionVisualizer.js +127 -127
  7. package/editor/actions/concrete/ActionInvalidateSampler.js +27 -27
  8. package/editor/actions/concrete/PatchTerrainHeightAction.js +85 -85
  9. package/editor/camera/camera_framing.js +104 -104
  10. package/editor/clipboard.js +184 -184
  11. package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -22
  12. package/editor/ecs/component/editors/ObservedStringEditor.js +34 -34
  13. package/editor/ecs/component/editors/common/two_way_sync.js +50 -50
  14. package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +19 -19
  15. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -35
  16. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
  17. package/editor/ecs/component/editors/ecs/SGMeshEditor.js +24 -24
  18. package/editor/ecs/component/editors/ecs/ShadedGeometryEditor.js +33 -33
  19. package/editor/ecs/component/editors/ecs/Trail3DEditor.js +45 -45
  20. package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -19
  21. package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +72 -72
  22. package/editor/ecs/component/editors/shade/MeshletGeometryEditor.js +30 -30
  23. package/editor/ecs/component/editors/shade/ShadeImageEditor.js +70 -70
  24. package/editor/ecs/component/editors/shade/ShadeMaterialEditor.js +34 -34
  25. package/editor/ecs/component/editors/shade/ShadeTextureEditor.js +46 -46
  26. package/editor/ecs/component/editors/shade/StandardShadeMaterialEditor.js +24 -24
  27. package/editor/ecs/component/prototypeObjectEditor.js +265 -265
  28. package/editor/ecs/component/registerBasicTypeEditors.js +100 -100
  29. package/editor/ecs/component/registerEngineComponentEditors.js +95 -95
  30. package/editor/entity_world_bounds.js +27 -27
  31. package/editor/particles/lut_ops.js +148 -148
  32. package/editor/particles/particleEditEvents.js +12 -12
  33. package/editor/particles/rebuildParticleEmitter.js +31 -31
  34. package/editor/persistence/EditorLayoutStore.js +71 -71
  35. package/editor/persistence/SceneMetadata.js +52 -52
  36. package/editor/persistence/SceneSlotManager.js +144 -144
  37. package/editor/persistence/buildNewSceneDataset.js +67 -67
  38. package/editor/persistence/files.js +68 -68
  39. package/editor/persistence/scene_import.js +143 -143
  40. package/editor/persistence/scene_serialization.js +79 -79
  41. package/editor/persistence/string_buffer_codec.js +41 -41
  42. package/editor/process/GridDisplayProcess.js +125 -125
  43. package/editor/process/SymbolicDisplayProcess.js +106 -108
  44. package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
  45. package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
  46. package/editor/process/symbolic/PathSymbolicDisplay.js +118 -118
  47. package/editor/process/symbolic/PositionedMarkerSymbolicDisplay.js +79 -79
  48. package/editor/process/symbolic/SoundEmitterSymbolicDisplay.js +57 -57
  49. package/editor/process/symbolic/SymbolicDisplay.js +62 -62
  50. package/editor/process/symbolic/gizmo_draw_wire_box_oriented.js +85 -85
  51. package/editor/process/symbolic/gizmo_draw_wire_circle.js +53 -53
  52. package/editor/prototypeEditorShell.js +156 -156
  53. package/editor/selection/editor_pick.js +130 -130
  54. package/editor/selection/screen_rect_frustum.js +119 -119
  55. package/editor/style/controls.scss +168 -168
  56. package/editor/style/inspector.scss +223 -223
  57. package/editor/templates/entity_templates.js +167 -167
  58. package/editor/tools/GridPaintTool.js +273 -273
  59. package/editor/tools/SelectionTool.js +307 -307
  60. package/editor/tools/TransformToolV2.js +378 -378
  61. package/editor/tools/engine/ToolEngine.js +188 -188
  62. package/editor/tools/paint/TerrainTexturePaintTool.js +250 -250
  63. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +501 -501
  64. package/editor/tools/v2/TransformControls.js +971 -971
  65. package/editor/tools/v2/handle/gizmo_drag_plane.js +141 -141
  66. package/editor/tools/v2/handle/gizmo_draw.js +399 -399
  67. package/editor/tools/v2/handle/gizmo_handles.js +364 -364
  68. package/editor/tools/v2/handle/gizmo_ray_shapes.js +239 -239
  69. package/editor/view/ecs/HierarchicalEntityListView.js +339 -339
  70. package/editor/view/ecs/components/DatGuiController.js +2 -2
  71. package/editor/view/ecs/components/common/AutoCanvasView.js +100 -100
  72. package/editor/view/ecs/components/common/TextController.js +59 -59
  73. package/editor/view/library/MeshLibraryView.js +175 -175
  74. package/editor/view/library/mesh_library_drag.js +63 -63
  75. package/editor/view/library/model_thumbnail_source.js +86 -86
  76. package/editor/view/makeEntityDecorators.js +125 -125
  77. package/editor/view/node-graph/NodeGraphSelection.js +89 -89
  78. package/editor/view/node-graph/NodeView.js +211 -211
  79. package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -36
  80. package/editor/view/node-graph/actions/NodesMoveAction.js +41 -41
  81. package/editor/view/node-graph/connection_wire_geometry.js +107 -107
  82. package/editor/view/particles/ColorLutGradientView.js +291 -291
  83. package/editor/view/particles/ScalarLutCurveView.js +279 -279
  84. package/editor/view/prepareMeshLibrary.js +188 -188
  85. package/editor/view/shell/SceneTreePanelView.js +119 -119
  86. package/editor/view/v2/SplitView.js +2 -2
  87. package/package.json +94 -94
  88. package/samples/generation/generators/interactive/mir_generator_place_buff_objects.js +362 -362
  89. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -738
  90. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -79
  91. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -93
  92. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -80
  93. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -164
  94. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -69
  95. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -102
  96. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -129
  97. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -72
  98. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -74
  99. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -169
  100. package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -254
  101. package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -250
  102. package/src/REVIEW_2026_08_06/adv-core.md +511 -511
  103. package/src/REVIEW_2026_08_06/adv-gfx.md +480 -480
  104. package/src/REVIEW_2026_08_06/adv-view.md +446 -446
  105. package/src/REVIEW_2026_08_06/api-core.md +296 -296
  106. package/src/REVIEW_2026_08_06/api-engine.md +195 -195
  107. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -507
  108. package/src/REVIEW_2026_08_06/core-binary.md +348 -348
  109. package/src/REVIEW_2026_08_06/core-collection.md +376 -376
  110. package/src/REVIEW_2026_08_06/core-math.md +221 -221
  111. package/src/REVIEW_2026_08_06/core-process.md +266 -266
  112. package/src/REVIEW_2026_08_06/core-science.md +223 -223
  113. package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -279
  114. package/src/REVIEW_2026_08_06/engine-animation.md +167 -167
  115. package/src/REVIEW_2026_08_06/engine-asset.md +166 -166
  116. package/src/REVIEW_2026_08_06/engine-ecs.md +920 -920
  117. package/src/REVIEW_2026_08_06/engine-input.md +197 -197
  118. package/src/REVIEW_2026_08_06/engine-misc.md +248 -248
  119. package/src/REVIEW_2026_08_06/engine-terrain.md +52 -52
  120. package/src/REVIEW_2026_08_06/generation.md +176 -176
  121. package/src/REVIEW_2026_08_06/geom-2d.md +434 -434
  122. package/src/REVIEW_2026_08_06/geom-3d.md +117 -117
  123. package/src/REVIEW_2026_08_06/gfx-gi.md +143 -143
  124. package/src/REVIEW_2026_08_06/gfx-render.md +740 -740
  125. package/src/REVIEW_2026_08_06/gfx-texture.md +690 -690
  126. package/src/REVIEW_2026_08_06/prior-criticals.md +49 -49
  127. package/src/REVIEW_2026_08_06/prior-highs.md +138 -138
  128. package/src/REVIEW_2026_08_06/test-gaps.md +364 -364
  129. package/src/REVIEW_2026_08_06/view.md +667 -667
  130. package/src/REVIEW_2026_08_06.md +610 -610
  131. package/src/avif/codec/dec/avif_dec.cpp +233 -233
  132. package/src/avif/codec/dec/avif_dec.js +16 -16
  133. package/src/core/binary/BitImage2.js +246 -246
  134. package/src/core/binary/align_32.js +21 -21
  135. package/src/core/binary/data_view/DataType2DataViewReaders.js +23 -23
  136. package/src/core/binary/data_view/DataType2DataViewWriters.js +25 -25
  137. package/src/core/binary/hash/XXH128Hash.js +55 -55
  138. package/src/core/binary/hash/XXH3_SECRET.js +25 -25
  139. package/src/core/binary/hash/xxh3_128.js +341 -341
  140. package/src/core/binary/hash/xxh3_64.js +241 -241
  141. package/src/core/binary/hash/xxh3_common.js +476 -476
  142. package/src/core/binary/lz4/LZ4_MAX_INPUT_SIZE.js +9 -9
  143. package/src/core/binary/lz4/lz4_compress_block.js +385 -385
  144. package/src/core/binary/lz4/lz4_compress_bound.js +25 -25
  145. package/src/core/binary/lz4/lz4_decompress_block.js +139 -139
  146. package/src/core/binary/lz4/lz4_decompress_bound.js +30 -30
  147. package/src/core/binary/meshopt/MESHOPT_GROUP_SIZE.js +18 -18
  148. package/src/core/binary/meshopt/MeshoptCompressionFilter.js +30 -30
  149. package/src/core/binary/meshopt/MeshoptCompressionMode.js +21 -21
  150. package/src/core/binary/meshopt/__meshopt_test_streams.js +237 -237
  151. package/src/core/binary/meshopt/meshopt_decode.js +73 -73
  152. package/src/core/binary/meshopt/meshopt_decode_byte_group.js +67 -67
  153. package/src/core/binary/meshopt/meshopt_decode_data_block.js +59 -59
  154. package/src/core/binary/meshopt/meshopt_decode_filter_color.js +69 -69
  155. package/src/core/binary/meshopt/meshopt_decode_filter_exponential.js +43 -43
  156. package/src/core/binary/meshopt/meshopt_decode_filter_octahedral.js +60 -60
  157. package/src/core/binary/meshopt/meshopt_decode_filter_quaternion.js +71 -71
  158. package/src/core/binary/meshopt/meshopt_decode_index_buffer.js +294 -294
  159. package/src/core/binary/meshopt/meshopt_decode_index_sequence.js +115 -115
  160. package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +381 -381
  161. package/src/core/binary/meshopt/meshopt_read_uint_var.js +59 -59
  162. package/src/core/binary/reinterpret_float32_as_uint32.js +13 -13
  163. package/src/core/binary/reinterpret_uint32_as_float32.js +14 -14
  164. package/src/core/binary/type/DataType2TypedArrayConstructorMapping.js +35 -35
  165. package/src/core/bvh2/bvh3/ebvh_geometry_query_any_triangle_ray.js +149 -149
  166. package/src/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +96 -96
  167. package/src/core/bvh2/bvh3/query/bvh_query_depth_range_in_frustum.js +101 -101
  168. package/src/core/bvh2/bvh3/query/bvh_query_leaves_generic.js +77 -77
  169. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +90 -90
  170. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js +94 -94
  171. package/src/core/bvh2/bvh3/query/bvh_query_user_data_generic.js +86 -86
  172. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js +101 -101
  173. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +92 -92
  174. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray.js +96 -96
  175. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray_segment.js +100 -100
  176. package/src/core/bvh2/visual/BVHGeometry.js +131 -131
  177. package/src/core/bvh8/BVH8.js +436 -436
  178. package/src/core/bvh8/aabb3_round_to_float32.js +22 -22
  179. package/src/core/bvh8/build/BVH8Converter.js +588 -588
  180. package/src/core/bvh8/build/TriangleCluster.js +122 -122
  181. package/src/core/bvh8/build/aabb3_compute_merge_cost.js +28 -28
  182. package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +17 -17
  183. package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -303
  184. package/src/core/bvh8/build/bvh8_from_proxy.js +256 -256
  185. package/src/core/bvh8/build/byte.js +9 -9
  186. package/src/core/bvh8/build/encode_bounds_e.js +11 -11
  187. package/src/core/bvh8/bvh8_convert_to_dot.js +132 -132
  188. package/src/core/bvh8/bvh8_count_primitives.js +98 -98
  189. package/src/core/bvh8/bvh8_geometry_validate.js +138 -138
  190. package/src/core/bvh8/bvh8_geometry_validate_indirect.js +166 -166
  191. package/src/core/bvh8/bvh8_get_node_bounds.js +34 -34
  192. package/src/core/bvh8/bvh8_get_node_child_bounds.js +52 -52
  193. package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -18
  194. package/src/core/bvh8/bvh8_node_count_triangles.js +27 -27
  195. package/src/core/bvh8/bvh8_quality.js +72 -72
  196. package/src/core/bvh8/bvh8_validate_structure.js +87 -87
  197. package/src/core/clipboard/obtainClipBoard.js +29 -29
  198. package/src/core/clipboard/safeClipboardReadText.js +55 -55
  199. package/src/core/clipboard/safeClipboardWriteText.js +23 -23
  200. package/src/core/collection/array/typed/typed_array_value_denormalize.js +52 -52
  201. package/src/core/collection/array/typed/typed_array_value_normalize.js +51 -51
  202. package/src/core/collection/union-find/union_find.js +79 -79
  203. package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -20
  204. package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -17
  205. package/src/core/color/colormap/MAGMA_LUT.js +26 -26
  206. package/src/core/color/illuminant/D65_TRISTIMULUS_XYZ.js +19 -19
  207. package/src/core/color/lab/lab_apply_hunt_adjustment.js +27 -27
  208. package/src/core/color/lab/lab_distance_hyab.js +23 -23
  209. package/src/core/color/lab/xyz_to_lab.js +62 -62
  210. package/src/core/color/operations/color_darken.js +24 -24
  211. package/src/core/color/operations/color_desaturate.js +24 -24
  212. package/src/core/color/operations/color_lighten.js +24 -24
  213. package/src/core/color/operations/color_saturate.js +24 -24
  214. package/src/core/color/operations/color_scale_okhsv_channel.js +45 -45
  215. package/src/core/color/tonemap/tonemap_aces.js +41 -41
  216. package/src/core/color/ycxcz/xyz_to_ycxcz.js +28 -28
  217. package/src/core/color/ycxcz/ycxcz_to_xyz.js +19 -19
  218. package/src/core/dom/isImageBitmap.js +12 -12
  219. package/src/core/function/frameThrottle.js +23 -23
  220. package/src/core/geom/2d/aabb/aabb2_array_combine.js +7 -7
  221. package/src/core/geom/2d/aabb/aabb2_compute_area.js +7 -7
  222. package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +7 -7
  223. package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +7 -7
  224. package/src/core/geom/2d/aabb/aabb2_from_v2_array.js +7 -7
  225. package/src/core/geom/2d/aabb/aabb2_intersects_point.js +7 -7
  226. package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +7 -7
  227. package/src/core/geom/2d/circle/circle_compute_circle_intersection.js +7 -7
  228. package/src/core/geom/2d/circle/circle_compute_circle_penetration.js +7 -7
  229. package/src/core/geom/2d/circle/circle_intersects_point.js +7 -7
  230. package/src/core/geom/2d/compute_polygon_area_2d.js +7 -7
  231. package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +7 -7
  232. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +7 -7
  233. package/src/core/geom/2d/convex-hull/orientation3_array.js +7 -7
  234. package/src/core/geom/2d/intersect_ray_2d.js +7 -7
  235. package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +7 -7
  236. package/src/core/geom/2d/line/line2_segment_intersection_fraction_2d.js +44 -44
  237. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +7 -7
  238. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_array_2d.js +7 -7
  239. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_2d.js +7 -7
  240. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_array_2d.js +7 -7
  241. package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +7 -7
  242. package/src/core/geom/2d/line/line_segment_line_segment_intersection_exists_2d.js +7 -7
  243. package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -25
  244. package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -46
  245. package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -52
  246. package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -69
  247. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -433
  248. package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.js +51 -51
  249. package/src/core/geom/2d/polygon/polygon2_clip_halfplane.js +50 -50
  250. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -84
  251. package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -52
  252. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -86
  253. package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -1280
  254. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -77
  255. package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -39
  256. package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -101
  257. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -7
  258. package/src/core/geom/2d/rectangle_to_aabb.js +7 -7
  259. package/src/core/geom/2d/spline_bezier2_2d.js +7 -7
  260. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +99 -99
  261. package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -35
  262. package/src/core/geom/2d/triangle2d_compute_area.js +7 -7
  263. package/src/core/geom/2d/triangle2d_contains_point.js +7 -7
  264. package/src/core/geom/2d/triangle2d_get_barycentric.js +7 -7
  265. package/src/core/geom/2d/v2_morton_encode.js +23 -23
  266. package/src/core/geom/3d/aabb/aabb3_array_intersects_frustum_degree.js +7 -7
  267. package/src/core/geom/3d/aabb/aabb3_box_surface_area_2.js +7 -7
  268. package/src/core/geom/3d/aabb/aabb3_build_frustum.js +5 -5
  269. package/src/core/geom/3d/aabb/aabb3_closest_point_on_surface.js +57 -57
  270. package/src/core/geom/3d/aabb/aabb3_combined_surface_area.js +7 -7
  271. package/src/core/geom/3d/aabb/aabb3_compute_half_surface_area.js +7 -7
  272. package/src/core/geom/3d/aabb/aabb3_compute_projected_area.js +7 -7
  273. package/src/core/geom/3d/aabb/aabb3_compute_surface_area.js +7 -7
  274. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +7 -7
  275. package/src/core/geom/3d/aabb/aabb3_estimate_projected_area2.js +7 -7
  276. package/src/core/geom/3d/aabb/aabb3_from_threejs_geometry.js +7 -7
  277. package/src/core/geom/3d/aabb/aabb3_from_triangle_group.js +46 -46
  278. package/src/core/geom/3d/aabb/aabb3_intersects_frustum_degree.js +7 -7
  279. package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +7 -7
  280. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +7 -7
  281. package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +7 -7
  282. package/src/core/geom/3d/aabb/aabb3_ray_segment_entry_distance.js +95 -95
  283. package/src/core/geom/3d/aabb/aabb3_raycast.js +7 -7
  284. package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -93
  285. package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.js +82 -82
  286. package/src/core/geom/3d/aabb/compute_aabb_from_points.js +7 -7
  287. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +7 -7
  288. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -486
  289. package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -174
  290. package/src/core/geom/3d/atlas/Chart.js +77 -77
  291. package/src/core/geom/3d/atlas/REVIEW_02.md +392 -392
  292. package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -315
  293. package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -16
  294. package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -188
  295. package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -107
  296. package/src/core/geom/3d/atlas/atlas_generate.js +50 -50
  297. package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -266
  298. package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -36
  299. package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -62
  300. package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -33
  301. package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -50
  302. package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -39
  303. package/src/core/geom/3d/atlas/io/bt_loop_uv.js +35 -35
  304. package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -46
  305. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +269 -269
  306. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +209 -209
  307. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -38
  308. package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -86
  309. package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -183
  310. package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -66
  311. package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -40
  312. package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -136
  313. package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -30
  314. package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -77
  315. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +116 -116
  316. package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +271 -271
  317. package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +191 -191
  318. package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -35
  319. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -67
  320. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -137
  321. package/src/core/geom/3d/compute_circle_bounding_box.js +7 -7
  322. package/src/core/geom/3d/cone/computeConeBoundingBox.js +7 -7
  323. package/src/core/geom/3d/cone/computeConePlaneSide.js +7 -7
  324. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +7 -7
  325. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts +38 -0
  326. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts.map +1 -0
  327. package/src/core/geom/3d/cylinder/cylinder3_raycast.js +105 -0
  328. package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -18
  329. package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -24
  330. package/src/core/geom/3d/frustum/clipping_volume_matrix4_project.js +7 -7
  331. package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +7 -7
  332. package/src/core/geom/3d/frustum/frustum3_compute_closest_point.js +117 -117
  333. package/src/core/geom/3d/frustum/frustum_matrix4_project.js +7 -7
  334. package/src/core/geom/3d/frustum/hexahedron_from_aabb.js +7 -7
  335. package/src/core/geom/3d/frustum/read_frustum_corner.js +17 -17
  336. package/src/core/geom/3d/frustum/read_three_planes_to_array.js +7 -7
  337. package/src/core/geom/3d/frustum/slice_frustum_linear_to_points.js +7 -7
  338. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -146
  339. package/src/core/geom/3d/gjk/gjk.js +430 -430
  340. package/src/core/geom/3d/gjk/gjk_epa_penetration.js +520 -520
  341. package/src/core/geom/3d/gjk/minkowski_support.js +71 -71
  342. package/src/core/geom/3d/gjk/mpr.js +368 -368
  343. package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +7 -7
  344. package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.js +7 -7
  345. package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -64
  346. package/src/core/geom/3d/line/line3_compute_segment_closest_point_to_aabb3_t.js +153 -153
  347. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +7 -7
  348. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js +7 -7
  349. package/src/core/geom/3d/line/line3_compute_segment_segment_closest_points.js +88 -88
  350. package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -71
  351. package/src/core/geom/3d/line/segment3_point_distance_to_segments.js +25 -25
  352. package/src/core/geom/3d/mat4/allocate_m4.js +7 -7
  353. package/src/core/geom/3d/mat4/apply_mat4_transform_to_direction_v3_array.js +7 -7
  354. package/src/core/geom/3d/mat4/apply_mat4_transform_to_v3_array.js +7 -7
  355. package/src/core/geom/3d/mat4/compose_matrix4_array.js +7 -7
  356. package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +7 -7
  357. package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +7 -7
  358. package/src/core/geom/3d/mat4/m4_compose.js +22 -22
  359. package/src/core/geom/3d/mat4/m4_compute_normal_matrix3.js +7 -7
  360. package/src/core/geom/3d/mat4/m4_decompose.js +64 -64
  361. package/src/core/geom/3d/mat4/m4_decompose_array.js +157 -157
  362. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +32 -32
  363. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -67
  364. package/src/core/geom/3d/mat4/m4_linear_matrix3.js +29 -29
  365. package/src/core/geom/3d/mat4/m4_look_at.js +96 -96
  366. package/src/core/geom/3d/mat4/m4_make_rotation_x.js +47 -47
  367. package/src/core/geom/3d/mat4/m4_make_rotation_y.js +47 -47
  368. package/src/core/geom/3d/mat4/m4_make_rotation_z.js +47 -47
  369. package/src/core/geom/3d/mat4/m4_normal_matrix3.js +39 -39
  370. package/src/core/geom/3d/mat4/m4_perspective.js +66 -66
  371. package/src/core/geom/3d/mat4/m4_transpose.js +48 -48
  372. package/src/core/geom/3d/morton/mortonEncode_LUT.js +7 -7
  373. package/src/core/geom/3d/morton/mortonEncode_magicbits.js +7 -7
  374. package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -5
  375. package/src/core/geom/3d/normal/hemioct/encode_unit_to_hemioct.js +28 -28
  376. package/src/core/geom/3d/normal/lambert_azimuth/decode_azimuth_to_unit.js +30 -30
  377. package/src/core/geom/3d/normal/lambert_azimuth/encode_unit_to_azimuth.js +19 -19
  378. package/src/core/geom/3d/normal/spherical/decode_spheremap_to_unit.js +38 -38
  379. package/src/core/geom/3d/normal/spherical/encode_unit_to_spheremap.js +30 -30
  380. package/src/core/geom/3d/plane/is_point_within_planes.js +7 -7
  381. package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -47
  382. package/src/core/geom/3d/plane/plane3_compute_convex_3_plane_intersection.js +7 -7
  383. package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +7 -7
  384. package/src/core/geom/3d/plane/plane3_intersect_plane.js +7 -7
  385. package/src/core/geom/3d/plane/plane3_three_compute_convex_3_plane_intersection.js +7 -7
  386. package/src/core/geom/3d/point/point3_raycast.d.ts +33 -0
  387. package/src/core/geom/3d/point/point3_raycast.d.ts.map +1 -0
  388. package/src/core/geom/3d/point/point3_raycast.js +47 -0
  389. package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -60
  390. package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -94
  391. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts +40 -0
  392. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts.map +1 -0
  393. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.js +94 -0
  394. package/src/core/geom/3d/quadric/QUADRIC3_SIZE.js +14 -14
  395. package/src/core/geom/3d/quadric/Quadric3.js +282 -282
  396. package/src/core/geom/3d/quadric/quadric3_add.js +33 -33
  397. package/src/core/geom/3d/quadric/quadric3_clear.js +25 -25
  398. package/src/core/geom/3d/quadric/quadric3_copy.js +27 -27
  399. package/src/core/geom/3d/quadric/quadric3_distance.js +36 -36
  400. package/src/core/geom/3d/quadric/quadric3_evaluate.js +25 -25
  401. package/src/core/geom/3d/quadric/quadric3_optimize.js +45 -45
  402. package/src/core/geom/3d/quadric/quadric3_scale.js +32 -32
  403. package/src/core/geom/3d/quadric/quadric3_set_from_plane_through_point.js +30 -30
  404. package/src/core/geom/3d/quadric/quadric3_set_from_vector4.js +35 -35
  405. package/src/core/geom/3d/quadric/quadric3_sub.js +36 -36
  406. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3.js +28 -28
  407. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3_inverse.js +56 -56
  408. package/src/core/geom/3d/quadric/quadric3_to_vector3.js +19 -19
  409. package/src/core/geom/3d/quaternion/quat3_createFromAxisAngle.js +7 -7
  410. package/src/core/geom/3d/quaternion/quat3_integrate.js +62 -62
  411. package/src/core/geom/3d/quaternion/quat3_multiply.js +31 -31
  412. package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -72
  413. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -69
  414. package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +7 -7
  415. package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +7 -7
  416. package/src/core/geom/3d/ray/ray3_array_compose.js +7 -7
  417. package/src/core/geom/3d/ray/ray3_compute_nearest_point_to_point.js +7 -7
  418. package/src/core/geom/3d/ray/ray3_distance_to_point.js +7 -7
  419. package/src/core/geom/3d/ray/ray3_shift_origin_along_direction.js +7 -7
  420. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +17 -0
  421. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  422. package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -0
  423. package/src/core/geom/3d/shape/CylinderShape3D.d.ts +1 -0
  424. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  425. package/src/core/geom/3d/shape/CylinderShape3D.js +21 -0
  426. package/src/core/geom/3d/shape/PointShape3D.d.ts +7 -0
  427. package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
  428. package/src/core/geom/3d/shape/PointShape3D.js +88 -63
  429. package/src/core/geom/3d/shape/PosedShape3D.d.ts +6 -0
  430. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  431. package/src/core/geom/3d/shape/PosedShape3D.js +27 -0
  432. package/src/core/geom/3d/shape/TransformedShape3D.d.ts +10 -0
  433. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  434. package/src/core/geom/3d/shape/TransformedShape3D.js +13 -0
  435. package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -0
  436. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
  437. package/src/core/geom/3d/shape/Triangle3D.js +26 -0
  438. package/src/core/geom/3d/shape/UnionShape3D.d.ts +6 -0
  439. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  440. package/src/core/geom/3d/shape/UnionShape3D.js +7 -0
  441. package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -45
  442. package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -44
  443. package/src/core/geom/3d/shape/shape3_mesh_from_geometry.js +64 -64
  444. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +7 -7
  445. package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -51
  446. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +7 -7
  447. package/src/core/geom/3d/sphere/compute_bounding_sphere_of_2_spheres.js +7 -7
  448. package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -53
  449. package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -30
  450. package/src/core/geom/3d/sphere/harmonics/sh3_rotate_bl.js +7 -7
  451. package/src/core/geom/3d/sphere/harmonics/sh_compute_K.js +7 -7
  452. package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +7 -7
  453. package/src/core/geom/3d/sphere/sphere_array_intersects_ray.js +7 -7
  454. package/src/core/geom/3d/sphere/sphere_intersects_point.js +7 -7
  455. package/src/core/geom/3d/sphere/sphere_matrix4_transform.js +5 -5
  456. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +7 -7
  457. package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +7 -7
  458. package/src/core/geom/3d/sphere/spherical_from_cartesian.js +7 -7
  459. package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +174 -174
  460. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +7 -7
  461. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_volume.js +7 -7
  462. package/src/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +7 -7
  463. package/src/core/geom/3d/tetrahedra/graphics/build_tetrahedral_mesh_geometry.js +39 -39
  464. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -48
  465. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +173 -173
  466. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_compute_tet_quality.js +22 -22
  467. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -146
  468. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -232
  469. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -255
  470. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -387
  471. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -140
  472. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +7 -7
  473. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -97
  474. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -66
  475. package/src/core/geom/3d/tetrahedra/triangle/triangle_decode_depth.js +59 -59
  476. package/src/core/geom/3d/tetrahedra/triangle/triangle_rasterized_depth_sample_index.js +54 -54
  477. package/src/core/geom/3d/tetrahedra/validate_neighbour.js +70 -70
  478. package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +314 -314
  479. package/src/core/geom/3d/topology/struct/binary/BinaryDataLayer.js +364 -364
  480. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +567 -567
  481. package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +1085 -1085
  482. package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -73
  483. package/src/core/geom/3d/topology/struct/binary/io/bt_loop_kill.js +35 -35
  484. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_append.js +119 -119
  485. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +84 -84
  486. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +172 -172
  487. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -56
  488. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +7 -7
  489. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +227 -227
  490. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +161 -161
  491. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +490 -490
  492. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +120 -120
  493. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +120 -120
  494. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -52
  495. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +115 -115
  496. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_flip.js +142 -142
  497. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -21
  498. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +92 -92
  499. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_split.js +179 -179
  500. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -28
  501. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_kill_only_edge.js +8 -8
  502. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -83
  503. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +89 -89
  504. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_kill.js +25 -25
  505. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +127 -127
  506. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -268
  507. package/src/core/geom/3d/topology/struct/binary/io/face/bt_kill_only_face.js +8 -8
  508. package/src/core/geom/3d/topology/struct/binary/io/face/bt_mesh_kill_degenerate_faces.js +72 -72
  509. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_kill_only_vert.js +7 -7
  510. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js +7 -7
  511. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js +48 -48
  512. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_split_pinched_vertices.js +256 -256
  513. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js +77 -77
  514. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +7 -7
  515. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_kill.js +7 -7
  516. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +148 -148
  517. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_kill_only.js +7 -7
  518. package/src/core/geom/3d/topology/struct/binary/query/bt_collect_boundary_segments.js +29 -29
  519. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_breaks_link_condition.js +107 -107
  520. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_is_legal.js +41 -41
  521. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_would_fold.js +120 -120
  522. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -42
  523. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_is_boundary.js +20 -20
  524. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_read_diamond.js +96 -96
  525. package/src/core/geom/3d/topology/struct/binary/query/bt_face_compute_newell_normal.js +72 -72
  526. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_attached_faces.js +7 -7
  527. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces.js +39 -39
  528. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces_weighted.js +93 -93
  529. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_neighbour_faces.js +7 -7
  530. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_shared_loop.js +48 -48
  531. package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -45
  532. package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -66
  533. package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +7 -7
  534. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -44
  535. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +32 -32
  536. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -53
  537. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -703
  538. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +181 -181
  539. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +133 -133
  540. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -82
  541. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -599
  542. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -108
  543. package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +7 -7
  544. package/src/core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js +7 -7
  545. package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +7 -7
  546. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +7 -7
  547. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +7 -7
  548. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricGeometry.js +7 -7
  549. package/src/core/geom/3d/triangle/compute_triangle_area_3d.js +7 -7
  550. package/src/core/geom/3d/triangle/compute_triangle_normal.js +7 -7
  551. package/src/core/geom/3d/triangle/rayTriangleIntersection.js +7 -7
  552. package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -65
  553. package/src/core/geom/3d/triangle/tri3_closest_point_barycentric.js +171 -171
  554. package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -79
  555. package/src/core/geom/3d/triangle/tri3_ray_intersection.js +160 -160
  556. package/src/core/geom/3d/triangle/tri3_ray_intersection_barycentric.js +96 -96
  557. package/src/core/geom/3d/triangle/tri3_raycast.d.ts +42 -0
  558. package/src/core/geom/3d/triangle/tri3_raycast.d.ts.map +1 -0
  559. package/src/core/geom/3d/triangle/tri3_raycast.js +94 -0
  560. package/src/core/geom/3d/triangle/triangle3_incenter.js +7 -7
  561. package/src/core/geom/3d/triangle/triangle_compute_plane_side.js +7 -7
  562. package/src/core/geom/3d/triangle/triangle_compute_signed_volume.js +7 -7
  563. package/src/core/geom/3d/triangle/triangle_intersects_clipping_volume.js +7 -7
  564. package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +7 -7
  565. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +7 -7
  566. package/src/core/geom/3d/util/make_justified_point_grid.js +7 -7
  567. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +381 -381
  568. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +684 -684
  569. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -554
  570. package/src/core/geom/mat3/m3_cm_from_translation.js +7 -7
  571. package/src/core/geom/mat3/m3_cm_invert.js +7 -7
  572. package/src/core/geom/mat3/m3_make_rotation.js +5 -5
  573. package/src/core/geom/mat3/m3_make_rotation_TBN.js +31 -31
  574. package/src/core/geom/mat3/m3_multiply_vec3.js +7 -7
  575. package/src/core/geom/packing/computeBoundingSphereOfSpheres.js +7 -7
  576. package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +7 -7
  577. package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +7 -7
  578. package/src/core/geom/packing/max-rect/cutArea.js +7 -7
  579. package/src/core/geom/packing/max-rect/findBestContainer.js +7 -7
  580. package/src/core/geom/packing/max-rect/packMaxRectangles.js +7 -7
  581. package/src/core/geom/packing/max-rect/packOneBox.js +7 -7
  582. package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +7 -7
  583. package/src/core/geom/packing/max-rect/removeRedundantBoxesArray.js +7 -7
  584. package/src/core/geom/random/randomPointInBox.js +7 -7
  585. package/src/core/geom/random/randomPointInCircle.js +7 -7
  586. package/src/core/geom/random/randomPointInPoint.js +7 -7
  587. package/src/core/geom/random/randomPointInSphere.js +7 -7
  588. package/src/core/geom/random/randomPointOnBox.js +7 -7
  589. package/src/core/geom/random/randomPointOnSphere.js +7 -7
  590. package/src/core/geom/random/random_orthonormal_basis.js +31 -31
  591. package/src/core/geom/vec/vector_axpy.js +15 -15
  592. package/src/core/geom/vec/vector_axpy_offset.js +20 -20
  593. package/src/core/geom/vec/vector_copy.js +7 -7
  594. package/src/core/geom/vec/vector_dot_offset.js +25 -25
  595. package/src/core/geom/vec/vector_scale_array.js +7 -7
  596. package/src/core/geom/vec2/v2_rotate.js +21 -21
  597. package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -21
  598. package/src/core/geom/vec3/v3_angle_atan2_between.js +7 -7
  599. package/src/core/geom/vec3/v3_angle_between.js +7 -7
  600. package/src/core/geom/vec3/v3_angle_cos_between.js +7 -7
  601. package/src/core/geom/vec3/v3_array_add_in_place.js +7 -7
  602. package/src/core/geom/vec3/v3_array_centroid.js +7 -7
  603. package/src/core/geom/vec3/v3_array_copy.js +7 -7
  604. package/src/core/geom/vec3/v3_array_displace_in_direction.js +7 -7
  605. package/src/core/geom/vec3/v3_array_equals.js +7 -7
  606. package/src/core/geom/vec3/v3_array_immediate_add.js +7 -7
  607. package/src/core/geom/vec3/v3_array_matrix4_rotate.js +7 -7
  608. package/src/core/geom/vec3/v3_array_normalize.js +7 -7
  609. package/src/core/geom/vec3/v3_array_scale.js +7 -7
  610. package/src/core/geom/vec3/v3_compute_interior_angle.js +7 -7
  611. package/src/core/geom/vec3/v3_cotangent.js +33 -33
  612. package/src/core/geom/vec3/v3_dot_array_array.js +7 -7
  613. package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -30
  614. package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -49
  615. package/src/core/geom/vec3/v3_matrix4_rotate.js +7 -7
  616. package/src/core/geom/vec3/v3_multiply.js +23 -23
  617. package/src/core/geom/vec3/v3_multiply_scalar.js +7 -7
  618. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +49 -49
  619. package/src/core/geom/vec3/v3_quat3_apply.js +7 -7
  620. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +7 -7
  621. package/src/core/geom/vec3/v3_quaternion_apply.js +39 -39
  622. package/src/core/geom/vec3/v3_quaternion_apply_inverse.js +41 -41
  623. package/src/core/geom/vec3/v3_reflect.js +24 -24
  624. package/src/core/geom/vec3/v3_scale_array.js +18 -18
  625. package/src/core/geom/vec3/v3_triple_cross.js +45 -45
  626. package/src/core/geom/vec3/v3_triple_cross_product.js +7 -7
  627. package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -21
  628. package/src/core/geom/vec4/v4_multiply_mat4.js +7 -7
  629. package/src/core/graph/csr/csr_graph_build_distance_field.js +112 -112
  630. package/src/core/graph/csr/csr_graph_find_edge.js +25 -25
  631. package/src/core/graph/csr/csr_graph_find_shortest_path.js +105 -105
  632. package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -94
  633. package/src/core/graph/graph_k_means_cluster.js +250 -250
  634. package/src/core/graph/layout/CircleLayout.js +1 -1
  635. package/src/core/graph/layout/box/BoxLayouter.js +1 -1
  636. package/src/core/graph/layout/graph_peel_topological_layer.js +45 -45
  637. package/src/core/graph/metis/BinaryTopologyFaceGraph.js +49 -49
  638. package/src/core/graph/metis/bt_mesh_build_face_graph.js +111 -111
  639. package/src/core/graph/metis/cluster_mesh_metis.js +137 -137
  640. package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -100
  641. package/src/core/graph/metis/native/bisection/compute_2way_params.js +86 -86
  642. package/src/core/graph/metis/native/bisection/fm_2way.js +297 -297
  643. package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -137
  644. package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -119
  645. package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -94
  646. package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -158
  647. package/src/core/graph/metis/native/coarsen/match_shem.js +175 -175
  648. package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -122
  649. package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -170
  650. package/src/core/graph/metis/native/metis_partition_kway.js +126 -126
  651. package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -53
  652. package/src/core/graph/metis/native/refine/fm_kway.js +487 -487
  653. package/src/core/graph/metis/native/refine/project_kway.js +43 -43
  654. package/src/core/graph/metis/native/refine/refine_kway.js +43 -43
  655. package/src/core/lang/reactive/pegjs/parser.js +3458 -3458
  656. package/src/core/math/complex/complex_horner_eval.js +31 -31
  657. package/src/core/math/computeWholeDivisorLow.js +33 -33
  658. package/src/core/math/f32_next_down.js +24 -24
  659. package/src/core/math/f32_next_up.js +27 -27
  660. package/src/core/math/hash/hash_mix2.js +22 -22
  661. package/src/core/math/hash/hash_mix3.js +19 -19
  662. package/src/core/math/linalg/cg/cg_solve.js +106 -106
  663. package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -59
  664. package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -43
  665. package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -122
  666. package/src/core/math/linalg/eigen/matrix_qr_in_place.js +190 -190
  667. package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -117
  668. package/src/core/math/linalg/givens/givens_apply_rows.js +28 -28
  669. package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -30
  670. package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -39
  671. package/src/core/math/linalg/lu_factor_linear_system.js +108 -108
  672. package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
  673. package/src/core/math/linalg/polynomial_add_into.js +29 -29
  674. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +159 -159
  675. package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -16
  676. package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -19
  677. package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -14
  678. package/src/core/math/linalg/polynomial_multiply.js +41 -41
  679. package/src/core/math/linalg/polynomial_real_roots_in_interval.js +211 -211
  680. package/src/core/math/linalg/polynomial_root_bound_cauchy.js +30 -30
  681. package/src/core/math/linalg/polynomial_scale_into.js +17 -17
  682. package/src/core/math/linalg/polynomial_sub_into.js +29 -29
  683. package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -96
  684. package/src/core/math/linalg/sor_optimal_omega.js +42 -42
  685. package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -88
  686. package/src/core/math/linalg/sparse/spm_matvec.js +22 -22
  687. package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -29
  688. package/src/core/math/lookup/LUT_HEATMAP.js +30 -30
  689. package/src/core/math/lookup/ParameterLookupTable.js +495 -495
  690. package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -6
  691. package/src/core/math/noise/curl_noise_3d.js +18 -18
  692. package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -28
  693. package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -48
  694. package/src/core/math/physics/kinematics/computeInterceptPoint.js +79 -79
  695. package/src/core/math/random/randomSeed.js +16 -16
  696. package/src/core/math/random/random_pick_weighted_index.js +26 -26
  697. package/src/core/math/round_half_away_from_zero.js +13 -13
  698. package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -107
  699. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +125 -125
  700. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +50 -50
  701. package/src/core/math/spline/spline3_hermite_reverse.js +49 -49
  702. package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -37
  703. package/src/core/math/statistics/hammersley_sequence.js +53 -53
  704. package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
  705. package/src/core/path/convertPathToURL.js +107 -107
  706. package/src/core/process/delay.js +16 -16
  707. package/src/core/science/units/MEASUREMENT_UNITS_SCIENTIFIC.js +87 -87
  708. package/src/core/science/units/MEASUREMENT_UNITS_SI.js +17 -17
  709. package/src/core/science/units/NamedUnit.js +48 -48
  710. package/src/core/science/units/UNIT_DIMENSION_MAPPING.js +29 -29
  711. package/src/core/science/units/UnitDimension.js +28 -28
  712. package/src/core/science/units/format_quantity.js +74 -74
  713. package/src/core/science/units/magnitude_prefix.js +127 -127
  714. package/src/core/science/units/si/AMPERE.js +13 -13
  715. package/src/core/science/units/si/CANDELA.js +13 -13
  716. package/src/core/science/units/si/DIMENSIONLESS.js +14 -14
  717. package/src/core/science/units/si/KELVIN.js +13 -13
  718. package/src/core/science/units/si/KILOGRAM.js +13 -13
  719. package/src/core/science/units/si/METER.js +13 -13
  720. package/src/core/science/units/si/MOLE.js +13 -13
  721. package/src/core/science/units/si/SECOND.js +13 -13
  722. package/src/core/science/units/si/derived/BECQUEREL.js +16 -16
  723. package/src/core/science/units/si/derived/COULOMB.js +13 -13
  724. package/src/core/science/units/si/derived/FARAD.js +13 -13
  725. package/src/core/science/units/si/derived/GRAY.js +16 -16
  726. package/src/core/science/units/si/derived/HENRY.js +13 -13
  727. package/src/core/science/units/si/derived/HERTZ.js +16 -16
  728. package/src/core/science/units/si/derived/JOULE.js +13 -13
  729. package/src/core/science/units/si/derived/KATAL.js +13 -13
  730. package/src/core/science/units/si/derived/LUMEN.js +14 -14
  731. package/src/core/science/units/si/derived/LUX.js +13 -13
  732. package/src/core/science/units/si/derived/NEWTON.js +13 -13
  733. package/src/core/science/units/si/derived/OHM.js +13 -13
  734. package/src/core/science/units/si/derived/PASCAL.js +13 -13
  735. package/src/core/science/units/si/derived/SIEMENS.js +13 -13
  736. package/src/core/science/units/si/derived/SIEVERT.js +16 -16
  737. package/src/core/science/units/si/derived/TESLA.js +13 -13
  738. package/src/core/science/units/si/derived/VOLT.js +13 -13
  739. package/src/core/science/units/si/derived/WATT.js +13 -13
  740. package/src/core/science/units/si/derived/WEBER.js +13 -13
  741. package/src/core/science/units/unit_matrix_to_string.js +403 -403
  742. package/src/engine/Engine.js +726 -726
  743. package/src/engine/EngineHarness.js +590 -590
  744. package/src/engine/REVIEW_2026_07_03.md +211 -211
  745. package/src/engine/animation/AnimatedActions.js +81 -81
  746. package/src/engine/animation/AnimationUtils.js +252 -252
  747. package/src/engine/animation/async/TimeSeries.js +300 -300
  748. package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -60
  749. package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -22
  750. package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -38
  751. package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -22
  752. package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -22
  753. package/src/engine/asset/GameAssetType.d.ts +0 -1
  754. package/src/engine/asset/GameAssetType.js +0 -1
  755. package/src/engine/asset/load_model_scene_bundle.js +27 -27
  756. package/src/engine/asset/loaders/font/FontAsset.js +21 -21
  757. package/src/engine/asset/loaders/font/FontAssetLoader.js +20 -20
  758. package/src/engine/asset/loaders/gltf_test_fixtures.js +358 -358
  759. package/src/engine/asset/loaders/image/ImageBitmapAssetLoader.js +52 -52
  760. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +94 -94
  761. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -59
  762. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -55
  763. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -74
  764. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -34
  765. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -46
  766. package/src/engine/control/first-person/DESIGN.md +640 -640
  767. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -563
  768. package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -134
  769. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2045 -2045
  770. package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -30
  771. package/src/engine/control/first-person/math/Spring.js +71 -71
  772. package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -23
  773. package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -41
  774. package/src/engine/control/first-person/math/computeMassRatios.js +44 -44
  775. package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -34
  776. package/src/engine/control/first-person/math/dampedSpringStep.js +72 -72
  777. package/src/engine/control/first-person/math/jumpDynamics.js +108 -108
  778. package/src/engine/control/first-person/math/stepTowards.js +20 -20
  779. package/src/engine/control/first-person/pose/FirstPersonPose.js +137 -137
  780. package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -27
  781. package/src/engine/control/first-person/test/buildTestPlayer.js +36 -36
  782. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -531
  783. package/src/engine/ecs/EntityManager.d.ts +7 -3
  784. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  785. package/src/engine/ecs/EntityManager.js +10 -5
  786. package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
  787. package/src/engine/ecs/async/SystemWorkerHost.js +329 -329
  788. package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -143
  789. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -246
  790. package/src/engine/ecs/async/WorkerSystem.js +702 -702
  791. package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -150
  792. package/src/engine/ecs/fow/FogOfWar.js +707 -707
  793. package/src/engine/ecs/fow/FogOfWarEdgeMode.js +40 -40
  794. package/src/engine/ecs/fow/fog_of_war_mask_world_bounds.js +24 -24
  795. package/src/engine/ecs/fow/fog_of_war_uv_transform.js +30 -30
  796. package/src/engine/ecs/gui/hud/testHudEdgeStick.js +132 -134
  797. package/src/engine/ecs/hierarchy/EntityChildIndex.js +140 -140
  798. package/src/engine/ecs/ik/IKProblem.js +42 -42
  799. package/src/engine/ecs/ik/InverseKinematicsSystem.js +193 -193
  800. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +134 -134
  801. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +97 -97
  802. package/src/engine/ecs/ik/ik_bone_chain.js +85 -85
  803. package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -119
  804. package/src/engine/ecs/parent/EntityNode.d.ts +1 -1
  805. package/src/engine/ecs/parent/EntityNode.js +2 -2
  806. package/src/engine/ecs/parent/ParentEntitySystem.js +255 -255
  807. package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
  808. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  809. package/src/engine/ecs/speaker/VoiceSystem.js +13 -7
  810. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +269 -269
  811. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -315
  812. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -331
  813. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -199
  814. package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -57
  815. package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -56
  816. package/src/engine/ecs/storage/binary/object-v2/README.md +110 -110
  817. package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -48
  818. package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -44
  819. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +265 -265
  820. package/src/engine/ecs/terrain/TerrainClouds.js +97 -97
  821. package/src/engine/ecs/terrain/ecs/StubTerrainBuildWorker.js +65 -65
  822. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +4 -0
  823. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  824. package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
  825. package/src/engine/ecs/terrain/ecs/TerrainSystem.js +209 -209
  826. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +316 -316
  827. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +416 -416
  828. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +525 -525
  829. package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +302 -302
  830. package/src/engine/ecs/terrain/tiles/TerrainTile.js +561 -561
  831. package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +896 -896
  832. package/src/engine/ecs/terrain/tiles/tile_geometry_to_meshlets.js +36 -36
  833. package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
  834. package/src/engine/ecs/transform/Transform64.js +814 -814
  835. package/src/engine/ecs/transform/t64_copy_from_transform.js +35 -35
  836. package/src/engine/ecs/transform/t64_equals_transform.js +55 -55
  837. package/src/engine/ecs/transform/t64_look_rotation.js +36 -36
  838. package/src/engine/ecs/transform/t64_transform_direction.js +25 -25
  839. package/src/engine/ecs/transform/transform_copy_from_t64.js +28 -28
  840. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +345 -345
  841. package/src/engine/ecs/transform-attachment/transform_attachment_find_descendant_by_name.js +65 -65
  842. package/src/engine/ecs/transform-attachment/transform_attachment_parent_of.js +24 -24
  843. package/src/engine/ecs/util/hideEntityGracefully.js +208 -208
  844. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -446
  845. package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -14
  846. package/src/engine/graphics/FrameThrottle.js +2 -2
  847. package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +207 -207
  848. package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +151 -151
  849. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -22
  850. package/src/engine/graphics/ecs/camera/Camera.js +165 -165
  851. package/src/engine/graphics/ecs/camera/camera_active_frustum.js +25 -25
  852. package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
  853. package/src/engine/graphics/ecs/camera/camera_traverse_active.js +18 -18
  854. package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +170 -170
  855. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -68
  856. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +296 -296
  857. package/src/engine/graphics/ecs/decal/v2/Decal.js +220 -220
  858. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +56 -56
  859. package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_0_1.js +38 -38
  860. package/src/engine/graphics/ecs/light/shadow/compute_view_frustum_aabb_in_space.js +62 -62
  861. package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +77 -77
  862. package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -33
  863. package/src/engine/graphics/ecs/mesh-v2/DeferredBoundsQueue.js +88 -88
  864. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
  865. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +303 -303
  866. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +1 -1
  867. package/src/engine/graphics/ecs/mesh-v2/aggregate/serialization/SGMeshSerializationAdapter.js +39 -39
  868. package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +36 -36
  869. package/src/engine/graphics/ecs/path/tube/build/GeometryStream.js +94 -94
  870. package/src/engine/graphics/ecs/path/tube/build/build_stream_catmullrom.js +310 -310
  871. package/src/engine/graphics/ecs/path/tube/build/build_stream_linear.js +73 -73
  872. package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +199 -199
  873. package/src/engine/graphics/ecs/path/tube/build/makeTubeStream.js +217 -217
  874. package/src/engine/graphics/ecs/path/tube/build/make_cap.js +340 -340
  875. package/src/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -40
  876. package/src/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +153 -153
  877. package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -64
  878. package/src/engine/graphics/ecs/trail3d/Trail3D.js +483 -483
  879. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +26 -26
  880. package/src/engine/graphics/ecs/trail3d/advance_trail.js +42 -42
  881. package/src/engine/graphics/ecs/trail3d/make_gradient_stroke.js +58 -58
  882. package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +90 -90
  883. package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +75 -75
  884. package/src/engine/graphics/ecs/trail3d/trail_head_position.js +19 -19
  885. package/src/engine/graphics/ecs/water/Water.js +104 -104
  886. package/src/engine/graphics/geometry/AttributeData.js +84 -84
  887. package/src/engine/graphics/geometry/buffered/geometry_compute_vertex_normals_indexed.js +49 -49
  888. package/src/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +288 -288
  889. package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -87
  890. package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -102
  891. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_buffered_geometry.js +52 -52
  892. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_indexed_geometry.js +20 -20
  893. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_unindexed_geometry.js +19 -19
  894. package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -135
  895. package/src/engine/graphics/impostors/voxel/README.md +149 -149
  896. package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -141
  897. package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -20
  898. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +2 -0
  899. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
  900. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +2 -0
  901. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
  902. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +2 -0
  903. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
  904. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts +1 -2
  905. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts.map +1 -1
  906. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +2 -0
  907. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
  908. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +2 -0
  909. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
  910. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +2 -0
  911. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
  912. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +2 -0
  913. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
  914. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +2 -0
  915. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
  916. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +2 -0
  917. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
  918. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +2 -0
  919. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
  920. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +2 -0
  921. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
  922. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +2 -0
  923. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
  924. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +2 -0
  925. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
  926. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +2 -0
  927. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
  928. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +2 -0
  929. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
  930. package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -45
  931. package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +229 -229
  932. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -10
  933. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
  934. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1149 -1149
  935. package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +472 -472
  936. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -2
  937. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -2
  938. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
  939. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +3 -5
  940. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +135 -135
  941. package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -168
  942. package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -18
  943. package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -2
  944. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +219 -219
  945. package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
  946. package/src/engine/graphics/render/frame_graph/FrameGraphScope.js +72 -72
  947. package/src/engine/graphics/render/frame_graph/FrameGraphState.js +39 -39
  948. package/src/engine/graphics/render/frame_graph/ResourceEntry.js +96 -96
  949. package/src/engine/graphics/render/gizmo/GizmoShapeBuffer.js +105 -105
  950. package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +162 -162
  951. package/src/engine/graphics/render/layers/RenderLayer.js +95 -95
  952. package/src/engine/graphics/sh3/build_injected_shader.js +56 -56
  953. package/src/engine/graphics/sh3/grid_dilate_average.js +175 -175
  954. package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +506 -506
  955. package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +161 -161
  956. package/src/engine/graphics/sh3/path_tracer/PathTracedRenderer.js +205 -205
  957. package/src/engine/graphics/sh3/path_tracer/PathTracedScene.js +698 -698
  958. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +490 -490
  959. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -2
  960. package/src/engine/graphics/sh3/path_tracer/make_sky_hosek.js +45 -45
  961. package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +73 -73
  962. package/src/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +32 -32
  963. package/src/engine/graphics/sh3/path_tracer/texture/apply_texture_clamping_to_coordinate.js +28 -28
  964. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +142 -142
  965. package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -70
  966. package/src/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +388 -388
  967. package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -277
  968. package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -57
  969. package/src/engine/graphics/shadows/vsm.glsl.js +43 -43
  970. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -81
  971. package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -15
  972. package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -26
  973. package/src/engine/graphics/texture/isImageBitmap.js +2 -2
  974. package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -189
  975. package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -51
  976. package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
  977. package/src/engine/graphics/texture/sampler/Sampler2DArray.js +172 -172
  978. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +466 -466
  979. package/src/engine/graphics/texture/sampler/compare/flip/FlipParameters.js +18 -18
  980. package/src/engine/graphics/texture/sampler/compare/flip/FlipPooling.js +33 -33
  981. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_feature_kernel.js +147 -147
  982. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_spatial_kernel.js +177 -177
  983. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_color_difference.js +104 -104
  984. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_feature_difference.js +76 -76
  985. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_pixels_per_degree.js +34 -34
  986. package/src/engine/graphics/texture/sampler/compare/flip/flip_constants.js +77 -77
  987. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_feature.js +48 -48
  988. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_spatial.js +86 -86
  989. package/src/engine/graphics/texture/sampler/compare/flip/flip_pool.js +71 -71
  990. package/src/engine/graphics/texture/sampler/compare/flip/flip_srgb_to_ycxcz.js +55 -55
  991. package/src/engine/graphics/texture/sampler/compare/flip/sampler2d_flip_ldr.js +80 -80
  992. package/src/engine/graphics/texture/sampler/filter/SeparableKernel.js +82 -82
  993. package/src/engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js +122 -122
  994. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +148 -148
  995. package/src/engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js +82 -82
  996. package/src/engine/graphics/texture/sampler/sampler2d_combine.js +67 -67
  997. package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -54
  998. package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -63
  999. package/src/engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js +33 -33
  1000. package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -68
  1001. package/src/engine/graphics/texture/shade_image_preview.js +103 -103
  1002. package/src/engine/graphics/trail/TrailSpawnMode.js +20 -20
  1003. package/src/engine/graphics/trail/tube/TubeX.js +573 -573
  1004. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +58 -58
  1005. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +34 -34
  1006. package/src/engine/graphics/trail/tube/tube_attributes_spec.js +69 -69
  1007. package/src/engine/graphics/trail/tube/tube_frame.js +83 -83
  1008. package/src/engine/graphics3/AnimationGraphSystem3.js +329 -329
  1009. package/src/engine/graphics3/AnimationSystem3.js +352 -352
  1010. package/src/engine/graphics3/CameraSystem3.js +170 -170
  1011. package/src/engine/graphics3/DecalSystem3.js +441 -441
  1012. package/src/engine/graphics3/FogOfWarSystem3.js +265 -265
  1013. package/src/engine/graphics3/GraphicsEngine3.js +776 -776
  1014. package/src/engine/graphics3/HighlightSystem3.js +241 -241
  1015. package/src/engine/graphics3/LightSystem3.js +211 -211
  1016. package/src/engine/graphics3/MeshSystem3.js +646 -646
  1017. package/src/engine/graphics3/ParticleEmitterSystem3.js +289 -289
  1018. package/src/engine/graphics3/PathDisplaySystem3.js +309 -309
  1019. package/src/engine/graphics3/PickingQuery.js +48 -48
  1020. package/src/engine/graphics3/PickingResult.js +47 -47
  1021. package/src/engine/graphics3/PickingSystem3.js +273 -273
  1022. package/src/engine/graphics3/ShadeCameraAdapter.js +55 -55
  1023. package/src/engine/graphics3/ShadedGeometrySystem3.js +169 -169
  1024. package/src/engine/graphics3/TerrainSystem3.js +566 -566
  1025. package/src/engine/graphics3/TooltipComponentSystem3.js +328 -328
  1026. package/src/engine/graphics3/Trail3DSystem3.js +221 -221
  1027. package/src/engine/graphics3/WaterSystem3.js +293 -293
  1028. package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
  1029. package/src/engine/graphics3/animation/wrap_clip_time.js +23 -23
  1030. package/src/engine/graphics3/animation/write_graph_playback.js +48 -48
  1031. package/src/engine/graphics3/camera_sync_from_transform.js +29 -29
  1032. package/src/engine/graphics3/debug/gizmo_view_from_camera.js +48 -48
  1033. package/src/engine/graphics3/decal/DECAL_CLUSTER.js +61 -61
  1034. package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +117 -117
  1035. package/src/engine/graphics3/decal/DecalAtlas.js +194 -194
  1036. package/src/engine/graphics3/decal/DecalAtlasFill.js +32 -32
  1037. package/src/engine/graphics3/decal/GPUDecalRenderer.js +360 -360
  1038. package/src/engine/graphics3/decal/chunk_decal_intersects_frustum.js +59 -59
  1039. package/src/engine/graphics3/decal/chunk_decal_sampling_info.js +57 -57
  1040. package/src/engine/graphics3/decal/chunk_decal_surface_frame.js +54 -54
  1041. package/src/engine/graphics3/decal/decal_atlas_border.js +69 -69
  1042. package/src/engine/graphics3/decal/decal_gpu_records.js +120 -120
  1043. package/src/engine/graphics3/decal/decal_material_samplers.js +109 -109
  1044. package/src/engine/graphics3/decal/graph_build_decal_clusters.js +212 -212
  1045. package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +207 -207
  1046. package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +108 -108
  1047. package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +151 -151
  1048. package/src/engine/graphics3/highlight/HIGHLIGHT_OUTLINE.js +23 -23
  1049. package/src/engine/graphics3/highlight/chunk_highlight_lookup.js +53 -53
  1050. package/src/engine/graphics3/highlight/pack_highlight_color.js +44 -44
  1051. package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
  1052. package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
  1053. package/src/engine/graphics3/load_cube_environment.js +68 -68
  1054. package/src/engine/graphics3/make_default_environment.js +107 -107
  1055. package/src/engine/graphics3/particles/particle_gpu_records.js +221 -221
  1056. package/src/engine/graphics3/path/build_tube_path_streams.js +98 -98
  1057. package/src/engine/graphics3/path/tube_stream_write_geometry.js +90 -90
  1058. package/src/engine/graphics3/preview/ModelPreviewFraming.js +35 -35
  1059. package/src/engine/graphics3/preview/make_model_preview_scene.js +154 -154
  1060. package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
  1061. package/src/engine/graphics3/preview/model_preview_framing.js +70 -70
  1062. package/src/engine/graphics3/shade_bundle_bounds.js +55 -55
  1063. package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
  1064. package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
  1065. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.js +481 -481
  1066. package/src/engine/graphics3/terrain/chunk_terrain_grid_overlay.js +61 -61
  1067. package/src/engine/graphics3/terrain/chunk_terrain_splat_mix.js +66 -66
  1068. package/src/engine/graphics3/terrain/pack_terrain_row_table.js +53 -53
  1069. package/src/engine/graphics3/terrain/shader_terrain_splat.js +215 -215
  1070. package/src/engine/graphics3/trail/trail_tube_write_geometry.js +158 -158
  1071. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -717
  1072. package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
  1073. package/src/engine/input/devices/GamepadDevice.js +466 -466
  1074. package/src/engine/input/devices/ManualInputDevice.js +152 -152
  1075. package/src/engine/input/devices/PointerCoordinates.js +26 -26
  1076. package/src/engine/input/devices/events/GamepadEvents.js +10 -10
  1077. package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
  1078. package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
  1079. package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
  1080. package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
  1081. package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
  1082. package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
  1083. package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
  1084. package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
  1085. package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
  1086. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
  1087. package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
  1088. package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
  1089. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
  1090. package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
  1091. package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
  1092. package/src/engine/input/ecs/components/Input.js +118 -118
  1093. package/src/engine/input/ecs/components/InputController.js +78 -78
  1094. package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
  1095. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
  1096. package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
  1097. package/src/engine/input/ecs/ism/InputBinding.js +71 -71
  1098. package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
  1099. package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
  1100. package/src/engine/input/ecs/ism/InputDirection.js +33 -33
  1101. package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
  1102. package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
  1103. package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
  1104. package/src/engine/input/ecs/ism/InputSource.js +67 -67
  1105. package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
  1106. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
  1107. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
  1108. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
  1109. package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
  1110. package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
  1111. package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
  1112. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
  1113. package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
  1114. package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
  1115. package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
  1116. package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
  1117. package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
  1118. package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
  1119. package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
  1120. package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
  1121. package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
  1122. package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
  1123. package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
  1124. package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
  1125. package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
  1126. package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
  1127. package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
  1128. package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
  1129. package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
  1130. package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
  1131. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
  1132. package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
  1133. package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
  1134. package/src/engine/input/ecs/systems/InputSystem.js +162 -162
  1135. package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
  1136. package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
  1137. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
  1138. package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
  1139. package/src/engine/interpolation/Interpoland.js +49 -49
  1140. package/src/engine/interpolation/Interpolated.js +149 -149
  1141. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
  1142. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
  1143. package/src/engine/interpolation/pose_interpoland.js +27 -27
  1144. package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
  1145. package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
  1146. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
  1147. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
  1148. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
  1149. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
  1150. package/src/engine/network/CONGESTION_CONTROL.md +87 -87
  1151. package/src/engine/network/README.md +52 -52
  1152. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
  1153. package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
  1154. package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
  1155. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
  1156. package/src/engine/network/convertPathToURL.js +2 -2
  1157. package/src/engine/network/core/MalformedPacketError.js +26 -26
  1158. package/src/engine/network/core/quantize/quantize_float.js +73 -73
  1159. package/src/engine/network/core/quantize/quantize_position.js +65 -65
  1160. package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
  1161. package/src/engine/network/core/sequence/seq16.js +69 -69
  1162. package/src/engine/network/core/sequence/seq32.js +73 -73
  1163. package/src/engine/network/ecs/NetworkSystem.js +84 -84
  1164. package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
  1165. package/src/engine/network/ecs/owner_authorization.js +55 -55
  1166. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
  1167. package/src/engine/network/replication/ScopeFilter.js +71 -71
  1168. package/src/engine/network/sim/ActionLog.js +152 -152
  1169. package/src/engine/network/sim/SimAction.js +273 -273
  1170. package/src/engine/network/sim/Snapshotter.js +222 -222
  1171. package/src/engine/network/state/FrameBufferRing.js +168 -168
  1172. package/src/engine/network/state/InputRing.js +134 -134
  1173. package/src/engine/network/state/PriorityAccumulator.js +180 -180
  1174. package/src/engine/network/time/TimeDilation.js +62 -62
  1175. package/src/engine/network/transport/Channel.js +336 -336
  1176. package/src/engine/network/transport/Transport.js +203 -203
  1177. package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
  1178. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
  1179. package/src/engine/network/transport/fragments/fragment_send.js +147 -147
  1180. package/src/engine/network/transport/fragments/packet_size.js +101 -101
  1181. package/src/engine/network/xhr.js +23 -23
  1182. package/src/engine/physics/BULLET_REVIEW.md +945 -945
  1183. package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
  1184. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
  1185. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
  1186. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
  1187. package/src/engine/physics/JOLT_REVIEW.md +913 -913
  1188. package/src/engine/physics/PLAN.md +1120 -1094
  1189. package/src/engine/physics/RAPIER_REVIEW.md +934 -934
  1190. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
  1191. package/src/engine/physics/REVIEW_002.md +151 -151
  1192. package/src/engine/physics/REVIEW_003.md +166 -166
  1193. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
  1194. package/src/engine/physics/constraint/DofMode.js +35 -35
  1195. package/src/engine/physics/constraint/solve_constraints.js +1 -1
  1196. package/src/engine/physics/contact/combine_material.js +35 -35
  1197. package/src/engine/physics/ecs/BodyKind.js +29 -29
  1198. package/src/engine/physics/ecs/Collider.js +177 -177
  1199. package/src/engine/physics/ecs/ColliderFlags.js +15 -15
  1200. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
  1201. package/src/engine/physics/ecs/Joint.js +475 -475
  1202. package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
  1203. package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
  1204. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  1205. package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
  1206. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
  1207. package/src/engine/physics/ecs/SleepState.js +12 -12
  1208. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
  1209. package/src/engine/physics/fluid/FluidField.js +867 -867
  1210. package/src/engine/physics/fluid/FluidSimulator.js +832 -832
  1211. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
  1212. package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
  1213. package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
  1214. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
  1215. package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
  1216. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
  1217. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
  1218. package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
  1219. package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
  1220. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
  1221. package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
  1222. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
  1223. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
  1224. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
  1225. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
  1226. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
  1227. package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
  1228. package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
  1229. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
  1230. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
  1231. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
  1232. package/src/engine/physics/fluid/prototype.js +126 -126
  1233. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
  1234. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
  1235. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
  1236. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
  1237. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
  1238. package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
  1239. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
  1240. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
  1241. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
  1242. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
  1243. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
  1244. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
  1245. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
  1246. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
  1247. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
  1248. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
  1249. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
  1250. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
  1251. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
  1252. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
  1253. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
  1254. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
  1255. package/src/engine/physics/integration/integrate_position.js +97 -97
  1256. package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
  1257. package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
  1258. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
  1259. package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
  1260. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
  1261. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
  1262. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  1263. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
  1264. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
  1265. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
  1266. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  1267. package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
  1268. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
  1269. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
  1270. package/src/engine/physics/persistence/solver_caches.js +309 -309
  1271. package/src/engine/physics/queries/overlap_shape.js +187 -187
  1272. package/src/engine/physics/queries/raycast.d.ts +7 -4
  1273. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  1274. package/src/engine/physics/queries/raycast.js +11 -5
  1275. package/src/engine/physics/queries/shape_cast.js +417 -417
  1276. package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
  1277. package/src/engine/physics/solver/friction_cone.js +37 -37
  1278. package/src/engine/physics/solver/solve_contacts.js +1 -1
  1279. package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
  1280. package/src/engine/platform/WebEnginePlatform.js +1 -2
  1281. package/src/engine/scene/SerializedScene.d.ts +2 -2
  1282. package/src/engine/scene/SerializedScene.d.ts.map +1 -1
  1283. package/src/engine/scene/transitionToScene.js +121 -121
  1284. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  1285. package/src/engine/simulation/Ticker.js +33 -0
  1286. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
  1287. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
  1288. package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
  1289. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
  1290. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
  1291. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
  1292. package/src/engine/sound/simulation/README.md +277 -277
  1293. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
  1294. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
  1295. package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
  1296. package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
  1297. package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
  1298. package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
  1299. package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
  1300. package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
  1301. package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
  1302. package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
  1303. package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
  1304. package/src/engine/sound/simulation/core/RayHit.js +50 -50
  1305. package/src/engine/sound/simulation/core/VolumeField.js +149 -149
  1306. package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
  1307. package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
  1308. package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
  1309. package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
  1310. package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
  1311. package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
  1312. package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
  1313. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
  1314. package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
  1315. package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
  1316. package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
  1317. package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
  1318. package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
  1319. package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
  1320. package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
  1321. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
  1322. package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
  1323. package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
  1324. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
  1325. package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
  1326. package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
  1327. package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
  1328. package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
  1329. package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
  1330. package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
  1331. package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
  1332. package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
  1333. package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
  1334. package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
  1335. package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
  1336. package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
  1337. package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
  1338. package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
  1339. package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
  1340. package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
  1341. package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
  1342. package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
  1343. package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
  1344. package/src/engine/sound/sopra/README.md +643 -643
  1345. package/src/engine/sound/sopra/SopraEngine.js +432 -432
  1346. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
  1347. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
  1348. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
  1349. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
  1350. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
  1351. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
  1352. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
  1353. package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
  1354. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
  1355. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
  1356. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
  1357. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
  1358. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
  1359. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
  1360. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
  1361. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
  1362. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
  1363. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
  1364. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
  1365. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
  1366. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
  1367. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
  1368. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
  1369. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
  1370. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
  1371. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
  1372. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
  1373. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  1374. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
  1375. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
  1376. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
  1377. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
  1378. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
  1379. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
  1380. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
  1381. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
  1382. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
  1383. package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
  1384. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
  1385. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
  1386. package/src/engine/sound/sopra/runtime/Voice.js +145 -145
  1387. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
  1388. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
  1389. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
  1390. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
  1391. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
  1392. package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
  1393. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
  1394. package/src/engine/ui/DraggableAspect.js +115 -115
  1395. package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
  1396. package/src/generation/COORDINATES.md +54 -54
  1397. package/src/generation/GridGenerationResource.js +24 -24
  1398. package/src/generation/GridTaskGroup.js +214 -214
  1399. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
  1400. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
  1401. package/src/generation/automata/CellularAutomata.js +22 -22
  1402. package/src/generation/compute_writes_union.js +25 -25
  1403. package/src/generation/filtering/CellFilter.js +117 -117
  1404. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
  1405. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
  1406. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
  1407. package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
  1408. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
  1409. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
  1410. package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
  1411. package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
  1412. package/src/generation/grid/GridData.js +233 -233
  1413. package/src/generation/grid/coords/grid_to_texel.js +10 -10
  1414. package/src/generation/grid/coords/texel_to_grid.js +10 -10
  1415. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
  1416. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
  1417. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
  1418. package/src/generation/grid/generation/GridTaskSequence.js +58 -58
  1419. package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
  1420. package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
  1421. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
  1422. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
  1423. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
  1424. package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
  1425. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
  1426. package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
  1427. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
  1428. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
  1429. package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
  1430. package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
  1431. package/src/generation/placement/action/GridCellAction.js +45 -45
  1432. package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
  1433. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
  1434. package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
  1435. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
  1436. package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
  1437. package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
  1438. package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
  1439. package/src/generation/theme/ThemeEngine.js +666 -666
  1440. package/src/shade/RENDERER_CONTRACT.md +568 -568
  1441. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
  1442. package/src/shade/device/ShadeDeviceFailure.js +104 -104
  1443. package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
  1444. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
  1445. package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
  1446. package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
  1447. package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
  1448. package/src/shade/device/mock/SoftwareGPUError.js +63 -63
  1449. package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
  1450. package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
  1451. package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
  1452. package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
  1453. package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
  1454. package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
  1455. package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
  1456. package/src/shade/device/mock/texture_copy.js +287 -287
  1457. package/src/shade/device/timing/GPUTimer.js +285 -285
  1458. package/src/shade/device/timing/GPUTimerArray.js +205 -205
  1459. package/src/shade/playground/add_random_lights.js +49 -49
  1460. package/src/shade/playground/add_random_meshes.js +83 -83
  1461. package/src/shade/playground/animation_prototype.js +508 -508
  1462. package/src/shade/playground/animation_prototype_skinning.js +647 -647
  1463. package/src/shade/playground/animation_prototype_stress.js +765 -765
  1464. package/src/shade/playground/enable_picker.js +59 -59
  1465. package/src/shade/playground/gltf_skinned_prototype.js +560 -560
  1466. package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
  1467. package/src/shade/playground/gltf_viewer.js +282 -282
  1468. package/src/shade/playground/load_default_environment.js +19 -19
  1469. package/src/shade/playground/main.js +2727 -2727
  1470. package/src/shade/playground/make_animated_instances.js +117 -117
  1471. package/src/shade/playground/prototype_virtual_texture.js +274 -274
  1472. package/src/shade/playground/prototype_volumetrics_01.js +206 -206
  1473. package/src/shade/playground/sample.js +234 -234
  1474. package/src/shade/playground/sample_load_gltf.js +102 -102
  1475. package/src/shade/playground/spread_node3d_grid.js +19 -19
  1476. package/src/shade/renderer/Renderer.js +2436 -2436
  1477. package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
  1478. package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
  1479. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
  1480. package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
  1481. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
  1482. package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
  1483. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
  1484. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
  1485. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
  1486. package/src/shade/renderer/binding/BindGroupManager.js +282 -282
  1487. package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
  1488. package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
  1489. package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
  1490. package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
  1491. package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
  1492. package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
  1493. package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
  1494. package/src/shade/renderer/camera/Camera.js +281 -281
  1495. package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
  1496. package/src/shade/renderer/camera/orbital/README.md +84 -84
  1497. package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
  1498. package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
  1499. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
  1500. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
  1501. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
  1502. package/src/shade/renderer/extension/FrameContext.js +263 -263
  1503. package/src/shade/renderer/extension/FramePhase.js +104 -104
  1504. package/src/shade/renderer/extension/FrameRecord.js +126 -126
  1505. package/src/shade/renderer/extension/GBufferTextures.js +83 -83
  1506. package/src/shade/renderer/extension/PresentTarget.js +34 -34
  1507. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
  1508. package/src/shade/renderer/extension/RenderExtension.js +69 -69
  1509. package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
  1510. package/src/shade/renderer/extension/SceneColor.js +40 -40
  1511. package/src/shade/renderer/extension/ViewTextures.js +78 -78
  1512. package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
  1513. package/src/shade/renderer/geometry/Geometry.js +608 -608
  1514. package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
  1515. package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
  1516. package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
  1517. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
  1518. package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
  1519. package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
  1520. package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
  1521. package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
  1522. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
  1523. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
  1524. package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
  1525. package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
  1526. package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
  1527. package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
  1528. package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
  1529. package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
  1530. package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
  1531. package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
  1532. package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
  1533. package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
  1534. package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
  1535. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
  1536. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
  1537. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
  1538. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
  1539. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
  1540. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
  1541. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
  1542. package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
  1543. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
  1544. package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
  1545. package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
  1546. package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
  1547. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
  1548. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
  1549. package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
  1550. package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
  1551. package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
  1552. package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
  1553. package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
  1554. package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
  1555. package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
  1556. package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
  1557. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
  1558. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
  1559. package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
  1560. package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
  1561. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
  1562. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
  1563. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
  1564. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
  1565. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
  1566. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
  1567. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
  1568. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
  1569. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
  1570. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
  1571. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
  1572. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
  1573. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
  1574. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
  1575. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
  1576. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
  1577. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
  1578. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
  1579. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
  1580. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
  1581. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
  1582. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
  1583. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
  1584. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
  1585. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
  1586. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
  1587. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
  1588. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
  1589. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
  1590. package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
  1591. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
  1592. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
  1593. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
  1594. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
  1595. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
  1596. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
  1597. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
  1598. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
  1599. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
  1600. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
  1601. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
  1602. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
  1603. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
  1604. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
  1605. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
  1606. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
  1607. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
  1608. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
  1609. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
  1610. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
  1611. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
  1612. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
  1613. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
  1614. package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
  1615. package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
  1616. package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
  1617. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
  1618. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
  1619. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
  1620. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
  1621. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
  1622. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
  1623. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
  1624. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
  1625. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
  1626. package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
  1627. package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
  1628. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
  1629. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
  1630. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
  1631. package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
  1632. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
  1633. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
  1634. package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
  1635. package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
  1636. package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
  1637. package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
  1638. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
  1639. package/src/shade/renderer/light/make_sunlight.js +35 -35
  1640. package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
  1641. package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
  1642. package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
  1643. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
  1644. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
  1645. package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
  1646. package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
  1647. package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
  1648. package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
  1649. package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
  1650. package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
  1651. package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
  1652. package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
  1653. package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
  1654. package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
  1655. package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
  1656. package/src/shade/renderer/loader/usd/UsdError.js +33 -33
  1657. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
  1658. package/src/shade/renderer/loader/usd/load_usd.js +226 -226
  1659. package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
  1660. package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
  1661. package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
  1662. package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
  1663. package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
  1664. package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
  1665. package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
  1666. package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
  1667. package/src/shade/renderer/material/ShadeMaterial.js +2 -2
  1668. package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
  1669. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  1670. package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
  1671. package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
  1672. package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
  1673. package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
  1674. package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
  1675. package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
  1676. package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
  1677. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
  1678. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
  1679. package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
  1680. package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
  1681. package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
  1682. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
  1683. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
  1684. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
  1685. package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
  1686. package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
  1687. package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
  1688. package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
  1689. package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
  1690. package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
  1691. package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
  1692. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
  1693. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
  1694. package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
  1695. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
  1696. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
  1697. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
  1698. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
  1699. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
  1700. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
  1701. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
  1702. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
  1703. package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
  1704. package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
  1705. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
  1706. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
  1707. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
  1708. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
  1709. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
  1710. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
  1711. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
  1712. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
  1713. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
  1714. package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
  1715. package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
  1716. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
  1717. package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
  1718. package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
  1719. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
  1720. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
  1721. package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
  1722. package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
  1723. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
  1724. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
  1725. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
  1726. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
  1727. package/src/shade/renderer/scene/DynamicMesh.js +70 -70
  1728. package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
  1729. package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
  1730. package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
  1731. package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
  1732. package/src/shade/renderer/scene/TransformAuthority.js +33 -33
  1733. package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
  1734. package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
  1735. package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
  1736. package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
  1737. package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
  1738. package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
  1739. package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
  1740. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
  1741. package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
  1742. package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
  1743. package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
  1744. package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
  1745. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
  1746. package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
  1747. package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
  1748. package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
  1749. package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
  1750. package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
  1751. package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
  1752. package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
  1753. package/src/shade/renderer/texture/virtual/README.md +276 -276
  1754. package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
  1755. package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
  1756. package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
  1757. package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
  1758. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
  1759. package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
  1760. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
  1761. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
  1762. package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
  1763. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
  1764. package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
  1765. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
  1766. package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
  1767. package/src/shade/renderer/water/shader_water.js +246 -246
  1768. package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
  1769. package/src/shade/vitest.setup.mjs +89 -89
  1770. package/src/shade/wgsl/validate_wgsl_source.js +316 -316
  1771. package/src/view/controller/controls/NumericIntervalControl.js +90 -90
  1772. package/src/view/controller/controls/Vector2Control.js +83 -83
  1773. package/src/view/controller/controls/Vector3Control.js +90 -90
  1774. package/src/view/elements/Group.js +56 -56
  1775. package/src/view/elements/drawCheckers.js +33 -33
  1776. package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
  1777. package/src/view/graphics/WebGLContextFailureView.js +76 -76
  1778. package/src/view/layout/DockPanel.js +51 -51
  1779. package/src/view/layout/SplitView.js +365 -365
  1780. package/src/view/layout/show_popup_menu.js +81 -81
  1781. package/src/view/minimap/MinimapCanvasView.js +411 -411
  1782. package/src/view/minimap/MinimapImageCache.js +88 -88
  1783. package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
  1784. package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
  1785. package/src/view/minimap/MinimapMarkerCollection.js +180 -180
  1786. package/src/view/minimap/dom/MinimapCameraView.js +206 -206
  1787. package/src/view/minimap/minimap_focus_area.js +97 -97
  1788. package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
  1789. package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
  1790. package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
  1791. package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
  1792. package/src/engine/ecs/attachment/Attachment.js +0 -57
  1793. package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
  1794. package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
  1795. package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
  1796. package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
  1797. package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
  1798. package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
  1799. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
  1800. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
  1801. package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
  1802. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
  1803. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
  1804. package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
  1805. package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
  1806. package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
  1807. package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
  1808. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
  1809. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
  1810. package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
  1811. package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
  1812. package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
  1813. package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
  1814. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
  1815. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
  1816. package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
  1817. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
  1818. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
  1819. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
  1820. package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
  1821. package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
  1822. package/src/shade/renderer/animation/_dump_resolve.mjs +0 -5
@@ -1,1738 +1,1738 @@
1
- import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
2
- import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
3
- import { body_id_index } from "../body/BodyStorage.js";
4
- import { combine_friction, combine_restitution } from "../contact/combine_material.js";
5
- import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
6
- import { gjk_epa_penetration } from "../../../core/geom/3d/gjk/gjk_epa_penetration.js";
7
- import { reduce_manifold_contacts } from "./reduce_manifold_contacts.js";
8
- import { mpr } from "../../../core/geom/3d/gjk/mpr.js";
9
- import { box_box_manifold, BOX_BOX_OUT_LENGTH } from "./box_box_manifold.js";
10
- import { convex_hull_clip, CONVEX_CONVEX_OUT_LENGTH } from "./convex_convex_manifold.js";
11
- import { get_mesh_convex_hull } from "./mesh_convex_hull.js";
12
- import { mesh_mesh_tet_contacts } from "./mesh_mesh_tet_manifold.js";
13
- import { box_triangle_contact, BOX_TRIANGLE_OUT_LENGTH } from "./box_triangle_contact.js";
14
- import {
15
- CAPSULE_BOX_CONTACT_STRIDE,
16
- CAPSULE_BOX_MAX_CONTACTS,
17
- capsule_box_multi_contacts,
18
- capsule_capsule_contact,
19
- CAPSULE_CAPSULE_CONTACT_STRIDE,
20
- capsule_capsule_multi_contacts,
21
- capsule_sphere_contact,
22
- } from "./capsule_contacts.js";
23
- import {
24
- capsule_triangle_contact,
25
- CAPSULE_TRIANGLE_CONTACT_STRIDE,
26
- CAPSULE_TRIANGLE_MAX_CONTACTS,
27
- } from "./capsule_triangle_contact.js";
28
- import { aabb3_transform_oriented_inverse } from "../../../core/geom/3d/aabb/aabb3_transform_oriented_inverse.js";
29
- import { decompose_to_triangles } from "./decomposition/decompose_to_triangles.js";
30
- import { TRIANGLE_FLOAT_STRIDE } from "./decomposition/triangle_buffer_layout.js";
31
- import { PosedShape3D } from "../../../core/geom/3d/shape/PosedShape3D.js";
32
- import { sphere_box_contact } from "./sphere_box_contact.js";
33
- import { sphere_sphere_contact } from "./sphere_sphere_contact.js";
34
- import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
35
-
36
- const posed_a = new PosedShape3D();
37
- const posed_b = new PosedShape3D();
38
-
39
- // Penetration axis + depth from the robust GJK + EPA query
40
- // (gjk_epa_penetration writes a unit normal here; it owns its own simplex
41
- // internally, so the narrowphase no longer keeps a simplex buffer).
42
- const epa_result = new Float64Array(3);
43
- const sphere_result = new Float64Array(4);
44
- const closed_form_result = new Float64Array(10);
45
-
46
- /** Scratch for the capsule-capsule multi-contact manifold (2 contacts max). */
47
- const capsule_capsule_result = new Float64Array(2 * CAPSULE_CAPSULE_CONTACT_STRIDE);
48
- const sphere_triangle_result = new Float64Array(10);
49
- const box_triangle_result = new Float64Array(BOX_TRIANGLE_OUT_LENGTH);
50
- const capsule_triangle_result = new Float64Array(CAPSULE_TRIANGLE_MAX_CONTACTS * CAPSULE_TRIANGLE_CONTACT_STRIDE);
51
- const box_manifold_result = new Float64Array(BOX_BOX_OUT_LENGTH);
52
- const convex_manifold_result = new Float64Array(CONVEX_CONVEX_OUT_LENGTH);
53
- const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAPSULE_BOX_CONTACT_STRIDE);
54
-
55
- /**
56
- * Candidate-contact stride: wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth,
57
- * feature_id, friction, restitution.
58
- *
59
- * The `feature_id` (offset 10) is a stable cross-frame identifier of the
60
- * geometric feature pair that produced this contact — used by the
61
- * match-and-merge pass in {@link narrowphase_step} to carry warm-start
62
- * impulses from the previous frame's manifold to the slot index that
63
- * corresponds to the same physical contact. A value of 0 means
64
- * "no feature info, fall back to position matching".
65
- *
66
- * `friction` (offset 11) and `restitution` (offset 12) are the COMBINED
67
- * coefficients for the specific (colliderA, colliderB) pair that produced this
68
- * contact, combined here (the only place that knows the exact source collider
69
- * on each side) and carried into the manifold so a compound body's per-collider
70
- * materials are honoured per-contact.
71
- *
72
- * @type {number}
73
- */
74
- const CANDIDATE_STRIDE = 13;
75
-
76
- /**
77
- * Combined friction / restitution for the collider pair currently being
78
- * dispatched. Set once at the top of {@link dispatch_pair} (which is called
79
- * per collider pair) and written into every contact that call appends, so
80
- * each contact carries the material of its actual source colliders. Module
81
- * scratch rather than threaded through every `append_contact` call site.
82
- * @type {number}
83
- */
84
- let g_pair_friction = 0;
85
- let g_pair_restitution = 0;
86
-
87
- /**
88
- * Maximum number of contacts emitted into the per-pair manifold after the
89
- * reduction step. Mirrors {@link MAX_CONTACTS_PER_MANIFOLD} in ManifoldStore.
90
- * @type {number}
91
- */
92
- const MAX_KEPT = MAX_CONTACTS_PER_MANIFOLD;
93
-
94
- /**
95
- * Position-fallback tolerance for warm-start matching: when a candidate
96
- * contact has no feature id (or none of the previous-frame contacts shares
97
- * its id), match by closest world_a within this 3-D distance.
98
- *
99
- * 2 cm matches the original PLAN.md spec for "Persistent manifold cache"
100
- * — generous enough to follow small inter-frame contact migration on
101
- * curved surfaces, tight enough that distinct contacts on a single
102
- * manifold (typically >5 cm apart) don't get confused with each other.
103
- *
104
- * @type {number}
105
- */
106
- const MATCH_TOL_SQR = 0.02 * 0.02;
107
-
108
- /**
109
- * Per-(colliderA, colliderB) feature-id salt. Each emitter numbers its
110
- * features in a small private range (sphere-box voronoi 1..27, capsule-box
111
- * 1..k, triangles 1..N, …), so two SUB-PAIRS of a compound body can produce
112
- * identical fids for unrelated features and warm-start impulses could be
113
- * inherited across collider pairs. Salting by the sub-pair index keeps every
114
- * fid unique across the body pair while staying exactly representable in f64
115
- * (sub_pair · 2³² + fid stays far below 2⁵³ for any realistic compound; no
116
- * single-pair emitter exceeds 2³²). Sub-pair 0 — the universal
117
- * single-collider case — is left unsalted, so simple bodies see unchanged
118
- * fids.
119
- * @type {number}
120
- */
121
- const FID_PAIR_SALT = 0x100000000; // 2^32
122
-
123
- /**
124
- * Distance ceiling for a feature-id warm-start match. A fid match may track
125
- * a fast-sliding contact far beyond {@link MATCH_TOL_SQR} — that is its
126
- * advantage over position matching — but an impulse inherited across half a
127
- * metre in one frame is stale by any standard (a teleported body re-deriving
128
- * the same voronoi bucket, or a fid collision between unrelated features).
129
- * The position fallback keeps its own 2 cm gate.
130
- * @type {number}
131
- */
132
- const FID_MATCH_MAX_D2 = 0.5 * 0.5;
133
-
134
- /**
135
- * Scratch for the previous-frame contact snapshot taken at the top of the
136
- * match-and-merge pass. Sized for {@link MAX_CONTACTS_PER_MANIFOLD}
137
- * contacts, 7 floats per contact:
138
- * 0 : feature_id
139
- * 1, 2, 3 : world_a x, y, z (for position-fallback matching)
140
- * 4, 5, 6 : j_n, j_t1, j_t2 (carried forward to the matched candidate)
141
- *
142
- * Snapshotting upfront decouples the read (from the slot's previous state)
143
- * from the write (the new contact data + impulse copy), avoiding the
144
- * read-after-write hazard when the matching mapping shuffles indices.
145
- *
146
- * @type {Float64Array}
147
- */
148
- const prev_snapshot = new Float64Array(MAX_CONTACTS_PER_MANIFOLD * 7);
149
-
150
- /**
151
- * Per-prev-contact "already claimed by a candidate" flag.
152
- * @type {Uint8Array}
153
- */
154
- const prev_claimed = new Uint8Array(MAX_CONTACTS_PER_MANIFOLD);
155
-
156
- /**
157
- * For each kept candidate, the matched prev-contact index in
158
- * `[0, prev_count)` or `-1` if no match.
159
- * @type {Int32Array}
160
- */
161
- const cand_to_prev = new Int32Array(MAX_KEPT);
162
-
163
- /**
164
- * Per-candidate "already claimed" flags for {@link redetect_pair_geometry}'s
165
- * 1:1 existing-contact → fresh-candidate matching. Sized to the candidate
166
- * buffer capacity (64) so it covers any per-pair candidate count. Without it,
167
- * several existing contacts that share one triangle's `feature_id` (the
168
- * box/capsule-triangle paths emit multiple contacts per triangle) would all
169
- * match the same first candidate, collapsing the manifold to duplicate witness
170
- * points.
171
- * @type {Uint8Array}
172
- */
173
- const redetect_claimed = new Uint8Array(64);
174
-
175
- /**
176
- * Per body-pair scratch buffer for candidate contacts produced by the
177
- * cross-product of A's colliders × B's colliders. Sized generously for
178
- * typical compound bodies (each collider-pair contributes 1..4 contacts;
179
- * 64 covers up to 4 colliders per body × 4 colliders × 4 contacts = 64).
180
- * @type {Float64Array}
181
- */
182
- const candidates = new Float64Array(64 * CANDIDATE_STRIDE);
183
-
184
- /**
185
- * Maximum triangles a concave-side enumerator can produce per pair.
186
- * The query AABB is bounded by the broadphase's fattened envelope of
187
- * the convex-side body, so a single concave-vs-convex pair typically
188
- * yields tens of triangles, not thousands. 1024 is the safety cap.
189
- *
190
- * For a heightmap the per-cell triangle count scales O(N²) with the
191
- * shape's {@link HeightMapShape3D#tessellation} (a sub-cell quad is 2
192
- * triangles, and there are N×N sub-cells per sampler cell). The bounded
193
- * query AABB keeps the cell count small, so a typical pair stays well
194
- * inside 1024 at moderate tessellation; the silent-drop backstop below
195
- * covers any overflow at extreme values.
196
- *
197
- * Enumerators return the TRUE overlap count and have no bounds check of
198
- * their own: writes past the scratch buffer are dropped (typed-array OOB
199
- * semantics), so on overflow the consume loop MUST clamp the returned
200
- * count to this cap or it reads `undefined` past the end and poisons the
201
- * manifold with NaN. The worst case after clamping is a missed contact
202
- * on a far edge of the filtered region, which the next-step
203
- * rebroadphase corrects.
204
- *
205
- * @type {number}
206
- */
207
- const MAX_TRIANGLES_PER_PAIR = 1024;
208
-
209
- /**
210
- * Per-pair scratch for the concave-side triangle decomposition.
211
- * @type {Float64Array}
212
- */
213
- const triangle_buffer = new Float64Array(MAX_TRIANGLES_PER_PAIR * TRIANGLE_FLOAT_STRIDE);
214
-
215
- /**
216
- * Flyweight triangle shape — rebound to each successive triangle slice
217
- * of `triangle_buffer` during the concave-side dispatch loop. Zero
218
- * allocation per triangle.
219
- * @type {Triangle3D}
220
- */
221
- const triangle_shape = new Triangle3D();
222
-
223
- /**
224
- * Scratch AABB buffers used only by the concave-side dispatch:
225
- * - `concave_local_aabb` : convex shape's local AABB (input to oriented transform)
226
- * - `concave_world_aabb` : convex shape's world AABB
227
- * - `concave_query_aabb` : convex shape's AABB projected into concave's body-local frame
228
- * (what the triangle enumerator filters against)
229
- */
230
- const concave_local_aabb = new Float64Array(6);
231
- const concave_world_aabb = new Float64Array(6);
232
- const concave_query_aabb = new Float64Array(6);
233
-
234
- /**
235
- * Rotate + translate a triangle's three vertices from a concave body's
236
- * local frame into world space: writes 9 floats `(A_w, B_w, C_w)` into
237
- * `out`. Inlined `q · v · q*` + translate per vertex (see
238
- * PosedShape3D.support for the V8-inliner rationale); the operation order
239
- * matches the blocks this replaced bit-exactly. Per-vertex cost ~21 flops
240
- * × 3 — essentially free next to any narrowphase kernel it feeds.
241
- *
242
- * Shared by the sphere / box / capsule closed-form triangle fast-paths in
243
- * the concave dispatch, which each carried an identical inlined copy.
244
- */
245
- function triangle_vertices_to_world(out, ax, ay, az, bx, by, bz, cx, cy, cz, qx, qy, qz, qw, px, py, pz) {
246
- const axi = qw * ax + qy * az - qz * ay;
247
- const ayi = qw * ay + qz * ax - qx * az;
248
- const azi = qw * az + qx * ay - qy * ax;
249
- const awi = -qx * ax - qy * ay - qz * az;
250
-
251
- out[0] = axi * qw + awi * -qx + ayi * -qz - azi * -qy + px;
252
- out[1] = ayi * qw + awi * -qy + azi * -qx - axi * -qz + py;
253
- out[2] = azi * qw + awi * -qz + axi * -qy - ayi * -qx + pz;
254
-
255
- const bxi = qw * bx + qy * bz - qz * by;
256
- const byi = qw * by + qz * bx - qx * bz;
257
- const bzi = qw * bz + qx * by - qy * bx;
258
- const bwi = -qx * bx - qy * by - qz * bz;
259
-
260
- out[3] = bxi * qw + bwi * -qx + byi * -qz - bzi * -qy + px;
261
- out[4] = byi * qw + bwi * -qy + bzi * -qx - bxi * -qz + py;
262
- out[5] = bzi * qw + bwi * -qz + bxi * -qy - byi * -qx + pz;
263
-
264
- const cxi = qw * cx + qy * cz - qz * cy;
265
- const cyi = qw * cy + qz * cx - qx * cz;
266
- const czi = qw * cz + qx * cy - qy * cx;
267
- const cwi = -qx * cx - qy * cy - qz * cz;
268
-
269
- out[6] = cxi * qw + cwi * -qx + cyi * -qz - czi * -qy + px;
270
- out[7] = cyi * qw + cwi * -qy + czi * -qx - cxi * -qz + py;
271
- out[8] = czi * qw + cwi * -qz + cxi * -qy - cyi * -qx + pz;
272
- }
273
-
274
- /** Destination for {@link triangle_vertices_to_world}. */
275
- const scratch_tri_world = new Float64Array(9);
276
-
277
- /**
278
- * Append one contact to the candidate buffer. Returns the new count.
279
- *
280
- * @param {number} count
281
- * @param {number} wax
282
- * @param {number} way
283
- * @param {number} waz
284
- * @param {number} wbx
285
- * @param {number} wby
286
- * @param {number} wbz
287
- * @param {number} nx
288
- * @param {number} ny
289
- * @param {number} nz
290
- * @param {number} depth
291
- * @param {number} feature_id stable cross-frame ID for warm-start matching;
292
- * `0` means no info — match-and-merge will fall back to position.
293
- * @returns {number}
294
- */
295
- function append_contact(count, wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth, feature_id) {
296
- if (count * CANDIDATE_STRIDE >= candidates.length){
297
- return count;
298
- }
299
-
300
- const off = count * CANDIDATE_STRIDE;
301
-
302
- candidates[off] = wax; candidates[off + 1] = way; candidates[off + 2] = waz;
303
- candidates[off + 3] = wbx; candidates[off + 4] = wby; candidates[off + 5] = wbz;
304
- candidates[off + 6] = nx; candidates[off + 7] = ny; candidates[off + 8] = nz;
305
- candidates[off + 9] = depth;
306
- candidates[off + 10] = feature_id;
307
- candidates[off + 11] = g_pair_friction;
308
- candidates[off + 12] = g_pair_restitution;
309
-
310
- return count + 1;
311
- }
312
-
313
- /**
314
- * Compute a stable voronoi-region feature id for a sphere-vs-box contact.
315
- * Returns one of 27 values in `[1, 27]` based on which of the 27 voronoi
316
- * regions of the box the sphere centre lies in (8 corners + 12 edges + 6
317
- * faces + 1 interior). Stable across frames as long as the sphere stays
318
- * in the same region — the typical case in steady-state contact.
319
- *
320
- * Encodes:
321
- * bucket_x ∈ {0, 1, 2} = sign(lx) bucket in box-local frame
322
- * bucket_y, bucket_z similarly
323
- * fid = 1 + bucket_x + 3·bucket_y + 9·bucket_z → 1..27
324
- *
325
- * The "+1" offset ensures the result is never 0, since 0 is reserved
326
- * for "no feature info, use position-fallback".
327
- *
328
- * @param {number} sx sphere centre x
329
- * @param {number} sy
330
- * @param {number} sz
331
- * @param {number} bx box centre x
332
- * @param {number} by
333
- * @param {number} bz
334
- * @param {number} bqx box quaternion x
335
- * @param {number} bqy
336
- * @param {number} bqz
337
- * @param {number} bqw
338
- * @param {number} hx box half-extent x
339
- * @param {number} hy
340
- * @param {number} hz
341
- * @returns {number}
342
- */
343
- function sphere_box_voronoi_fid(sx, sy, sz, bx, by, bz, bqx, bqy, bqz, bqw, hx, hy, hz) {
344
- // Inverse-rotate (sx - bx, sy - by, sz - bz) by the box's quaternion
345
- // to get the sphere centre in box-local frame. Inlined for the same
346
- // V8-inliner reason described in PosedShape3D.support — see
347
- // core/geom/vec3/v3_quat3_apply_inverse.js for the canonical form.
348
- const dx = sx - bx;
349
- const dy = sy - by;
350
- const dz = sz - bz;
351
-
352
- const tx0 = bqw * dx - bqy * dz + bqz * dy;
353
- const ty0 = bqw * dy - bqz * dx + bqx * dz;
354
- const tz0 = bqw * dz - bqx * dy + bqy * dx;
355
- const tw0 = bqx * dx + bqy * dy + bqz * dz;
356
-
357
- const lx = tx0 * bqw + tw0 * bqx + ty0 * bqz - tz0 * bqy;
358
- const ly = ty0 * bqw + tw0 * bqy + tz0 * bqx - tx0 * bqz;
359
- const lz = tz0 * bqw + tw0 * bqz + tx0 * bqy - ty0 * bqx;
360
-
361
- const bx_b = lx <= -hx ? 0 : (lx >= hx ? 2 : 1);
362
- const by_b = ly <= -hy ? 0 : (ly >= hy ? 2 : 1);
363
- const bz_b = lz <= -hz ? 0 : (lz >= hz ? 2 : 1);
364
-
365
- return 1 + bx_b + 3 * by_b + 9 * bz_b;
366
- }
367
-
368
- /**
369
- * Multi-point contact manifold between two convex polytopes (authored
370
- * ConvexHullShape3D, or a convex MeshShape3D represented as one hull). Robust
371
- * GJK + EPA finds the separating axis in O(support queries) — face-count
372
- * independent, where SAT's edge-pair term is prohibitive — then
373
- * {@link convex_hull_clip} clips the reference/incident faces into a face-on-face
374
- * patch around it (Bullet's clipHullAgainstHull). A single GJK+EPA contact can't
375
- * resist a stack's toppling torque; the clipped patch holds it. No cross-frame
376
- * state → reset-and-resimulate determinism.
377
- *
378
- * @param {number} count
379
- * @param {{vertices,face_offsets,face_loops,support}} hullA in A's local frame
380
- * @param {Transform} trA
381
- * @param {{vertices,face_offsets,face_loops,support}} hullB in B's local frame
382
- * @param {Transform} trB
383
- * @param {Function} append_contact
384
- * @returns {number}
385
- */
386
- function hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact) {
387
- posed_a.setup(hullA, trA.position, trA.rotation);
388
- posed_b.setup(hullB, trB.position, trB.rotation);
389
- const depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
390
- if (!(depth > 0) || !Number.isFinite(depth)) return count;
391
-
392
- // convex_hull_clip orients the axis B→A robustly (via vertex centroids) and
393
- // writes the final normal into convex_manifold_result[0..2].
394
- convex_hull_clip(convex_manifold_result,
395
- hullA, trA.position, trA.rotation,
396
- hullB, trB.position, trB.rotation,
397
- epa_result[0], epa_result[1], epa_result[2]);
398
- const nx = convex_manifold_result[0], ny = convex_manifold_result[1], nz = convex_manifold_result[2];
399
- const cc = convex_manifold_result[3] | 0;
400
- // Clipped points migrate across features as the hulls rotate, so leave
401
- // fid = 0 and let the match-and-merge pass position-match (as box-box does).
402
- for (let k = 0; k < cc; k++) {
403
- const base = 4 + k * 7;
404
- count = append_contact(count,
405
- convex_manifold_result[base], convex_manifold_result[base + 1], convex_manifold_result[base + 2],
406
- convex_manifold_result[base + 3], convex_manifold_result[base + 4], convex_manifold_result[base + 5],
407
- nx, ny, nz,
408
- convex_manifold_result[base + 6],
409
- 0);
410
- }
411
- return count;
412
- }
413
-
414
- /**
415
- * Run pairwise narrowphase for one (colliderA, colliderB) tuple — dispatches
416
- * by shape type and appends 0..K contacts to the candidate buffer. Returns
417
- * the new candidate count.
418
- *
419
- * @param {number} count
420
- * @param {Collider} colA
421
- * @param {Transform} trA
422
- * @param {Collider} colB
423
- * @param {Transform} trB
424
- * @returns {number}
425
- */
426
- function dispatch_pair(count, colA, trA, colB, trB) {
427
- const shapeA = colA.shape;
428
- const shapeB = colB.shape;
429
-
430
- // Per-contact materials: combine the two source colliders' coefficients
431
- // once here (this is the only place that knows the exact collider on each
432
- // side) and stamp them onto every contact this dispatch appends. The
433
- // `deepest_pair_penetration` query passes bare `{shape}` adapters with no
434
- // material fields — it never writes to a manifold, so 0 is fine there.
435
- const fa = colA.friction, fb = colB.friction;
436
-
437
- if (fa !== undefined && fb !== undefined) {
438
-
439
- g_pair_friction = combine_friction(fa, fb);
440
- g_pair_restitution = combine_restitution(colA.restitution, colB.restitution);
441
-
442
- } else {
443
- g_pair_friction = 0;
444
- g_pair_restitution = 0;
445
- }
446
-
447
- // isSphereShape3D covers both UnitSphereShape3D (fixed radius 1) and
448
- // SphereShape3D (arbitrary radius). Both expose `radius`.
449
- const isSphereA = shapeA.isSphereShape3D === true;
450
- const isSphereB = shapeB.isSphereShape3D === true;
451
- // isBoxShape3D covers both UnitCubeShape3D (fixed 0.5) and BoxShape3D
452
- // (arbitrary half-extents). Both expose `half_extents` as a Vector3.
453
- const isBoxA = shapeA.isBoxShape3D === true;
454
- const isBoxB = shapeB.isBoxShape3D === true;
455
- const isCapsuleA = shapeA.isCapsuleShape3D === true;
456
- const isCapsuleB = shapeB.isCapsuleShape3D === true;
457
-
458
- // sphere-sphere
459
- if (isSphereA && isSphereB) {
460
- const ra = shapeA.radius, rb = shapeB.radius;
461
-
462
- const ok = sphere_sphere_contact(
463
- sphere_result,
464
- trA.position.x, trA.position.y, trA.position.z,
465
- trB.position.x, trB.position.y, trB.position.z,
466
- ra, rb
467
- );
468
-
469
- if (!ok) return count;
470
-
471
- const nx = sphere_result[0], ny = sphere_result[1], nz = sphere_result[2];
472
-
473
- // Sphere-sphere produces exactly one contact per pair; fid = 1
474
- // identifies it as a real feature (distinguishes from "no info" = 0)
475
- // and is trivially stable across frames. Witnesses are the surface
476
- // points along the (unit) normal, scaled by each sphere's radius.
477
- return append_contact(count,
478
- trA.position.x - nx * ra, trA.position.y - ny * ra, trA.position.z - nz * ra,
479
- trB.position.x + nx * rb, trB.position.y + ny * rb, trB.position.z + nz * rb,
480
- nx, ny, nz, sphere_result[3], 1);
481
- }
482
-
483
- // sphere ↔ box
484
- if ((isSphereA && isBoxB) || (isBoxA && isSphereB)) {
485
- const sphereTr = isSphereA ? trA : trB;
486
- const sphereShape = isSphereA ? shapeA : shapeB;
487
- const boxTr = isSphereA ? trB : trA;
488
- const boxShape = isSphereA ? shapeB : shapeA;
489
- const bh = boxShape.half_extents;
490
-
491
- const ok = sphere_box_contact(
492
- closed_form_result,
493
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius,
494
- boxTr.position.x, boxTr.position.y, boxTr.position.z,
495
- boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
496
- bh.x, bh.y, bh.z
497
- );
498
-
499
- if (!ok) return count;
500
-
501
- let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
502
-
503
- const depth = closed_form_result[3];
504
- const wsx = closed_form_result[4], wsy = closed_form_result[5], wsz = closed_form_result[6];
505
- const wbx = closed_form_result[7], wby = closed_form_result[8], wbz = closed_form_result[9];
506
- // Feature id from the box-local voronoi region (1..27) — stable
507
- // while the sphere stays on the same face / edge / vertex of the
508
- // box, which is the steady-state case for resting / sliding contact.
509
-
510
- const fid = sphere_box_voronoi_fid(
511
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z,
512
- boxTr.position.x, boxTr.position.y, boxTr.position.z,
513
- boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
514
- bh.x, bh.y, bh.z
515
- );
516
-
517
- if (isSphereA) {
518
- return append_contact(count, wsx, wsy, wsz, wbx, wby, wbz, nx, ny, nz, depth, fid);
519
- } else {
520
- return append_contact(count, wbx, wby, wbz, wsx, wsy, wsz, -nx, -ny, -nz, depth, fid);
521
- }
522
- }
523
-
524
- // box-box multi-point
525
- if (isBoxA && isBoxB) {
526
- const ah = shapeA.half_extents;
527
- const bh = shapeB.half_extents;
528
- const ok = box_box_manifold(
529
- box_manifold_result,
530
- trA.position.x, trA.position.y, trA.position.z,
531
- trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
532
- ah.x, ah.y, ah.z,
533
- trB.position.x, trB.position.y, trB.position.z,
534
- trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
535
- bh.x, bh.y, bh.z
536
- );
537
- if (!ok) return count;
538
- const nx = box_manifold_result[0], ny = box_manifold_result[1], nz = box_manifold_result[2];
539
- const cc = box_manifold_result[3] | 0;
540
- // Box-box manifolds: closed-form clipping doesn't expose stable
541
- // per-contact feature ids (a contact migrates from "incident-vertex k"
542
- // to "clip-intersection on edge j" as the boxes rotate). Leave
543
- // fid = 0 so the match-and-merge pass uses position-fallback —
544
- // the per-contact clipped points are spread by face geometry and
545
- // typically stay >>MATCH_TOL apart, so position matching is
546
- // unambiguous frame-to-frame.
547
- for (let k = 0; k < cc; k++) {
548
- const base = 4 + k * 7;
549
- count = append_contact(count,
550
- box_manifold_result[base], box_manifold_result[base + 1], box_manifold_result[base + 2],
551
- box_manifold_result[base + 3], box_manifold_result[base + 4], box_manifold_result[base + 5],
552
- nx, ny, nz,
553
- box_manifold_result[base + 6],
554
- 0);
555
- }
556
- return count;
557
- }
558
-
559
- // capsule-capsule (multi-point: near-parallel overlap emits both ends of
560
- // the overlap interval; everything else the single closest-point contact)
561
- if (isCapsuleA && isCapsuleB) {
562
- const a_shape = colA.shape, b_shape = colB.shape;
563
- const n_cc = capsule_capsule_multi_contacts(
564
- capsule_capsule_result,
565
- trA.position.x, trA.position.y, trA.position.z,
566
- trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
567
- a_shape.radius, a_shape.height * 0.5,
568
- trB.position.x, trB.position.y, trB.position.z,
569
- trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
570
- b_shape.radius, b_shape.height * 0.5
571
- );
572
- for (let k = 0; k < n_cc; k++) {
573
- const off = k * CAPSULE_CAPSULE_CONTACT_STRIDE;
574
- // fid = k + 1: stable per interval end (1-based; 0 = no info).
575
- count = append_contact(count,
576
- capsule_capsule_result[off], capsule_capsule_result[off + 1], capsule_capsule_result[off + 2],
577
- capsule_capsule_result[off + 3], capsule_capsule_result[off + 4], capsule_capsule_result[off + 5],
578
- capsule_capsule_result[off + 6], capsule_capsule_result[off + 7], capsule_capsule_result[off + 8],
579
- capsule_capsule_result[off + 9], k + 1);
580
- }
581
- return count;
582
- }
583
-
584
- // capsule ↔ sphere
585
- if ((isCapsuleA && isSphereB) || (isSphereA && isCapsuleB)) {
586
- const capsuleTr = isCapsuleA ? trA : trB;
587
- const capsuleShape = isCapsuleA ? colA.shape : colB.shape;
588
- const sphereTr = isCapsuleA ? trB : trA;
589
- const sphereShape = isCapsuleA ? colB.shape : colA.shape;
590
- const ok = capsule_sphere_contact(
591
- closed_form_result,
592
- capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
593
- capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
594
- capsuleShape.radius, capsuleShape.height * 0.5,
595
- sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius
596
- );
597
- if (!ok) return count;
598
- let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
599
- const depth = closed_form_result[3];
600
- const cap_x = closed_form_result[4], cap_y = closed_form_result[5], cap_z = closed_form_result[6];
601
- const sph_x = closed_form_result[7], sph_y = closed_form_result[8], sph_z = closed_form_result[9];
602
- // Single contact per capsule-sphere pair; fid = 1.
603
- if (isCapsuleA) {
604
- return append_contact(count, cap_x, cap_y, cap_z, sph_x, sph_y, sph_z, nx, ny, nz, depth, 1);
605
- } else {
606
- return append_contact(count, sph_x, sph_y, sph_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, 1);
607
- }
608
- }
609
-
610
- // capsule ↔ box (multi-point — 1 closest-segment + up to 2 cap-centres)
611
- if ((isCapsuleA && isBoxB) || (isBoxA && isCapsuleB)) {
612
- const capsuleTr = isCapsuleA ? trA : trB;
613
- const capsuleShape = isCapsuleA ? shapeA : shapeB;
614
- const boxTr = isCapsuleA ? trB : trA;
615
- const boxShape = isCapsuleA ? shapeB : shapeA;
616
- const bh = boxShape.half_extents;
617
- const cc = capsule_box_multi_contacts(
618
- capsule_box_multi_result,
619
- capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
620
- capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
621
- capsuleShape.radius, capsuleShape.height * 0.5,
622
- boxTr.position.x, boxTr.position.y, boxTr.position.z,
623
- boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
624
- bh.x, bh.y, bh.z
625
- );
626
- if (cc === 0) return count;
627
- // multi_result layout per contact: cap_x/y/z (A side), box_x/y/z (B side), nx/ny/nz, depth.
628
- // Feature id per sub-contact: 1 = closest-segment, 2/3 = caps. The
629
- // emission order from capsule_box_multi_contacts is stable across
630
- // frames for the same geometric configuration.
631
- for (let k = 0; k < cc; k++) {
632
- const o = k * CAPSULE_BOX_CONTACT_STRIDE;
633
- const cap_x = capsule_box_multi_result[o];
634
- const cap_y = capsule_box_multi_result[o + 1];
635
- const cap_z = capsule_box_multi_result[o + 2];
636
- const box_x = capsule_box_multi_result[o + 3];
637
- const box_y = capsule_box_multi_result[o + 4];
638
- const box_z = capsule_box_multi_result[o + 5];
639
- const nx = capsule_box_multi_result[o + 6];
640
- const ny = capsule_box_multi_result[o + 7];
641
- const nz = capsule_box_multi_result[o + 8];
642
- const depth = capsule_box_multi_result[o + 9];
643
- const sub_fid = k + 1;
644
- if (isCapsuleA) {
645
- count = append_contact(count, cap_x, cap_y, cap_z, box_x, box_y, box_z, nx, ny, nz, depth, sub_fid);
646
- } else {
647
- count = append_contact(count, box_x, box_y, box_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, sub_fid);
648
- }
649
- }
650
- return count;
651
- }
652
-
653
- // convex hull ↔ convex hull (multi-point clipping via GJK + EPA + face-clip).
654
- if (shapeA.isConvexHullShape3D === true && shapeB.isConvexHullShape3D === true) {
655
- return hull_pair_contacts(count, shapeA, trA, shapeB, trB, append_contact);
656
- }
657
-
658
- // mesh ↔ mesh. A globally-convex mesh collides as a single cached hull
659
- // (one GJK + EPA + clip) — the greedy decomposition fragments even a convex
660
- // mesh, so detecting convexity and routing through the hull path is the
661
- // scalable fast path. When BOTH meshes are convex, use it; otherwise fall to
662
- // the per-piece decomposed path (convex pieces vs convex pieces).
663
- if (shapeA.isMeshShape3D === true && shapeB.isMeshShape3D === true) {
664
- const hullA = get_mesh_convex_hull(shapeA);
665
- const hullB = get_mesh_convex_hull(shapeB);
666
- if (hullA !== null && hullB !== null) {
667
- return hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact);
668
- }
669
- return mesh_mesh_tet_contacts(count, shapeA, trA, shapeB, trB, append_contact);
670
- }
671
-
672
- // ── Concave (non-convex) path ───────────────────────────────────────
673
- //
674
- // If either shape has `is_convex === false`, GJK on the whole shape
675
- // produces incorrect results (Minkowski difference is not convex).
676
- // We decompose the concave side into triangles overlapping the
677
- // convex side's AABB, then run convex per-triangle GJK + EPA.
678
- //
679
- // Concave-vs-concave is intentionally NOT supported in v1: the
680
- // M×N triangle pairs would dominate runtime, and the physics
681
- // engine's design contract requires at least one side to be
682
- // static / kinematic for concave shapes anyway (the broadphase +
683
- // filter should keep such pairs out of the narrowphase entirely).
684
- // If one slips through, we skip rather than burn cycles.
685
- const isConcaveA = shapeA.is_convex === false;
686
- const isConcaveB = shapeB.is_convex === false;
687
- if (isConcaveA && isConcaveB) return count;
688
- if (isConcaveA || isConcaveB) {
689
- const concave_col = isConcaveA ? colA : colB;
690
- const concave_tr = isConcaveA ? trA : trB;
691
- const convex_col = isConcaveA ? colB : colA;
692
- const convex_tr = isConcaveA ? trB : trA;
693
-
694
- // 1. Convex shape's world AABB.
695
- convex_col.shape.compute_bounding_box(concave_local_aabb);
696
- aabb3_transform_oriented(
697
- concave_world_aabb, 0,
698
- concave_local_aabb[0], concave_local_aabb[1], concave_local_aabb[2],
699
- concave_local_aabb[3], concave_local_aabb[4], concave_local_aabb[5],
700
- convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
701
- convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w
702
- );
703
-
704
- // 2. Project into concave's body-local frame.
705
- aabb3_transform_oriented_inverse(
706
- concave_query_aabb, 0,
707
- concave_world_aabb,
708
- concave_tr.position.x, concave_tr.position.y, concave_tr.position.z,
709
- concave_tr.rotation.x, concave_tr.rotation.y, concave_tr.rotation.z, concave_tr.rotation.w
710
- );
711
-
712
- // 3. Decompose concave shape into triangles overlapping the query.
713
- // The enumerator returns the TRUE overlap count — clamp to the
714
- // scratch capacity (overflow writes were dropped; reading past the
715
- // buffer would poison the manifold with NaN).
716
- let tri_count = decompose_to_triangles(
717
- triangle_buffer, 0, concave_col.shape,
718
- concave_query_aabb[0], concave_query_aabb[1], concave_query_aabb[2],
719
- concave_query_aabb[3], concave_query_aabb[4], concave_query_aabb[5]
720
- );
721
- if (tri_count > MAX_TRIANGLES_PER_PAIR) tri_count = MAX_TRIANGLES_PER_PAIR;
722
- if (tri_count === 0) return count;
723
-
724
- // 4. Set up the convex side once; the concave side gets a
725
- // Triangle3D rebound to each triangle in the loop. We keep
726
- // the concave side as our internal "A" so EPA's sign-check
727
- // convention matches the convex fallback below; the final
728
- // append_contact() swaps if the original A was the convex one.
729
- posed_b.setup(convex_col.shape, convex_tr.position, convex_tr.rotation);
730
- posed_a.shape = triangle_shape;
731
- posed_a.px = concave_tr.position.x;
732
- posed_a.py = concave_tr.position.y;
733
- posed_a.pz = concave_tr.position.z;
734
- posed_a.qx = concave_tr.rotation.x;
735
- posed_a.qy = concave_tr.rotation.y;
736
- posed_a.qz = concave_tr.rotation.z;
737
- posed_a.qw = concave_tr.rotation.w;
738
-
739
- // Pre-compute the centre-axis used by EPA's sign-check loop.
740
- // For convex-vs-convex this is `(B - A) = convex_centre − concave_centre`.
741
- const convex_wx = convex_tr.position.x;
742
- const convex_wy = convex_tr.position.y;
743
- const convex_wz = convex_tr.position.z;
744
-
745
- // Track the candidate-buffer index at the start of this
746
- // concave dispatch — the per-triangle dedup pass scans from
747
- // here to the current count, ignoring contacts from earlier
748
- // collider pairs in the same body pair.
749
- const pair_start_count = count;
750
-
751
- // Pre-compute concave's rotation components for the q · v · q⁻¹
752
- // rotations done per-triangle below (face normal + centroid).
753
- const cqx = concave_tr.rotation.x;
754
- const cqy = concave_tr.rotation.y;
755
- const cqz = concave_tr.rotation.z;
756
- const cqw = concave_tr.rotation.w;
757
- const c_pos_x = concave_tr.position.x;
758
- const c_pos_y = concave_tr.position.y;
759
- const c_pos_z = concave_tr.position.z;
760
-
761
- // Sphere fast-path: when the convex side is a sphere we bypass GJK+EPA
762
- // entirely per triangle and use the closed-form
763
- // {@link sphere_triangle_contact}. This avoids the EPA precision
764
- // wall on Triangle3D (whose support function is degenerate along
765
- // the face normal — all 3 vertices project to the same value),
766
- // which was producing noisy depths at small penetrations and
767
- // letting dropped spheres tunnel through heightmaps / meshes.
768
- const isSphereConvex = convex_col.shape.isSphereShape3D === true;
769
- const sphere_radius = isSphereConvex ? convex_col.shape.radius : 0;
770
-
771
- // Box fast-path: closed-form {@link box_triangle_contact} via SAT
772
- // over 13 axes + polygon clipping for face-vs-face contacts.
773
- // Same motivation as the sphere path — Triangle3D's degenerate
774
- // face-normal support kills EPA precision and produces noisy
775
- // depths. The box path uses world-space triangle vertices.
776
- const isBoxConvex = convex_col.shape.isBoxShape3D === true;
777
- const box_half_extents = isBoxConvex ? convex_col.shape.half_extents : null;
778
-
779
- // Capsule fast-path: closed-form {@link capsule_triangle_contact}
780
- // via segment-vs-triangle closest-point + cap-centre sphere
781
- // queries for a multi-point manifold. Same motivation as the
782
- // sphere and box paths.
783
- const isCapsuleConvex = convex_col.shape.isCapsuleShape3D === true;
784
- const capsule_shape = isCapsuleConvex ? convex_col.shape : null;
785
-
786
- for (let i = 0; i < tri_count; i++) {
787
- const tri_offset = i * TRIANGLE_FLOAT_STRIDE;
788
- triangle_shape.bind(triangle_buffer, tri_offset);
789
-
790
- const ax = triangle_buffer[tri_offset ];
791
- const ay = triangle_buffer[tri_offset + 1];
792
- const az = triangle_buffer[tri_offset + 2];
793
-
794
- const bx = triangle_buffer[tri_offset + 3];
795
- const by = triangle_buffer[tri_offset + 4];
796
- const bz = triangle_buffer[tri_offset + 5];
797
-
798
- const cx_v = triangle_buffer[tri_offset + 6];
799
- const cy_v = triangle_buffer[tri_offset + 7];
800
- const cz_v = triangle_buffer[tri_offset + 8];
801
-
802
- // Triangle decomposition emits a stable per-triangle feature_id
803
- // at offset 9 (TRIANGLE_FEATURE_ID_OFFSET) — same triangle of
804
- // the same shape gets the same id across frames. This is the
805
- // gold-standard fid for the match-and-merge pass.
806
- const tri_fid = triangle_buffer[tri_offset + 9];
807
-
808
- // Triangle face normal in body-local frame: (B − A) × (C − A).
809
- // Winding convention (CCW from outside) gives an outward face
810
- // normal — the heightmap / mesh enumerators both promise this.
811
- const e1x_l = bx - ax, e1y_l = by - ay, e1z_l = bz - az;
812
- const e2x_l = cx_v - ax, e2y_l = cy_v - ay, e2z_l = cz_v - az;
813
-
814
- const fnx_l = e1y_l * e2z_l - e1z_l * e2y_l;
815
- const fny_l = e1z_l * e2x_l - e1x_l * e2z_l;
816
- const fnz_l = e1x_l * e2y_l - e1y_l * e2x_l;
817
-
818
- // Rotate face normal to world via concave's quaternion
819
- // (q · v · q⁻¹). Inlined for V8 inliner — see PosedShape3D.support.
820
- const fnix = cqw * fnx_l + cqy * fnz_l - cqz * fny_l;
821
- const fniy = cqw * fny_l + cqz * fnx_l - cqx * fnz_l;
822
- const fniz = cqw * fnz_l + cqx * fny_l - cqy * fnx_l;
823
- const fniw = -cqx * fnx_l - cqy * fny_l - cqz * fnz_l;
824
-
825
- const fnx_w = fnix * cqw - fniw * cqx - fniy * cqz + fniz * cqy;
826
- const fny_w = fniy * cqw - fniw * cqy - fniz * cqx + fnix * cqz;
827
- const fnz_w = fniz * cqw - fniw * cqz - fnix * cqy + fniy * cqx;
828
-
829
- // Sphere-vs-triangle closed-form fast-path.
830
- if (isSphereConvex) {
831
-
832
- // Rotate each triangle vertex from concave-local to world
833
- // via the shared helper.
834
- triangle_vertices_to_world(
835
- scratch_tri_world,
836
- ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
837
- cqx, cqy, cqz, cqw,
838
- c_pos_x, c_pos_y, c_pos_z
839
- );
840
- const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
841
- const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
842
- const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
843
-
844
- const ok = sphere_triangle_contact(
845
- sphere_triangle_result,
846
- convex_wx, convex_wy, convex_wz, sphere_radius,
847
- ax_w, ay_w, az_w,
848
- bx_w, by_w, bz_w,
849
- cx_w, cy_w, cz_w
850
- );
851
-
852
- if (!ok) continue;
853
-
854
- // sphere_triangle_contact's normal points from the
855
- // triangle surface toward the sphere centre — same
856
- // direction as the post-sign-check EPA MTV in the
857
- // fallback path below (concave A → convex B).
858
- const sd = sphere_triangle_result[3];
859
- const n_t2s_x = sphere_triangle_result[0];
860
- const n_t2s_y = sphere_triangle_result[1];
861
- const n_t2s_z = sphere_triangle_result[2];
862
-
863
- // One-sided rejection: the sphere must lie on the
864
- // outward side of the triangle. If the contact normal
865
- // opposes the face normal, the sphere is behind /
866
- // inside the solid — skip rather than push it deeper.
867
- if (n_t2s_x * fnx_w + n_t2s_y * fny_w + n_t2s_z * fnz_w <= 0) continue;
868
-
869
- // Stored normal convention is "B → A". `nx, ny, nz` here
870
- // points convex → concave (which the existing EPA branch
871
- // also produces just before append). Dedup uses the
872
- // post-swap stored_n so adjacent-triangle duplicates
873
- // collapse the same way regardless of code path.
874
- const nx_s = -n_t2s_x;
875
- const ny_s = -n_t2s_y;
876
- const nz_s = -n_t2s_z;
877
-
878
- const stored_nx_s = isConcaveA ? nx_s : -nx_s;
879
- const stored_ny_s = isConcaveA ? ny_s : -ny_s;
880
- const stored_nz_s = isConcaveA ? nz_s : -nz_s;
881
- let is_duplicate_s = false;
882
- for (let k = pair_start_count; k < count; k++) {
883
- const ko = k * CANDIDATE_STRIDE;
884
- const dnx = candidates[ko + 6] - stored_nx_s;
885
- const dny = candidates[ko + 7] - stored_ny_s;
886
- const dnz = candidates[ko + 8] - stored_nz_s;
887
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
888
- is_duplicate_s = true;
889
- break;
890
- }
891
- }
892
- if (is_duplicate_s) continue;
893
-
894
- // Surface witnesses from the closed-form solver:
895
- // result[4..6] = sphere surface point (convex side)
896
- // result[7..9] = triangle closest point (concave side)
897
- // Use the actual witnesses rather than body centres —
898
- // unlike the EPA fallback (which uses body centres
899
- // because flat-faced support witnesses can be arbitrarily
900
- // far from the contact patch), closest-point-on-triangle
901
- // is exact and lies on the contact patch.
902
- const sphere_wx = sphere_triangle_result[4];
903
- const sphere_wy = sphere_triangle_result[5];
904
- const sphere_wz = sphere_triangle_result[6];
905
- const tri_wx = sphere_triangle_result[7];
906
- const tri_wy = sphere_triangle_result[8];
907
- const tri_wz = sphere_triangle_result[9];
908
- if (isConcaveA) {
909
- count = append_contact(count,
910
- tri_wx, tri_wy, tri_wz,
911
- sphere_wx, sphere_wy, sphere_wz,
912
- nx_s, ny_s, nz_s, sd, tri_fid);
913
- } else {
914
- count = append_contact(count,
915
- sphere_wx, sphere_wy, sphere_wz,
916
- tri_wx, tri_wy, tri_wz,
917
- -nx_s, -ny_s, -nz_s, sd, tri_fid);
918
- }
919
- continue;
920
- }
921
-
922
- // Box-vs-triangle closed-form fast-path.
923
- if (isBoxConvex) {
924
- // Rotate each triangle vertex from concave-local to world
925
- // via the shared helper.
926
- triangle_vertices_to_world(
927
- scratch_tri_world,
928
- ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
929
- cqx, cqy, cqz, cqw,
930
- c_pos_x, c_pos_y, c_pos_z
931
- );
932
- const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
933
- const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
934
- const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
935
-
936
- const ok = box_triangle_contact(
937
- box_triangle_result,
938
- convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
939
- convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
940
- box_half_extents.x, box_half_extents.y, box_half_extents.z,
941
- ax_w, ay_w, az_w,
942
- bx_w, by_w, bz_w,
943
- cx_w, cy_w, cz_w
944
- );
945
- if (!ok) continue;
946
-
947
- // Same convention as sphere_triangle: result normal points
948
- // from triangle surface toward box centre. To match the
949
- // EPA branch's `nx, ny, nz` (which points convex →
950
- // concave = box → triangle), negate.
951
- const n_t2box_x = box_triangle_result[0];
952
- const n_t2box_y = box_triangle_result[1];
953
- const n_t2box_z = box_triangle_result[2];
954
-
955
- // One-sided rejection: the box must lie on the outward
956
- // side of the triangle. If the contact normal opposes
957
- // the face normal, the box is inside the solid → skip.
958
- if (n_t2box_x * fnx_w + n_t2box_y * fny_w + n_t2box_z * fnz_w <= 0) continue;
959
-
960
- const nx_b = -n_t2box_x;
961
- const ny_b = -n_t2box_y;
962
- const nz_b = -n_t2box_z;
963
- const stored_nx_b = isConcaveA ? nx_b : -nx_b;
964
- const stored_ny_b = isConcaveA ? ny_b : -ny_b;
965
- const stored_nz_b = isConcaveA ? nz_b : -nz_b;
966
-
967
- // Emit each clipped contact. The contact count from
968
- // box_triangle_contact is at most 4; combined with the
969
- // existing per-pair candidate cap (64 entries), tens of
970
- // overlapping triangles per pair are safely accommodated.
971
- const cc = box_triangle_result[3] | 0;
972
- for (let k = 0; k < cc; k++) {
973
- const base = 4 + k * 7;
974
- const t_wx = box_triangle_result[base];
975
- const t_wy = box_triangle_result[base + 1];
976
- const t_wz = box_triangle_result[base + 2];
977
- const b_wx = box_triangle_result[base + 3];
978
- const b_wy = box_triangle_result[base + 4];
979
- const b_wz = box_triangle_result[base + 5];
980
- const d_k = box_triangle_result[base + 6];
981
-
982
- // Dedup against earlier candidates in THIS dispatch
983
- // (adjacent triangles can produce coincident contacts
984
- // — same as the sphere and EPA paths).
985
- let is_duplicate_k = false;
986
- for (let q = pair_start_count; q < count; q++) {
987
- const qo = q * CANDIDATE_STRIDE;
988
- const dnx = candidates[qo + 6] - stored_nx_b;
989
- const dny = candidates[qo + 7] - stored_ny_b;
990
- const dnz = candidates[qo + 8] - stored_nz_b;
991
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
992
- // Same normal; check positions too (a
993
- // multi-point manifold has distinct points
994
- // with the same normal — those should NOT
995
- // be deduped). Compare the world-A side
996
- // (triangle surface or box surface depending
997
- // on dispatcher swap).
998
- const pa_x = isConcaveA ? t_wx : b_wx;
999
- const pa_y = isConcaveA ? t_wy : b_wy;
1000
- const pa_z = isConcaveA ? t_wz : b_wz;
1001
- const dpx = candidates[qo] - pa_x;
1002
- const dpy = candidates[qo + 1] - pa_y;
1003
- const dpz = candidates[qo + 2] - pa_z;
1004
- if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1005
- is_duplicate_k = true;
1006
- break;
1007
- }
1008
- }
1009
- }
1010
- if (is_duplicate_k) continue;
1011
-
1012
- if (isConcaveA) {
1013
- count = append_contact(count,
1014
- t_wx, t_wy, t_wz,
1015
- b_wx, b_wy, b_wz,
1016
- nx_b, ny_b, nz_b, d_k, tri_fid);
1017
- } else {
1018
- count = append_contact(count,
1019
- b_wx, b_wy, b_wz,
1020
- t_wx, t_wy, t_wz,
1021
- -nx_b, -ny_b, -nz_b, d_k, tri_fid);
1022
- }
1023
- }
1024
- continue;
1025
- }
1026
-
1027
- // Capsule-vs-triangle closed-form fast-path.
1028
- if (isCapsuleConvex) {
1029
- // Rotate each triangle vertex from concave-local to world
1030
- // via the shared helper.
1031
- triangle_vertices_to_world(
1032
- scratch_tri_world,
1033
- ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
1034
- cqx, cqy, cqz, cqw,
1035
- c_pos_x, c_pos_y, c_pos_z
1036
- );
1037
- const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
1038
- const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
1039
- const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
1040
-
1041
- const cap_cc = capsule_triangle_contact(
1042
- capsule_triangle_result,
1043
- convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
1044
- convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
1045
- capsule_shape.radius, capsule_shape.height * 0.5,
1046
- ax_w, ay_w, az_w,
1047
- bx_w, by_w, bz_w,
1048
- cx_w, cy_w, cz_w
1049
- );
1050
- if (cap_cc === 0) continue;
1051
-
1052
- // Each sub-contact's normal can be distinct (a flat
1053
- // capsule produces primary + endpoint contacts whose
1054
- // normals all point ≈ same direction, but an edge or
1055
- // vertex contact at a cap can deviate). Apply
1056
- // face-normal rejection per-contact.
1057
- for (let k = 0; k < cap_cc; k++) {
1058
- const base = k * CAPSULE_TRIANGLE_CONTACT_STRIDE;
1059
- const cap_x = capsule_triangle_result[base];
1060
- const cap_y = capsule_triangle_result[base + 1];
1061
- const cap_z = capsule_triangle_result[base + 2];
1062
- const ct_x = capsule_triangle_result[base + 3];
1063
- const ct_y = capsule_triangle_result[base + 4];
1064
- const ct_z = capsule_triangle_result[base + 5];
1065
- const n_t2cap_x = capsule_triangle_result[base + 6];
1066
- const n_t2cap_y = capsule_triangle_result[base + 7];
1067
- const n_t2cap_z = capsule_triangle_result[base + 8];
1068
- const d_c = capsule_triangle_result[base + 9];
1069
-
1070
- // One-sided face-normal rejection: capsule must be
1071
- // on the outward side of the triangle.
1072
- if (n_t2cap_x * fnx_w + n_t2cap_y * fny_w + n_t2cap_z * fnz_w <= 0) continue;
1073
-
1074
- // Stored normal "convex → concave" = "capsule →
1075
- // triangle" = negated t2cap.
1076
- const nx_c = -n_t2cap_x;
1077
- const ny_c = -n_t2cap_y;
1078
- const nz_c = -n_t2cap_z;
1079
- const stored_nx_c = isConcaveA ? nx_c : -nx_c;
1080
- const stored_ny_c = isConcaveA ? ny_c : -ny_c;
1081
- const stored_nz_c = isConcaveA ? nz_c : -nz_c;
1082
-
1083
- // Dedup against earlier candidates in THIS dispatch.
1084
- let is_duplicate_c = false;
1085
- for (let q = pair_start_count; q < count; q++) {
1086
- const qo = q * CANDIDATE_STRIDE;
1087
- const dnx = candidates[qo + 6] - stored_nx_c;
1088
- const dny = candidates[qo + 7] - stored_ny_c;
1089
- const dnz = candidates[qo + 8] - stored_nz_c;
1090
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1091
- const pa_x = isConcaveA ? ct_x : cap_x;
1092
- const pa_y = isConcaveA ? ct_y : cap_y;
1093
- const pa_z = isConcaveA ? ct_z : cap_z;
1094
- const dpx = candidates[qo] - pa_x;
1095
- const dpy = candidates[qo + 1] - pa_y;
1096
- const dpz = candidates[qo + 2] - pa_z;
1097
- if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1098
- is_duplicate_c = true;
1099
- break;
1100
- }
1101
- }
1102
- }
1103
- if (is_duplicate_c) continue;
1104
-
1105
- if (isConcaveA) {
1106
- count = append_contact(count,
1107
- ct_x, ct_y, ct_z,
1108
- cap_x, cap_y, cap_z,
1109
- nx_c, ny_c, nz_c, d_c, tri_fid);
1110
- } else {
1111
- count = append_contact(count,
1112
- cap_x, cap_y, cap_z,
1113
- ct_x, ct_y, ct_z,
1114
- -nx_c, -ny_c, -nz_c, d_c, tri_fid);
1115
- }
1116
- }
1117
- continue;
1118
- }
1119
-
1120
- // Robust GJK + EPA (see gjk/gjk_epa_penetration.js): returns the
1121
- // penetration depth and writes a UNIT axis into epa_result. We scale
1122
- // it to the MTV vector (ex,ey,ez) the tuned sign-check / one-sided
1123
- // rejection / dedup below already consume. The old gjk_with_axis +
1124
- // expanding_polytope_algorithm pair was replaced because it returned
1125
- // a non-minimal axis on degenerate simplices (see memory:
1126
- // feedback_epa_unreliable_polytopes); MPR is kept as a secondary
1127
- // safety net for the rare case the robust query reports no overlap.
1128
- let ex, ey, ez, depth;
1129
- const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1130
- if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1131
- ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1132
- depth = pen_depth;
1133
- } else {
1134
- if (!mpr(epa_result, 0, posed_a, posed_b)) continue;
1135
- ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1136
- depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1137
- if (!(depth > 0) || !Number.isFinite(depth)) continue;
1138
- }
1139
-
1140
- // Sign-check: validate MTV direction against (convex −
1141
- // triangle_centroid). EPA's polytope-closest-face normal
1142
- // can come out on the wrong side of the origin; this flip
1143
- // canonicalises MTV to point from triangle (A) toward the
1144
- // convex shape (B).
1145
- const cent_lx = (ax + bx + cx_v) / 3;
1146
- const cent_ly = (ay + by + cy_v) / 3;
1147
- const cent_lz = (az + bz + cz_v) / 3;
1148
- // Inlined q · v · q* + translate.
1149
- const cix = cqw * cent_lx + cqy * cent_lz - cqz * cent_ly;
1150
- const ciy = cqw * cent_ly + cqz * cent_lx - cqx * cent_lz;
1151
- const ciz = cqw * cent_lz + cqx * cent_ly - cqy * cent_lx;
1152
- const ciw = -cqx * cent_lx - cqy * cent_ly - cqz * cent_lz;
1153
- const cent_wx = cix * cqw + ciw * -cqx + ciy * -cqz - ciz * -cqy + c_pos_x;
1154
- const cent_wy = ciy * cqw + ciw * -cqy + ciz * -cqx - cix * -cqz + c_pos_y;
1155
- const cent_wz = ciz * cqw + ciw * -cqz + cix * -cqy - ciy * -cqx + c_pos_z;
1156
-
1157
- const tri_ab_x = convex_wx - cent_wx;
1158
- const tri_ab_y = convex_wy - cent_wy;
1159
- const tri_ab_z = convex_wz - cent_wz;
1160
- if (ex * tri_ab_x + ey * tri_ab_y + ez * tri_ab_z < 0) {
1161
- ex = -ex; ey = -ey; ez = -ez;
1162
- }
1163
-
1164
- // One-sided rejection (post-sign-check). MTV now points
1165
- // from triangle (A) toward convex shape (B). For a CCW
1166
- // outward-wound triangle, the convex shape is on the
1167
- // OUTWARD side iff MTV aligns with the face normal. If it
1168
- // opposes the face normal, the convex shape is on the
1169
- // back / inward side — invalid for heightmap/mesh (it's
1170
- // inside the solid). Skip the triangle rather than push
1171
- // the body deeper into the solid through the opposite
1172
- // face on the next step.
1173
- if (ex * fnx_w + ey * fny_w + ez * fnz_w <= 0) continue;
1174
-
1175
- // After the validated flip, MTV points from triangle
1176
- // (concave-side A) into convex (B). Stored normal is
1177
- // "B → A" so we negate. Final A/B order matches
1178
- // append_contact's contract: original A first, original B
1179
- // second, normal "from original B toward original A".
1180
- const inv = 1 / depth;
1181
- const nx = -ex * inv;
1182
- const ny = -ey * inv;
1183
- const nz = -ez * inv;
1184
-
1185
- // Dedup against earlier contacts emitted in THIS dispatch.
1186
- // Adjacent triangles (heightmap cells sharing a diagonal;
1187
- // mesh triangles sharing an edge or vertex) often report
1188
- // identical contacts (same normal, same body-centre
1189
- // application points), and feeding duplicates to the
1190
- // sequential-impulse solver makes it escalate the impulse
1191
- // across iterations without bound. We deduplicate by
1192
- // contact normal within a small angular threshold: same
1193
- // body pair + same normal + same body-centre positions =
1194
- // the same physical contact, only one copy belongs in the
1195
- // manifold.
1196
- const stored_nx = isConcaveA ? nx : -nx;
1197
- const stored_ny = isConcaveA ? ny : -ny;
1198
- const stored_nz = isConcaveA ? nz : -nz;
1199
- let is_duplicate = false;
1200
- for (let k = pair_start_count; k < count; k++) {
1201
- const ko = k * CANDIDATE_STRIDE;
1202
- const dnx = candidates[ko + 6] - stored_nx;
1203
- const dny = candidates[ko + 7] - stored_ny;
1204
- const dnz = candidates[ko + 8] - stored_nz;
1205
- if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1206
- is_duplicate = true;
1207
- break;
1208
- }
1209
- }
1210
- if (is_duplicate) continue;
1211
-
1212
- // Contact application points: body centres rather than
1213
- // the triangle's centroid. The existing GJK+EPA fallback
1214
- // (below) uses the same convention — for vertical contacts
1215
- // (sphere/box on a flat surface, the common case), the
1216
- // lever arm r × n vanishes and the impulse resolves
1217
- // cleanly.
1218
- if (isConcaveA) {
1219
- count = append_contact(count,
1220
- c_pos_x, c_pos_y, c_pos_z,
1221
- convex_wx, convex_wy, convex_wz,
1222
- nx, ny, nz, depth, tri_fid);
1223
- } else {
1224
- count = append_contact(count,
1225
- convex_wx, convex_wy, convex_wz,
1226
- c_pos_x, c_pos_y, c_pos_z,
1227
- -nx, -ny, -nz, depth, tri_fid);
1228
- }
1229
- }
1230
-
1231
- return count;
1232
- }
1233
-
1234
- // Robust GJK + EPA fallback for any convex pair without a closed form or a
1235
- // dedicated branch above (e.g. a primitive vs an authored ConvexHullShape3D,
1236
- // a cylinder/cone). gjk_epa_penetration returns the depth and writes a UNIT
1237
- // axis into epa_result; we scale to the MTV vector (ex,ey,ez) the body-centre
1238
- // sign-guard below consumes. Replaces the old gjk_with_axis +
1239
- // expanding_polytope_algorithm pair, which returned a non-minimal axis on the
1240
- // degenerate simplices its GJK produced (see memory:
1241
- // feedback_epa_unreliable_polytopes). MPR is kept as a secondary safety net.
1242
- posed_a.setup(colA.shape, trA.position, trA.rotation);
1243
- posed_b.setup(colB.shape, trB.position, trB.rotation);
1244
-
1245
- let ex, ey, ez, depth;
1246
- const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1247
- if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1248
- ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1249
- depth = pen_depth;
1250
- } else {
1251
- if (!mpr(epa_result, 0, posed_a, posed_b)) return count;
1252
- ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1253
- depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1254
- if (!(depth > 0) || !Number.isFinite(depth)) return count;
1255
- }
1256
-
1257
- // EPA's output vector should point along the minimum-translation
1258
- // axis from A into B (the direction you'd push B by to separate).
1259
- // On non-convergent EPA — common on smooth high-poly colliders like
1260
- // a torus knot, where the polytope can't tighten onto the true
1261
- // closest face within the iteration cap — the fallback returns
1262
- // *some* face's normal, and that face can be on either side of the
1263
- // origin. Resulting MTV occasionally points B *into* A instead of
1264
- // *away from* A; the solver then sees a "separating" relative
1265
- // velocity, clamps the normal impulse to zero, and the bodies pass
1266
- // straight through each other.
1267
- //
1268
- // Validate against the vector from body A's centre to body B's
1269
- // centre — EPA's direction must correlate positively with it. Flip
1270
- // if not. For convex bodies whose origin is inside the geometry,
1271
- // body-centre-to-body-centre is a strong proxy for the correct
1272
- // separation direction. For pathological cases (deeply
1273
- // interpenetrating bodies with coincident centres) the dot product
1274
- // is near zero and we leave EPA's guess intact.
1275
- const ab_x = trB.position.x - trA.position.x;
1276
- const ab_y = trB.position.y - trA.position.y;
1277
- const ab_z = trB.position.z - trA.position.z;
1278
- if (ex * ab_x + ey * ab_y + ez * ab_z < 0) {
1279
- ex = -ex; ey = -ey; ez = -ez;
1280
- }
1281
-
1282
- const inv = 1 / depth;
1283
- const nx = -ex * inv, ny = -ey * inv, nz = -ez * inv; // stored normal, B → A
1284
-
1285
- // Contact application points: body centres rather than support-function
1286
- // witnesses. The natural-sounding alternative — `posed_a.support(+EPA_dir)`
1287
- // and `posed_b.support(-EPA_dir)` — interacts badly with flat-faced
1288
- // supports: a large floor's support in `+Y` returns a *corner* (the box
1289
- // support is multi-valued along an axis-aligned direction and picks the
1290
- // sign-tied corner), which can be tens of metres from the actual
1291
- // contact patch. That blows up the solver's lever arm `r × n` and
1292
- // collapses `m_eff` to ~0, leaving the impulse vanishingly small.
1293
- //
1294
- // Body centres give `r ‖ n` for vertical contacts (sphere/knot landing
1295
- // on a floor — the common case), so `r × n = 0` and `m_eff = invM`
1296
- // resolves the impact cleanly. For oblique contacts the application
1297
- // point lies on the line between centres, which is the right-ish lever
1298
- // arm for translational impulse and degrades gracefully on angular
1299
- // response. A future closed-form mesh-vs-box path will compute a
1300
- // proper face-projected witness; until then, body centres are the
1301
- // robust fallback for the GJK+EPA route.
1302
- // GJK+EPA fallback has no stable per-frame feature info; emit fid = 0
1303
- // so match-and-merge uses position-fallback. Single contact per pair.
1304
- return append_contact(count,
1305
- trA.position.x, trA.position.y, trA.position.z,
1306
- trB.position.x, trB.position.y, trB.position.z,
1307
- nx, ny, nz,
1308
- depth, 0
1309
- );
1310
- }
1311
-
1312
- // Reusable single-pair adapters for the penetration query below — no per-call
1313
- // allocation. dispatch_pair only reads `.shape` off a collider and
1314
- // `.position` / `.rotation` off a transform, so these minimal stand-ins are
1315
- // all it needs.
1316
- const _pp_colA = { shape: null };
1317
- const _pp_colB = { shape: null };
1318
- const _pp_trA = { position: null, rotation: null };
1319
- const _pp_trB = { position: null, rotation: null };
1320
-
1321
-
1322
- /**
1323
- * Single-pair penetration query: the depth and world normal of the DEEPEST
1324
- * contact the narrowphase would generate for one posed shape pair.
1325
- *
1326
- * Routes through the exact same {@link dispatch_pair} the contact solver
1327
- * consumes — closed-form for every sphere / box / capsule pair (box-box via
1328
- * SAT, so the true minimum-translation axis is found rather than the
1329
- * centroid-seeded portal MPR would pick), triangle decomposition + closed-form
1330
- * per triangle for convex-vs-concave, and GJK + EPA (+ MPR) for any other
1331
- * convex pair. The deepest contact's depth is the minimum-translation distance
1332
- * and its normal is the MTV axis, so the result is correct for every shape pair
1333
- * the engine can build and agrees bit-for-bit with what the solver acts on.
1334
- *
1335
- * The normal follows the narrowphase's stored convention: a unit vector
1336
- * pointing from B toward A — the direction to translate A to separate it.
1337
- *
1338
- * Concave-vs-concave is not dispatched (the narrowphase skips it) and returns
1339
- * 0; callers needing to reject that case must check `is_convex` themselves.
1340
- *
1341
- * Not re-entrant: shares the module-level candidate / scratch buffers with
1342
- * {@link narrowphase_step}. Intended for main-thread queries run outside a
1343
- * step (depenetration, overlap depth, tooling) — never from inside one.
1344
- *
1345
- * @param {Float64Array|number[]} out_normal length ≥ 3; receives the unit B→A
1346
- * normal on penetration (untouched when the return value is 0)
1347
- * @param {AbstractShape3D} shapeA
1348
- * @param {{x:number,y:number,z:number}} posA
1349
- * @param {{x:number,y:number,z:number,w:number}} rotA
1350
- * @param {AbstractShape3D} shapeB
1351
- * @param {{x:number,y:number,z:number}} posB
1352
- * @param {{x:number,y:number,z:number,w:number}} rotB
1353
- * @returns {number} deepest penetration depth (> 0) or 0 if separated
1354
- */
1355
- export function deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB, posB, rotB) {
1356
- _pp_colA.shape = shapeA;
1357
- _pp_trA.position = posA;
1358
- _pp_trA.rotation = rotA;
1359
- _pp_colB.shape = shapeB;
1360
- _pp_trB.position = posB;
1361
- _pp_trB.rotation = rotB;
1362
-
1363
- const n = dispatch_pair(0, _pp_colA, _pp_trA, _pp_colB, _pp_trB);
1364
- if (n === 0) {
1365
- return 0;
1366
- }
1367
-
1368
- // Deepest contact = the minimum-translation depth; its stored normal is the
1369
- // separation axis. (For multi-point manifolds — box-box, capsule-box, a
1370
- // convex straddling several mesh triangles — every point shares the
1371
- // separating axis, so the max depth along it is the distance to separate.)
1372
- let best_depth = -1;
1373
- let best_off = 0;
1374
- for (let i = 0; i < n; i++) {
1375
- const off = i * CANDIDATE_STRIDE;
1376
- const d = candidates[off + 9];
1377
- if (d > best_depth) {
1378
- best_depth = d;
1379
- best_off = off;
1380
- }
1381
- }
1382
-
1383
- if (!(best_depth > 0) || !Number.isFinite(best_depth)) {
1384
- return 0;
1385
- }
1386
-
1387
- out_normal[0] = candidates[best_off + 6];
1388
- out_normal[1] = candidates[best_off + 7];
1389
- out_normal[2] = candidates[best_off + 8];
1390
-
1391
- return best_depth;
1392
- }
1393
-
1394
- /**
1395
- * For every pair in `pair_list`, do a cross-product over A's collider list ×
1396
- * B's collider list, accumulate candidate contacts, reduce to ≤4, and write
1397
- * to the manifold slot.
1398
- *
1399
- * @param {PairList} pair_list
1400
- * @param {ManifoldStore} manifolds
1401
- * @param {Array<Array<{collider: Collider, transform: Transform}>>} lists
1402
- * per-body collider lists, indexed by body-storage slot index. Typically
1403
- * `system.__body_collider_lists` — passed in directly so this helper
1404
- * has no dependency on `PhysicsSystem`.
1405
- */
1406
- export function narrowphase_step(pair_list, manifolds, lists) {
1407
- const count = pair_list.count;
1408
-
1409
- for (let i = 0; i < count; i++) {
1410
- const idA = pair_list.get_a(i);
1411
- const idB = pair_list.get_b(i);
1412
-
1413
- const idxA = body_id_index(idA);
1414
- const idxB = body_id_index(idB);
1415
-
1416
- const list_a = lists[idxA];
1417
- const list_b = lists[idxB];
1418
-
1419
- const slot = manifolds.find(idA, idB);
1420
-
1421
- if (list_a === undefined || list_b === undefined
1422
- || list_a.length === 0 || list_b.length === 0
1423
- ) {
1424
-
1425
- manifolds.clear_contacts(slot);
1426
- continue;
1427
-
1428
- }
1429
-
1430
- let cand_count = 0;
1431
-
1432
- const la_len = list_a.length;
1433
- const lb_len = list_b.length;
1434
-
1435
- let sub_pair = 0;
1436
- for (let a = 0; a < la_len; a++) {
1437
- const ea = list_a[a];
1438
-
1439
- for (let b = 0; b < lb_len; b++) {
1440
- const eb = list_b[b];
1441
-
1442
- const before = cand_count;
1443
- cand_count = dispatch_pair(
1444
- cand_count,
1445
- ea.collider,
1446
- ea.transform,
1447
- eb.collider,
1448
- eb.transform
1449
- );
1450
-
1451
- // Salt this sub-pair's fids ({@link FID_PAIR_SALT}) so a
1452
- // compound body cannot fid-match — and inherit warm-start
1453
- // impulses — across different collider pairs. fid 0 stays 0.
1454
- if (sub_pair !== 0) {
1455
- const salt = sub_pair * FID_PAIR_SALT;
1456
- for (let c = before; c < cand_count; c++) {
1457
- const co = c * CANDIDATE_STRIDE;
1458
- if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1459
- }
1460
- }
1461
- sub_pair++;
1462
- }
1463
- }
1464
-
1465
- if (cand_count === 0) {
1466
- // No contacts this frame for an existing manifold. Keep the
1467
- // slot in the cache (the grace window in advance_frame() will
1468
- // evict it if this persists), but zero out impulses and
1469
- // contact count — there's nothing for the solver to act on
1470
- // and stale impulses would mislead next frame's warm-start
1471
- // if contact re-establishes at a different feature.
1472
- manifolds.clear_contacts(slot);
1473
- continue;
1474
- }
1475
-
1476
- const kept = reduce_manifold_contacts(candidates, cand_count, CANDIDATE_STRIDE, MAX_KEPT, 9);
1477
-
1478
- // ── Match-and-merge: feature-id (with position fallback) ──────
1479
- //
1480
- // Snapshot prev-frame state for matching + impulse carry-over.
1481
- const data = manifolds.data_buffer;
1482
- const slot_off = manifolds.slot_data_offset(slot);
1483
- const prev_count_raw = manifolds.contact_count(slot);
1484
- const prev_count = prev_count_raw > MAX_CONTACTS_PER_MANIFOLD
1485
- ? MAX_CONTACTS_PER_MANIFOLD
1486
- : prev_count_raw;
1487
- for (let j = 0; j < prev_count; j++) {
1488
- const off = slot_off + j * CONTACT_STRIDE;
1489
- const snap_off = j * 7;
1490
- prev_snapshot[snap_off] = data[off + 13]; // feature_id
1491
- prev_snapshot[snap_off + 1] = data[off]; // world_a x
1492
- prev_snapshot[snap_off + 2] = data[off + 1]; // world_a y
1493
- prev_snapshot[snap_off + 3] = data[off + 2]; // world_a z
1494
- prev_snapshot[snap_off + 4] = data[off + 10]; // j_n
1495
- prev_snapshot[snap_off + 5] = data[off + 11]; // j_t1
1496
- prev_snapshot[snap_off + 6] = data[off + 12]; // j_t2
1497
- prev_claimed[j] = 0;
1498
- }
1499
-
1500
- // For each new candidate, find a matching prev contact.
1501
- // Step 1: feature-id match (only if BOTH sides have a non-zero
1502
- // feature_id — fid = 0 means "no info"). Same-fid prev
1503
- // contacts are disambiguated by NEAREST previous witness:
1504
- // a fid identifies the FEATURE (e.g. a triangle), not the
1505
- // contact point — the box/capsule-triangle paths emit up
1506
- // to 4 contacts sharing one triangle's fid, and the
1507
- // reducer reorders them frame to frame, so first-match
1508
- // would shuffle warm-start impulses between a face's own
1509
- // contact points. (redetect_pair_geometry documents and
1510
- // fixes the same failure for the per-substep path.)
1511
- // Step 2: position-fallback within MATCH_TOL_SQR.
1512
- for (let k = 0; k < kept; k++) {
1513
- const cand_off = k * CANDIDATE_STRIDE;
1514
- const cand_fid = candidates[cand_off + 10];
1515
- const cand_ax = candidates[cand_off];
1516
- const cand_ay = candidates[cand_off + 1];
1517
- const cand_az = candidates[cand_off + 2];
1518
-
1519
- let best_prev = -1;
1520
- if (cand_fid !== 0) {
1521
- // Gated by FID_MATCH_MAX_D2: a fid recomputed half a metre
1522
- // away (teleport, fid collision) must not inherit impulses.
1523
- let best_d2 = FID_MATCH_MAX_D2;
1524
- for (let j = 0; j < prev_count; j++) {
1525
- if (prev_claimed[j]) continue;
1526
- if (prev_snapshot[j * 7] !== cand_fid) continue;
1527
- const snap_off = j * 7;
1528
- const dx = prev_snapshot[snap_off + 1] - cand_ax;
1529
- const dy = prev_snapshot[snap_off + 2] - cand_ay;
1530
- const dz = prev_snapshot[snap_off + 3] - cand_az;
1531
- const d2 = dx * dx + dy * dy + dz * dz;
1532
- if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1533
- }
1534
- }
1535
- if (best_prev === -1) {
1536
- let best_d2 = MATCH_TOL_SQR;
1537
- for (let j = 0; j < prev_count; j++) {
1538
- if (prev_claimed[j]) continue;
1539
- const snap_off = j * 7;
1540
- const dx = prev_snapshot[snap_off + 1] - cand_ax;
1541
- const dy = prev_snapshot[snap_off + 2] - cand_ay;
1542
- const dz = prev_snapshot[snap_off + 3] - cand_az;
1543
- const d2 = dx * dx + dy * dy + dz * dz;
1544
- if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1545
- }
1546
- }
1547
- cand_to_prev[k] = best_prev;
1548
- if (best_prev !== -1) prev_claimed[best_prev] = 1;
1549
- }
1550
-
1551
- // Reset count without zeroing the data slab — set_contact below
1552
- // will overwrite geometry, and matched candidates will inherit
1553
- // the impulses we snapshotted above (written back at the new
1554
- // slot index after set_contact).
1555
- manifolds.begin_refill(slot);
1556
-
1557
- for (let k = 0; k < kept; k++) {
1558
- const off = k * CANDIDATE_STRIDE;
1559
- // Target slot index = k (we write candidates 0..kept-1 into
1560
- // slot indices 0..kept-1). The impulse carry-over below
1561
- // copies the matched prev contact's impulses into THIS k,
1562
- // so the geometric and warm-start state stay correlated
1563
- // even if the matching mapping permuted the order.
1564
- manifolds.set_contact(
1565
- slot, k,
1566
- candidates[off], candidates[off + 1], candidates[off + 2],
1567
- candidates[off + 3], candidates[off + 4], candidates[off + 5],
1568
- candidates[off + 6], candidates[off + 7], candidates[off + 8],
1569
- candidates[off + 9],
1570
- candidates[off + 10],
1571
- candidates[off + 11], candidates[off + 12]
1572
- );
1573
- const prev_j = cand_to_prev[k];
1574
- if (prev_j !== -1) {
1575
- // Copy prev_j's impulse to this slot index.
1576
- const dst_off = slot_off + k * CONTACT_STRIDE;
1577
- const src_off = prev_j * 7;
1578
- data[dst_off + 10] = prev_snapshot[src_off + 4];
1579
- data[dst_off + 11] = prev_snapshot[src_off + 5];
1580
- data[dst_off + 12] = prev_snapshot[src_off + 6];
1581
- } else {
1582
- // No match — explicitly zero the impulses, since this
1583
- // slot index may have stale data from an earlier frame's
1584
- // contact at the same index.
1585
- manifolds.clear_impulses(slot, k);
1586
- }
1587
- }
1588
- }
1589
- }
1590
-
1591
- /**
1592
- * Re-detect contact GEOMETRY for one existing manifold slot at the bodies'
1593
- * current poses, updating the witness points / normal / depth of the slot's
1594
- * existing contacts in place. Does NOT change the contact count, the
1595
- * feature ids, or the accumulated impulses — it only refreshes geometry.
1596
- *
1597
- * This is the per-substep concave path (TGS): for a contact pair involving a
1598
- * concave body, the contact *feature* (which triangle is deepest, and its
1599
- * normal) genuinely changes as the body rocks, so the solver's cheap analytic
1600
- * refresh — which freezes the feature for the whole outer step — pumps energy
1601
- * in. Re-running the narrowphase geometry each substep gives a fresh,
1602
- * correct normal so the body settles instead of rocking. Convex pairs keep
1603
- * the analytic refresh and never call this (their feature is stable).
1604
- *
1605
- * Matching is by feature id (stable per-triangle for the decomposition path),
1606
- * so a contact's geometry tracks the same triangle across substeps. A contact
1607
- * whose triangle isn't found this substep keeps its previous geometry (a rare
1608
- * transient; the once-per-frame {@link narrowphase_step} re-establishes the
1609
- * contact set next outer step). Count never changes here, so the solver's
1610
- * per-contact scratch (sized once at prepare) stays aligned.
1611
- *
1612
- * @param {ManifoldStore} manifolds
1613
- * @param {number} slot
1614
- * @param {Array<{collider: Collider, transform: Transform}>} list_a
1615
- * @param {Array<{collider: Collider, transform: Transform}>} list_b
1616
- */
1617
- export function redetect_pair_geometry(manifolds, slot, list_a, list_b) {
1618
- if (list_a === undefined || list_b === undefined) return;
1619
- const la_len = list_a.length;
1620
- const lb_len = list_b.length;
1621
- if (la_len === 0 || lb_len === 0) return;
1622
-
1623
- const count = manifolds.contact_count(slot);
1624
- if (count === 0) return;
1625
-
1626
- let cc = 0;
1627
- let sub_pair = 0;
1628
- for (let a = 0; a < la_len; a++) {
1629
- const ea = list_a[a];
1630
- for (let b = 0; b < lb_len; b++) {
1631
- const eb = list_b[b];
1632
- const before = cc;
1633
- cc = dispatch_pair(cc, ea.collider, ea.transform, eb.collider, eb.transform);
1634
- // Stored fids are sub-pair-salted (see narrowphase_step's dispatch
1635
- // loop) — salt the redetected candidates identically or no fid
1636
- // would ever match for a compound body's non-first sub-pair.
1637
- if (sub_pair !== 0) {
1638
- const salt = sub_pair * FID_PAIR_SALT;
1639
- for (let c = before; c < cc; c++) {
1640
- const co = c * CANDIDATE_STRIDE;
1641
- if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1642
- }
1643
- }
1644
- sub_pair++;
1645
- }
1646
- }
1647
- if (cc === 0) return; // nothing re-detected this substep — keep frozen geometry
1648
-
1649
- const data = manifolds.data_buffer;
1650
- const slot_off = manifolds.slot_data_offset(slot);
1651
-
1652
- // Match each existing contact to a DISTINCT fresh candidate. feature_id
1653
- // identifies the TRIANGLE, not the contact point — the box/capsule-triangle
1654
- // paths emit several contacts for one triangle, all sharing that triangle's
1655
- // single fid (a flat box-on-heightmap cell yields fids like [6,6,6,7]). A
1656
- // plain first-match-by-fid therefore maps every same-fid existing contact
1657
- // onto the SAME candidate, collapsing the manifold to duplicate witness
1658
- // points → a degenerate support polygon the solver can't damp (the
1659
- // box-on-heightmap rattle). So: gate by fid, disambiguate same-fid
1660
- // candidates by NEAREST previous witness (world-A) position, and claim each
1661
- // candidate so no two existing contacts take the same one. For the common
1662
- // unique-fid case (sphere/mesh: one contact per triangle) this picks that
1663
- // single candidate exactly once — identical to the old behaviour.
1664
- for (let k = 0; k < cc; k++) redetect_claimed[k] = 0;
1665
-
1666
- for (let j = 0; j < count; j++) {
1667
- const off = slot_off + j * CONTACT_STRIDE;
1668
- const fid = data[off + 13];
1669
- if (fid === 0) continue; // no feature info to match on
1670
-
1671
- // Previous witness (world-A) of this existing contact — the anchor we
1672
- // disambiguate same-fid candidates against.
1673
- const pax = data[off];
1674
- const pay = data[off + 1];
1675
- const paz = data[off + 2];
1676
-
1677
- let best_k = -1;
1678
- let best_d2 = Infinity;
1679
- for (let k = 0; k < cc; k++) {
1680
- if (redetect_claimed[k] === 1) continue;
1681
- const co = k * CANDIDATE_STRIDE;
1682
- if (candidates[co + 10] !== fid) continue;
1683
- const dx = candidates[co] - pax;
1684
- const dy = candidates[co + 1] - pay;
1685
- const dz = candidates[co + 2] - paz;
1686
- const d2 = dx * dx + dy * dy + dz * dz;
1687
- if (d2 < best_d2) {
1688
- best_d2 = d2;
1689
- best_k = k;
1690
- }
1691
- }
1692
-
1693
- // Position fallback (mirrors narrowphase_step's match-and-merge). A
1694
- // single triangle's clipped contact count is NOT stable across sub-mm
1695
- // pose changes — a box straddling a cell seam can have one triangle
1696
- // yield 4 points one substep and 3 the next — so an existing contact can
1697
- // outnumber this substep's same-fid candidates. Freezing its stale
1698
- // witness then duplicates a sibling contact's point, leaving a
1699
- // degenerate (sub-dimensional) manifold. Instead, claim the nearest
1700
- // unclaimed candidate of ANY fid: every contact keeps a DISTINCT live
1701
- // witness. The fid label is left intact and re-resolved by the next
1702
- // once-per-step narrowphase. (At a fixed pose the same-fid match always
1703
- // succeeds, so this never fires for the depth-equality guards or the
1704
- // one-contact-per-triangle sphere/mesh paths.)
1705
- if (best_k === -1) {
1706
- for (let k = 0; k < cc; k++) {
1707
- if (redetect_claimed[k] === 1) continue;
1708
- const co = k * CANDIDATE_STRIDE;
1709
- const dx = candidates[co] - pax;
1710
- const dy = candidates[co + 1] - pay;
1711
- const dz = candidates[co + 2] - paz;
1712
- const d2 = dx * dx + dy * dy + dz * dz;
1713
- if (d2 < best_d2) {
1714
- best_d2 = d2;
1715
- best_k = k;
1716
- }
1717
- }
1718
- }
1719
-
1720
- if (best_k === -1) continue; // no unclaimed candidate at all this substep — keep frozen geometry
1721
-
1722
- redetect_claimed[best_k] = 1;
1723
- const co = best_k * CANDIDATE_STRIDE;
1724
- // Overwrite geometry only: witnesses, normal, depth. (Count, feature
1725
- // ids and accumulated impulses are intentionally left untouched — see
1726
- // the function contract.)
1727
- data[off] = candidates[co];
1728
- data[off + 1] = candidates[co + 1];
1729
- data[off + 2] = candidates[co + 2];
1730
- data[off + 3] = candidates[co + 3];
1731
- data[off + 4] = candidates[co + 4];
1732
- data[off + 5] = candidates[co + 5];
1733
- data[off + 6] = candidates[co + 6];
1734
- data[off + 7] = candidates[co + 7];
1735
- data[off + 8] = candidates[co + 8];
1736
- data[off + 9] = candidates[co + 9];
1737
- }
1738
- }
1
+ import { aabb3_transform_oriented } from "../../../core/geom/3d/aabb/aabb3_transform_oriented.js";
2
+ import { Triangle3D } from "../../../core/geom/3d/shape/Triangle3D.js";
3
+ import { body_id_index } from "../body/BodyStorage.js";
4
+ import { combine_friction, combine_restitution } from "../contact/combine_material.js";
5
+ import { CONTACT_STRIDE, MAX_CONTACTS_PER_MANIFOLD } from "../contact/ManifoldStore.js";
6
+ import { gjk_epa_penetration } from "../../../core/geom/3d/gjk/gjk_epa_penetration.js";
7
+ import { reduce_manifold_contacts } from "./reduce_manifold_contacts.js";
8
+ import { mpr } from "../../../core/geom/3d/gjk/mpr.js";
9
+ import { box_box_manifold, BOX_BOX_OUT_LENGTH } from "./box_box_manifold.js";
10
+ import { convex_hull_clip, CONVEX_CONVEX_OUT_LENGTH } from "./convex_convex_manifold.js";
11
+ import { get_mesh_convex_hull } from "./mesh_convex_hull.js";
12
+ import { mesh_mesh_tet_contacts } from "./mesh_mesh_tet_manifold.js";
13
+ import { box_triangle_contact, BOX_TRIANGLE_OUT_LENGTH } from "./box_triangle_contact.js";
14
+ import {
15
+ CAPSULE_BOX_CONTACT_STRIDE,
16
+ CAPSULE_BOX_MAX_CONTACTS,
17
+ capsule_box_multi_contacts,
18
+ capsule_capsule_contact,
19
+ CAPSULE_CAPSULE_CONTACT_STRIDE,
20
+ capsule_capsule_multi_contacts,
21
+ capsule_sphere_contact,
22
+ } from "./capsule_contacts.js";
23
+ import {
24
+ capsule_triangle_contact,
25
+ CAPSULE_TRIANGLE_CONTACT_STRIDE,
26
+ CAPSULE_TRIANGLE_MAX_CONTACTS,
27
+ } from "./capsule_triangle_contact.js";
28
+ import { aabb3_transform_oriented_inverse } from "../../../core/geom/3d/aabb/aabb3_transform_oriented_inverse.js";
29
+ import { decompose_to_triangles } from "./decomposition/decompose_to_triangles.js";
30
+ import { TRIANGLE_FLOAT_STRIDE } from "./decomposition/triangle_buffer_layout.js";
31
+ import { PosedShape3D } from "../../../core/geom/3d/shape/PosedShape3D.js";
32
+ import { sphere_box_contact } from "./sphere_box_contact.js";
33
+ import { sphere_sphere_contact } from "./sphere_sphere_contact.js";
34
+ import { sphere_triangle_contact } from "./sphere_triangle_contact.js";
35
+
36
+ const posed_a = new PosedShape3D();
37
+ const posed_b = new PosedShape3D();
38
+
39
+ // Penetration axis + depth from the robust GJK + EPA query
40
+ // (gjk_epa_penetration writes a unit normal here; it owns its own simplex
41
+ // internally, so the narrowphase no longer keeps a simplex buffer).
42
+ const epa_result = new Float64Array(3);
43
+ const sphere_result = new Float64Array(4);
44
+ const closed_form_result = new Float64Array(10);
45
+
46
+ /** Scratch for the capsule-capsule multi-contact manifold (2 contacts max). */
47
+ const capsule_capsule_result = new Float64Array(2 * CAPSULE_CAPSULE_CONTACT_STRIDE);
48
+ const sphere_triangle_result = new Float64Array(10);
49
+ const box_triangle_result = new Float64Array(BOX_TRIANGLE_OUT_LENGTH);
50
+ const capsule_triangle_result = new Float64Array(CAPSULE_TRIANGLE_MAX_CONTACTS * CAPSULE_TRIANGLE_CONTACT_STRIDE);
51
+ const box_manifold_result = new Float64Array(BOX_BOX_OUT_LENGTH);
52
+ const convex_manifold_result = new Float64Array(CONVEX_CONVEX_OUT_LENGTH);
53
+ const capsule_box_multi_result = new Float64Array(CAPSULE_BOX_MAX_CONTACTS * CAPSULE_BOX_CONTACT_STRIDE);
54
+
55
+ /**
56
+ * Candidate-contact stride: wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth,
57
+ * feature_id, friction, restitution.
58
+ *
59
+ * The `feature_id` (offset 10) is a stable cross-frame identifier of the
60
+ * geometric feature pair that produced this contact — used by the
61
+ * match-and-merge pass in {@link narrowphase_step} to carry warm-start
62
+ * impulses from the previous frame's manifold to the slot index that
63
+ * corresponds to the same physical contact. A value of 0 means
64
+ * "no feature info, fall back to position matching".
65
+ *
66
+ * `friction` (offset 11) and `restitution` (offset 12) are the COMBINED
67
+ * coefficients for the specific (colliderA, colliderB) pair that produced this
68
+ * contact, combined here (the only place that knows the exact source collider
69
+ * on each side) and carried into the manifold so a compound body's per-collider
70
+ * materials are honoured per-contact.
71
+ *
72
+ * @type {number}
73
+ */
74
+ const CANDIDATE_STRIDE = 13;
75
+
76
+ /**
77
+ * Combined friction / restitution for the collider pair currently being
78
+ * dispatched. Set once at the top of {@link dispatch_pair} (which is called
79
+ * per collider pair) and written into every contact that call appends, so
80
+ * each contact carries the material of its actual source colliders. Module
81
+ * scratch rather than threaded through every `append_contact` call site.
82
+ * @type {number}
83
+ */
84
+ let g_pair_friction = 0;
85
+ let g_pair_restitution = 0;
86
+
87
+ /**
88
+ * Maximum number of contacts emitted into the per-pair manifold after the
89
+ * reduction step. Mirrors {@link MAX_CONTACTS_PER_MANIFOLD} in ManifoldStore.
90
+ * @type {number}
91
+ */
92
+ const MAX_KEPT = MAX_CONTACTS_PER_MANIFOLD;
93
+
94
+ /**
95
+ * Position-fallback tolerance for warm-start matching: when a candidate
96
+ * contact has no feature id (or none of the previous-frame contacts shares
97
+ * its id), match by closest world_a within this 3-D distance.
98
+ *
99
+ * 2 cm matches the original PLAN.md spec for "Persistent manifold cache"
100
+ * — generous enough to follow small inter-frame contact migration on
101
+ * curved surfaces, tight enough that distinct contacts on a single
102
+ * manifold (typically >5 cm apart) don't get confused with each other.
103
+ *
104
+ * @type {number}
105
+ */
106
+ const MATCH_TOL_SQR = 0.02 * 0.02;
107
+
108
+ /**
109
+ * Per-(colliderA, colliderB) feature-id salt. Each emitter numbers its
110
+ * features in a small private range (sphere-box voronoi 1..27, capsule-box
111
+ * 1..k, triangles 1..N, …), so two SUB-PAIRS of a compound body can produce
112
+ * identical fids for unrelated features and warm-start impulses could be
113
+ * inherited across collider pairs. Salting by the sub-pair index keeps every
114
+ * fid unique across the body pair while staying exactly representable in f64
115
+ * (sub_pair · 2³² + fid stays far below 2⁵³ for any realistic compound; no
116
+ * single-pair emitter exceeds 2³²). Sub-pair 0 — the universal
117
+ * single-collider case — is left unsalted, so simple bodies see unchanged
118
+ * fids.
119
+ * @type {number}
120
+ */
121
+ const FID_PAIR_SALT = 0x100000000; // 2^32
122
+
123
+ /**
124
+ * Distance ceiling for a feature-id warm-start match. A fid match may track
125
+ * a fast-sliding contact far beyond {@link MATCH_TOL_SQR} — that is its
126
+ * advantage over position matching — but an impulse inherited across half a
127
+ * metre in one frame is stale by any standard (a teleported body re-deriving
128
+ * the same voronoi bucket, or a fid collision between unrelated features).
129
+ * The position fallback keeps its own 2 cm gate.
130
+ * @type {number}
131
+ */
132
+ const FID_MATCH_MAX_D2 = 0.5 * 0.5;
133
+
134
+ /**
135
+ * Scratch for the previous-frame contact snapshot taken at the top of the
136
+ * match-and-merge pass. Sized for {@link MAX_CONTACTS_PER_MANIFOLD}
137
+ * contacts, 7 floats per contact:
138
+ * 0 : feature_id
139
+ * 1, 2, 3 : world_a x, y, z (for position-fallback matching)
140
+ * 4, 5, 6 : j_n, j_t1, j_t2 (carried forward to the matched candidate)
141
+ *
142
+ * Snapshotting upfront decouples the read (from the slot's previous state)
143
+ * from the write (the new contact data + impulse copy), avoiding the
144
+ * read-after-write hazard when the matching mapping shuffles indices.
145
+ *
146
+ * @type {Float64Array}
147
+ */
148
+ const prev_snapshot = new Float64Array(MAX_CONTACTS_PER_MANIFOLD * 7);
149
+
150
+ /**
151
+ * Per-prev-contact "already claimed by a candidate" flag.
152
+ * @type {Uint8Array}
153
+ */
154
+ const prev_claimed = new Uint8Array(MAX_CONTACTS_PER_MANIFOLD);
155
+
156
+ /**
157
+ * For each kept candidate, the matched prev-contact index in
158
+ * `[0, prev_count)` or `-1` if no match.
159
+ * @type {Int32Array}
160
+ */
161
+ const cand_to_prev = new Int32Array(MAX_KEPT);
162
+
163
+ /**
164
+ * Per-candidate "already claimed" flags for {@link redetect_pair_geometry}'s
165
+ * 1:1 existing-contact → fresh-candidate matching. Sized to the candidate
166
+ * buffer capacity (64) so it covers any per-pair candidate count. Without it,
167
+ * several existing contacts that share one triangle's `feature_id` (the
168
+ * box/capsule-triangle paths emit multiple contacts per triangle) would all
169
+ * match the same first candidate, collapsing the manifold to duplicate witness
170
+ * points.
171
+ * @type {Uint8Array}
172
+ */
173
+ const redetect_claimed = new Uint8Array(64);
174
+
175
+ /**
176
+ * Per body-pair scratch buffer for candidate contacts produced by the
177
+ * cross-product of A's colliders × B's colliders. Sized generously for
178
+ * typical compound bodies (each collider-pair contributes 1..4 contacts;
179
+ * 64 covers up to 4 colliders per body × 4 colliders × 4 contacts = 64).
180
+ * @type {Float64Array}
181
+ */
182
+ const candidates = new Float64Array(64 * CANDIDATE_STRIDE);
183
+
184
+ /**
185
+ * Maximum triangles a concave-side enumerator can produce per pair.
186
+ * The query AABB is bounded by the broadphase's fattened envelope of
187
+ * the convex-side body, so a single concave-vs-convex pair typically
188
+ * yields tens of triangles, not thousands. 1024 is the safety cap.
189
+ *
190
+ * For a heightmap the per-cell triangle count scales O(N²) with the
191
+ * shape's {@link HeightMapShape3D#tessellation} (a sub-cell quad is 2
192
+ * triangles, and there are N×N sub-cells per sampler cell). The bounded
193
+ * query AABB keeps the cell count small, so a typical pair stays well
194
+ * inside 1024 at moderate tessellation; the silent-drop backstop below
195
+ * covers any overflow at extreme values.
196
+ *
197
+ * Enumerators return the TRUE overlap count and have no bounds check of
198
+ * their own: writes past the scratch buffer are dropped (typed-array OOB
199
+ * semantics), so on overflow the consume loop MUST clamp the returned
200
+ * count to this cap or it reads `undefined` past the end and poisons the
201
+ * manifold with NaN. The worst case after clamping is a missed contact
202
+ * on a far edge of the filtered region, which the next-step
203
+ * rebroadphase corrects.
204
+ *
205
+ * @type {number}
206
+ */
207
+ const MAX_TRIANGLES_PER_PAIR = 1024;
208
+
209
+ /**
210
+ * Per-pair scratch for the concave-side triangle decomposition.
211
+ * @type {Float64Array}
212
+ */
213
+ const triangle_buffer = new Float64Array(MAX_TRIANGLES_PER_PAIR * TRIANGLE_FLOAT_STRIDE);
214
+
215
+ /**
216
+ * Flyweight triangle shape — rebound to each successive triangle slice
217
+ * of `triangle_buffer` during the concave-side dispatch loop. Zero
218
+ * allocation per triangle.
219
+ * @type {Triangle3D}
220
+ */
221
+ const triangle_shape = new Triangle3D();
222
+
223
+ /**
224
+ * Scratch AABB buffers used only by the concave-side dispatch:
225
+ * - `concave_local_aabb` : convex shape's local AABB (input to oriented transform)
226
+ * - `concave_world_aabb` : convex shape's world AABB
227
+ * - `concave_query_aabb` : convex shape's AABB projected into concave's body-local frame
228
+ * (what the triangle enumerator filters against)
229
+ */
230
+ const concave_local_aabb = new Float64Array(6);
231
+ const concave_world_aabb = new Float64Array(6);
232
+ const concave_query_aabb = new Float64Array(6);
233
+
234
+ /**
235
+ * Rotate + translate a triangle's three vertices from a concave body's
236
+ * local frame into world space: writes 9 floats `(A_w, B_w, C_w)` into
237
+ * `out`. Inlined `q · v · q*` + translate per vertex (see
238
+ * PosedShape3D.support for the V8-inliner rationale); the operation order
239
+ * matches the blocks this replaced bit-exactly. Per-vertex cost ~21 flops
240
+ * × 3 — essentially free next to any narrowphase kernel it feeds.
241
+ *
242
+ * Shared by the sphere / box / capsule closed-form triangle fast-paths in
243
+ * the concave dispatch, which each carried an identical inlined copy.
244
+ */
245
+ function triangle_vertices_to_world(out, ax, ay, az, bx, by, bz, cx, cy, cz, qx, qy, qz, qw, px, py, pz) {
246
+ const axi = qw * ax + qy * az - qz * ay;
247
+ const ayi = qw * ay + qz * ax - qx * az;
248
+ const azi = qw * az + qx * ay - qy * ax;
249
+ const awi = -qx * ax - qy * ay - qz * az;
250
+
251
+ out[0] = axi * qw + awi * -qx + ayi * -qz - azi * -qy + px;
252
+ out[1] = ayi * qw + awi * -qy + azi * -qx - axi * -qz + py;
253
+ out[2] = azi * qw + awi * -qz + axi * -qy - ayi * -qx + pz;
254
+
255
+ const bxi = qw * bx + qy * bz - qz * by;
256
+ const byi = qw * by + qz * bx - qx * bz;
257
+ const bzi = qw * bz + qx * by - qy * bx;
258
+ const bwi = -qx * bx - qy * by - qz * bz;
259
+
260
+ out[3] = bxi * qw + bwi * -qx + byi * -qz - bzi * -qy + px;
261
+ out[4] = byi * qw + bwi * -qy + bzi * -qx - bxi * -qz + py;
262
+ out[5] = bzi * qw + bwi * -qz + bxi * -qy - byi * -qx + pz;
263
+
264
+ const cxi = qw * cx + qy * cz - qz * cy;
265
+ const cyi = qw * cy + qz * cx - qx * cz;
266
+ const czi = qw * cz + qx * cy - qy * cx;
267
+ const cwi = -qx * cx - qy * cy - qz * cz;
268
+
269
+ out[6] = cxi * qw + cwi * -qx + cyi * -qz - czi * -qy + px;
270
+ out[7] = cyi * qw + cwi * -qy + czi * -qx - cxi * -qz + py;
271
+ out[8] = czi * qw + cwi * -qz + cxi * -qy - cyi * -qx + pz;
272
+ }
273
+
274
+ /** Destination for {@link triangle_vertices_to_world}. */
275
+ const scratch_tri_world = new Float64Array(9);
276
+
277
+ /**
278
+ * Append one contact to the candidate buffer. Returns the new count.
279
+ *
280
+ * @param {number} count
281
+ * @param {number} wax
282
+ * @param {number} way
283
+ * @param {number} waz
284
+ * @param {number} wbx
285
+ * @param {number} wby
286
+ * @param {number} wbz
287
+ * @param {number} nx
288
+ * @param {number} ny
289
+ * @param {number} nz
290
+ * @param {number} depth
291
+ * @param {number} feature_id stable cross-frame ID for warm-start matching;
292
+ * `0` means no info — match-and-merge will fall back to position.
293
+ * @returns {number}
294
+ */
295
+ function append_contact(count, wax, way, waz, wbx, wby, wbz, nx, ny, nz, depth, feature_id) {
296
+ if (count * CANDIDATE_STRIDE >= candidates.length){
297
+ return count;
298
+ }
299
+
300
+ const off = count * CANDIDATE_STRIDE;
301
+
302
+ candidates[off] = wax; candidates[off + 1] = way; candidates[off + 2] = waz;
303
+ candidates[off + 3] = wbx; candidates[off + 4] = wby; candidates[off + 5] = wbz;
304
+ candidates[off + 6] = nx; candidates[off + 7] = ny; candidates[off + 8] = nz;
305
+ candidates[off + 9] = depth;
306
+ candidates[off + 10] = feature_id;
307
+ candidates[off + 11] = g_pair_friction;
308
+ candidates[off + 12] = g_pair_restitution;
309
+
310
+ return count + 1;
311
+ }
312
+
313
+ /**
314
+ * Compute a stable voronoi-region feature id for a sphere-vs-box contact.
315
+ * Returns one of 27 values in `[1, 27]` based on which of the 27 voronoi
316
+ * regions of the box the sphere centre lies in (8 corners + 12 edges + 6
317
+ * faces + 1 interior). Stable across frames as long as the sphere stays
318
+ * in the same region — the typical case in steady-state contact.
319
+ *
320
+ * Encodes:
321
+ * bucket_x ∈ {0, 1, 2} = sign(lx) bucket in box-local frame
322
+ * bucket_y, bucket_z similarly
323
+ * fid = 1 + bucket_x + 3·bucket_y + 9·bucket_z → 1..27
324
+ *
325
+ * The "+1" offset ensures the result is never 0, since 0 is reserved
326
+ * for "no feature info, use position-fallback".
327
+ *
328
+ * @param {number} sx sphere centre x
329
+ * @param {number} sy
330
+ * @param {number} sz
331
+ * @param {number} bx box centre x
332
+ * @param {number} by
333
+ * @param {number} bz
334
+ * @param {number} bqx box quaternion x
335
+ * @param {number} bqy
336
+ * @param {number} bqz
337
+ * @param {number} bqw
338
+ * @param {number} hx box half-extent x
339
+ * @param {number} hy
340
+ * @param {number} hz
341
+ * @returns {number}
342
+ */
343
+ function sphere_box_voronoi_fid(sx, sy, sz, bx, by, bz, bqx, bqy, bqz, bqw, hx, hy, hz) {
344
+ // Inverse-rotate (sx - bx, sy - by, sz - bz) by the box's quaternion
345
+ // to get the sphere centre in box-local frame. Inlined for the same
346
+ // V8-inliner reason described in PosedShape3D.support — see
347
+ // core/geom/vec3/v3_quat3_apply_inverse.js for the canonical form.
348
+ const dx = sx - bx;
349
+ const dy = sy - by;
350
+ const dz = sz - bz;
351
+
352
+ const tx0 = bqw * dx - bqy * dz + bqz * dy;
353
+ const ty0 = bqw * dy - bqz * dx + bqx * dz;
354
+ const tz0 = bqw * dz - bqx * dy + bqy * dx;
355
+ const tw0 = bqx * dx + bqy * dy + bqz * dz;
356
+
357
+ const lx = tx0 * bqw + tw0 * bqx + ty0 * bqz - tz0 * bqy;
358
+ const ly = ty0 * bqw + tw0 * bqy + tz0 * bqx - tx0 * bqz;
359
+ const lz = tz0 * bqw + tw0 * bqz + tx0 * bqy - ty0 * bqx;
360
+
361
+ const bx_b = lx <= -hx ? 0 : (lx >= hx ? 2 : 1);
362
+ const by_b = ly <= -hy ? 0 : (ly >= hy ? 2 : 1);
363
+ const bz_b = lz <= -hz ? 0 : (lz >= hz ? 2 : 1);
364
+
365
+ return 1 + bx_b + 3 * by_b + 9 * bz_b;
366
+ }
367
+
368
+ /**
369
+ * Multi-point contact manifold between two convex polytopes (authored
370
+ * ConvexHullShape3D, or a convex MeshShape3D represented as one hull). Robust
371
+ * GJK + EPA finds the separating axis in O(support queries) — face-count
372
+ * independent, where SAT's edge-pair term is prohibitive — then
373
+ * {@link convex_hull_clip} clips the reference/incident faces into a face-on-face
374
+ * patch around it (Bullet's clipHullAgainstHull). A single GJK+EPA contact can't
375
+ * resist a stack's toppling torque; the clipped patch holds it. No cross-frame
376
+ * state → reset-and-resimulate determinism.
377
+ *
378
+ * @param {number} count
379
+ * @param {{vertices,face_offsets,face_loops,support}} hullA in A's local frame
380
+ * @param {Transform} trA
381
+ * @param {{vertices,face_offsets,face_loops,support}} hullB in B's local frame
382
+ * @param {Transform} trB
383
+ * @param {Function} append_contact
384
+ * @returns {number}
385
+ */
386
+ function hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact) {
387
+ posed_a.setup(hullA, trA.position, trA.rotation);
388
+ posed_b.setup(hullB, trB.position, trB.rotation);
389
+ const depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
390
+ if (!(depth > 0) || !Number.isFinite(depth)) return count;
391
+
392
+ // convex_hull_clip orients the axis B→A robustly (via vertex centroids) and
393
+ // writes the final normal into convex_manifold_result[0..2].
394
+ convex_hull_clip(convex_manifold_result,
395
+ hullA, trA.position, trA.rotation,
396
+ hullB, trB.position, trB.rotation,
397
+ epa_result[0], epa_result[1], epa_result[2]);
398
+ const nx = convex_manifold_result[0], ny = convex_manifold_result[1], nz = convex_manifold_result[2];
399
+ const cc = convex_manifold_result[3] | 0;
400
+ // Clipped points migrate across features as the hulls rotate, so leave
401
+ // fid = 0 and let the match-and-merge pass position-match (as box-box does).
402
+ for (let k = 0; k < cc; k++) {
403
+ const base = 4 + k * 7;
404
+ count = append_contact(count,
405
+ convex_manifold_result[base], convex_manifold_result[base + 1], convex_manifold_result[base + 2],
406
+ convex_manifold_result[base + 3], convex_manifold_result[base + 4], convex_manifold_result[base + 5],
407
+ nx, ny, nz,
408
+ convex_manifold_result[base + 6],
409
+ 0);
410
+ }
411
+ return count;
412
+ }
413
+
414
+ /**
415
+ * Run pairwise narrowphase for one (colliderA, colliderB) tuple — dispatches
416
+ * by shape type and appends 0..K contacts to the candidate buffer. Returns
417
+ * the new candidate count.
418
+ *
419
+ * @param {number} count
420
+ * @param {Collider} colA
421
+ * @param {Transform} trA
422
+ * @param {Collider} colB
423
+ * @param {Transform} trB
424
+ * @returns {number}
425
+ */
426
+ function dispatch_pair(count, colA, trA, colB, trB) {
427
+ const shapeA = colA.shape;
428
+ const shapeB = colB.shape;
429
+
430
+ // Per-contact materials: combine the two source colliders' coefficients
431
+ // once here (this is the only place that knows the exact collider on each
432
+ // side) and stamp them onto every contact this dispatch appends. The
433
+ // `deepest_pair_penetration` query passes bare `{shape}` adapters with no
434
+ // material fields — it never writes to a manifold, so 0 is fine there.
435
+ const fa = colA.friction, fb = colB.friction;
436
+
437
+ if (fa !== undefined && fb !== undefined) {
438
+
439
+ g_pair_friction = combine_friction(fa, fb);
440
+ g_pair_restitution = combine_restitution(colA.restitution, colB.restitution);
441
+
442
+ } else {
443
+ g_pair_friction = 0;
444
+ g_pair_restitution = 0;
445
+ }
446
+
447
+ // isSphereShape3D covers both UnitSphereShape3D (fixed radius 1) and
448
+ // SphereShape3D (arbitrary radius). Both expose `radius`.
449
+ const isSphereA = shapeA.isSphereShape3D === true;
450
+ const isSphereB = shapeB.isSphereShape3D === true;
451
+ // isBoxShape3D covers both UnitCubeShape3D (fixed 0.5) and BoxShape3D
452
+ // (arbitrary half-extents). Both expose `half_extents` as a Vector3.
453
+ const isBoxA = shapeA.isBoxShape3D === true;
454
+ const isBoxB = shapeB.isBoxShape3D === true;
455
+ const isCapsuleA = shapeA.isCapsuleShape3D === true;
456
+ const isCapsuleB = shapeB.isCapsuleShape3D === true;
457
+
458
+ // sphere-sphere
459
+ if (isSphereA && isSphereB) {
460
+ const ra = shapeA.radius, rb = shapeB.radius;
461
+
462
+ const ok = sphere_sphere_contact(
463
+ sphere_result,
464
+ trA.position.x, trA.position.y, trA.position.z,
465
+ trB.position.x, trB.position.y, trB.position.z,
466
+ ra, rb
467
+ );
468
+
469
+ if (!ok) return count;
470
+
471
+ const nx = sphere_result[0], ny = sphere_result[1], nz = sphere_result[2];
472
+
473
+ // Sphere-sphere produces exactly one contact per pair; fid = 1
474
+ // identifies it as a real feature (distinguishes from "no info" = 0)
475
+ // and is trivially stable across frames. Witnesses are the surface
476
+ // points along the (unit) normal, scaled by each sphere's radius.
477
+ return append_contact(count,
478
+ trA.position.x - nx * ra, trA.position.y - ny * ra, trA.position.z - nz * ra,
479
+ trB.position.x + nx * rb, trB.position.y + ny * rb, trB.position.z + nz * rb,
480
+ nx, ny, nz, sphere_result[3], 1);
481
+ }
482
+
483
+ // sphere ↔ box
484
+ if ((isSphereA && isBoxB) || (isBoxA && isSphereB)) {
485
+ const sphereTr = isSphereA ? trA : trB;
486
+ const sphereShape = isSphereA ? shapeA : shapeB;
487
+ const boxTr = isSphereA ? trB : trA;
488
+ const boxShape = isSphereA ? shapeB : shapeA;
489
+ const bh = boxShape.half_extents;
490
+
491
+ const ok = sphere_box_contact(
492
+ closed_form_result,
493
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius,
494
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
495
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
496
+ bh.x, bh.y, bh.z
497
+ );
498
+
499
+ if (!ok) return count;
500
+
501
+ let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
502
+
503
+ const depth = closed_form_result[3];
504
+ const wsx = closed_form_result[4], wsy = closed_form_result[5], wsz = closed_form_result[6];
505
+ const wbx = closed_form_result[7], wby = closed_form_result[8], wbz = closed_form_result[9];
506
+ // Feature id from the box-local voronoi region (1..27) — stable
507
+ // while the sphere stays on the same face / edge / vertex of the
508
+ // box, which is the steady-state case for resting / sliding contact.
509
+
510
+ const fid = sphere_box_voronoi_fid(
511
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z,
512
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
513
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
514
+ bh.x, bh.y, bh.z
515
+ );
516
+
517
+ if (isSphereA) {
518
+ return append_contact(count, wsx, wsy, wsz, wbx, wby, wbz, nx, ny, nz, depth, fid);
519
+ } else {
520
+ return append_contact(count, wbx, wby, wbz, wsx, wsy, wsz, -nx, -ny, -nz, depth, fid);
521
+ }
522
+ }
523
+
524
+ // box-box multi-point
525
+ if (isBoxA && isBoxB) {
526
+ const ah = shapeA.half_extents;
527
+ const bh = shapeB.half_extents;
528
+ const ok = box_box_manifold(
529
+ box_manifold_result,
530
+ trA.position.x, trA.position.y, trA.position.z,
531
+ trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
532
+ ah.x, ah.y, ah.z,
533
+ trB.position.x, trB.position.y, trB.position.z,
534
+ trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
535
+ bh.x, bh.y, bh.z
536
+ );
537
+ if (!ok) return count;
538
+ const nx = box_manifold_result[0], ny = box_manifold_result[1], nz = box_manifold_result[2];
539
+ const cc = box_manifold_result[3] | 0;
540
+ // Box-box manifolds: closed-form clipping doesn't expose stable
541
+ // per-contact feature ids (a contact migrates from "incident-vertex k"
542
+ // to "clip-intersection on edge j" as the boxes rotate). Leave
543
+ // fid = 0 so the match-and-merge pass uses position-fallback —
544
+ // the per-contact clipped points are spread by face geometry and
545
+ // typically stay >>MATCH_TOL apart, so position matching is
546
+ // unambiguous frame-to-frame.
547
+ for (let k = 0; k < cc; k++) {
548
+ const base = 4 + k * 7;
549
+ count = append_contact(count,
550
+ box_manifold_result[base], box_manifold_result[base + 1], box_manifold_result[base + 2],
551
+ box_manifold_result[base + 3], box_manifold_result[base + 4], box_manifold_result[base + 5],
552
+ nx, ny, nz,
553
+ box_manifold_result[base + 6],
554
+ 0);
555
+ }
556
+ return count;
557
+ }
558
+
559
+ // capsule-capsule (multi-point: near-parallel overlap emits both ends of
560
+ // the overlap interval; everything else the single closest-point contact)
561
+ if (isCapsuleA && isCapsuleB) {
562
+ const a_shape = colA.shape, b_shape = colB.shape;
563
+ const n_cc = capsule_capsule_multi_contacts(
564
+ capsule_capsule_result,
565
+ trA.position.x, trA.position.y, trA.position.z,
566
+ trA.rotation.x, trA.rotation.y, trA.rotation.z, trA.rotation.w,
567
+ a_shape.radius, a_shape.height * 0.5,
568
+ trB.position.x, trB.position.y, trB.position.z,
569
+ trB.rotation.x, trB.rotation.y, trB.rotation.z, trB.rotation.w,
570
+ b_shape.radius, b_shape.height * 0.5
571
+ );
572
+ for (let k = 0; k < n_cc; k++) {
573
+ const off = k * CAPSULE_CAPSULE_CONTACT_STRIDE;
574
+ // fid = k + 1: stable per interval end (1-based; 0 = no info).
575
+ count = append_contact(count,
576
+ capsule_capsule_result[off], capsule_capsule_result[off + 1], capsule_capsule_result[off + 2],
577
+ capsule_capsule_result[off + 3], capsule_capsule_result[off + 4], capsule_capsule_result[off + 5],
578
+ capsule_capsule_result[off + 6], capsule_capsule_result[off + 7], capsule_capsule_result[off + 8],
579
+ capsule_capsule_result[off + 9], k + 1);
580
+ }
581
+ return count;
582
+ }
583
+
584
+ // capsule ↔ sphere
585
+ if ((isCapsuleA && isSphereB) || (isSphereA && isCapsuleB)) {
586
+ const capsuleTr = isCapsuleA ? trA : trB;
587
+ const capsuleShape = isCapsuleA ? colA.shape : colB.shape;
588
+ const sphereTr = isCapsuleA ? trB : trA;
589
+ const sphereShape = isCapsuleA ? colB.shape : colA.shape;
590
+ const ok = capsule_sphere_contact(
591
+ closed_form_result,
592
+ capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
593
+ capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
594
+ capsuleShape.radius, capsuleShape.height * 0.5,
595
+ sphereTr.position.x, sphereTr.position.y, sphereTr.position.z, sphereShape.radius
596
+ );
597
+ if (!ok) return count;
598
+ let nx = closed_form_result[0], ny = closed_form_result[1], nz = closed_form_result[2];
599
+ const depth = closed_form_result[3];
600
+ const cap_x = closed_form_result[4], cap_y = closed_form_result[5], cap_z = closed_form_result[6];
601
+ const sph_x = closed_form_result[7], sph_y = closed_form_result[8], sph_z = closed_form_result[9];
602
+ // Single contact per capsule-sphere pair; fid = 1.
603
+ if (isCapsuleA) {
604
+ return append_contact(count, cap_x, cap_y, cap_z, sph_x, sph_y, sph_z, nx, ny, nz, depth, 1);
605
+ } else {
606
+ return append_contact(count, sph_x, sph_y, sph_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, 1);
607
+ }
608
+ }
609
+
610
+ // capsule ↔ box (multi-point — 1 closest-segment + up to 2 cap-centres)
611
+ if ((isCapsuleA && isBoxB) || (isBoxA && isCapsuleB)) {
612
+ const capsuleTr = isCapsuleA ? trA : trB;
613
+ const capsuleShape = isCapsuleA ? shapeA : shapeB;
614
+ const boxTr = isCapsuleA ? trB : trA;
615
+ const boxShape = isCapsuleA ? shapeB : shapeA;
616
+ const bh = boxShape.half_extents;
617
+ const cc = capsule_box_multi_contacts(
618
+ capsule_box_multi_result,
619
+ capsuleTr.position.x, capsuleTr.position.y, capsuleTr.position.z,
620
+ capsuleTr.rotation.x, capsuleTr.rotation.y, capsuleTr.rotation.z, capsuleTr.rotation.w,
621
+ capsuleShape.radius, capsuleShape.height * 0.5,
622
+ boxTr.position.x, boxTr.position.y, boxTr.position.z,
623
+ boxTr.rotation.x, boxTr.rotation.y, boxTr.rotation.z, boxTr.rotation.w,
624
+ bh.x, bh.y, bh.z
625
+ );
626
+ if (cc === 0) return count;
627
+ // multi_result layout per contact: cap_x/y/z (A side), box_x/y/z (B side), nx/ny/nz, depth.
628
+ // Feature id per sub-contact: 1 = closest-segment, 2/3 = caps. The
629
+ // emission order from capsule_box_multi_contacts is stable across
630
+ // frames for the same geometric configuration.
631
+ for (let k = 0; k < cc; k++) {
632
+ const o = k * CAPSULE_BOX_CONTACT_STRIDE;
633
+ const cap_x = capsule_box_multi_result[o];
634
+ const cap_y = capsule_box_multi_result[o + 1];
635
+ const cap_z = capsule_box_multi_result[o + 2];
636
+ const box_x = capsule_box_multi_result[o + 3];
637
+ const box_y = capsule_box_multi_result[o + 4];
638
+ const box_z = capsule_box_multi_result[o + 5];
639
+ const nx = capsule_box_multi_result[o + 6];
640
+ const ny = capsule_box_multi_result[o + 7];
641
+ const nz = capsule_box_multi_result[o + 8];
642
+ const depth = capsule_box_multi_result[o + 9];
643
+ const sub_fid = k + 1;
644
+ if (isCapsuleA) {
645
+ count = append_contact(count, cap_x, cap_y, cap_z, box_x, box_y, box_z, nx, ny, nz, depth, sub_fid);
646
+ } else {
647
+ count = append_contact(count, box_x, box_y, box_z, cap_x, cap_y, cap_z, -nx, -ny, -nz, depth, sub_fid);
648
+ }
649
+ }
650
+ return count;
651
+ }
652
+
653
+ // convex hull ↔ convex hull (multi-point clipping via GJK + EPA + face-clip).
654
+ if (shapeA.isConvexHullShape3D === true && shapeB.isConvexHullShape3D === true) {
655
+ return hull_pair_contacts(count, shapeA, trA, shapeB, trB, append_contact);
656
+ }
657
+
658
+ // mesh ↔ mesh. A globally-convex mesh collides as a single cached hull
659
+ // (one GJK + EPA + clip) — the greedy decomposition fragments even a convex
660
+ // mesh, so detecting convexity and routing through the hull path is the
661
+ // scalable fast path. When BOTH meshes are convex, use it; otherwise fall to
662
+ // the per-piece decomposed path (convex pieces vs convex pieces).
663
+ if (shapeA.isMeshShape3D === true && shapeB.isMeshShape3D === true) {
664
+ const hullA = get_mesh_convex_hull(shapeA);
665
+ const hullB = get_mesh_convex_hull(shapeB);
666
+ if (hullA !== null && hullB !== null) {
667
+ return hull_pair_contacts(count, hullA, trA, hullB, trB, append_contact);
668
+ }
669
+ return mesh_mesh_tet_contacts(count, shapeA, trA, shapeB, trB, append_contact);
670
+ }
671
+
672
+ // ── Concave (non-convex) path ───────────────────────────────────────
673
+ //
674
+ // If either shape has `is_convex === false`, GJK on the whole shape
675
+ // produces incorrect results (Minkowski difference is not convex).
676
+ // We decompose the concave side into triangles overlapping the
677
+ // convex side's AABB, then run convex per-triangle GJK + EPA.
678
+ //
679
+ // Concave-vs-concave is intentionally NOT supported in v1: the
680
+ // M×N triangle pairs would dominate runtime, and the physics
681
+ // engine's design contract requires at least one side to be
682
+ // static / kinematic for concave shapes anyway (the broadphase +
683
+ // filter should keep such pairs out of the narrowphase entirely).
684
+ // If one slips through, we skip rather than burn cycles.
685
+ const isConcaveA = shapeA.is_convex === false;
686
+ const isConcaveB = shapeB.is_convex === false;
687
+ if (isConcaveA && isConcaveB) return count;
688
+ if (isConcaveA || isConcaveB) {
689
+ const concave_col = isConcaveA ? colA : colB;
690
+ const concave_tr = isConcaveA ? trA : trB;
691
+ const convex_col = isConcaveA ? colB : colA;
692
+ const convex_tr = isConcaveA ? trB : trA;
693
+
694
+ // 1. Convex shape's world AABB.
695
+ convex_col.shape.compute_bounding_box(concave_local_aabb);
696
+ aabb3_transform_oriented(
697
+ concave_world_aabb, 0,
698
+ concave_local_aabb[0], concave_local_aabb[1], concave_local_aabb[2],
699
+ concave_local_aabb[3], concave_local_aabb[4], concave_local_aabb[5],
700
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
701
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w
702
+ );
703
+
704
+ // 2. Project into concave's body-local frame.
705
+ aabb3_transform_oriented_inverse(
706
+ concave_query_aabb, 0,
707
+ concave_world_aabb,
708
+ concave_tr.position.x, concave_tr.position.y, concave_tr.position.z,
709
+ concave_tr.rotation.x, concave_tr.rotation.y, concave_tr.rotation.z, concave_tr.rotation.w
710
+ );
711
+
712
+ // 3. Decompose concave shape into triangles overlapping the query.
713
+ // The enumerator returns the TRUE overlap count — clamp to the
714
+ // scratch capacity (overflow writes were dropped; reading past the
715
+ // buffer would poison the manifold with NaN).
716
+ let tri_count = decompose_to_triangles(
717
+ triangle_buffer, 0, concave_col.shape,
718
+ concave_query_aabb[0], concave_query_aabb[1], concave_query_aabb[2],
719
+ concave_query_aabb[3], concave_query_aabb[4], concave_query_aabb[5]
720
+ );
721
+ if (tri_count > MAX_TRIANGLES_PER_PAIR) tri_count = MAX_TRIANGLES_PER_PAIR;
722
+ if (tri_count === 0) return count;
723
+
724
+ // 4. Set up the convex side once; the concave side gets a
725
+ // Triangle3D rebound to each triangle in the loop. We keep
726
+ // the concave side as our internal "A" so EPA's sign-check
727
+ // convention matches the convex fallback below; the final
728
+ // append_contact() swaps if the original A was the convex one.
729
+ posed_b.setup(convex_col.shape, convex_tr.position, convex_tr.rotation);
730
+ posed_a.shape = triangle_shape;
731
+ posed_a.px = concave_tr.position.x;
732
+ posed_a.py = concave_tr.position.y;
733
+ posed_a.pz = concave_tr.position.z;
734
+ posed_a.qx = concave_tr.rotation.x;
735
+ posed_a.qy = concave_tr.rotation.y;
736
+ posed_a.qz = concave_tr.rotation.z;
737
+ posed_a.qw = concave_tr.rotation.w;
738
+
739
+ // Pre-compute the centre-axis used by EPA's sign-check loop.
740
+ // For convex-vs-convex this is `(B - A) = convex_centre − concave_centre`.
741
+ const convex_wx = convex_tr.position.x;
742
+ const convex_wy = convex_tr.position.y;
743
+ const convex_wz = convex_tr.position.z;
744
+
745
+ // Track the candidate-buffer index at the start of this
746
+ // concave dispatch — the per-triangle dedup pass scans from
747
+ // here to the current count, ignoring contacts from earlier
748
+ // collider pairs in the same body pair.
749
+ const pair_start_count = count;
750
+
751
+ // Pre-compute concave's rotation components for the q · v · q⁻¹
752
+ // rotations done per-triangle below (face normal + centroid).
753
+ const cqx = concave_tr.rotation.x;
754
+ const cqy = concave_tr.rotation.y;
755
+ const cqz = concave_tr.rotation.z;
756
+ const cqw = concave_tr.rotation.w;
757
+ const c_pos_x = concave_tr.position.x;
758
+ const c_pos_y = concave_tr.position.y;
759
+ const c_pos_z = concave_tr.position.z;
760
+
761
+ // Sphere fast-path: when the convex side is a sphere we bypass GJK+EPA
762
+ // entirely per triangle and use the closed-form
763
+ // {@link sphere_triangle_contact}. This avoids the EPA precision
764
+ // wall on Triangle3D (whose support function is degenerate along
765
+ // the face normal — all 3 vertices project to the same value),
766
+ // which was producing noisy depths at small penetrations and
767
+ // letting dropped spheres tunnel through heightmaps / meshes.
768
+ const isSphereConvex = convex_col.shape.isSphereShape3D === true;
769
+ const sphere_radius = isSphereConvex ? convex_col.shape.radius : 0;
770
+
771
+ // Box fast-path: closed-form {@link box_triangle_contact} via SAT
772
+ // over 13 axes + polygon clipping for face-vs-face contacts.
773
+ // Same motivation as the sphere path — Triangle3D's degenerate
774
+ // face-normal support kills EPA precision and produces noisy
775
+ // depths. The box path uses world-space triangle vertices.
776
+ const isBoxConvex = convex_col.shape.isBoxShape3D === true;
777
+ const box_half_extents = isBoxConvex ? convex_col.shape.half_extents : null;
778
+
779
+ // Capsule fast-path: closed-form {@link capsule_triangle_contact}
780
+ // via segment-vs-triangle closest-point + cap-centre sphere
781
+ // queries for a multi-point manifold. Same motivation as the
782
+ // sphere and box paths.
783
+ const isCapsuleConvex = convex_col.shape.isCapsuleShape3D === true;
784
+ const capsule_shape = isCapsuleConvex ? convex_col.shape : null;
785
+
786
+ for (let i = 0; i < tri_count; i++) {
787
+ const tri_offset = i * TRIANGLE_FLOAT_STRIDE;
788
+ triangle_shape.bind(triangle_buffer, tri_offset);
789
+
790
+ const ax = triangle_buffer[tri_offset ];
791
+ const ay = triangle_buffer[tri_offset + 1];
792
+ const az = triangle_buffer[tri_offset + 2];
793
+
794
+ const bx = triangle_buffer[tri_offset + 3];
795
+ const by = triangle_buffer[tri_offset + 4];
796
+ const bz = triangle_buffer[tri_offset + 5];
797
+
798
+ const cx_v = triangle_buffer[tri_offset + 6];
799
+ const cy_v = triangle_buffer[tri_offset + 7];
800
+ const cz_v = triangle_buffer[tri_offset + 8];
801
+
802
+ // Triangle decomposition emits a stable per-triangle feature_id
803
+ // at offset 9 (TRIANGLE_FEATURE_ID_OFFSET) — same triangle of
804
+ // the same shape gets the same id across frames. This is the
805
+ // gold-standard fid for the match-and-merge pass.
806
+ const tri_fid = triangle_buffer[tri_offset + 9];
807
+
808
+ // Triangle face normal in body-local frame: (B − A) × (C − A).
809
+ // Winding convention (CCW from outside) gives an outward face
810
+ // normal — the heightmap / mesh enumerators both promise this.
811
+ const e1x_l = bx - ax, e1y_l = by - ay, e1z_l = bz - az;
812
+ const e2x_l = cx_v - ax, e2y_l = cy_v - ay, e2z_l = cz_v - az;
813
+
814
+ const fnx_l = e1y_l * e2z_l - e1z_l * e2y_l;
815
+ const fny_l = e1z_l * e2x_l - e1x_l * e2z_l;
816
+ const fnz_l = e1x_l * e2y_l - e1y_l * e2x_l;
817
+
818
+ // Rotate face normal to world via concave's quaternion
819
+ // (q · v · q⁻¹). Inlined for V8 inliner — see PosedShape3D.support.
820
+ const fnix = cqw * fnx_l + cqy * fnz_l - cqz * fny_l;
821
+ const fniy = cqw * fny_l + cqz * fnx_l - cqx * fnz_l;
822
+ const fniz = cqw * fnz_l + cqx * fny_l - cqy * fnx_l;
823
+ const fniw = -cqx * fnx_l - cqy * fny_l - cqz * fnz_l;
824
+
825
+ const fnx_w = fnix * cqw - fniw * cqx - fniy * cqz + fniz * cqy;
826
+ const fny_w = fniy * cqw - fniw * cqy - fniz * cqx + fnix * cqz;
827
+ const fnz_w = fniz * cqw - fniw * cqz - fnix * cqy + fniy * cqx;
828
+
829
+ // Sphere-vs-triangle closed-form fast-path.
830
+ if (isSphereConvex) {
831
+
832
+ // Rotate each triangle vertex from concave-local to world
833
+ // via the shared helper.
834
+ triangle_vertices_to_world(
835
+ scratch_tri_world,
836
+ ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
837
+ cqx, cqy, cqz, cqw,
838
+ c_pos_x, c_pos_y, c_pos_z
839
+ );
840
+ const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
841
+ const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
842
+ const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
843
+
844
+ const ok = sphere_triangle_contact(
845
+ sphere_triangle_result,
846
+ convex_wx, convex_wy, convex_wz, sphere_radius,
847
+ ax_w, ay_w, az_w,
848
+ bx_w, by_w, bz_w,
849
+ cx_w, cy_w, cz_w
850
+ );
851
+
852
+ if (!ok) continue;
853
+
854
+ // sphere_triangle_contact's normal points from the
855
+ // triangle surface toward the sphere centre — same
856
+ // direction as the post-sign-check EPA MTV in the
857
+ // fallback path below (concave A → convex B).
858
+ const sd = sphere_triangle_result[3];
859
+ const n_t2s_x = sphere_triangle_result[0];
860
+ const n_t2s_y = sphere_triangle_result[1];
861
+ const n_t2s_z = sphere_triangle_result[2];
862
+
863
+ // One-sided rejection: the sphere must lie on the
864
+ // outward side of the triangle. If the contact normal
865
+ // opposes the face normal, the sphere is behind /
866
+ // inside the solid — skip rather than push it deeper.
867
+ if (n_t2s_x * fnx_w + n_t2s_y * fny_w + n_t2s_z * fnz_w <= 0) continue;
868
+
869
+ // Stored normal convention is "B → A". `nx, ny, nz` here
870
+ // points convex → concave (which the existing EPA branch
871
+ // also produces just before append). Dedup uses the
872
+ // post-swap stored_n so adjacent-triangle duplicates
873
+ // collapse the same way regardless of code path.
874
+ const nx_s = -n_t2s_x;
875
+ const ny_s = -n_t2s_y;
876
+ const nz_s = -n_t2s_z;
877
+
878
+ const stored_nx_s = isConcaveA ? nx_s : -nx_s;
879
+ const stored_ny_s = isConcaveA ? ny_s : -ny_s;
880
+ const stored_nz_s = isConcaveA ? nz_s : -nz_s;
881
+ let is_duplicate_s = false;
882
+ for (let k = pair_start_count; k < count; k++) {
883
+ const ko = k * CANDIDATE_STRIDE;
884
+ const dnx = candidates[ko + 6] - stored_nx_s;
885
+ const dny = candidates[ko + 7] - stored_ny_s;
886
+ const dnz = candidates[ko + 8] - stored_nz_s;
887
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
888
+ is_duplicate_s = true;
889
+ break;
890
+ }
891
+ }
892
+ if (is_duplicate_s) continue;
893
+
894
+ // Surface witnesses from the closed-form solver:
895
+ // result[4..6] = sphere surface point (convex side)
896
+ // result[7..9] = triangle closest point (concave side)
897
+ // Use the actual witnesses rather than body centres —
898
+ // unlike the EPA fallback (which uses body centres
899
+ // because flat-faced support witnesses can be arbitrarily
900
+ // far from the contact patch), closest-point-on-triangle
901
+ // is exact and lies on the contact patch.
902
+ const sphere_wx = sphere_triangle_result[4];
903
+ const sphere_wy = sphere_triangle_result[5];
904
+ const sphere_wz = sphere_triangle_result[6];
905
+ const tri_wx = sphere_triangle_result[7];
906
+ const tri_wy = sphere_triangle_result[8];
907
+ const tri_wz = sphere_triangle_result[9];
908
+ if (isConcaveA) {
909
+ count = append_contact(count,
910
+ tri_wx, tri_wy, tri_wz,
911
+ sphere_wx, sphere_wy, sphere_wz,
912
+ nx_s, ny_s, nz_s, sd, tri_fid);
913
+ } else {
914
+ count = append_contact(count,
915
+ sphere_wx, sphere_wy, sphere_wz,
916
+ tri_wx, tri_wy, tri_wz,
917
+ -nx_s, -ny_s, -nz_s, sd, tri_fid);
918
+ }
919
+ continue;
920
+ }
921
+
922
+ // Box-vs-triangle closed-form fast-path.
923
+ if (isBoxConvex) {
924
+ // Rotate each triangle vertex from concave-local to world
925
+ // via the shared helper.
926
+ triangle_vertices_to_world(
927
+ scratch_tri_world,
928
+ ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
929
+ cqx, cqy, cqz, cqw,
930
+ c_pos_x, c_pos_y, c_pos_z
931
+ );
932
+ const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
933
+ const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
934
+ const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
935
+
936
+ const ok = box_triangle_contact(
937
+ box_triangle_result,
938
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
939
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
940
+ box_half_extents.x, box_half_extents.y, box_half_extents.z,
941
+ ax_w, ay_w, az_w,
942
+ bx_w, by_w, bz_w,
943
+ cx_w, cy_w, cz_w
944
+ );
945
+ if (!ok) continue;
946
+
947
+ // Same convention as sphere_triangle: result normal points
948
+ // from triangle surface toward box centre. To match the
949
+ // EPA branch's `nx, ny, nz` (which points convex →
950
+ // concave = box → triangle), negate.
951
+ const n_t2box_x = box_triangle_result[0];
952
+ const n_t2box_y = box_triangle_result[1];
953
+ const n_t2box_z = box_triangle_result[2];
954
+
955
+ // One-sided rejection: the box must lie on the outward
956
+ // side of the triangle. If the contact normal opposes
957
+ // the face normal, the box is inside the solid → skip.
958
+ if (n_t2box_x * fnx_w + n_t2box_y * fny_w + n_t2box_z * fnz_w <= 0) continue;
959
+
960
+ const nx_b = -n_t2box_x;
961
+ const ny_b = -n_t2box_y;
962
+ const nz_b = -n_t2box_z;
963
+ const stored_nx_b = isConcaveA ? nx_b : -nx_b;
964
+ const stored_ny_b = isConcaveA ? ny_b : -ny_b;
965
+ const stored_nz_b = isConcaveA ? nz_b : -nz_b;
966
+
967
+ // Emit each clipped contact. The contact count from
968
+ // box_triangle_contact is at most 4; combined with the
969
+ // existing per-pair candidate cap (64 entries), tens of
970
+ // overlapping triangles per pair are safely accommodated.
971
+ const cc = box_triangle_result[3] | 0;
972
+ for (let k = 0; k < cc; k++) {
973
+ const base = 4 + k * 7;
974
+ const t_wx = box_triangle_result[base];
975
+ const t_wy = box_triangle_result[base + 1];
976
+ const t_wz = box_triangle_result[base + 2];
977
+ const b_wx = box_triangle_result[base + 3];
978
+ const b_wy = box_triangle_result[base + 4];
979
+ const b_wz = box_triangle_result[base + 5];
980
+ const d_k = box_triangle_result[base + 6];
981
+
982
+ // Dedup against earlier candidates in THIS dispatch
983
+ // (adjacent triangles can produce coincident contacts
984
+ // — same as the sphere and EPA paths).
985
+ let is_duplicate_k = false;
986
+ for (let q = pair_start_count; q < count; q++) {
987
+ const qo = q * CANDIDATE_STRIDE;
988
+ const dnx = candidates[qo + 6] - stored_nx_b;
989
+ const dny = candidates[qo + 7] - stored_ny_b;
990
+ const dnz = candidates[qo + 8] - stored_nz_b;
991
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
992
+ // Same normal; check positions too (a
993
+ // multi-point manifold has distinct points
994
+ // with the same normal — those should NOT
995
+ // be deduped). Compare the world-A side
996
+ // (triangle surface or box surface depending
997
+ // on dispatcher swap).
998
+ const pa_x = isConcaveA ? t_wx : b_wx;
999
+ const pa_y = isConcaveA ? t_wy : b_wy;
1000
+ const pa_z = isConcaveA ? t_wz : b_wz;
1001
+ const dpx = candidates[qo] - pa_x;
1002
+ const dpy = candidates[qo + 1] - pa_y;
1003
+ const dpz = candidates[qo + 2] - pa_z;
1004
+ if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1005
+ is_duplicate_k = true;
1006
+ break;
1007
+ }
1008
+ }
1009
+ }
1010
+ if (is_duplicate_k) continue;
1011
+
1012
+ if (isConcaveA) {
1013
+ count = append_contact(count,
1014
+ t_wx, t_wy, t_wz,
1015
+ b_wx, b_wy, b_wz,
1016
+ nx_b, ny_b, nz_b, d_k, tri_fid);
1017
+ } else {
1018
+ count = append_contact(count,
1019
+ b_wx, b_wy, b_wz,
1020
+ t_wx, t_wy, t_wz,
1021
+ -nx_b, -ny_b, -nz_b, d_k, tri_fid);
1022
+ }
1023
+ }
1024
+ continue;
1025
+ }
1026
+
1027
+ // Capsule-vs-triangle closed-form fast-path.
1028
+ if (isCapsuleConvex) {
1029
+ // Rotate each triangle vertex from concave-local to world
1030
+ // via the shared helper.
1031
+ triangle_vertices_to_world(
1032
+ scratch_tri_world,
1033
+ ax, ay, az, bx, by, bz, cx_v, cy_v, cz_v,
1034
+ cqx, cqy, cqz, cqw,
1035
+ c_pos_x, c_pos_y, c_pos_z
1036
+ );
1037
+ const ax_w = scratch_tri_world[0], ay_w = scratch_tri_world[1], az_w = scratch_tri_world[2];
1038
+ const bx_w = scratch_tri_world[3], by_w = scratch_tri_world[4], bz_w = scratch_tri_world[5];
1039
+ const cx_w = scratch_tri_world[6], cy_w = scratch_tri_world[7], cz_w = scratch_tri_world[8];
1040
+
1041
+ const cap_cc = capsule_triangle_contact(
1042
+ capsule_triangle_result,
1043
+ convex_tr.position.x, convex_tr.position.y, convex_tr.position.z,
1044
+ convex_tr.rotation.x, convex_tr.rotation.y, convex_tr.rotation.z, convex_tr.rotation.w,
1045
+ capsule_shape.radius, capsule_shape.height * 0.5,
1046
+ ax_w, ay_w, az_w,
1047
+ bx_w, by_w, bz_w,
1048
+ cx_w, cy_w, cz_w
1049
+ );
1050
+ if (cap_cc === 0) continue;
1051
+
1052
+ // Each sub-contact's normal can be distinct (a flat
1053
+ // capsule produces primary + endpoint contacts whose
1054
+ // normals all point ≈ same direction, but an edge or
1055
+ // vertex contact at a cap can deviate). Apply
1056
+ // face-normal rejection per-contact.
1057
+ for (let k = 0; k < cap_cc; k++) {
1058
+ const base = k * CAPSULE_TRIANGLE_CONTACT_STRIDE;
1059
+ const cap_x = capsule_triangle_result[base];
1060
+ const cap_y = capsule_triangle_result[base + 1];
1061
+ const cap_z = capsule_triangle_result[base + 2];
1062
+ const ct_x = capsule_triangle_result[base + 3];
1063
+ const ct_y = capsule_triangle_result[base + 4];
1064
+ const ct_z = capsule_triangle_result[base + 5];
1065
+ const n_t2cap_x = capsule_triangle_result[base + 6];
1066
+ const n_t2cap_y = capsule_triangle_result[base + 7];
1067
+ const n_t2cap_z = capsule_triangle_result[base + 8];
1068
+ const d_c = capsule_triangle_result[base + 9];
1069
+
1070
+ // One-sided face-normal rejection: capsule must be
1071
+ // on the outward side of the triangle.
1072
+ if (n_t2cap_x * fnx_w + n_t2cap_y * fny_w + n_t2cap_z * fnz_w <= 0) continue;
1073
+
1074
+ // Stored normal "convex → concave" = "capsule →
1075
+ // triangle" = negated t2cap.
1076
+ const nx_c = -n_t2cap_x;
1077
+ const ny_c = -n_t2cap_y;
1078
+ const nz_c = -n_t2cap_z;
1079
+ const stored_nx_c = isConcaveA ? nx_c : -nx_c;
1080
+ const stored_ny_c = isConcaveA ? ny_c : -ny_c;
1081
+ const stored_nz_c = isConcaveA ? nz_c : -nz_c;
1082
+
1083
+ // Dedup against earlier candidates in THIS dispatch.
1084
+ let is_duplicate_c = false;
1085
+ for (let q = pair_start_count; q < count; q++) {
1086
+ const qo = q * CANDIDATE_STRIDE;
1087
+ const dnx = candidates[qo + 6] - stored_nx_c;
1088
+ const dny = candidates[qo + 7] - stored_ny_c;
1089
+ const dnz = candidates[qo + 8] - stored_nz_c;
1090
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1091
+ const pa_x = isConcaveA ? ct_x : cap_x;
1092
+ const pa_y = isConcaveA ? ct_y : cap_y;
1093
+ const pa_z = isConcaveA ? ct_z : cap_z;
1094
+ const dpx = candidates[qo] - pa_x;
1095
+ const dpy = candidates[qo + 1] - pa_y;
1096
+ const dpz = candidates[qo + 2] - pa_z;
1097
+ if (dpx * dpx + dpy * dpy + dpz * dpz < 1e-6) {
1098
+ is_duplicate_c = true;
1099
+ break;
1100
+ }
1101
+ }
1102
+ }
1103
+ if (is_duplicate_c) continue;
1104
+
1105
+ if (isConcaveA) {
1106
+ count = append_contact(count,
1107
+ ct_x, ct_y, ct_z,
1108
+ cap_x, cap_y, cap_z,
1109
+ nx_c, ny_c, nz_c, d_c, tri_fid);
1110
+ } else {
1111
+ count = append_contact(count,
1112
+ cap_x, cap_y, cap_z,
1113
+ ct_x, ct_y, ct_z,
1114
+ -nx_c, -ny_c, -nz_c, d_c, tri_fid);
1115
+ }
1116
+ }
1117
+ continue;
1118
+ }
1119
+
1120
+ // Robust GJK + EPA (see gjk/gjk_epa_penetration.js): returns the
1121
+ // penetration depth and writes a UNIT axis into epa_result. We scale
1122
+ // it to the MTV vector (ex,ey,ez) the tuned sign-check / one-sided
1123
+ // rejection / dedup below already consume. The old gjk_with_axis +
1124
+ // expanding_polytope_algorithm pair was replaced because it returned
1125
+ // a non-minimal axis on degenerate simplices (see memory:
1126
+ // feedback_epa_unreliable_polytopes); MPR is kept as a secondary
1127
+ // safety net for the rare case the robust query reports no overlap.
1128
+ let ex, ey, ez, depth;
1129
+ const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1130
+ if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1131
+ ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1132
+ depth = pen_depth;
1133
+ } else {
1134
+ if (!mpr(epa_result, 0, posed_a, posed_b)) continue;
1135
+ ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1136
+ depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1137
+ if (!(depth > 0) || !Number.isFinite(depth)) continue;
1138
+ }
1139
+
1140
+ // Sign-check: validate MTV direction against (convex −
1141
+ // triangle_centroid). EPA's polytope-closest-face normal
1142
+ // can come out on the wrong side of the origin; this flip
1143
+ // canonicalises MTV to point from triangle (A) toward the
1144
+ // convex shape (B).
1145
+ const cent_lx = (ax + bx + cx_v) / 3;
1146
+ const cent_ly = (ay + by + cy_v) / 3;
1147
+ const cent_lz = (az + bz + cz_v) / 3;
1148
+ // Inlined q · v · q* + translate.
1149
+ const cix = cqw * cent_lx + cqy * cent_lz - cqz * cent_ly;
1150
+ const ciy = cqw * cent_ly + cqz * cent_lx - cqx * cent_lz;
1151
+ const ciz = cqw * cent_lz + cqx * cent_ly - cqy * cent_lx;
1152
+ const ciw = -cqx * cent_lx - cqy * cent_ly - cqz * cent_lz;
1153
+ const cent_wx = cix * cqw + ciw * -cqx + ciy * -cqz - ciz * -cqy + c_pos_x;
1154
+ const cent_wy = ciy * cqw + ciw * -cqy + ciz * -cqx - cix * -cqz + c_pos_y;
1155
+ const cent_wz = ciz * cqw + ciw * -cqz + cix * -cqy - ciy * -cqx + c_pos_z;
1156
+
1157
+ const tri_ab_x = convex_wx - cent_wx;
1158
+ const tri_ab_y = convex_wy - cent_wy;
1159
+ const tri_ab_z = convex_wz - cent_wz;
1160
+ if (ex * tri_ab_x + ey * tri_ab_y + ez * tri_ab_z < 0) {
1161
+ ex = -ex; ey = -ey; ez = -ez;
1162
+ }
1163
+
1164
+ // One-sided rejection (post-sign-check). MTV now points
1165
+ // from triangle (A) toward convex shape (B). For a CCW
1166
+ // outward-wound triangle, the convex shape is on the
1167
+ // OUTWARD side iff MTV aligns with the face normal. If it
1168
+ // opposes the face normal, the convex shape is on the
1169
+ // back / inward side — invalid for heightmap/mesh (it's
1170
+ // inside the solid). Skip the triangle rather than push
1171
+ // the body deeper into the solid through the opposite
1172
+ // face on the next step.
1173
+ if (ex * fnx_w + ey * fny_w + ez * fnz_w <= 0) continue;
1174
+
1175
+ // After the validated flip, MTV points from triangle
1176
+ // (concave-side A) into convex (B). Stored normal is
1177
+ // "B → A" so we negate. Final A/B order matches
1178
+ // append_contact's contract: original A first, original B
1179
+ // second, normal "from original B toward original A".
1180
+ const inv = 1 / depth;
1181
+ const nx = -ex * inv;
1182
+ const ny = -ey * inv;
1183
+ const nz = -ez * inv;
1184
+
1185
+ // Dedup against earlier contacts emitted in THIS dispatch.
1186
+ // Adjacent triangles (heightmap cells sharing a diagonal;
1187
+ // mesh triangles sharing an edge or vertex) often report
1188
+ // identical contacts (same normal, same body-centre
1189
+ // application points), and feeding duplicates to the
1190
+ // sequential-impulse solver makes it escalate the impulse
1191
+ // across iterations without bound. We deduplicate by
1192
+ // contact normal within a small angular threshold: same
1193
+ // body pair + same normal + same body-centre positions =
1194
+ // the same physical contact, only one copy belongs in the
1195
+ // manifold.
1196
+ const stored_nx = isConcaveA ? nx : -nx;
1197
+ const stored_ny = isConcaveA ? ny : -ny;
1198
+ const stored_nz = isConcaveA ? nz : -nz;
1199
+ let is_duplicate = false;
1200
+ for (let k = pair_start_count; k < count; k++) {
1201
+ const ko = k * CANDIDATE_STRIDE;
1202
+ const dnx = candidates[ko + 6] - stored_nx;
1203
+ const dny = candidates[ko + 7] - stored_ny;
1204
+ const dnz = candidates[ko + 8] - stored_nz;
1205
+ if (dnx * dnx + dny * dny + dnz * dnz < 0.001) {
1206
+ is_duplicate = true;
1207
+ break;
1208
+ }
1209
+ }
1210
+ if (is_duplicate) continue;
1211
+
1212
+ // Contact application points: body centres rather than
1213
+ // the triangle's centroid. The existing GJK+EPA fallback
1214
+ // (below) uses the same convention — for vertical contacts
1215
+ // (sphere/box on a flat surface, the common case), the
1216
+ // lever arm r × n vanishes and the impulse resolves
1217
+ // cleanly.
1218
+ if (isConcaveA) {
1219
+ count = append_contact(count,
1220
+ c_pos_x, c_pos_y, c_pos_z,
1221
+ convex_wx, convex_wy, convex_wz,
1222
+ nx, ny, nz, depth, tri_fid);
1223
+ } else {
1224
+ count = append_contact(count,
1225
+ convex_wx, convex_wy, convex_wz,
1226
+ c_pos_x, c_pos_y, c_pos_z,
1227
+ -nx, -ny, -nz, depth, tri_fid);
1228
+ }
1229
+ }
1230
+
1231
+ return count;
1232
+ }
1233
+
1234
+ // Robust GJK + EPA fallback for any convex pair without a closed form or a
1235
+ // dedicated branch above (e.g. a primitive vs an authored ConvexHullShape3D,
1236
+ // a cylinder/cone). gjk_epa_penetration returns the depth and writes a UNIT
1237
+ // axis into epa_result; we scale to the MTV vector (ex,ey,ez) the body-centre
1238
+ // sign-guard below consumes. Replaces the old gjk_with_axis +
1239
+ // expanding_polytope_algorithm pair, which returned a non-minimal axis on the
1240
+ // degenerate simplices its GJK produced (see memory:
1241
+ // feedback_epa_unreliable_polytopes). MPR is kept as a secondary safety net.
1242
+ posed_a.setup(colA.shape, trA.position, trA.rotation);
1243
+ posed_b.setup(colB.shape, trB.position, trB.rotation);
1244
+
1245
+ let ex, ey, ez, depth;
1246
+ const pen_depth = gjk_epa_penetration(epa_result, posed_a, posed_b);
1247
+ if (pen_depth > 0 && Number.isFinite(pen_depth)) {
1248
+ ex = epa_result[0] * pen_depth; ey = epa_result[1] * pen_depth; ez = epa_result[2] * pen_depth;
1249
+ depth = pen_depth;
1250
+ } else {
1251
+ if (!mpr(epa_result, 0, posed_a, posed_b)) return count;
1252
+ ex = epa_result[0]; ey = epa_result[1]; ez = epa_result[2];
1253
+ depth = Math.sqrt(ex * ex + ey * ey + ez * ez);
1254
+ if (!(depth > 0) || !Number.isFinite(depth)) return count;
1255
+ }
1256
+
1257
+ // EPA's output vector should point along the minimum-translation
1258
+ // axis from A into B (the direction you'd push B by to separate).
1259
+ // On non-convergent EPA — common on smooth high-poly colliders like
1260
+ // a torus knot, where the polytope can't tighten onto the true
1261
+ // closest face within the iteration cap — the fallback returns
1262
+ // *some* face's normal, and that face can be on either side of the
1263
+ // origin. Resulting MTV occasionally points B *into* A instead of
1264
+ // *away from* A; the solver then sees a "separating" relative
1265
+ // velocity, clamps the normal impulse to zero, and the bodies pass
1266
+ // straight through each other.
1267
+ //
1268
+ // Validate against the vector from body A's centre to body B's
1269
+ // centre — EPA's direction must correlate positively with it. Flip
1270
+ // if not. For convex bodies whose origin is inside the geometry,
1271
+ // body-centre-to-body-centre is a strong proxy for the correct
1272
+ // separation direction. For pathological cases (deeply
1273
+ // interpenetrating bodies with coincident centres) the dot product
1274
+ // is near zero and we leave EPA's guess intact.
1275
+ const ab_x = trB.position.x - trA.position.x;
1276
+ const ab_y = trB.position.y - trA.position.y;
1277
+ const ab_z = trB.position.z - trA.position.z;
1278
+ if (ex * ab_x + ey * ab_y + ez * ab_z < 0) {
1279
+ ex = -ex; ey = -ey; ez = -ez;
1280
+ }
1281
+
1282
+ const inv = 1 / depth;
1283
+ const nx = -ex * inv, ny = -ey * inv, nz = -ez * inv; // stored normal, B → A
1284
+
1285
+ // Contact application points: body centres rather than support-function
1286
+ // witnesses. The natural-sounding alternative — `posed_a.support(+EPA_dir)`
1287
+ // and `posed_b.support(-EPA_dir)` — interacts badly with flat-faced
1288
+ // supports: a large floor's support in `+Y` returns a *corner* (the box
1289
+ // support is multi-valued along an axis-aligned direction and picks the
1290
+ // sign-tied corner), which can be tens of metres from the actual
1291
+ // contact patch. That blows up the solver's lever arm `r × n` and
1292
+ // collapses `m_eff` to ~0, leaving the impulse vanishingly small.
1293
+ //
1294
+ // Body centres give `r ‖ n` for vertical contacts (sphere/knot landing
1295
+ // on a floor — the common case), so `r × n = 0` and `m_eff = invM`
1296
+ // resolves the impact cleanly. For oblique contacts the application
1297
+ // point lies on the line between centres, which is the right-ish lever
1298
+ // arm for translational impulse and degrades gracefully on angular
1299
+ // response. A future closed-form mesh-vs-box path will compute a
1300
+ // proper face-projected witness; until then, body centres are the
1301
+ // robust fallback for the GJK+EPA route.
1302
+ // GJK+EPA fallback has no stable per-frame feature info; emit fid = 0
1303
+ // so match-and-merge uses position-fallback. Single contact per pair.
1304
+ return append_contact(count,
1305
+ trA.position.x, trA.position.y, trA.position.z,
1306
+ trB.position.x, trB.position.y, trB.position.z,
1307
+ nx, ny, nz,
1308
+ depth, 0
1309
+ );
1310
+ }
1311
+
1312
+ // Reusable single-pair adapters for the penetration query below — no per-call
1313
+ // allocation. dispatch_pair only reads `.shape` off a collider and
1314
+ // `.position` / `.rotation` off a transform, so these minimal stand-ins are
1315
+ // all it needs.
1316
+ const _pp_colA = { shape: null };
1317
+ const _pp_colB = { shape: null };
1318
+ const _pp_trA = { position: null, rotation: null };
1319
+ const _pp_trB = { position: null, rotation: null };
1320
+
1321
+
1322
+ /**
1323
+ * Single-pair penetration query: the depth and world normal of the DEEPEST
1324
+ * contact the narrowphase would generate for one posed shape pair.
1325
+ *
1326
+ * Routes through the exact same {@link dispatch_pair} the contact solver
1327
+ * consumes — closed-form for every sphere / box / capsule pair (box-box via
1328
+ * SAT, so the true minimum-translation axis is found rather than the
1329
+ * centroid-seeded portal MPR would pick), triangle decomposition + closed-form
1330
+ * per triangle for convex-vs-concave, and GJK + EPA (+ MPR) for any other
1331
+ * convex pair. The deepest contact's depth is the minimum-translation distance
1332
+ * and its normal is the MTV axis, so the result is correct for every shape pair
1333
+ * the engine can build and agrees bit-for-bit with what the solver acts on.
1334
+ *
1335
+ * The normal follows the narrowphase's stored convention: a unit vector
1336
+ * pointing from B toward A — the direction to translate A to separate it.
1337
+ *
1338
+ * Concave-vs-concave is not dispatched (the narrowphase skips it) and returns
1339
+ * 0; callers needing to reject that case must check `is_convex` themselves.
1340
+ *
1341
+ * Not re-entrant: shares the module-level candidate / scratch buffers with
1342
+ * {@link narrowphase_step}. Intended for main-thread queries run outside a
1343
+ * step (depenetration, overlap depth, tooling) — never from inside one.
1344
+ *
1345
+ * @param {Float64Array|number[]} out_normal length ≥ 3; receives the unit B→A
1346
+ * normal on penetration (untouched when the return value is 0)
1347
+ * @param {AbstractShape3D} shapeA
1348
+ * @param {{x:number,y:number,z:number}} posA
1349
+ * @param {{x:number,y:number,z:number,w:number}} rotA
1350
+ * @param {AbstractShape3D} shapeB
1351
+ * @param {{x:number,y:number,z:number}} posB
1352
+ * @param {{x:number,y:number,z:number,w:number}} rotB
1353
+ * @returns {number} deepest penetration depth (> 0) or 0 if separated
1354
+ */
1355
+ export function deepest_pair_penetration(out_normal, shapeA, posA, rotA, shapeB, posB, rotB) {
1356
+ _pp_colA.shape = shapeA;
1357
+ _pp_trA.position = posA;
1358
+ _pp_trA.rotation = rotA;
1359
+ _pp_colB.shape = shapeB;
1360
+ _pp_trB.position = posB;
1361
+ _pp_trB.rotation = rotB;
1362
+
1363
+ const n = dispatch_pair(0, _pp_colA, _pp_trA, _pp_colB, _pp_trB);
1364
+ if (n === 0) {
1365
+ return 0;
1366
+ }
1367
+
1368
+ // Deepest contact = the minimum-translation depth; its stored normal is the
1369
+ // separation axis. (For multi-point manifolds — box-box, capsule-box, a
1370
+ // convex straddling several mesh triangles — every point shares the
1371
+ // separating axis, so the max depth along it is the distance to separate.)
1372
+ let best_depth = -1;
1373
+ let best_off = 0;
1374
+ for (let i = 0; i < n; i++) {
1375
+ const off = i * CANDIDATE_STRIDE;
1376
+ const d = candidates[off + 9];
1377
+ if (d > best_depth) {
1378
+ best_depth = d;
1379
+ best_off = off;
1380
+ }
1381
+ }
1382
+
1383
+ if (!(best_depth > 0) || !Number.isFinite(best_depth)) {
1384
+ return 0;
1385
+ }
1386
+
1387
+ out_normal[0] = candidates[best_off + 6];
1388
+ out_normal[1] = candidates[best_off + 7];
1389
+ out_normal[2] = candidates[best_off + 8];
1390
+
1391
+ return best_depth;
1392
+ }
1393
+
1394
+ /**
1395
+ * For every pair in `pair_list`, do a cross-product over A's collider list ×
1396
+ * B's collider list, accumulate candidate contacts, reduce to ≤4, and write
1397
+ * to the manifold slot.
1398
+ *
1399
+ * @param {PairList} pair_list
1400
+ * @param {ManifoldStore} manifolds
1401
+ * @param {Array<Array<{collider: Collider, transform: Transform}>>} lists
1402
+ * per-body collider lists, indexed by body-storage slot index. Typically
1403
+ * `system.__body_collider_lists` — passed in directly so this helper
1404
+ * has no dependency on `PhysicsSystem`.
1405
+ */
1406
+ export function narrowphase_step(pair_list, manifolds, lists) {
1407
+ const count = pair_list.count;
1408
+
1409
+ for (let i = 0; i < count; i++) {
1410
+ const idA = pair_list.get_a(i);
1411
+ const idB = pair_list.get_b(i);
1412
+
1413
+ const idxA = body_id_index(idA);
1414
+ const idxB = body_id_index(idB);
1415
+
1416
+ const list_a = lists[idxA];
1417
+ const list_b = lists[idxB];
1418
+
1419
+ const slot = manifolds.find(idA, idB);
1420
+
1421
+ if (list_a === undefined || list_b === undefined
1422
+ || list_a.length === 0 || list_b.length === 0
1423
+ ) {
1424
+
1425
+ manifolds.clear_contacts(slot);
1426
+ continue;
1427
+
1428
+ }
1429
+
1430
+ let cand_count = 0;
1431
+
1432
+ const la_len = list_a.length;
1433
+ const lb_len = list_b.length;
1434
+
1435
+ let sub_pair = 0;
1436
+ for (let a = 0; a < la_len; a++) {
1437
+ const ea = list_a[a];
1438
+
1439
+ for (let b = 0; b < lb_len; b++) {
1440
+ const eb = list_b[b];
1441
+
1442
+ const before = cand_count;
1443
+ cand_count = dispatch_pair(
1444
+ cand_count,
1445
+ ea.collider,
1446
+ ea.transform,
1447
+ eb.collider,
1448
+ eb.transform
1449
+ );
1450
+
1451
+ // Salt this sub-pair's fids ({@link FID_PAIR_SALT}) so a
1452
+ // compound body cannot fid-match — and inherit warm-start
1453
+ // impulses — across different collider pairs. fid 0 stays 0.
1454
+ if (sub_pair !== 0) {
1455
+ const salt = sub_pair * FID_PAIR_SALT;
1456
+ for (let c = before; c < cand_count; c++) {
1457
+ const co = c * CANDIDATE_STRIDE;
1458
+ if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1459
+ }
1460
+ }
1461
+ sub_pair++;
1462
+ }
1463
+ }
1464
+
1465
+ if (cand_count === 0) {
1466
+ // No contacts this frame for an existing manifold. Keep the
1467
+ // slot in the cache (the grace window in advance_frame() will
1468
+ // evict it if this persists), but zero out impulses and
1469
+ // contact count — there's nothing for the solver to act on
1470
+ // and stale impulses would mislead next frame's warm-start
1471
+ // if contact re-establishes at a different feature.
1472
+ manifolds.clear_contacts(slot);
1473
+ continue;
1474
+ }
1475
+
1476
+ const kept = reduce_manifold_contacts(candidates, cand_count, CANDIDATE_STRIDE, MAX_KEPT, 9);
1477
+
1478
+ // ── Match-and-merge: feature-id (with position fallback) ──────
1479
+ //
1480
+ // Snapshot prev-frame state for matching + impulse carry-over.
1481
+ const data = manifolds.data_buffer;
1482
+ const slot_off = manifolds.slot_data_offset(slot);
1483
+ const prev_count_raw = manifolds.contact_count(slot);
1484
+ const prev_count = prev_count_raw > MAX_CONTACTS_PER_MANIFOLD
1485
+ ? MAX_CONTACTS_PER_MANIFOLD
1486
+ : prev_count_raw;
1487
+ for (let j = 0; j < prev_count; j++) {
1488
+ const off = slot_off + j * CONTACT_STRIDE;
1489
+ const snap_off = j * 7;
1490
+ prev_snapshot[snap_off] = data[off + 13]; // feature_id
1491
+ prev_snapshot[snap_off + 1] = data[off]; // world_a x
1492
+ prev_snapshot[snap_off + 2] = data[off + 1]; // world_a y
1493
+ prev_snapshot[snap_off + 3] = data[off + 2]; // world_a z
1494
+ prev_snapshot[snap_off + 4] = data[off + 10]; // j_n
1495
+ prev_snapshot[snap_off + 5] = data[off + 11]; // j_t1
1496
+ prev_snapshot[snap_off + 6] = data[off + 12]; // j_t2
1497
+ prev_claimed[j] = 0;
1498
+ }
1499
+
1500
+ // For each new candidate, find a matching prev contact.
1501
+ // Step 1: feature-id match (only if BOTH sides have a non-zero
1502
+ // feature_id — fid = 0 means "no info"). Same-fid prev
1503
+ // contacts are disambiguated by NEAREST previous witness:
1504
+ // a fid identifies the FEATURE (e.g. a triangle), not the
1505
+ // contact point — the box/capsule-triangle paths emit up
1506
+ // to 4 contacts sharing one triangle's fid, and the
1507
+ // reducer reorders them frame to frame, so first-match
1508
+ // would shuffle warm-start impulses between a face's own
1509
+ // contact points. (redetect_pair_geometry documents and
1510
+ // fixes the same failure for the per-substep path.)
1511
+ // Step 2: position-fallback within MATCH_TOL_SQR.
1512
+ for (let k = 0; k < kept; k++) {
1513
+ const cand_off = k * CANDIDATE_STRIDE;
1514
+ const cand_fid = candidates[cand_off + 10];
1515
+ const cand_ax = candidates[cand_off];
1516
+ const cand_ay = candidates[cand_off + 1];
1517
+ const cand_az = candidates[cand_off + 2];
1518
+
1519
+ let best_prev = -1;
1520
+ if (cand_fid !== 0) {
1521
+ // Gated by FID_MATCH_MAX_D2: a fid recomputed half a metre
1522
+ // away (teleport, fid collision) must not inherit impulses.
1523
+ let best_d2 = FID_MATCH_MAX_D2;
1524
+ for (let j = 0; j < prev_count; j++) {
1525
+ if (prev_claimed[j]) continue;
1526
+ if (prev_snapshot[j * 7] !== cand_fid) continue;
1527
+ const snap_off = j * 7;
1528
+ const dx = prev_snapshot[snap_off + 1] - cand_ax;
1529
+ const dy = prev_snapshot[snap_off + 2] - cand_ay;
1530
+ const dz = prev_snapshot[snap_off + 3] - cand_az;
1531
+ const d2 = dx * dx + dy * dy + dz * dz;
1532
+ if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1533
+ }
1534
+ }
1535
+ if (best_prev === -1) {
1536
+ let best_d2 = MATCH_TOL_SQR;
1537
+ for (let j = 0; j < prev_count; j++) {
1538
+ if (prev_claimed[j]) continue;
1539
+ const snap_off = j * 7;
1540
+ const dx = prev_snapshot[snap_off + 1] - cand_ax;
1541
+ const dy = prev_snapshot[snap_off + 2] - cand_ay;
1542
+ const dz = prev_snapshot[snap_off + 3] - cand_az;
1543
+ const d2 = dx * dx + dy * dy + dz * dz;
1544
+ if (d2 < best_d2) { best_d2 = d2; best_prev = j; }
1545
+ }
1546
+ }
1547
+ cand_to_prev[k] = best_prev;
1548
+ if (best_prev !== -1) prev_claimed[best_prev] = 1;
1549
+ }
1550
+
1551
+ // Reset count without zeroing the data slab — set_contact below
1552
+ // will overwrite geometry, and matched candidates will inherit
1553
+ // the impulses we snapshotted above (written back at the new
1554
+ // slot index after set_contact).
1555
+ manifolds.begin_refill(slot);
1556
+
1557
+ for (let k = 0; k < kept; k++) {
1558
+ const off = k * CANDIDATE_STRIDE;
1559
+ // Target slot index = k (we write candidates 0..kept-1 into
1560
+ // slot indices 0..kept-1). The impulse carry-over below
1561
+ // copies the matched prev contact's impulses into THIS k,
1562
+ // so the geometric and warm-start state stay correlated
1563
+ // even if the matching mapping permuted the order.
1564
+ manifolds.set_contact(
1565
+ slot, k,
1566
+ candidates[off], candidates[off + 1], candidates[off + 2],
1567
+ candidates[off + 3], candidates[off + 4], candidates[off + 5],
1568
+ candidates[off + 6], candidates[off + 7], candidates[off + 8],
1569
+ candidates[off + 9],
1570
+ candidates[off + 10],
1571
+ candidates[off + 11], candidates[off + 12]
1572
+ );
1573
+ const prev_j = cand_to_prev[k];
1574
+ if (prev_j !== -1) {
1575
+ // Copy prev_j's impulse to this slot index.
1576
+ const dst_off = slot_off + k * CONTACT_STRIDE;
1577
+ const src_off = prev_j * 7;
1578
+ data[dst_off + 10] = prev_snapshot[src_off + 4];
1579
+ data[dst_off + 11] = prev_snapshot[src_off + 5];
1580
+ data[dst_off + 12] = prev_snapshot[src_off + 6];
1581
+ } else {
1582
+ // No match — explicitly zero the impulses, since this
1583
+ // slot index may have stale data from an earlier frame's
1584
+ // contact at the same index.
1585
+ manifolds.clear_impulses(slot, k);
1586
+ }
1587
+ }
1588
+ }
1589
+ }
1590
+
1591
+ /**
1592
+ * Re-detect contact GEOMETRY for one existing manifold slot at the bodies'
1593
+ * current poses, updating the witness points / normal / depth of the slot's
1594
+ * existing contacts in place. Does NOT change the contact count, the
1595
+ * feature ids, or the accumulated impulses — it only refreshes geometry.
1596
+ *
1597
+ * This is the per-substep concave path (TGS): for a contact pair involving a
1598
+ * concave body, the contact *feature* (which triangle is deepest, and its
1599
+ * normal) genuinely changes as the body rocks, so the solver's cheap analytic
1600
+ * refresh — which freezes the feature for the whole outer step — pumps energy
1601
+ * in. Re-running the narrowphase geometry each substep gives a fresh,
1602
+ * correct normal so the body settles instead of rocking. Convex pairs keep
1603
+ * the analytic refresh and never call this (their feature is stable).
1604
+ *
1605
+ * Matching is by feature id (stable per-triangle for the decomposition path),
1606
+ * so a contact's geometry tracks the same triangle across substeps. A contact
1607
+ * whose triangle isn't found this substep keeps its previous geometry (a rare
1608
+ * transient; the once-per-frame {@link narrowphase_step} re-establishes the
1609
+ * contact set next outer step). Count never changes here, so the solver's
1610
+ * per-contact scratch (sized once at prepare) stays aligned.
1611
+ *
1612
+ * @param {ManifoldStore} manifolds
1613
+ * @param {number} slot
1614
+ * @param {Array<{collider: Collider, transform: Transform}>} list_a
1615
+ * @param {Array<{collider: Collider, transform: Transform}>} list_b
1616
+ */
1617
+ export function redetect_pair_geometry(manifolds, slot, list_a, list_b) {
1618
+ if (list_a === undefined || list_b === undefined) return;
1619
+ const la_len = list_a.length;
1620
+ const lb_len = list_b.length;
1621
+ if (la_len === 0 || lb_len === 0) return;
1622
+
1623
+ const count = manifolds.contact_count(slot);
1624
+ if (count === 0) return;
1625
+
1626
+ let cc = 0;
1627
+ let sub_pair = 0;
1628
+ for (let a = 0; a < la_len; a++) {
1629
+ const ea = list_a[a];
1630
+ for (let b = 0; b < lb_len; b++) {
1631
+ const eb = list_b[b];
1632
+ const before = cc;
1633
+ cc = dispatch_pair(cc, ea.collider, ea.transform, eb.collider, eb.transform);
1634
+ // Stored fids are sub-pair-salted (see narrowphase_step's dispatch
1635
+ // loop) — salt the redetected candidates identically or no fid
1636
+ // would ever match for a compound body's non-first sub-pair.
1637
+ if (sub_pair !== 0) {
1638
+ const salt = sub_pair * FID_PAIR_SALT;
1639
+ for (let c = before; c < cc; c++) {
1640
+ const co = c * CANDIDATE_STRIDE;
1641
+ if (candidates[co + 10] !== 0) candidates[co + 10] += salt;
1642
+ }
1643
+ }
1644
+ sub_pair++;
1645
+ }
1646
+ }
1647
+ if (cc === 0) return; // nothing re-detected this substep — keep frozen geometry
1648
+
1649
+ const data = manifolds.data_buffer;
1650
+ const slot_off = manifolds.slot_data_offset(slot);
1651
+
1652
+ // Match each existing contact to a DISTINCT fresh candidate. feature_id
1653
+ // identifies the TRIANGLE, not the contact point — the box/capsule-triangle
1654
+ // paths emit several contacts for one triangle, all sharing that triangle's
1655
+ // single fid (a flat box-on-heightmap cell yields fids like [6,6,6,7]). A
1656
+ // plain first-match-by-fid therefore maps every same-fid existing contact
1657
+ // onto the SAME candidate, collapsing the manifold to duplicate witness
1658
+ // points → a degenerate support polygon the solver can't damp (the
1659
+ // box-on-heightmap rattle). So: gate by fid, disambiguate same-fid
1660
+ // candidates by NEAREST previous witness (world-A) position, and claim each
1661
+ // candidate so no two existing contacts take the same one. For the common
1662
+ // unique-fid case (sphere/mesh: one contact per triangle) this picks that
1663
+ // single candidate exactly once — identical to the old behaviour.
1664
+ for (let k = 0; k < cc; k++) redetect_claimed[k] = 0;
1665
+
1666
+ for (let j = 0; j < count; j++) {
1667
+ const off = slot_off + j * CONTACT_STRIDE;
1668
+ const fid = data[off + 13];
1669
+ if (fid === 0) continue; // no feature info to match on
1670
+
1671
+ // Previous witness (world-A) of this existing contact — the anchor we
1672
+ // disambiguate same-fid candidates against.
1673
+ const pax = data[off];
1674
+ const pay = data[off + 1];
1675
+ const paz = data[off + 2];
1676
+
1677
+ let best_k = -1;
1678
+ let best_d2 = Infinity;
1679
+ for (let k = 0; k < cc; k++) {
1680
+ if (redetect_claimed[k] === 1) continue;
1681
+ const co = k * CANDIDATE_STRIDE;
1682
+ if (candidates[co + 10] !== fid) continue;
1683
+ const dx = candidates[co] - pax;
1684
+ const dy = candidates[co + 1] - pay;
1685
+ const dz = candidates[co + 2] - paz;
1686
+ const d2 = dx * dx + dy * dy + dz * dz;
1687
+ if (d2 < best_d2) {
1688
+ best_d2 = d2;
1689
+ best_k = k;
1690
+ }
1691
+ }
1692
+
1693
+ // Position fallback (mirrors narrowphase_step's match-and-merge). A
1694
+ // single triangle's clipped contact count is NOT stable across sub-mm
1695
+ // pose changes — a box straddling a cell seam can have one triangle
1696
+ // yield 4 points one substep and 3 the next — so an existing contact can
1697
+ // outnumber this substep's same-fid candidates. Freezing its stale
1698
+ // witness then duplicates a sibling contact's point, leaving a
1699
+ // degenerate (sub-dimensional) manifold. Instead, claim the nearest
1700
+ // unclaimed candidate of ANY fid: every contact keeps a DISTINCT live
1701
+ // witness. The fid label is left intact and re-resolved by the next
1702
+ // once-per-step narrowphase. (At a fixed pose the same-fid match always
1703
+ // succeeds, so this never fires for the depth-equality guards or the
1704
+ // one-contact-per-triangle sphere/mesh paths.)
1705
+ if (best_k === -1) {
1706
+ for (let k = 0; k < cc; k++) {
1707
+ if (redetect_claimed[k] === 1) continue;
1708
+ const co = k * CANDIDATE_STRIDE;
1709
+ const dx = candidates[co] - pax;
1710
+ const dy = candidates[co + 1] - pay;
1711
+ const dz = candidates[co + 2] - paz;
1712
+ const d2 = dx * dx + dy * dy + dz * dz;
1713
+ if (d2 < best_d2) {
1714
+ best_d2 = d2;
1715
+ best_k = k;
1716
+ }
1717
+ }
1718
+ }
1719
+
1720
+ if (best_k === -1) continue; // no unclaimed candidate at all this substep — keep frozen geometry
1721
+
1722
+ redetect_claimed[best_k] = 1;
1723
+ const co = best_k * CANDIDATE_STRIDE;
1724
+ // Overwrite geometry only: witnesses, normal, depth. (Count, feature
1725
+ // ids and accumulated impulses are intentionally left untouched — see
1726
+ // the function contract.)
1727
+ data[off] = candidates[co];
1728
+ data[off + 1] = candidates[co + 1];
1729
+ data[off + 2] = candidates[co + 2];
1730
+ data[off + 3] = candidates[co + 3];
1731
+ data[off + 4] = candidates[co + 4];
1732
+ data[off + 5] = candidates[co + 5];
1733
+ data[off + 6] = candidates[co + 6];
1734
+ data[off + 7] = candidates[co + 7];
1735
+ data[off + 8] = candidates[co + 8];
1736
+ data[off + 9] = candidates[co + 9];
1737
+ }
1738
+ }