@woosh/meep-engine 3.0.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1823) hide show
  1. package/LICENSE +188 -188
  2. package/README.md +152 -145
  3. package/editor/Editor.js +598 -598
  4. package/editor/EditorCameraNavigation.js +205 -205
  5. package/editor/EditorKeyMap.js +182 -182
  6. package/editor/SelectionVisualizer.js +127 -127
  7. package/editor/actions/concrete/ActionInvalidateSampler.js +27 -27
  8. package/editor/actions/concrete/PatchTerrainHeightAction.js +85 -85
  9. package/editor/camera/camera_framing.js +104 -104
  10. package/editor/clipboard.js +184 -184
  11. package/editor/ecs/component/editors/ObservedIntegerEditor.js +22 -22
  12. package/editor/ecs/component/editors/ObservedStringEditor.js +34 -34
  13. package/editor/ecs/component/editors/common/two_way_sync.js +50 -50
  14. package/editor/ecs/component/editors/ecs/GridObstacleEditor.js +19 -19
  15. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.js +35 -35
  16. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.js +17 -17
  17. package/editor/ecs/component/editors/ecs/SGMeshEditor.js +24 -24
  18. package/editor/ecs/component/editors/ecs/ShadedGeometryEditor.js +33 -33
  19. package/editor/ecs/component/editors/ecs/Trail3DEditor.js +45 -45
  20. package/editor/ecs/component/editors/ecs/buildGridObstaclePreview.js +19 -19
  21. package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.js +72 -72
  22. package/editor/ecs/component/editors/shade/MeshletGeometryEditor.js +30 -30
  23. package/editor/ecs/component/editors/shade/ShadeImageEditor.js +70 -70
  24. package/editor/ecs/component/editors/shade/ShadeMaterialEditor.js +34 -34
  25. package/editor/ecs/component/editors/shade/ShadeTextureEditor.js +46 -46
  26. package/editor/ecs/component/editors/shade/StandardShadeMaterialEditor.js +24 -24
  27. package/editor/ecs/component/prototypeObjectEditor.js +265 -265
  28. package/editor/ecs/component/registerBasicTypeEditors.js +100 -100
  29. package/editor/ecs/component/registerEngineComponentEditors.js +95 -95
  30. package/editor/entity_world_bounds.js +27 -27
  31. package/editor/particles/lut_ops.js +148 -148
  32. package/editor/particles/particleEditEvents.js +12 -12
  33. package/editor/particles/rebuildParticleEmitter.js +31 -31
  34. package/editor/persistence/EditorLayoutStore.js +71 -71
  35. package/editor/persistence/SceneMetadata.js +52 -52
  36. package/editor/persistence/SceneSlotManager.js +144 -144
  37. package/editor/persistence/buildNewSceneDataset.js +67 -67
  38. package/editor/persistence/files.js +68 -68
  39. package/editor/persistence/scene_import.js +143 -143
  40. package/editor/persistence/scene_serialization.js +79 -79
  41. package/editor/persistence/string_buffer_codec.js +41 -41
  42. package/editor/process/GridDisplayProcess.js +125 -125
  43. package/editor/process/SymbolicDisplayProcess.js +106 -108
  44. package/editor/process/symbolic/CameraSymbolicDisplay.js +146 -146
  45. package/editor/process/symbolic/LightSymbolicDisplay.js +172 -172
  46. package/editor/process/symbolic/PathSymbolicDisplay.js +118 -118
  47. package/editor/process/symbolic/PositionedMarkerSymbolicDisplay.js +79 -79
  48. package/editor/process/symbolic/SoundEmitterSymbolicDisplay.js +57 -57
  49. package/editor/process/symbolic/SymbolicDisplay.js +62 -62
  50. package/editor/process/symbolic/gizmo_draw_wire_box_oriented.js +85 -85
  51. package/editor/process/symbolic/gizmo_draw_wire_circle.js +53 -53
  52. package/editor/prototypeEditorShell.js +156 -156
  53. package/editor/selection/editor_pick.js +130 -130
  54. package/editor/selection/screen_rect_frustum.js +119 -119
  55. package/editor/style/controls.scss +168 -168
  56. package/editor/style/inspector.scss +223 -223
  57. package/editor/templates/entity_templates.js +167 -167
  58. package/editor/tools/GridPaintTool.js +273 -273
  59. package/editor/tools/SelectionTool.js +307 -307
  60. package/editor/tools/TransformToolV2.js +378 -378
  61. package/editor/tools/engine/ToolEngine.js +188 -188
  62. package/editor/tools/paint/TerrainTexturePaintTool.js +250 -250
  63. package/editor/tools/v2/BlenderCameraOrientationGizmo.js +501 -501
  64. package/editor/tools/v2/TransformControls.js +971 -971
  65. package/editor/tools/v2/handle/gizmo_drag_plane.js +141 -141
  66. package/editor/tools/v2/handle/gizmo_draw.js +399 -399
  67. package/editor/tools/v2/handle/gizmo_handles.js +364 -364
  68. package/editor/tools/v2/handle/gizmo_ray_shapes.js +239 -239
  69. package/editor/view/ecs/HierarchicalEntityListView.js +339 -339
  70. package/editor/view/ecs/components/DatGuiController.js +2 -2
  71. package/editor/view/ecs/components/common/AutoCanvasView.js +100 -100
  72. package/editor/view/ecs/components/common/TextController.js +59 -59
  73. package/editor/view/library/MeshLibraryView.js +175 -175
  74. package/editor/view/library/mesh_library_drag.js +63 -63
  75. package/editor/view/library/model_thumbnail_source.js +86 -86
  76. package/editor/view/makeEntityDecorators.js +125 -125
  77. package/editor/view/node-graph/NodeGraphSelection.js +89 -89
  78. package/editor/view/node-graph/NodeView.js +211 -211
  79. package/editor/view/node-graph/actions/ConnectionDeleteAction.js +36 -36
  80. package/editor/view/node-graph/actions/NodesMoveAction.js +41 -41
  81. package/editor/view/node-graph/connection_wire_geometry.js +107 -107
  82. package/editor/view/particles/ColorLutGradientView.js +291 -291
  83. package/editor/view/particles/ScalarLutCurveView.js +279 -279
  84. package/editor/view/prepareMeshLibrary.js +188 -188
  85. package/editor/view/shell/SceneTreePanelView.js +119 -119
  86. package/editor/view/v2/SplitView.js +2 -2
  87. package/package.json +94 -94
  88. package/samples/generation/generators/interactive/mir_generator_place_buff_objects.js +362 -362
  89. package/src/DUPLICATION_AUDIT_2026_08_07/00_core_reuse_surface.md +738 -738
  90. package/src/DUPLICATION_AUDIT_2026_08_07/A_core_math_geom.md +79 -79
  91. package/src/DUPLICATION_AUDIT_2026_08_07/B_core_data.md +93 -93
  92. package/src/DUPLICATION_AUDIT_2026_08_07/C_gfx_render.md +80 -80
  93. package/src/DUPLICATION_AUDIT_2026_08_07/D_gfx_features.md +164 -164
  94. package/src/DUPLICATION_AUDIT_2026_08_07/E_physics.md +69 -69
  95. package/src/DUPLICATION_AUDIT_2026_08_07/F_sound.md +102 -102
  96. package/src/DUPLICATION_AUDIT_2026_08_07/G_net_save.md +129 -129
  97. package/src/DUPLICATION_AUDIT_2026_08_07/H_engine_misc.md +72 -72
  98. package/src/DUPLICATION_AUDIT_2026_08_07/I_generation_view.md +74 -74
  99. package/src/DUPLICATION_AUDIT_2026_08_07/K_crosscutting.md +169 -169
  100. package/src/DUPLICATION_AUDIT_2026_08_07.md +254 -254
  101. package/src/DUPLICATION_AUDIT_REMEDIATION.md +250 -250
  102. package/src/REVIEW_2026_08_06/adv-core.md +511 -511
  103. package/src/REVIEW_2026_08_06/adv-gfx.md +480 -480
  104. package/src/REVIEW_2026_08_06/adv-view.md +446 -446
  105. package/src/REVIEW_2026_08_06/api-core.md +296 -296
  106. package/src/REVIEW_2026_08_06/api-engine.md +195 -195
  107. package/src/REVIEW_2026_08_06/bvh-phys-sound-net.md +507 -507
  108. package/src/REVIEW_2026_08_06/core-binary.md +348 -348
  109. package/src/REVIEW_2026_08_06/core-collection.md +376 -376
  110. package/src/REVIEW_2026_08_06/core-math.md +221 -221
  111. package/src/REVIEW_2026_08_06/core-process.md +266 -266
  112. package/src/REVIEW_2026_08_06/core-science.md +223 -223
  113. package/src/REVIEW_2026_08_06/engine-ai-nav.md +279 -279
  114. package/src/REVIEW_2026_08_06/engine-animation.md +167 -167
  115. package/src/REVIEW_2026_08_06/engine-asset.md +166 -166
  116. package/src/REVIEW_2026_08_06/engine-ecs.md +920 -920
  117. package/src/REVIEW_2026_08_06/engine-input.md +197 -197
  118. package/src/REVIEW_2026_08_06/engine-misc.md +248 -248
  119. package/src/REVIEW_2026_08_06/engine-terrain.md +52 -52
  120. package/src/REVIEW_2026_08_06/generation.md +176 -176
  121. package/src/REVIEW_2026_08_06/geom-2d.md +434 -434
  122. package/src/REVIEW_2026_08_06/geom-3d.md +117 -117
  123. package/src/REVIEW_2026_08_06/gfx-gi.md +143 -143
  124. package/src/REVIEW_2026_08_06/gfx-render.md +740 -740
  125. package/src/REVIEW_2026_08_06/gfx-texture.md +690 -690
  126. package/src/REVIEW_2026_08_06/prior-criticals.md +49 -49
  127. package/src/REVIEW_2026_08_06/prior-highs.md +138 -138
  128. package/src/REVIEW_2026_08_06/test-gaps.md +364 -364
  129. package/src/REVIEW_2026_08_06/view.md +667 -667
  130. package/src/REVIEW_2026_08_06.md +610 -610
  131. package/src/avif/codec/dec/avif_dec.cpp +233 -233
  132. package/src/avif/codec/dec/avif_dec.js +16 -16
  133. package/src/core/binary/BitImage2.js +246 -246
  134. package/src/core/binary/align_32.js +21 -21
  135. package/src/core/binary/data_view/DataType2DataViewReaders.js +23 -23
  136. package/src/core/binary/data_view/DataType2DataViewWriters.js +25 -25
  137. package/src/core/binary/hash/XXH128Hash.js +55 -55
  138. package/src/core/binary/hash/XXH3_SECRET.js +25 -25
  139. package/src/core/binary/hash/xxh3_128.js +341 -341
  140. package/src/core/binary/hash/xxh3_64.js +241 -241
  141. package/src/core/binary/hash/xxh3_common.js +476 -476
  142. package/src/core/binary/lz4/LZ4_MAX_INPUT_SIZE.js +9 -9
  143. package/src/core/binary/lz4/lz4_compress_block.js +385 -385
  144. package/src/core/binary/lz4/lz4_compress_bound.js +25 -25
  145. package/src/core/binary/lz4/lz4_decompress_block.js +139 -139
  146. package/src/core/binary/lz4/lz4_decompress_bound.js +30 -30
  147. package/src/core/binary/meshopt/MESHOPT_GROUP_SIZE.js +18 -18
  148. package/src/core/binary/meshopt/MeshoptCompressionFilter.js +30 -30
  149. package/src/core/binary/meshopt/MeshoptCompressionMode.js +21 -21
  150. package/src/core/binary/meshopt/__meshopt_test_streams.js +237 -237
  151. package/src/core/binary/meshopt/meshopt_decode.js +73 -73
  152. package/src/core/binary/meshopt/meshopt_decode_byte_group.js +67 -67
  153. package/src/core/binary/meshopt/meshopt_decode_data_block.js +59 -59
  154. package/src/core/binary/meshopt/meshopt_decode_filter_color.js +69 -69
  155. package/src/core/binary/meshopt/meshopt_decode_filter_exponential.js +43 -43
  156. package/src/core/binary/meshopt/meshopt_decode_filter_octahedral.js +60 -60
  157. package/src/core/binary/meshopt/meshopt_decode_filter_quaternion.js +71 -71
  158. package/src/core/binary/meshopt/meshopt_decode_index_buffer.js +294 -294
  159. package/src/core/binary/meshopt/meshopt_decode_index_sequence.js +115 -115
  160. package/src/core/binary/meshopt/meshopt_decode_vertex_buffer.js +381 -381
  161. package/src/core/binary/meshopt/meshopt_read_uint_var.js +59 -59
  162. package/src/core/binary/reinterpret_float32_as_uint32.js +13 -13
  163. package/src/core/binary/reinterpret_uint32_as_float32.js +14 -14
  164. package/src/core/binary/type/DataType2TypedArrayConstructorMapping.js +35 -35
  165. package/src/core/bvh2/bvh3/ebvh_geometry_query_any_triangle_ray.js +149 -149
  166. package/src/core/bvh2/bvh3/ebvh_sort_for_traversal_depth_first.js +96 -96
  167. package/src/core/bvh2/bvh3/query/bvh_query_depth_range_in_frustum.js +101 -101
  168. package/src/core/bvh2/bvh3/query/bvh_query_leaves_generic.js +77 -77
  169. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray.js +90 -90
  170. package/src/core/bvh2/bvh3/query/bvh_query_leaves_ray_segment.js +94 -94
  171. package/src/core/bvh2/bvh3/query/bvh_query_user_data_generic.js +86 -86
  172. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_capsule.js +101 -101
  173. package/src/core/bvh2/bvh3/query/bvh_query_user_data_intersects_sphere.js +92 -92
  174. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray.js +96 -96
  175. package/src/core/bvh2/bvh3/query/bvh_query_user_data_ray_segment.js +100 -100
  176. package/src/core/bvh2/visual/BVHGeometry.js +131 -131
  177. package/src/core/bvh8/BVH8.js +436 -436
  178. package/src/core/bvh8/aabb3_round_to_float32.js +22 -22
  179. package/src/core/bvh8/build/BVH8Converter.js +588 -588
  180. package/src/core/bvh8/build/TriangleCluster.js +122 -122
  181. package/src/core/bvh8/build/aabb3_compute_merge_cost.js +28 -28
  182. package/src/core/bvh8/build/aabb3_from_triangle_by_index.js +17 -17
  183. package/src/core/bvh8/build/bvh8_build_for_geometry.js +303 -303
  184. package/src/core/bvh8/build/bvh8_from_proxy.js +256 -256
  185. package/src/core/bvh8/build/byte.js +9 -9
  186. package/src/core/bvh8/build/encode_bounds_e.js +11 -11
  187. package/src/core/bvh8/bvh8_convert_to_dot.js +132 -132
  188. package/src/core/bvh8/bvh8_count_primitives.js +98 -98
  189. package/src/core/bvh8/bvh8_geometry_validate.js +138 -138
  190. package/src/core/bvh8/bvh8_geometry_validate_indirect.js +166 -166
  191. package/src/core/bvh8/bvh8_get_node_bounds.js +34 -34
  192. package/src/core/bvh8/bvh8_get_node_child_bounds.js +52 -52
  193. package/src/core/bvh8/bvh8_node_child_surface_area.js +18 -18
  194. package/src/core/bvh8/bvh8_node_count_triangles.js +27 -27
  195. package/src/core/bvh8/bvh8_quality.js +72 -72
  196. package/src/core/bvh8/bvh8_validate_structure.js +87 -87
  197. package/src/core/clipboard/obtainClipBoard.js +29 -29
  198. package/src/core/clipboard/safeClipboardReadText.js +55 -55
  199. package/src/core/clipboard/safeClipboardWriteText.js +23 -23
  200. package/src/core/collection/array/typed/typed_array_value_denormalize.js +52 -52
  201. package/src/core/collection/array/typed/typed_array_value_normalize.js +51 -51
  202. package/src/core/collection/union-find/union_find.js +79 -79
  203. package/src/core/color/YCoCg/rgb_to_ycocg.js +20 -20
  204. package/src/core/color/YCoCg/ycocg_to_rgb.js +17 -17
  205. package/src/core/color/colormap/MAGMA_LUT.js +26 -26
  206. package/src/core/color/illuminant/D65_TRISTIMULUS_XYZ.js +19 -19
  207. package/src/core/color/lab/lab_apply_hunt_adjustment.js +27 -27
  208. package/src/core/color/lab/lab_distance_hyab.js +23 -23
  209. package/src/core/color/lab/xyz_to_lab.js +62 -62
  210. package/src/core/color/operations/color_darken.js +24 -24
  211. package/src/core/color/operations/color_desaturate.js +24 -24
  212. package/src/core/color/operations/color_lighten.js +24 -24
  213. package/src/core/color/operations/color_saturate.js +24 -24
  214. package/src/core/color/operations/color_scale_okhsv_channel.js +45 -45
  215. package/src/core/color/tonemap/tonemap_aces.js +41 -41
  216. package/src/core/color/ycxcz/xyz_to_ycxcz.js +28 -28
  217. package/src/core/color/ycxcz/ycxcz_to_xyz.js +19 -19
  218. package/src/core/dom/isImageBitmap.js +12 -12
  219. package/src/core/function/frameThrottle.js +23 -23
  220. package/src/core/geom/2d/aabb/aabb2_array_combine.js +7 -7
  221. package/src/core/geom/2d/aabb/aabb2_compute_area.js +7 -7
  222. package/src/core/geom/2d/aabb/aabb2_compute_center_from_multiple.js +7 -7
  223. package/src/core/geom/2d/aabb/aabb2_compute_overlap.js +7 -7
  224. package/src/core/geom/2d/aabb/aabb2_from_v2_array.js +7 -7
  225. package/src/core/geom/2d/aabb/aabb2_intersects_point.js +7 -7
  226. package/src/core/geom/2d/aabb/aabb2_overlap_exists.js +7 -7
  227. package/src/core/geom/2d/circle/circle_compute_circle_intersection.js +7 -7
  228. package/src/core/geom/2d/circle/circle_compute_circle_penetration.js +7 -7
  229. package/src/core/geom/2d/circle/circle_intersects_point.js +7 -7
  230. package/src/core/geom/2d/compute_polygon_area_2d.js +7 -7
  231. package/src/core/geom/2d/convex-hull/fixed_convex_hull_humus.js +7 -7
  232. package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +7 -7
  233. package/src/core/geom/2d/convex-hull/orientation3_array.js +7 -7
  234. package/src/core/geom/2d/intersect_ray_2d.js +7 -7
  235. package/src/core/geom/2d/line/line2_distance_to_point_sqr.js +7 -7
  236. package/src/core/geom/2d/line/line2_segment_intersection_fraction_2d.js +44 -44
  237. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_2d.js +7 -7
  238. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_array_2d.js +7 -7
  239. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_2d.js +7 -7
  240. package/src/core/geom/2d/line/line_segment_compute_line_segment_intersection_vectors_array_2d.js +7 -7
  241. package/src/core/geom/2d/line/line_segment_intersection_fraction_2d.js +7 -7
  242. package/src/core/geom/2d/line/line_segment_line_segment_intersection_exists_2d.js +7 -7
  243. package/src/core/geom/2d/line/segment2_intersects_segment2_2d.js +25 -25
  244. package/src/core/geom/2d/line/segment2_segment_compute_intersection_array_2d.js +46 -46
  245. package/src/core/geom/2d/line/segment2_segment_intersection_fractions.js +52 -52
  246. package/src/core/geom/2d/oriented-box/min_area_rect_2d.js +69 -69
  247. package/src/core/geom/2d/polygon/TRIANGULATION_DESIGN.md +433 -433
  248. package/src/core/geom/2d/polygon/polygon2_clip_axis_halfplane.js +51 -51
  249. package/src/core/geom/2d/polygon/polygon2_clip_halfplane.js +50 -50
  250. package/src/core/geom/2d/polygon/polygon2_is_counter_clockwise.js +84 -84
  251. package/src/core/geom/2d/polygon/polygon2_signed_area.js +52 -52
  252. package/src/core/geom/2d/polygon/polygon2_triangulate.corpus.js +86 -86
  253. package/src/core/geom/2d/polygon/polygon2_triangulate.js +1280 -1280
  254. package/src/core/geom/2d/polygon/polygon2_triangulation_deviation.js +77 -77
  255. package/src/core/geom/2d/quad-tree/qt_collect_by_circle.js +39 -39
  256. package/src/core/geom/2d/quad-tree/qt_match_data_by_circle.js +101 -101
  257. package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +7 -7
  258. package/src/core/geom/2d/rectangle_to_aabb.js +7 -7
  259. package/src/core/geom/2d/spline_bezier2_2d.js +7 -7
  260. package/src/core/geom/2d/triangle/tri2_rasterize_conservative.js +99 -99
  261. package/src/core/geom/2d/triangle/tri2_signed_area.js +35 -35
  262. package/src/core/geom/2d/triangle2d_compute_area.js +7 -7
  263. package/src/core/geom/2d/triangle2d_contains_point.js +7 -7
  264. package/src/core/geom/2d/triangle2d_get_barycentric.js +7 -7
  265. package/src/core/geom/2d/v2_morton_encode.js +23 -23
  266. package/src/core/geom/3d/aabb/aabb3_array_intersects_frustum_degree.js +7 -7
  267. package/src/core/geom/3d/aabb/aabb3_box_surface_area_2.js +7 -7
  268. package/src/core/geom/3d/aabb/aabb3_build_frustum.js +5 -5
  269. package/src/core/geom/3d/aabb/aabb3_closest_point_on_surface.js +57 -57
  270. package/src/core/geom/3d/aabb/aabb3_combined_surface_area.js +7 -7
  271. package/src/core/geom/3d/aabb/aabb3_compute_half_surface_area.js +7 -7
  272. package/src/core/geom/3d/aabb/aabb3_compute_projected_area.js +7 -7
  273. package/src/core/geom/3d/aabb/aabb3_compute_surface_area.js +7 -7
  274. package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +7 -7
  275. package/src/core/geom/3d/aabb/aabb3_estimate_projected_area2.js +7 -7
  276. package/src/core/geom/3d/aabb/aabb3_from_threejs_geometry.js +7 -7
  277. package/src/core/geom/3d/aabb/aabb3_from_triangle_group.js +46 -46
  278. package/src/core/geom/3d/aabb/aabb3_intersects_frustum_degree.js +7 -7
  279. package/src/core/geom/3d/aabb/aabb3_intersects_line_segment.js +7 -7
  280. package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +7 -7
  281. package/src/core/geom/3d/aabb/aabb3_nearest_point_on_surface.js +7 -7
  282. package/src/core/geom/3d/aabb/aabb3_ray_segment_entry_distance.js +95 -95
  283. package/src/core/geom/3d/aabb/aabb3_raycast.js +7 -7
  284. package/src/core/geom/3d/aabb/aabb3_transform_oriented.js +93 -93
  285. package/src/core/geom/3d/aabb/aabb3_transform_oriented_inverse.js +82 -82
  286. package/src/core/geom/3d/aabb/compute_aabb_from_points.js +7 -7
  287. package/src/core/geom/3d/aabb/compute_triangle_group_aabb3.js +7 -7
  288. package/src/core/geom/3d/atlas/ATLAS_PORT_PLAN.md +486 -486
  289. package/src/core/geom/3d/atlas/BFF_PLAN.md +174 -174
  290. package/src/core/geom/3d/atlas/Chart.js +77 -77
  291. package/src/core/geom/3d/atlas/REVIEW_02.md +392 -392
  292. package/src/core/geom/3d/atlas/atlas_bench_lib.js +315 -315
  293. package/src/core/geom/3d/atlas/atlas_chart_type.js +16 -16
  294. package/src/core/geom/3d/atlas/atlas_compute_charts.js +188 -188
  295. package/src/core/geom/3d/atlas/atlas_extract_chart.js +107 -107
  296. package/src/core/geom/3d/atlas/atlas_generate.js +50 -50
  297. package/src/core/geom/3d/atlas/atlas_test_fixtures.js +266 -266
  298. package/src/core/geom/3d/atlas/atlas_write_chart_uvs.js +36 -36
  299. package/src/core/geom/3d/atlas/data/atlas_build_edge_dihedrals.js +62 -62
  300. package/src/core/geom/3d/atlas/data/atlas_build_edge_lengths.js +33 -33
  301. package/src/core/geom/3d/atlas/data/atlas_build_face_areas.js +50 -50
  302. package/src/core/geom/3d/atlas/io/atlas_mesh_weld_colocals.js +39 -39
  303. package/src/core/geom/3d/atlas/io/bt_loop_uv.js +35 -35
  304. package/src/core/geom/3d/atlas/pack/ChartPlacement.js +46 -46
  305. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_bff.js +269 -269
  306. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_lscm.js +209 -209
  307. package/src/core/geom/3d/atlas/param/atlas_chart_parameterize_ortho.js +38 -38
  308. package/src/core/geom/3d/atlas/param/atlas_chart_stretch_metric.js +86 -86
  309. package/src/core/geom/3d/atlas/param/atlas_piecewise_resegment.js +183 -183
  310. package/src/core/geom/3d/atlas/param/fit_plane_basis.js +66 -66
  311. package/src/core/geom/3d/atlas/segment/RegionGraph.js +40 -40
  312. package/src/core/geom/3d/atlas/segment/atlas_build_region_graph.js +136 -136
  313. package/src/core/geom/3d/atlas/segment/atlas_chart_roundness_delta.js +30 -30
  314. package/src/core/geom/3d/atlas/segment/atlas_compute_planar_regions.js +77 -77
  315. package/src/core/geom/3d/atlas/segment/atlas_merge_charts.js +116 -116
  316. package/src/core/geom/3d/atlas/segment/atlas_segment_charts.js +271 -271
  317. package/src/core/geom/3d/atlas/segment/atlas_segment_charts_atomic.js +191 -191
  318. package/src/core/geom/3d/box/box3_projected_half_extent.js +35 -35
  319. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_closed.js +67 -67
  320. package/src/core/geom/3d/capsule/capsule_intersects_aabb3_iterative.js +137 -137
  321. package/src/core/geom/3d/compute_circle_bounding_box.js +7 -7
  322. package/src/core/geom/3d/cone/computeConeBoundingBox.js +7 -7
  323. package/src/core/geom/3d/cone/computeConePlaneSide.js +7 -7
  324. package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +7 -7
  325. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts +38 -0
  326. package/src/core/geom/3d/cylinder/cylinder3_raycast.d.ts.map +1 -0
  327. package/src/core/geom/3d/cylinder/cylinder3_raycast.js +105 -0
  328. package/src/core/geom/3d/equirectangular/equirectangular_direction_to_uv.js +18 -18
  329. package/src/core/geom/3d/equirectangular/equirectangular_uv_to_direction.js +24 -24
  330. package/src/core/geom/3d/frustum/clipping_volume_matrix4_project.js +7 -7
  331. package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +7 -7
  332. package/src/core/geom/3d/frustum/frustum3_compute_closest_point.js +117 -117
  333. package/src/core/geom/3d/frustum/frustum_matrix4_project.js +7 -7
  334. package/src/core/geom/3d/frustum/hexahedron_from_aabb.js +7 -7
  335. package/src/core/geom/3d/frustum/read_frustum_corner.js +17 -17
  336. package/src/core/geom/3d/frustum/read_three_planes_to_array.js +7 -7
  337. package/src/core/geom/3d/frustum/slice_frustum_linear_to_points.js +7 -7
  338. package/src/core/geom/3d/gjk/GJK_REVIEW_NOTES.md +146 -146
  339. package/src/core/geom/3d/gjk/gjk.js +430 -430
  340. package/src/core/geom/3d/gjk/gjk_epa_penetration.js +520 -520
  341. package/src/core/geom/3d/gjk/minkowski_support.js +71 -71
  342. package/src/core/geom/3d/gjk/mpr.js +368 -368
  343. package/src/core/geom/3d/line/line3_closest_points_segment_segment.js +7 -7
  344. package/src/core/geom/3d/line/line3_compute_nearest_point_to_point.js +7 -7
  345. package/src/core/geom/3d/line/line3_compute_point_segment_point_distance_unfolded.js +64 -64
  346. package/src/core/geom/3d/line/line3_compute_segment_closest_point_to_aabb3_t.js +153 -153
  347. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_aabb3_t.js +7 -7
  348. package/src/core/geom/3d/line/line3_compute_segment_nearest_point_to_point_t.js +7 -7
  349. package/src/core/geom/3d/line/line3_compute_segment_segment_closest_points.js +88 -88
  350. package/src/core/geom/3d/line/line3_segment_segment_closest_point_pair.js +71 -71
  351. package/src/core/geom/3d/line/segment3_point_distance_to_segments.js +25 -25
  352. package/src/core/geom/3d/mat4/allocate_m4.js +7 -7
  353. package/src/core/geom/3d/mat4/apply_mat4_transform_to_direction_v3_array.js +7 -7
  354. package/src/core/geom/3d/mat4/apply_mat4_transform_to_v3_array.js +7 -7
  355. package/src/core/geom/3d/mat4/compose_matrix4_array.js +7 -7
  356. package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +7 -7
  357. package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +7 -7
  358. package/src/core/geom/3d/mat4/m4_compose.js +22 -22
  359. package/src/core/geom/3d/mat4/m4_compute_normal_matrix3.js +7 -7
  360. package/src/core/geom/3d/mat4/m4_decompose.js +64 -64
  361. package/src/core/geom/3d/mat4/m4_decompose_array.js +157 -157
  362. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale.js +32 -32
  363. package/src/core/geom/3d/mat4/m4_from_rotation_translation_scale_scalar.js +67 -67
  364. package/src/core/geom/3d/mat4/m4_linear_matrix3.js +29 -29
  365. package/src/core/geom/3d/mat4/m4_look_at.js +96 -96
  366. package/src/core/geom/3d/mat4/m4_make_rotation_x.js +47 -47
  367. package/src/core/geom/3d/mat4/m4_make_rotation_y.js +47 -47
  368. package/src/core/geom/3d/mat4/m4_make_rotation_z.js +47 -47
  369. package/src/core/geom/3d/mat4/m4_normal_matrix3.js +39 -39
  370. package/src/core/geom/3d/mat4/m4_perspective.js +66 -66
  371. package/src/core/geom/3d/mat4/m4_transpose.js +48 -48
  372. package/src/core/geom/3d/morton/mortonEncode_LUT.js +7 -7
  373. package/src/core/geom/3d/morton/mortonEncode_magicbits.js +7 -7
  374. package/src/core/geom/3d/normal/hemioct/encode_unit3_hemioct.js +5 -5
  375. package/src/core/geom/3d/normal/hemioct/encode_unit_to_hemioct.js +28 -28
  376. package/src/core/geom/3d/normal/lambert_azimuth/decode_azimuth_to_unit.js +30 -30
  377. package/src/core/geom/3d/normal/lambert_azimuth/encode_unit_to_azimuth.js +19 -19
  378. package/src/core/geom/3d/normal/spherical/decode_spheremap_to_unit.js +38 -38
  379. package/src/core/geom/3d/normal/spherical/encode_unit_to_spheremap.js +30 -30
  380. package/src/core/geom/3d/plane/is_point_within_planes.js +7 -7
  381. package/src/core/geom/3d/plane/orient3d_fast_point.js +47 -47
  382. package/src/core/geom/3d/plane/plane3_compute_convex_3_plane_intersection.js +7 -7
  383. package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +7 -7
  384. package/src/core/geom/3d/plane/plane3_intersect_plane.js +7 -7
  385. package/src/core/geom/3d/plane/plane3_three_compute_convex_3_plane_intersection.js +7 -7
  386. package/src/core/geom/3d/point/point3_raycast.d.ts +33 -0
  387. package/src/core/geom/3d/point/point3_raycast.d.ts.map +1 -0
  388. package/src/core/geom/3d/point/point3_raycast.js +47 -0
  389. package/src/core/geom/3d/polygon/polygon3_compute_normal.js +60 -60
  390. package/src/core/geom/3d/polygon/polygon3_triangulate.js +94 -94
  391. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts +40 -0
  392. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.d.ts.map +1 -0
  393. package/src/core/geom/3d/polyhedron/convex_polyhedron3_raycast.js +94 -0
  394. package/src/core/geom/3d/quadric/QUADRIC3_SIZE.js +14 -14
  395. package/src/core/geom/3d/quadric/Quadric3.js +282 -282
  396. package/src/core/geom/3d/quadric/quadric3_add.js +33 -33
  397. package/src/core/geom/3d/quadric/quadric3_clear.js +25 -25
  398. package/src/core/geom/3d/quadric/quadric3_copy.js +27 -27
  399. package/src/core/geom/3d/quadric/quadric3_distance.js +36 -36
  400. package/src/core/geom/3d/quadric/quadric3_evaluate.js +25 -25
  401. package/src/core/geom/3d/quadric/quadric3_optimize.js +45 -45
  402. package/src/core/geom/3d/quadric/quadric3_scale.js +32 -32
  403. package/src/core/geom/3d/quadric/quadric3_set_from_plane_through_point.js +30 -30
  404. package/src/core/geom/3d/quadric/quadric3_set_from_vector4.js +35 -35
  405. package/src/core/geom/3d/quadric/quadric3_sub.js +36 -36
  406. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3.js +28 -28
  407. package/src/core/geom/3d/quadric/quadric3_to_tensor_m3_inverse.js +56 -56
  408. package/src/core/geom/3d/quadric/quadric3_to_vector3.js +19 -19
  409. package/src/core/geom/3d/quaternion/quat3_createFromAxisAngle.js +7 -7
  410. package/src/core/geom/3d/quaternion/quat3_integrate.js +62 -62
  411. package/src/core/geom/3d/quaternion/quat3_multiply.js +31 -31
  412. package/src/core/geom/3d/quaternion/quat3_nlerp.js +72 -72
  413. package/src/core/geom/3d/quaternion/quat3_to_matrix3.js +69 -69
  414. package/src/core/geom/3d/quaternion/quat_decode_from_uint32.js +7 -7
  415. package/src/core/geom/3d/quaternion/quat_encode_to_uint32.js +7 -7
  416. package/src/core/geom/3d/ray/ray3_array_compose.js +7 -7
  417. package/src/core/geom/3d/ray/ray3_compute_nearest_point_to_point.js +7 -7
  418. package/src/core/geom/3d/ray/ray3_distance_to_point.js +7 -7
  419. package/src/core/geom/3d/ray/ray3_shift_origin_along_direction.js +7 -7
  420. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts +17 -0
  421. package/src/core/geom/3d/shape/ConvexHullShape3D.d.ts.map +1 -1
  422. package/src/core/geom/3d/shape/ConvexHullShape3D.js +43 -0
  423. package/src/core/geom/3d/shape/CylinderShape3D.d.ts +1 -0
  424. package/src/core/geom/3d/shape/CylinderShape3D.d.ts.map +1 -1
  425. package/src/core/geom/3d/shape/CylinderShape3D.js +21 -0
  426. package/src/core/geom/3d/shape/PointShape3D.d.ts +7 -0
  427. package/src/core/geom/3d/shape/PointShape3D.d.ts.map +1 -1
  428. package/src/core/geom/3d/shape/PointShape3D.js +88 -63
  429. package/src/core/geom/3d/shape/PosedShape3D.d.ts +6 -0
  430. package/src/core/geom/3d/shape/PosedShape3D.d.ts.map +1 -1
  431. package/src/core/geom/3d/shape/PosedShape3D.js +27 -0
  432. package/src/core/geom/3d/shape/TransformedShape3D.d.ts +10 -0
  433. package/src/core/geom/3d/shape/TransformedShape3D.d.ts.map +1 -1
  434. package/src/core/geom/3d/shape/TransformedShape3D.js +13 -0
  435. package/src/core/geom/3d/shape/Triangle3D.d.ts +1 -0
  436. package/src/core/geom/3d/shape/Triangle3D.d.ts.map +1 -1
  437. package/src/core/geom/3d/shape/Triangle3D.js +26 -0
  438. package/src/core/geom/3d/shape/UnionShape3D.d.ts +6 -0
  439. package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
  440. package/src/core/geom/3d/shape/UnionShape3D.js +7 -0
  441. package/src/core/geom/3d/shape/UnitCubeShape3D.js +45 -45
  442. package/src/core/geom/3d/shape/UnitSphereShape3D.js +44 -44
  443. package/src/core/geom/3d/shape/shape3_mesh_from_geometry.js +64 -64
  444. package/src/core/geom/3d/shape/shape_mesh_from_geometry.js +7 -7
  445. package/src/core/geom/3d/shape/util/compute_signed_distance_gradient_by_sampling.js +51 -51
  446. package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +7 -7
  447. package/src/core/geom/3d/sphere/compute_bounding_sphere_of_2_spheres.js +7 -7
  448. package/src/core/geom/3d/sphere/harmonics/sh3_convolve_with_zh.js +53 -53
  449. package/src/core/geom/3d/sphere/harmonics/sh3_ggx_zh.js +30 -30
  450. package/src/core/geom/3d/sphere/harmonics/sh3_rotate_bl.js +7 -7
  451. package/src/core/geom/3d/sphere/harmonics/sh_compute_K.js +7 -7
  452. package/src/core/geom/3d/sphere/sphere_array_intersects_point.js +7 -7
  453. package/src/core/geom/3d/sphere/sphere_array_intersects_ray.js +7 -7
  454. package/src/core/geom/3d/sphere/sphere_intersects_point.js +7 -7
  455. package/src/core/geom/3d/sphere/sphere_matrix4_transform.js +5 -5
  456. package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +7 -7
  457. package/src/core/geom/3d/sphere/sphere_radius_sqr_from_v3_array_transformed.js +7 -7
  458. package/src/core/geom/3d/sphere/spherical_from_cartesian.js +7 -7
  459. package/src/core/geom/3d/tetrahedra/compute_tetrahedral_mesh_from_surface.js +174 -174
  460. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_quality.js +7 -7
  461. package/src/core/geom/3d/tetrahedra/compute_tetrahedron_volume.js +7 -7
  462. package/src/core/geom/3d/tetrahedra/delaunay/fill_in_a_cavity.js +7 -7
  463. package/src/core/geom/3d/tetrahedra/graphics/build_tetrahedral_mesh_geometry.js +39 -39
  464. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_build_vertex_to_tets_map.js +48 -48
  465. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_carve_outside_surface.js +173 -173
  466. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_compute_tet_quality.js +22 -22
  467. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_find_tets_around_edge.js +146 -146
  468. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_23.js +232 -232
  469. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_flip_32.js +255 -255
  470. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_improve_quality.js +387 -387
  471. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_smooth_vertex.js +140 -140
  472. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +7 -7
  473. package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_vertex_is_boundary.js +97 -97
  474. package/src/core/geom/3d/tetrahedra/tetrahedron_compute_quality.js +66 -66
  475. package/src/core/geom/3d/tetrahedra/triangle/triangle_decode_depth.js +59 -59
  476. package/src/core/geom/3d/tetrahedra/triangle/triangle_rasterized_depth_sample_index.js +54 -54
  477. package/src/core/geom/3d/tetrahedra/validate_neighbour.js +70 -70
  478. package/src/core/geom/3d/topology/struct/binary/BinaryDataBlock.js +314 -314
  479. package/src/core/geom/3d/topology/struct/binary/BinaryDataLayer.js +364 -364
  480. package/src/core/geom/3d/topology/struct/binary/BinaryElementPool.js +567 -567
  481. package/src/core/geom/3d/topology/struct/binary/BinaryTopology.js +1085 -1085
  482. package/src/core/geom/3d/topology/struct/binary/io/bt_boundary_loop.js +73 -73
  483. package/src/core/geom/3d/topology/struct/binary/io/bt_loop_kill.js +35 -35
  484. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_append.js +119 -119
  485. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_close_boundary_holes.js +84 -84
  486. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compact.js +172 -172
  487. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_face_normals.js +56 -56
  488. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadratics.js +7 -7
  489. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_compute_vertex_quadrics.js +227 -227
  490. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_decouple.js +161 -161
  491. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_face_island_erode.js +490 -490
  492. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_fill_small_holes.js +120 -120
  493. package/src/core/geom/3d/topology/struct/binary/io/bt_mesh_from_indexed_geometry.js +120 -120
  494. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_collapse.js +52 -52
  495. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_create.js +115 -115
  496. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_flip.js +142 -142
  497. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_get_or_create.js +21 -21
  498. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_kill_parallels.js +92 -92
  499. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_split.js +179 -179
  500. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_edge_swap_vertex_slots.js +28 -28
  501. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_kill_only_edge.js +8 -8
  502. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_fuse_duplicate_edges.js +83 -83
  503. package/src/core/geom/3d/topology/struct/binary/io/edge/bt_mesh_kill_short_edges.js +89 -89
  504. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_kill.js +25 -25
  505. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_poke.js +127 -127
  506. package/src/core/geom/3d/topology/struct/binary/io/face/bt_face_triangulate.js +268 -268
  507. package/src/core/geom/3d/topology/struct/binary/io/face/bt_kill_only_face.js +8 -8
  508. package/src/core/geom/3d/topology/struct/binary/io/face/bt_mesh_kill_degenerate_faces.js +72 -72
  509. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_kill_only_vert.js +7 -7
  510. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_merge_verts_by_distance.js +7 -7
  511. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_relative_merge_distance.js +48 -48
  512. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_split_pinched_vertices.js +256 -256
  513. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_mesh_vertex_merge_distance.js +77 -77
  514. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_fuse_duplicate_edges.js +7 -7
  515. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vert_kill.js +7 -7
  516. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_fuse_duplicate_edges.js +148 -148
  517. package/src/core/geom/3d/topology/struct/binary/io/vertex/bt_vertex_kill_only.js +7 -7
  518. package/src/core/geom/3d/topology/struct/binary/query/bt_collect_boundary_segments.js +29 -29
  519. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_collapse_breaks_link_condition.js +107 -107
  520. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_is_legal.js +41 -41
  521. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_flip_would_fold.js +120 -120
  522. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_get.js +42 -42
  523. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_is_boundary.js +20 -20
  524. package/src/core/geom/3d/topology/struct/binary/query/bt_edge_read_diamond.js +96 -96
  525. package/src/core/geom/3d/topology/struct/binary/query/bt_face_compute_newell_normal.js +72 -72
  526. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_attached_faces.js +7 -7
  527. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces.js +39 -39
  528. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_edge_adjacent_faces_weighted.js +93 -93
  529. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_neighbour_faces.js +7 -7
  530. package/src/core/geom/3d/topology/struct/binary/query/bt_face_get_shared_loop.js +48 -48
  531. package/src/core/geom/3d/topology/struct/binary/query/bt_face_island_flood_fill.js +45 -45
  532. package/src/core/geom/3d/topology/struct/binary/query/bt_face_read_triangle.js +66 -66
  533. package/src/core/geom/3d/topology/struct/binary/query/bt_faces_shared_loop.js +7 -7
  534. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_assert_valid.js +44 -44
  535. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_build_boundary_euclidean_distance_field.js +32 -32
  536. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_compute_face_islands.js +53 -53
  537. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_face_find_path.js +703 -703
  538. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_sample_interior_grid_points.js +181 -181
  539. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_segment_penetrates_surface.js +133 -133
  540. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_surface_ray_parity.js +82 -82
  541. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_validate.js +599 -599
  542. package/src/core/geom/3d/topology/struct/binary/query/bt_mesh_walk_boundary_loops.js +108 -108
  543. package/src/core/geom/3d/topology/struct/binary/query/bt_query_edge_is_boundary.js +7 -7
  544. package/src/core/geom/3d/triangle/computeTriangleClosestPointToPointBarycentric.js +7 -7
  545. package/src/core/geom/3d/triangle/computeTriangleRayIntersection.js +7 -7
  546. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentric.js +7 -7
  547. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricEdge.js +7 -7
  548. package/src/core/geom/3d/triangle/computeTriangleRayIntersectionBarycentricGeometry.js +7 -7
  549. package/src/core/geom/3d/triangle/compute_triangle_area_3d.js +7 -7
  550. package/src/core/geom/3d/triangle/compute_triangle_normal.js +7 -7
  551. package/src/core/geom/3d/triangle/rayTriangleIntersection.js +7 -7
  552. package/src/core/geom/3d/triangle/tri3_closest_point.js +65 -65
  553. package/src/core/geom/3d/triangle/tri3_closest_point_barycentric.js +171 -171
  554. package/src/core/geom/3d/triangle/tri3_flatten_isometric.js +79 -79
  555. package/src/core/geom/3d/triangle/tri3_ray_intersection.js +160 -160
  556. package/src/core/geom/3d/triangle/tri3_ray_intersection_barycentric.js +96 -96
  557. package/src/core/geom/3d/triangle/tri3_raycast.d.ts +42 -0
  558. package/src/core/geom/3d/triangle/tri3_raycast.d.ts.map +1 -0
  559. package/src/core/geom/3d/triangle/tri3_raycast.js +94 -0
  560. package/src/core/geom/3d/triangle/triangle3_incenter.js +7 -7
  561. package/src/core/geom/3d/triangle/triangle_compute_plane_side.js +7 -7
  562. package/src/core/geom/3d/triangle/triangle_compute_signed_volume.js +7 -7
  563. package/src/core/geom/3d/triangle/triangle_intersects_clipping_volume.js +7 -7
  564. package/src/core/geom/3d/triangle/triangle_mesh_compute_signed_volume.js +7 -7
  565. package/src/core/geom/3d/triangle/v3_compute_triangle_normal.js +7 -7
  566. package/src/core/geom/3d/util/make_justified_point_grid.js +7 -7
  567. package/src/core/geom/GEOMETRY_REVIEW_002_API_ISSUES.md +381 -381
  568. package/src/core/geom/GEOMETRY_REVIEW_002_NAMING.md +684 -684
  569. package/src/core/geom/MANIFOLD_COMPARISON_2026_08_01.md +554 -554
  570. package/src/core/geom/mat3/m3_cm_from_translation.js +7 -7
  571. package/src/core/geom/mat3/m3_cm_invert.js +7 -7
  572. package/src/core/geom/mat3/m3_make_rotation.js +5 -5
  573. package/src/core/geom/mat3/m3_make_rotation_TBN.js +31 -31
  574. package/src/core/geom/mat3/m3_multiply_vec3.js +7 -7
  575. package/src/core/geom/packing/computeBoundingSphereOfSpheres.js +7 -7
  576. package/src/core/geom/packing/max-rect/cost/costByBestShortSide.js +7 -7
  577. package/src/core/geom/packing/max-rect/cost/costByRemainingArea.js +7 -7
  578. package/src/core/geom/packing/max-rect/cutArea.js +7 -7
  579. package/src/core/geom/packing/max-rect/findBestContainer.js +7 -7
  580. package/src/core/geom/packing/max-rect/packMaxRectangles.js +7 -7
  581. package/src/core/geom/packing/max-rect/packOneBox.js +7 -7
  582. package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +7 -7
  583. package/src/core/geom/packing/max-rect/removeRedundantBoxesArray.js +7 -7
  584. package/src/core/geom/random/randomPointInBox.js +7 -7
  585. package/src/core/geom/random/randomPointInCircle.js +7 -7
  586. package/src/core/geom/random/randomPointInPoint.js +7 -7
  587. package/src/core/geom/random/randomPointInSphere.js +7 -7
  588. package/src/core/geom/random/randomPointOnBox.js +7 -7
  589. package/src/core/geom/random/randomPointOnSphere.js +7 -7
  590. package/src/core/geom/random/random_orthonormal_basis.js +31 -31
  591. package/src/core/geom/vec/vector_axpy.js +15 -15
  592. package/src/core/geom/vec/vector_axpy_offset.js +20 -20
  593. package/src/core/geom/vec/vector_copy.js +7 -7
  594. package/src/core/geom/vec/vector_dot_offset.js +25 -25
  595. package/src/core/geom/vec/vector_scale_array.js +7 -7
  596. package/src/core/geom/vec2/v2_rotate.js +21 -21
  597. package/src/core/geom/vec2/v2_rotate_cos_sin.js +21 -21
  598. package/src/core/geom/vec3/v3_angle_atan2_between.js +7 -7
  599. package/src/core/geom/vec3/v3_angle_between.js +7 -7
  600. package/src/core/geom/vec3/v3_angle_cos_between.js +7 -7
  601. package/src/core/geom/vec3/v3_array_add_in_place.js +7 -7
  602. package/src/core/geom/vec3/v3_array_centroid.js +7 -7
  603. package/src/core/geom/vec3/v3_array_copy.js +7 -7
  604. package/src/core/geom/vec3/v3_array_displace_in_direction.js +7 -7
  605. package/src/core/geom/vec3/v3_array_equals.js +7 -7
  606. package/src/core/geom/vec3/v3_array_immediate_add.js +7 -7
  607. package/src/core/geom/vec3/v3_array_matrix4_rotate.js +7 -7
  608. package/src/core/geom/vec3/v3_array_normalize.js +7 -7
  609. package/src/core/geom/vec3/v3_array_scale.js +7 -7
  610. package/src/core/geom/vec3/v3_compute_interior_angle.js +7 -7
  611. package/src/core/geom/vec3/v3_cotangent.js +33 -33
  612. package/src/core/geom/vec3/v3_dot_array_array.js +7 -7
  613. package/src/core/geom/vec3/v3_matrix3_multiply.js +30 -30
  614. package/src/core/geom/vec3/v3_matrix3_rotate.js +49 -49
  615. package/src/core/geom/vec3/v3_matrix4_rotate.js +7 -7
  616. package/src/core/geom/vec3/v3_multiply.js +23 -23
  617. package/src/core/geom/vec3/v3_multiply_scalar.js +7 -7
  618. package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.js +49 -49
  619. package/src/core/geom/vec3/v3_quat3_apply.js +7 -7
  620. package/src/core/geom/vec3/v3_quat3_apply_inverse.js +7 -7
  621. package/src/core/geom/vec3/v3_quaternion_apply.js +39 -39
  622. package/src/core/geom/vec3/v3_quaternion_apply_inverse.js +41 -41
  623. package/src/core/geom/vec3/v3_reflect.js +24 -24
  624. package/src/core/geom/vec3/v3_scale_array.js +18 -18
  625. package/src/core/geom/vec3/v3_triple_cross.js +45 -45
  626. package/src/core/geom/vec3/v3_triple_cross_product.js +7 -7
  627. package/src/core/geom/vec3/v3_uniform_sample_cone.js +21 -21
  628. package/src/core/geom/vec4/v4_multiply_mat4.js +7 -7
  629. package/src/core/graph/csr/csr_graph_build_distance_field.js +112 -112
  630. package/src/core/graph/csr/csr_graph_find_edge.js +25 -25
  631. package/src/core/graph/csr/csr_graph_find_shortest_path.js +105 -105
  632. package/src/core/graph/csr/csr_graph_from_undirected_edge_list.js +94 -94
  633. package/src/core/graph/graph_k_means_cluster.js +250 -250
  634. package/src/core/graph/layout/CircleLayout.js +1 -1
  635. package/src/core/graph/layout/box/BoxLayouter.js +1 -1
  636. package/src/core/graph/layout/graph_peel_topological_layer.js +45 -45
  637. package/src/core/graph/metis/BinaryTopologyFaceGraph.js +49 -49
  638. package/src/core/graph/metis/bt_mesh_build_face_graph.js +111 -111
  639. package/src/core/graph/metis/cluster_mesh_metis.js +137 -137
  640. package/src/core/graph/metis/native/bisection/bisect_graph.js +100 -100
  641. package/src/core/graph/metis/native/bisection/compute_2way_params.js +86 -86
  642. package/src/core/graph/metis/native/bisection/fm_2way.js +297 -297
  643. package/src/core/graph/metis/native/bisection/grow_bisection.js +137 -137
  644. package/src/core/graph/metis/native/bisection/split_graph_two_way.js +119 -119
  645. package/src/core/graph/metis/native/coarsen/coarsen_graph.js +94 -94
  646. package/src/core/graph/metis/native/coarsen/create_coarse_graph.js +158 -158
  647. package/src/core/graph/metis/native/coarsen/match_shem.js +175 -175
  648. package/src/core/graph/metis/native/initial/initial_kway_bfs.js +122 -122
  649. package/src/core/graph/metis/native/initial/initial_kway_recursive_bisection.js +170 -170
  650. package/src/core/graph/metis/native/metis_partition_kway.js +126 -126
  651. package/src/core/graph/metis/native/refine/RefinementScratch.js +53 -53
  652. package/src/core/graph/metis/native/refine/fm_kway.js +487 -487
  653. package/src/core/graph/metis/native/refine/project_kway.js +43 -43
  654. package/src/core/graph/metis/native/refine/refine_kway.js +43 -43
  655. package/src/core/lang/reactive/pegjs/parser.js +3458 -3458
  656. package/src/core/math/complex/complex_horner_eval.js +31 -31
  657. package/src/core/math/computeWholeDivisorLow.js +33 -33
  658. package/src/core/math/f32_next_down.js +24 -24
  659. package/src/core/math/f32_next_up.js +27 -27
  660. package/src/core/math/hash/hash_mix2.js +22 -22
  661. package/src/core/math/hash/hash_mix3.js +19 -19
  662. package/src/core/math/linalg/cg/cg_solve.js +106 -106
  663. package/src/core/math/linalg/cg/cg_solve_normal_equations.js +59 -59
  664. package/src/core/math/linalg/cubic_residual_times_derivative_accumulate.js +43 -43
  665. package/src/core/math/linalg/eigen/matrix_householder_in_place.js +122 -122
  666. package/src/core/math/linalg/eigen/matrix_qr_in_place.js +190 -190
  667. package/src/core/math/linalg/eigen/matrix_symmetric_3x3_eigen.js +117 -117
  668. package/src/core/math/linalg/givens/givens_apply_rows.js +28 -28
  669. package/src/core/math/linalg/givens/givens_apply_rows_offset.js +30 -30
  670. package/src/core/math/linalg/givens/givens_rotation_coefficients.js +39 -39
  671. package/src/core/math/linalg/lu_factor_linear_system.js +108 -108
  672. package/src/core/math/linalg/lu_solve_linear_system.js +53 -53
  673. package/src/core/math/linalg/polynomial_add_into.js +29 -29
  674. package/src/core/math/linalg/polynomial_complex_roots_aberth_ehrlich.js +159 -159
  675. package/src/core/math/linalg/polynomial_cubic_derivative_eval.js +16 -16
  676. package/src/core/math/linalg/polynomial_cubic_horner_eval.js +19 -19
  677. package/src/core/math/linalg/polynomial_cubic_second_derivative_eval.js +14 -14
  678. package/src/core/math/linalg/polynomial_multiply.js +41 -41
  679. package/src/core/math/linalg/polynomial_real_roots_in_interval.js +211 -211
  680. package/src/core/math/linalg/polynomial_root_bound_cauchy.js +30 -30
  681. package/src/core/math/linalg/polynomial_scale_into.js +17 -17
  682. package/src/core/math/linalg/polynomial_sub_into.js +29 -29
  683. package/src/core/math/linalg/solve_linear_system_GEPP_2x2.js +96 -96
  684. package/src/core/math/linalg/sor_optimal_omega.js +42 -42
  685. package/src/core/math/linalg/sparse/SparseMatrixCSR.js +88 -88
  686. package/src/core/math/linalg/sparse/spm_matvec.js +22 -22
  687. package/src/core/math/linalg/sparse/spm_matvec_transpose.js +29 -29
  688. package/src/core/math/lookup/LUT_HEATMAP.js +30 -30
  689. package/src/core/math/lookup/ParameterLookupTable.js +495 -495
  690. package/src/core/math/lookup/ParameterLookupTableFlags.js +6 -6
  691. package/src/core/math/noise/curl_noise_3d.js +18 -18
  692. package/src/core/math/physics/brdf/cone_cosine_from_roughness.js +28 -28
  693. package/src/core/math/physics/brdf/reflection_sample_weight.js +48 -48
  694. package/src/core/math/physics/kinematics/computeInterceptPoint.js +79 -79
  695. package/src/core/math/random/randomSeed.js +16 -16
  696. package/src/core/math/random/random_pick_weighted_index.js +26 -26
  697. package/src/core/math/round_half_away_from_zero.js +13 -13
  698. package/src/core/math/spline/spline3_hermite_apply_transform.js +107 -107
  699. package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_1d.js +125 -125
  700. package/src/core/math/spline/spline3_hermite_intersects_spline3_hermite.js +50 -50
  701. package/src/core/math/spline/spline3_hermite_reverse.js +49 -49
  702. package/src/core/math/spline/spline3_hermite_to_monomial.js +37 -37
  703. package/src/core/math/statistics/hammersley_sequence.js +53 -53
  704. package/src/core/model/reactive/model/util/createRandomReactiveExpression.js +185 -185
  705. package/src/core/path/convertPathToURL.js +107 -107
  706. package/src/core/process/delay.js +16 -16
  707. package/src/core/science/units/MEASUREMENT_UNITS_SCIENTIFIC.js +87 -87
  708. package/src/core/science/units/MEASUREMENT_UNITS_SI.js +17 -17
  709. package/src/core/science/units/NamedUnit.js +48 -48
  710. package/src/core/science/units/UNIT_DIMENSION_MAPPING.js +29 -29
  711. package/src/core/science/units/UnitDimension.js +28 -28
  712. package/src/core/science/units/format_quantity.js +74 -74
  713. package/src/core/science/units/magnitude_prefix.js +127 -127
  714. package/src/core/science/units/si/AMPERE.js +13 -13
  715. package/src/core/science/units/si/CANDELA.js +13 -13
  716. package/src/core/science/units/si/DIMENSIONLESS.js +14 -14
  717. package/src/core/science/units/si/KELVIN.js +13 -13
  718. package/src/core/science/units/si/KILOGRAM.js +13 -13
  719. package/src/core/science/units/si/METER.js +13 -13
  720. package/src/core/science/units/si/MOLE.js +13 -13
  721. package/src/core/science/units/si/SECOND.js +13 -13
  722. package/src/core/science/units/si/derived/BECQUEREL.js +16 -16
  723. package/src/core/science/units/si/derived/COULOMB.js +13 -13
  724. package/src/core/science/units/si/derived/FARAD.js +13 -13
  725. package/src/core/science/units/si/derived/GRAY.js +16 -16
  726. package/src/core/science/units/si/derived/HENRY.js +13 -13
  727. package/src/core/science/units/si/derived/HERTZ.js +16 -16
  728. package/src/core/science/units/si/derived/JOULE.js +13 -13
  729. package/src/core/science/units/si/derived/KATAL.js +13 -13
  730. package/src/core/science/units/si/derived/LUMEN.js +14 -14
  731. package/src/core/science/units/si/derived/LUX.js +13 -13
  732. package/src/core/science/units/si/derived/NEWTON.js +13 -13
  733. package/src/core/science/units/si/derived/OHM.js +13 -13
  734. package/src/core/science/units/si/derived/PASCAL.js +13 -13
  735. package/src/core/science/units/si/derived/SIEMENS.js +13 -13
  736. package/src/core/science/units/si/derived/SIEVERT.js +16 -16
  737. package/src/core/science/units/si/derived/TESLA.js +13 -13
  738. package/src/core/science/units/si/derived/VOLT.js +13 -13
  739. package/src/core/science/units/si/derived/WATT.js +13 -13
  740. package/src/core/science/units/si/derived/WEBER.js +13 -13
  741. package/src/core/science/units/unit_matrix_to_string.js +403 -403
  742. package/src/engine/Engine.js +726 -726
  743. package/src/engine/EngineHarness.js +590 -590
  744. package/src/engine/REVIEW_2026_07_03.md +211 -211
  745. package/src/engine/animation/AnimatedActions.js +81 -81
  746. package/src/engine/animation/AnimationUtils.js +252 -252
  747. package/src/engine/animation/async/TimeSeries.js +300 -300
  748. package/src/engine/animation/curve/animation_curve_binary_codec.js +60 -60
  749. package/src/engine/animation/curve/draw/canvas_point_to_curve.js +22 -22
  750. package/src/engine/animation/curve/draw/curve_point_to_canvas.js +38 -38
  751. package/src/engine/animation/curve/draw/position_canvas_to_curve.js +22 -22
  752. package/src/engine/animation/curve/draw/position_curve_to_canvas.js +22 -22
  753. package/src/engine/asset/GameAssetType.d.ts +0 -1
  754. package/src/engine/asset/GameAssetType.js +0 -1
  755. package/src/engine/asset/load_model_scene_bundle.js +27 -27
  756. package/src/engine/asset/loaders/font/FontAsset.js +21 -21
  757. package/src/engine/asset/loaders/font/FontAssetLoader.js +20 -20
  758. package/src/engine/asset/loaders/gltf_test_fixtures.js +358 -358
  759. package/src/engine/asset/loaders/image/ImageBitmapAssetLoader.js +52 -52
  760. package/src/engine/asset/loaders/image/ImageRGBADataLoader.js +94 -94
  761. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterAverage.js +59 -59
  762. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterNone.js +55 -55
  763. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterPaeth.js +74 -74
  764. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterSub.js +34 -34
  765. package/src/engine/asset/loaders/image/png/filter/png_filter_unFilterUp.js +46 -46
  766. package/src/engine/control/first-person/DESIGN.md +640 -640
  767. package/src/engine/control/first-person/DESIGN_EXTENSIONS.md +563 -563
  768. package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -134
  769. package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +2045 -2045
  770. package/src/engine/control/first-person/mastery/DecisionPoint.js +30 -30
  771. package/src/engine/control/first-person/math/Spring.js +71 -71
  772. package/src/engine/control/first-person/math/computeJumpFromApex.js +23 -23
  773. package/src/engine/control/first-person/math/computeLRCBreathRate.js +41 -41
  774. package/src/engine/control/first-person/math/computeMassRatios.js +44 -44
  775. package/src/engine/control/first-person/math/criticallyDampedSpring.js +34 -34
  776. package/src/engine/control/first-person/math/dampedSpringStep.js +72 -72
  777. package/src/engine/control/first-person/math/jumpDynamics.js +108 -108
  778. package/src/engine/control/first-person/math/stepTowards.js +20 -20
  779. package/src/engine/control/first-person/pose/FirstPersonPose.js +137 -137
  780. package/src/engine/control/first-person/pose/FirstPersonPosture.js +27 -27
  781. package/src/engine/control/first-person/test/buildTestPlayer.js +36 -36
  782. package/src/engine/ecs/ASYNC_SYSTEMS_PROPOSAL.md +531 -531
  783. package/src/engine/ecs/EntityManager.d.ts +7 -3
  784. package/src/engine/ecs/EntityManager.d.ts.map +1 -1
  785. package/src/engine/ecs/EntityManager.js +10 -5
  786. package/src/engine/ecs/HIERARCHY_REVIEW.md +717 -717
  787. package/src/engine/ecs/async/SystemWorkerHost.js +329 -329
  788. package/src/engine/ecs/async/SystemWorkerLoopback.js +143 -143
  789. package/src/engine/ecs/async/WORKER_SYSTEM_PLAN.md +246 -246
  790. package/src/engine/ecs/async/WorkerSystem.js +702 -702
  791. package/src/engine/ecs/async/WorkerSystemProtocol.js +150 -150
  792. package/src/engine/ecs/fow/FogOfWar.js +707 -707
  793. package/src/engine/ecs/fow/FogOfWarEdgeMode.js +40 -40
  794. package/src/engine/ecs/fow/fog_of_war_mask_world_bounds.js +24 -24
  795. package/src/engine/ecs/fow/fog_of_war_uv_transform.js +30 -30
  796. package/src/engine/ecs/gui/hud/testHudEdgeStick.js +132 -134
  797. package/src/engine/ecs/hierarchy/EntityChildIndex.js +140 -140
  798. package/src/engine/ecs/ik/IKProblem.js +42 -42
  799. package/src/engine/ecs/ik/InverseKinematicsSystem.js +193 -193
  800. package/src/engine/ecs/ik/OneBoneSurfaceAlignmentSolver.js +134 -134
  801. package/src/engine/ecs/ik/TwoBoneInverseKinematicsSolver.js +97 -97
  802. package/src/engine/ecs/ik/ik_bone_chain.js +85 -85
  803. package/src/engine/ecs/ik/ik_probe_terrain_contact.js +119 -119
  804. package/src/engine/ecs/parent/EntityNode.d.ts +1 -1
  805. package/src/engine/ecs/parent/EntityNode.js +2 -2
  806. package/src/engine/ecs/parent/ParentEntitySystem.js +255 -255
  807. package/src/engine/ecs/speaker/VoiceSystem.d.ts +2 -2
  808. package/src/engine/ecs/speaker/VoiceSystem.d.ts.map +1 -1
  809. package/src/engine/ecs/speaker/VoiceSystem.js +13 -7
  810. package/src/engine/ecs/storage/binary/SERIALIZATION_COVERAGE.md +269 -269
  811. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectDeSerializer.js +315 -315
  812. package/src/engine/ecs/storage/binary/object-v2/BinaryObjectSerializer.js +331 -331
  813. package/src/engine/ecs/storage/binary/object-v2/DESIGN.md +199 -199
  814. package/src/engine/ecs/storage/binary/object-v2/ObjectBinaryFormat.js +57 -57
  815. package/src/engine/ecs/storage/binary/object-v2/ObjectSerializationAdapter.js +56 -56
  816. package/src/engine/ecs/storage/binary/object-v2/README.md +110 -110
  817. package/src/engine/ecs/storage/binary/object-v2/objectStringCodec.js +48 -48
  818. package/src/engine/ecs/storage/binary/object-v2/serializerTestHarness.js +44 -44
  819. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +265 -265
  820. package/src/engine/ecs/terrain/TerrainClouds.js +97 -97
  821. package/src/engine/ecs/terrain/ecs/StubTerrainBuildWorker.js +65 -65
  822. package/src/engine/ecs/terrain/ecs/Terrain.d.ts +4 -0
  823. package/src/engine/ecs/terrain/ecs/Terrain.d.ts.map +1 -1
  824. package/src/engine/ecs/terrain/ecs/Terrain.js +790 -790
  825. package/src/engine/ecs/terrain/ecs/TerrainSystem.js +209 -209
  826. package/src/engine/ecs/terrain/ecs/cling/ClingToTerrainSystem.js +316 -316
  827. package/src/engine/ecs/terrain/ecs/layers/TerrainLayers.js +416 -416
  828. package/src/engine/ecs/terrain/ecs/splat/SplatMapping.js +525 -525
  829. package/src/engine/ecs/terrain/overlay/TerrainOverlay.js +302 -302
  830. package/src/engine/ecs/terrain/tiles/TerrainTile.js +561 -561
  831. package/src/engine/ecs/terrain/tiles/TerrainTileManager.js +896 -896
  832. package/src/engine/ecs/terrain/tiles/tile_geometry_to_meshlets.js +36 -36
  833. package/src/engine/ecs/terrain/util/loadVisibleTerrainTiles.js +129 -129
  834. package/src/engine/ecs/transform/Transform64.js +814 -814
  835. package/src/engine/ecs/transform/t64_copy_from_transform.js +35 -35
  836. package/src/engine/ecs/transform/t64_equals_transform.js +55 -55
  837. package/src/engine/ecs/transform/t64_look_rotation.js +36 -36
  838. package/src/engine/ecs/transform/t64_transform_direction.js +25 -25
  839. package/src/engine/ecs/transform/transform_copy_from_t64.js +28 -28
  840. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +345 -345
  841. package/src/engine/ecs/transform-attachment/transform_attachment_find_descendant_by_name.js +65 -65
  842. package/src/engine/ecs/transform-attachment/transform_attachment_parent_of.js +24 -24
  843. package/src/engine/ecs/util/hideEntityGracefully.js +208 -208
  844. package/src/engine/graphics/CONTEXT_LOSS_RECOVERY_PLAN.md +446 -446
  845. package/src/engine/graphics/FULL_SCREEN_QUAD_VERTEX_SHADER.js +14 -14
  846. package/src/engine/graphics/FrameThrottle.js +2 -2
  847. package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +207 -207
  848. package/src/engine/graphics/ecs/animation/animator/graph/AnimationTransition.js +151 -151
  849. package/src/engine/graphics/ecs/animation/animator/graph/definition/serialization/AnimationGraphDefinitionAssetLoader.js +22 -22
  850. package/src/engine/graphics/ecs/camera/Camera.js +165 -165
  851. package/src/engine/graphics/ecs/camera/camera_active_frustum.js +25 -25
  852. package/src/engine/graphics/ecs/camera/camera_find_active.js +28 -28
  853. package/src/engine/graphics/ecs/camera/camera_traverse_active.js +18 -18
  854. package/src/engine/graphics/ecs/camera/pp/PerfectPanner.js +170 -170
  855. package/src/engine/graphics/ecs/camera/serialization/CameraSerializationAdapter.js +68 -68
  856. package/src/engine/graphics/ecs/camera/topdown/TopDownCameraController.js +296 -296
  857. package/src/engine/graphics/ecs/decal/v2/Decal.js +220 -220
  858. package/src/engine/graphics/ecs/decal/v2/DecalSerializationAdapter.js +56 -56
  859. package/src/engine/graphics/ecs/decal/v2/DecalSerializationUpgrader_0_1.js +38 -38
  860. package/src/engine/graphics/ecs/light/shadow/compute_view_frustum_aabb_in_space.js +62 -62
  861. package/src/engine/graphics/ecs/light/shadow/extend_shadow_camera_near_for_casters.js +77 -77
  862. package/src/engine/graphics/ecs/make_bvh_depth_range_computer.js +33 -33
  863. package/src/engine/graphics/ecs/mesh-v2/DeferredBoundsQueue.js +88 -88
  864. package/src/engine/graphics/ecs/mesh-v2/ShadedGeometry.js +428 -428
  865. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMesh.js +303 -303
  866. package/src/engine/graphics/ecs/mesh-v2/aggregate/SGMeshSystem.d.ts +1 -1
  867. package/src/engine/graphics/ecs/mesh-v2/aggregate/serialization/SGMeshSerializationAdapter.js +39 -39
  868. package/src/engine/graphics/ecs/mesh-v2/sg_hierarchy_compute_bounding_box_via_parent_entity.js +36 -36
  869. package/src/engine/graphics/ecs/path/tube/build/GeometryStream.js +94 -94
  870. package/src/engine/graphics/ecs/path/tube/build/build_stream_catmullrom.js +310 -310
  871. package/src/engine/graphics/ecs/path/tube/build/build_stream_linear.js +73 -73
  872. package/src/engine/graphics/ecs/path/tube/build/computeFrenetFrames.js +199 -199
  873. package/src/engine/graphics/ecs/path/tube/build/makeTubeStream.js +217 -217
  874. package/src/engine/graphics/ecs/path/tube/build/make_cap.js +340 -340
  875. package/src/engine/graphics/ecs/path/tube/build/make_ring_faces.js +40 -40
  876. package/src/engine/graphics/ecs/path/tube/build/make_ring_vertices.js +153 -153
  877. package/src/engine/graphics/ecs/render_layers_compute_depth_range.js +64 -64
  878. package/src/engine/graphics/ecs/trail3d/Trail3D.js +483 -483
  879. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +26 -26
  880. package/src/engine/graphics/ecs/trail3d/advance_trail.js +42 -42
  881. package/src/engine/graphics/ecs/trail3d/make_gradient_stroke.js +58 -58
  882. package/src/engine/graphics/ecs/trail3d/seed_trail_stroke.js +90 -90
  883. package/src/engine/graphics/ecs/trail3d/seed_trail_tube.js +75 -75
  884. package/src/engine/graphics/ecs/trail3d/trail_head_position.js +19 -19
  885. package/src/engine/graphics/ecs/water/Water.js +104 -104
  886. package/src/engine/graphics/geometry/AttributeData.js +84 -84
  887. package/src/engine/graphics/geometry/buffered/geometry_compute_vertex_normals_indexed.js +49 -49
  888. package/src/engine/graphics/geometry/buffered/query/GeometrySpatialQueryAccelerator.js +288 -288
  889. package/src/engine/graphics/geometry/buffered/query/nearest_point_on_triangle_record.js +87 -87
  890. package/src/engine/graphics/geometry/buffered/read_triangle_vertices.js +102 -102
  891. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_buffered_geometry.js +52 -52
  892. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_indexed_geometry.js +20 -20
  893. package/src/engine/graphics/geometry/bvh/buffered/bvh32_from_unindexed_geometry.js +19 -19
  894. package/src/engine/graphics/geometry/optimization/geometry_optimize_uv_tension.js +135 -135
  895. package/src/engine/graphics/impostors/voxel/README.md +149 -149
  896. package/src/engine/graphics/impostors/voxel/VoxelImpostorDescription.js +141 -141
  897. package/src/engine/graphics/particles/node-based/codegen/glsl/getTypeByteSize.js +20 -20
  898. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts +2 -0
  899. package/src/engine/graphics/particles/node-based/nodes/FloatConstant.d.ts.map +1 -1
  900. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts +2 -0
  901. package/src/engine/graphics/particles/node-based/nodes/ReadFloatUniform.d.ts.map +1 -1
  902. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts +2 -0
  903. package/src/engine/graphics/particles/node-based/nodes/ReadVelocity.d.ts.map +1 -1
  904. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts +1 -2
  905. package/src/engine/graphics/particles/node-based/nodes/ShaderNode.d.ts.map +1 -1
  906. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts +2 -0
  907. package/src/engine/graphics/particles/node-based/nodes/Vector3Constant.d.ts.map +1 -1
  908. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts +2 -0
  909. package/src/engine/graphics/particles/node-based/nodes/Vector3Merge.d.ts.map +1 -1
  910. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts +2 -0
  911. package/src/engine/graphics/particles/node-based/nodes/Vector3Split.d.ts.map +1 -1
  912. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts +2 -0
  913. package/src/engine/graphics/particles/node-based/nodes/WriteVelocity.d.ts.map +1 -1
  914. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts +2 -0
  915. package/src/engine/graphics/particles/node-based/nodes/attribute/ReadVector3Attribute.d.ts.map +1 -1
  916. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts +2 -0
  917. package/src/engine/graphics/particles/node-based/nodes/attribute/WriteVector3Attribute.d.ts.map +1 -1
  918. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts +2 -0
  919. package/src/engine/graphics/particles/node-based/nodes/math/AddFloatNode.d.ts.map +1 -1
  920. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts +2 -0
  921. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Add.d.ts.map +1 -1
  922. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts +2 -0
  923. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Divide.d.ts.map +1 -1
  924. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts +2 -0
  925. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Multiply.d.ts.map +1 -1
  926. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts +2 -0
  927. package/src/engine/graphics/particles/node-based/nodes/math/Vector3Subtract.d.ts.map +1 -1
  928. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts +2 -0
  929. package/src/engine/graphics/particles/node-based/nodes/noise/CurlNoiseNode.d.ts.map +1 -1
  930. package/src/engine/graphics/particles/node-based/nodes/particle_data_type_component_count.js +45 -45
  931. package/src/engine/graphics/particles/particular/engine/ParticularEngine.js +229 -229
  932. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts +2 -10
  933. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.d.ts.map +1 -1
  934. package/src/engine/graphics/particles/particular/engine/emitter/ParticleEmitter.js +1149 -1149
  935. package/src/engine/graphics/particles/particular/engine/emitter/ParticlePool.js +472 -472
  936. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTable.js +2 -2
  937. package/src/engine/graphics/particles/particular/engine/parameter/ParameterLookupTableFlags.js +2 -2
  938. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.d.ts.map +1 -1
  939. package/src/engine/graphics/particles/particular/engine/parameter/ParameterSet.js +3 -5
  940. package/src/engine/graphics/particles/particular/engine/shader/ShaderManager.js +135 -135
  941. package/src/engine/graphics/particles/particular/engine/utils/distribute_points_on_indexed_triangles.js +168 -168
  942. package/src/engine/graphics/render/forward_plus/plugin/forwardPlusMaterialMarker.js +18 -18
  943. package/src/engine/graphics/render/forward_plus/read_frustum_corner.js +2 -2
  944. package/src/engine/graphics/render/frame_graph/FRAME_GRAPH_RECOMMENDATIONS.md +219 -219
  945. package/src/engine/graphics/render/frame_graph/FrameGraph.js +900 -900
  946. package/src/engine/graphics/render/frame_graph/FrameGraphScope.js +72 -72
  947. package/src/engine/graphics/render/frame_graph/FrameGraphState.js +39 -39
  948. package/src/engine/graphics/render/frame_graph/ResourceEntry.js +96 -96
  949. package/src/engine/graphics/render/gizmo/GizmoShapeBuffer.js +105 -105
  950. package/src/engine/graphics/render/gizmo/GizmoShapeRenderingInterface.js +162 -162
  951. package/src/engine/graphics/render/layers/RenderLayer.js +95 -95
  952. package/src/engine/graphics/sh3/build_injected_shader.js +56 -56
  953. package/src/engine/graphics/sh3/grid_dilate_average.js +175 -175
  954. package/src/engine/graphics/sh3/path_tracer/BufferedGeometryBVH.js +506 -506
  955. package/src/engine/graphics/sh3/path_tracer/PathTracedMesh.js +161 -161
  956. package/src/engine/graphics/sh3/path_tracer/PathTracedRenderer.js +205 -205
  957. package/src/engine/graphics/sh3/path_tracer/PathTracedScene.js +698 -698
  958. package/src/engine/graphics/sh3/path_tracer/REVIEW_001_ACTION_PLAN.md +490 -490
  959. package/src/engine/graphics/sh3/path_tracer/geometry/compute_triangle_group_aabb3.js +2 -2
  960. package/src/engine/graphics/sh3/path_tracer/make_sky_hosek.js +45 -45
  961. package/src/engine/graphics/sh3/path_tracer/populate_path_traced_scene_from_ecd.js +73 -73
  962. package/src/engine/graphics/sh3/path_tracer/ray_hit_apply_transform.js +32 -32
  963. package/src/engine/graphics/sh3/path_tracer/texture/apply_texture_clamping_to_coordinate.js +28 -28
  964. package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +142 -142
  965. package/src/engine/graphics/sh3/sky/hosek/render_hosek_sky_to_equirectangular.js +70 -70
  966. package/src/engine/graphics/sh3/sky/hosek/sky_hosek_compute_irradiance_by_direction.js +388 -388
  967. package/src/engine/graphics/shaders/chunks/glsl_octahedral_impostor.js +277 -277
  968. package/src/engine/graphics/shaders/chunks/glsl_voxel_impostor_splat.js +57 -57
  969. package/src/engine/graphics/shadows/vsm.glsl.js +43 -43
  970. package/src/engine/graphics/texture/3d/scs3d_sample_linear3.js +81 -81
  971. package/src/engine/graphics/texture/EnvironmentTextureProjection.js +15 -15
  972. package/src/engine/graphics/texture/channel_count_to_format_prefix.js +26 -26
  973. package/src/engine/graphics/texture/isImageBitmap.js +2 -2
  974. package/src/engine/graphics/texture/reflection/convolve_equirectangular_reflection.js +189 -189
  975. package/src/engine/graphics/texture/reflection/equirectangular_reflection_roughness.js +51 -51
  976. package/src/engine/graphics/texture/sampler/HarmonicDiffusionGrid.js +145 -145
  977. package/src/engine/graphics/texture/sampler/Sampler2DArray.js +172 -172
  978. package/src/engine/graphics/texture/sampler/compare/FLIP_IMPLEMENTATION_PLAN.md +466 -466
  979. package/src/engine/graphics/texture/sampler/compare/flip/FlipParameters.js +18 -18
  980. package/src/engine/graphics/texture/sampler/compare/flip/FlipPooling.js +33 -33
  981. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_feature_kernel.js +147 -147
  982. package/src/engine/graphics/texture/sampler/compare/flip/flip_build_spatial_kernel.js +177 -177
  983. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_color_difference.js +104 -104
  984. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_feature_difference.js +76 -76
  985. package/src/engine/graphics/texture/sampler/compare/flip/flip_compute_pixels_per_degree.js +34 -34
  986. package/src/engine/graphics/texture/sampler/compare/flip/flip_constants.js +77 -77
  987. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_feature.js +48 -48
  988. package/src/engine/graphics/texture/sampler/compare/flip/flip_filter_spatial.js +86 -86
  989. package/src/engine/graphics/texture/sampler/compare/flip/flip_pool.js +71 -71
  990. package/src/engine/graphics/texture/sampler/compare/flip/flip_srgb_to_ycxcz.js +55 -55
  991. package/src/engine/graphics/texture/sampler/compare/flip/sampler2d_flip_ldr.js +80 -80
  992. package/src/engine/graphics/texture/sampler/filter/SeparableKernel.js +82 -82
  993. package/src/engine/graphics/texture/sampler/filter/sampler2d_convolve_separable.js +122 -122
  994. package/src/engine/graphics/texture/sampler/resize/sampler2d_scale_down_lanczos.js +148 -148
  995. package/src/engine/graphics/texture/sampler/sampler2d_channel_apply_lut.js +82 -82
  996. package/src/engine/graphics/texture/sampler/sampler2d_combine.js +67 -67
  997. package/src/engine/graphics/texture/sampler/sampler2d_linear_f16_to_srgb_u8.js +54 -54
  998. package/src/engine/graphics/texture/sampler/sampler2d_sample_equirectangular_direction.js +63 -63
  999. package/src/engine/graphics/texture/sampler/sampler2d_srgb_to_linear.js +33 -33
  1000. package/src/engine/graphics/texture/sampler/sampler2d_srgb_u8_to_linear_f16.js +68 -68
  1001. package/src/engine/graphics/texture/shade_image_preview.js +103 -103
  1002. package/src/engine/graphics/trail/TrailSpawnMode.js +20 -20
  1003. package/src/engine/graphics/trail/tube/TubeX.js +573 -573
  1004. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +58 -58
  1005. package/src/engine/graphics/trail/tube/simulator/TubeXFixedPhysicsSimulator.js +34 -34
  1006. package/src/engine/graphics/trail/tube/tube_attributes_spec.js +69 -69
  1007. package/src/engine/graphics/trail/tube/tube_frame.js +83 -83
  1008. package/src/engine/graphics3/AnimationGraphSystem3.js +329 -329
  1009. package/src/engine/graphics3/AnimationSystem3.js +352 -352
  1010. package/src/engine/graphics3/CameraSystem3.js +170 -170
  1011. package/src/engine/graphics3/DecalSystem3.js +441 -441
  1012. package/src/engine/graphics3/FogOfWarSystem3.js +265 -265
  1013. package/src/engine/graphics3/GraphicsEngine3.js +776 -776
  1014. package/src/engine/graphics3/HighlightSystem3.js +241 -241
  1015. package/src/engine/graphics3/LightSystem3.js +211 -211
  1016. package/src/engine/graphics3/MeshSystem3.js +646 -646
  1017. package/src/engine/graphics3/ParticleEmitterSystem3.js +289 -289
  1018. package/src/engine/graphics3/PathDisplaySystem3.js +309 -309
  1019. package/src/engine/graphics3/PickingQuery.js +48 -48
  1020. package/src/engine/graphics3/PickingResult.js +47 -47
  1021. package/src/engine/graphics3/PickingSystem3.js +273 -273
  1022. package/src/engine/graphics3/ShadeCameraAdapter.js +55 -55
  1023. package/src/engine/graphics3/ShadedGeometrySystem3.js +169 -169
  1024. package/src/engine/graphics3/TerrainSystem3.js +566 -566
  1025. package/src/engine/graphics3/TooltipComponentSystem3.js +328 -328
  1026. package/src/engine/graphics3/Trail3DSystem3.js +221 -221
  1027. package/src/engine/graphics3/WaterSystem3.js +293 -293
  1028. package/src/engine/graphics3/animation/ClipPlayback.js +84 -84
  1029. package/src/engine/graphics3/animation/wrap_clip_time.js +23 -23
  1030. package/src/engine/graphics3/animation/write_graph_playback.js +48 -48
  1031. package/src/engine/graphics3/camera_sync_from_transform.js +29 -29
  1032. package/src/engine/graphics3/debug/gizmo_view_from_camera.js +48 -48
  1033. package/src/engine/graphics3/decal/DECAL_CLUSTER.js +61 -61
  1034. package/src/engine/graphics3/decal/DECAL_GPU_RECORD.js +117 -117
  1035. package/src/engine/graphics3/decal/DecalAtlas.js +194 -194
  1036. package/src/engine/graphics3/decal/DecalAtlasFill.js +32 -32
  1037. package/src/engine/graphics3/decal/GPUDecalRenderer.js +360 -360
  1038. package/src/engine/graphics3/decal/chunk_decal_intersects_frustum.js +59 -59
  1039. package/src/engine/graphics3/decal/chunk_decal_sampling_info.js +57 -57
  1040. package/src/engine/graphics3/decal/chunk_decal_surface_frame.js +54 -54
  1041. package/src/engine/graphics3/decal/decal_atlas_border.js +69 -69
  1042. package/src/engine/graphics3/decal/decal_gpu_records.js +120 -120
  1043. package/src/engine/graphics3/decal/decal_material_samplers.js +109 -109
  1044. package/src/engine/graphics3/decal/graph_build_decal_clusters.js +212 -212
  1045. package/src/engine/graphics3/decal/shader_cluster_assign_decals.js +207 -207
  1046. package/src/engine/graphics3/decal/shader_cull_decals_frustum.js +108 -108
  1047. package/src/engine/graphics3/highlight/GPUHighlightRenderer.js +151 -151
  1048. package/src/engine/graphics3/highlight/HIGHLIGHT_OUTLINE.js +23 -23
  1049. package/src/engine/graphics3/highlight/chunk_highlight_lookup.js +53 -53
  1050. package/src/engine/graphics3/highlight/pack_highlight_color.js +44 -44
  1051. package/src/engine/graphics3/highlight/pack_highlight_table.js +74 -74
  1052. package/src/engine/graphics3/highlight/shader_highlight_outline.js +150 -150
  1053. package/src/engine/graphics3/load_cube_environment.js +68 -68
  1054. package/src/engine/graphics3/make_default_environment.js +107 -107
  1055. package/src/engine/graphics3/particles/particle_gpu_records.js +221 -221
  1056. package/src/engine/graphics3/path/build_tube_path_streams.js +98 -98
  1057. package/src/engine/graphics3/path/tube_stream_write_geometry.js +90 -90
  1058. package/src/engine/graphics3/preview/ModelPreviewFraming.js +35 -35
  1059. package/src/engine/graphics3/preview/make_model_preview_scene.js +154 -154
  1060. package/src/engine/graphics3/preview/make_model_thumbnail.js +48 -48
  1061. package/src/engine/graphics3/preview/model_preview_framing.js +70 -70
  1062. package/src/engine/graphics3/shade_bundle_bounds.js +55 -55
  1063. package/src/engine/graphics3/shade_camera_projection_ray.js +77 -77
  1064. package/src/engine/graphics3/shade_node_to_entity_composition.js +84 -84
  1065. package/src/engine/graphics3/terrain/GPUTerrainSplatRenderer.js +481 -481
  1066. package/src/engine/graphics3/terrain/chunk_terrain_grid_overlay.js +61 -61
  1067. package/src/engine/graphics3/terrain/chunk_terrain_splat_mix.js +66 -66
  1068. package/src/engine/graphics3/terrain/pack_terrain_row_table.js +53 -53
  1069. package/src/engine/graphics3/terrain/shader_terrain_splat.js +215 -215
  1070. package/src/engine/graphics3/trail/trail_tube_write_geometry.js +158 -158
  1071. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts +2 -2
  1072. package/src/engine/grid/grid2transform/GridPosition2TransformSystem.d.ts.map +1 -1
  1073. package/src/engine/input/GAMEPAD_API_PROPOSAL.md +717 -717
  1074. package/src/engine/input/analog/apply_radial_dead_zone.js +38 -38
  1075. package/src/engine/input/devices/GamepadDevice.js +466 -466
  1076. package/src/engine/input/devices/ManualInputDevice.js +152 -152
  1077. package/src/engine/input/devices/PointerCoordinates.js +26 -26
  1078. package/src/engine/input/devices/events/GamepadEvents.js +10 -10
  1079. package/src/engine/input/devices/gamepad/GamepadAxes.js +19 -19
  1080. package/src/engine/input/devices/gamepad/GamepadButtons.js +29 -29
  1081. package/src/engine/input/devices/gamepad/GamepadCoordinates.js +17 -17
  1082. package/src/engine/input/devices/gamepad/GamepadDeviceLayout.js +323 -323
  1083. package/src/engine/input/devices/gamepad/GamepadHandle.js +286 -286
  1084. package/src/engine/input/devices/gamepad/GamepadLayout.js +160 -160
  1085. package/src/engine/input/devices/gamepad/GamepadStandardMapping.js +31 -31
  1086. package/src/engine/input/devices/gamepad/GamepadStick.js +169 -169
  1087. package/src/engine/input/devices/gamepad/GamepadStickCalibration.js +119 -119
  1088. package/src/engine/input/devices/gamepad/GamepadStickCalibrator.js +250 -250
  1089. package/src/engine/input/devices/gamepad/gamepad_layout_database.js +144 -144
  1090. package/src/engine/input/devices/gamepad/gamepad_read_hat_axis.js +61 -61
  1091. package/src/engine/input/devices/gamepad/gamepad_write_dpad_vector.js +30 -30
  1092. package/src/engine/input/devices/gamepad/prototypeGamepadTester.js +2124 -2124
  1093. package/src/engine/input/devices/mouse/MouseButtons.js +22 -22
  1094. package/src/engine/input/ecs/components/Input.js +118 -118
  1095. package/src/engine/input/ecs/components/InputController.js +78 -78
  1096. package/src/engine/input/ecs/ism/ISM_DESIGN.md +1523 -1523
  1097. package/src/engine/input/ecs/ism/ISM_IMPLEMENTATION_PLAN.md +627 -627
  1098. package/src/engine/input/ecs/ism/InputActionEvent.js +40 -40
  1099. package/src/engine/input/ecs/ism/InputBinding.js +71 -71
  1100. package/src/engine/input/ecs/ism/InputCoordinateKind.js +25 -25
  1101. package/src/engine/input/ecs/ism/InputCoordinateSource.js +86 -86
  1102. package/src/engine/input/ecs/ism/InputDirection.js +33 -33
  1103. package/src/engine/input/ecs/ism/InputMapProgram.js +236 -236
  1104. package/src/engine/input/ecs/ism/InputMapSystem.js +976 -976
  1105. package/src/engine/input/ecs/ism/InputOcclusion.js +31 -31
  1106. package/src/engine/input/ecs/ism/InputSource.js +67 -67
  1107. package/src/engine/input/ecs/ism/InputSourceTable.js +107 -107
  1108. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannel.js +93 -93
  1109. package/src/engine/input/ecs/ism/coordinate/InputCoordinateChannelType.js +11 -11
  1110. package/src/engine/input/ecs/ism/coordinate/InputCoordinateDevice.js +153 -153
  1111. package/src/engine/input/ecs/ism/coordinate/InputCoordinateSwitches.js +160 -160
  1112. package/src/engine/input/ecs/ism/device/GAMEPAD_INPUT_DEVICE_ID.js +9 -9
  1113. package/src/engine/input/ecs/ism/device/GamepadInputDeviceAdapter.js +88 -88
  1114. package/src/engine/input/ecs/ism/device/InputDeviceAdapter.js +72 -72
  1115. package/src/engine/input/ecs/ism/device/KEYBOARD_INPUT_DEVICE_ID.js +9 -9
  1116. package/src/engine/input/ecs/ism/device/KeyboardInputDeviceAdapter.js +63 -63
  1117. package/src/engine/input/ecs/ism/device/ManualInputDeviceAdapter.js +55 -55
  1118. package/src/engine/input/ecs/ism/device/POINTER_INPUT_DEVICE_ID.js +9 -9
  1119. package/src/engine/input/ecs/ism/device/PointerInputDeviceAdapter.js +160 -160
  1120. package/src/engine/input/ecs/ism/map/InputBinding.js +88 -88
  1121. package/src/engine/input/ecs/ism/map/InputCoordinateBinding.js +102 -102
  1122. package/src/engine/input/ecs/ism/map/InputLayer.js +220 -220
  1123. package/src/engine/input/ecs/ism/map/InputMap.js +331 -331
  1124. package/src/engine/input/ecs/ism/prototypeInputMap.js +309 -309
  1125. package/src/engine/input/ecs/ism/trigger/InputTrigger.js +148 -148
  1126. package/src/engine/input/ecs/ism/trigger/InputTriggerChord.js +173 -173
  1127. package/src/engine/input/ecs/ism/trigger/InputTriggerGamepadButton.js +37 -37
  1128. package/src/engine/input/ecs/ism/trigger/InputTriggerHold.js +176 -176
  1129. package/src/engine/input/ecs/ism/trigger/InputTriggerKey.js +39 -39
  1130. package/src/engine/input/ecs/ism/trigger/InputTriggerMouseButton.js +38 -38
  1131. package/src/engine/input/ecs/ism/trigger/InputTriggerRelease.js +120 -120
  1132. package/src/engine/input/ecs/ism/trigger/InputTriggerSequence.js +315 -315
  1133. package/src/engine/input/ecs/ism/trigger/InputTriggerSwitch.js +105 -105
  1134. package/src/engine/input/ecs/ism/trigger/InputTriggerType.js +17 -17
  1135. package/src/engine/input/ecs/systems/InputControllerSystem.js +253 -253
  1136. package/src/engine/input/ecs/systems/InputSystem.js +162 -162
  1137. package/src/engine/intelligence/behavior/primitive/FailingBehaviorSerializationAdapter.js +25 -25
  1138. package/src/engine/intelligence/behavior/primitive/SucceedingBehaviorSerializationAdapter.js +26 -26
  1139. package/src/engine/intelligence/behavior/util/AsynchronousDelayAction.js +55 -55
  1140. package/src/engine/interpolation/BinaryInterpolationAdapter.js +56 -56
  1141. package/src/engine/interpolation/Interpoland.js +49 -49
  1142. package/src/engine/interpolation/Interpolated.js +149 -149
  1143. package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -61
  1144. package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -57
  1145. package/src/engine/interpolation/pose_interpoland.js +27 -27
  1146. package/src/engine/navigation/ecs/path_following/PathFollower.js +222 -222
  1147. package/src/engine/navigation/mesh/NavigationMesh.js +145 -145
  1148. package/src/engine/navigation/mesh/OVERLAPPING_SURFACES_2026_08_25.md +661 -0
  1149. package/src/engine/navigation/mesh/PATHFINDING_PLAN.md +229 -229
  1150. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.d.ts.map +1 -1
  1151. package/src/engine/navigation/mesh/build/clip_soup_against_overhangs.js +628 -520
  1152. package/src/engine/network/CONGESTION_CONTROL.md +87 -87
  1153. package/src/engine/network/README.md +52 -52
  1154. package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +43 -43
  1155. package/src/engine/network/adapters/TransformInterpolationAdapter.js +84 -84
  1156. package/src/engine/network/adapters/TransformReplicationAdapter.js +87 -87
  1157. package/src/engine/network/adapters/Vector3InterpolationAdapter.js +46 -46
  1158. package/src/engine/network/convertPathToURL.js +2 -2
  1159. package/src/engine/network/core/MalformedPacketError.js +26 -26
  1160. package/src/engine/network/core/quantize/quantize_float.js +73 -73
  1161. package/src/engine/network/core/quantize/quantize_position.js +65 -65
  1162. package/src/engine/network/core/sequence/ack_bitfield.js +77 -77
  1163. package/src/engine/network/core/sequence/seq16.js +69 -69
  1164. package/src/engine/network/core/sequence/seq32.js +73 -73
  1165. package/src/engine/network/ecs/NetworkSystem.js +84 -84
  1166. package/src/engine/network/ecs/components/NetworkIdentity.js +73 -73
  1167. package/src/engine/network/ecs/owner_authorization.js +55 -55
  1168. package/src/engine/network/ecs/serialization/NetworkIdentitySerializationAdapter.js +64 -64
  1169. package/src/engine/network/replication/ScopeFilter.js +71 -71
  1170. package/src/engine/network/sim/ActionLog.js +152 -152
  1171. package/src/engine/network/sim/SimAction.js +273 -273
  1172. package/src/engine/network/sim/Snapshotter.js +222 -222
  1173. package/src/engine/network/state/FrameBufferRing.js +168 -168
  1174. package/src/engine/network/state/InputRing.js +134 -134
  1175. package/src/engine/network/state/PriorityAccumulator.js +180 -180
  1176. package/src/engine/network/time/TimeDilation.js +62 -62
  1177. package/src/engine/network/transport/Channel.js +336 -336
  1178. package/src/engine/network/transport/Transport.js +203 -203
  1179. package/src/engine/network/transport/adapters/NodeUDPTransport.js +186 -186
  1180. package/src/engine/network/transport/adapters/WebRTCDataChannelTransport.js +105 -105
  1181. package/src/engine/network/transport/fragments/fragment_send.js +147 -147
  1182. package/src/engine/network/transport/fragments/packet_size.js +101 -101
  1183. package/src/engine/network/xhr.js +23 -23
  1184. package/src/engine/physics/BULLET_REVIEW.md +945 -945
  1185. package/src/engine/physics/CANNON_REVIEW.md +1300 -1300
  1186. package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -208
  1187. package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -364
  1188. package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -287
  1189. package/src/engine/physics/JOLT_REVIEW.md +913 -913
  1190. package/src/engine/physics/PLAN.md +1120 -1094
  1191. package/src/engine/physics/RAPIER_REVIEW.md +934 -934
  1192. package/src/engine/physics/REVIEW_001_ACTION_PLAN.md +642 -642
  1193. package/src/engine/physics/REVIEW_002.md +151 -151
  1194. package/src/engine/physics/REVIEW_003.md +166 -166
  1195. package/src/engine/physics/broadphase/compute_fat_world_aabb.js +97 -97
  1196. package/src/engine/physics/constraint/DofMode.js +35 -35
  1197. package/src/engine/physics/constraint/solve_constraints.js +1 -1
  1198. package/src/engine/physics/contact/combine_material.js +35 -35
  1199. package/src/engine/physics/ecs/BodyKind.js +29 -29
  1200. package/src/engine/physics/ecs/Collider.js +177 -177
  1201. package/src/engine/physics/ecs/ColliderFlags.js +15 -15
  1202. package/src/engine/physics/ecs/ColliderSerializationAdapter.js +37 -37
  1203. package/src/engine/physics/ecs/Joint.js +475 -475
  1204. package/src/engine/physics/ecs/JointSerializationAdapter.js +72 -72
  1205. package/src/engine/physics/ecs/PhysicsSystem.d.ts +3 -0
  1206. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  1207. package/src/engine/physics/ecs/PhysicsSystem.js +7 -2
  1208. package/src/engine/physics/ecs/RigidBodySerializationUpgrader_0_1.js +37 -37
  1209. package/src/engine/physics/ecs/SleepState.js +12 -12
  1210. package/src/engine/physics/ecs/find_non_finite_physics_state.js +76 -76
  1211. package/src/engine/physics/fluid/FluidField.js +867 -867
  1212. package/src/engine/physics/fluid/FluidSimulator.js +832 -832
  1213. package/src/engine/physics/fluid/REVIEW_02_PLAN.md +243 -243
  1214. package/src/engine/physics/fluid/SliceVisualiser.js +190 -190
  1215. package/src/engine/physics/fluid/ecs/FluidComponent.js +239 -239
  1216. package/src/engine/physics/fluid/ecs/FluidEffectorsComponent.js +89 -89
  1217. package/src/engine/physics/fluid/ecs/FluidObstacle.js +97 -97
  1218. package/src/engine/physics/fluid/ecs/FluidObstacleSystem.js +348 -348
  1219. package/src/engine/physics/fluid/ecs/FluidSystem.js +142 -142
  1220. package/src/engine/physics/fluid/ecs/FluidWorkerCore.js +123 -123
  1221. package/src/engine/physics/fluid/ecs/WorkerFluidSystem.js +395 -395
  1222. package/src/engine/physics/fluid/ecs/fluid.worker.js +12 -12
  1223. package/src/engine/physics/fluid/ecs/fluid_build_world_to_grid.js +32 -32
  1224. package/src/engine/physics/fluid/ecs/fluid_gather_visible_effectors.js +47 -47
  1225. package/src/engine/physics/fluid/ecs/fluid_reanchor_field.js +85 -85
  1226. package/src/engine/physics/fluid/ecs/fluid_sync_effectors_from_transform.js +16 -16
  1227. package/src/engine/physics/fluid/ecs/fluid_worker_protocol.js +111 -111
  1228. package/src/engine/physics/fluid/ecs/makeFluidWorker.js +18 -18
  1229. package/src/engine/physics/fluid/effector/AbstractFluidEffector.js +81 -81
  1230. package/src/engine/physics/fluid/effector/GlobalFluidEffector.js +202 -202
  1231. package/src/engine/physics/fluid/effector/ImpulseFluidEffector.js +257 -257
  1232. package/src/engine/physics/fluid/effector/WakeFluidEffector.js +405 -405
  1233. package/src/engine/physics/fluid/prototype.js +126 -126
  1234. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_scalar.js +143 -143
  1235. package/src/engine/physics/fluid/solver/v3_grid_advect_maccormack_velocity.js +162 -162
  1236. package/src/engine/physics/fluid/solver/v3_grid_advect_sl_velocity.js +107 -107
  1237. package/src/engine/physics/fluid/solver/v3_grid_apply_vorticity_confinement.js +131 -131
  1238. package/src/engine/physics/fluid/solver/v3_grid_compute_solid_neighbour_mask.js +90 -90
  1239. package/src/engine/physics/fluid/solver/v3_grid_limit8_at_indices.js +38 -38
  1240. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_constant.js +77 -77
  1241. package/src/engine/physics/fluid/solver/v3_grid_patch_edges_uniform.js +113 -113
  1242. package/src/engine/physics/fluid/solver/v3_grid_sample_scalar_masked.js +92 -92
  1243. package/src/engine/physics/fluid/solver/v3_grid_shift_in_place.js +107 -107
  1244. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure.js +135 -135
  1245. package/src/engine/physics/fluid/solver/v3_grid_solve_pressure_pcg.js +511 -511
  1246. package/src/engine/physics/fluid/solver/v3_grid_subtract_pressure_gradient.js +108 -108
  1247. package/src/engine/physics/fluid/solver/v3_mac_advect_maccormack_velocity.js +317 -317
  1248. package/src/engine/physics/fluid/solver/v3_mac_advect_scalar.js +236 -236
  1249. package/src/engine/physics/fluid/solver/v3_mac_advect_sl_velocity.js +217 -217
  1250. package/src/engine/physics/fluid/solver/v3_mac_apply_vorticity_confinement.js +174 -174
  1251. package/src/engine/physics/fluid/solver/v3_mac_clip_trace.js +95 -95
  1252. package/src/engine/physics/fluid/solver/v3_mac_compute_divergence.js +77 -77
  1253. package/src/engine/physics/fluid/solver/v3_mac_compute_face_solid.js +131 -131
  1254. package/src/engine/physics/fluid/solver/v3_mac_subtract_pressure_gradient.js +104 -104
  1255. package/src/engine/physics/inertia/world_inverse_inertia.js +160 -160
  1256. package/src/engine/physics/integration/integrate_position.js +97 -97
  1257. package/src/engine/physics/narrowphase/box_box_manifold.js +681 -681
  1258. package/src/engine/physics/narrowphase/box_triangle_contact.js +906 -906
  1259. package/src/engine/physics/narrowphase/capsule_triangle_contact.js +395 -395
  1260. package/src/engine/physics/narrowphase/compute_penetration.js +369 -369
  1261. package/src/engine/physics/narrowphase/decomposition/decompose_to_triangles.js +55 -55
  1262. package/src/engine/physics/narrowphase/decomposition/triangle_buffer_layout.js +37 -37
  1263. package/src/engine/physics/narrowphase/mesh_mesh_tet_manifold.js +117 -117
  1264. package/src/engine/physics/narrowphase/narrowphase_step.js +1738 -1738
  1265. package/src/engine/physics/narrowphase/reduce_manifold_contacts.js +74 -74
  1266. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts +12 -11
  1267. package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
  1268. package/src/engine/physics/narrowphase/refine_ray_hit.js +228 -36
  1269. package/src/engine/physics/narrowphase/sphere_sphere_contact.js +52 -52
  1270. package/src/engine/physics/narrowphase/sphere_triangle_contact.js +138 -138
  1271. package/src/engine/physics/persistence/solver_caches.js +309 -309
  1272. package/src/engine/physics/queries/overlap_shape.js +187 -187
  1273. package/src/engine/physics/queries/raycast.d.ts +7 -4
  1274. package/src/engine/physics/queries/raycast.d.ts.map +1 -1
  1275. package/src/engine/physics/queries/raycast.js +11 -5
  1276. package/src/engine/physics/queries/shape_cast.js +417 -417
  1277. package/src/engine/physics/solver/apply_impulse_to_body.js +86 -86
  1278. package/src/engine/physics/solver/friction_cone.js +37 -37
  1279. package/src/engine/physics/solver/solve_contacts.js +1 -1
  1280. package/src/engine/platform/WebEnginePlatform.d.ts.map +1 -1
  1281. package/src/engine/platform/WebEnginePlatform.js +1 -2
  1282. package/src/engine/scene/SerializedScene.d.ts +2 -2
  1283. package/src/engine/scene/SerializedScene.d.ts.map +1 -1
  1284. package/src/engine/scene/transitionToScene.js +121 -121
  1285. package/src/engine/simulation/Ticker.d.ts.map +1 -1
  1286. package/src/engine/simulation/Ticker.js +33 -0
  1287. package/src/engine/sound/ecs/audio/LiveEmitterSet.js +345 -345
  1288. package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -140
  1289. package/src/engine/sound/simulation/AcousticSimulator.js +646 -646
  1290. package/src/engine/sound/simulation/IMPLEMENTATION_PLAN.md +432 -432
  1291. package/src/engine/sound/simulation/PROBE_PLACEMENT.md +250 -250
  1292. package/src/engine/sound/simulation/PROBE_TRANSFER_RESEARCH.md +322 -322
  1293. package/src/engine/sound/simulation/README.md +277 -277
  1294. package/src/engine/sound/simulation/REFLECTION_IR_PLAN.md +261 -261
  1295. package/src/engine/sound/simulation/VISIBILITY_GRAPH_PLAN.md +121 -121
  1296. package/src/engine/sound/simulation/configureAcousticSimulation.js +48 -48
  1297. package/src/engine/sound/simulation/core/AcousticMedium.js +113 -113
  1298. package/src/engine/sound/simulation/core/AcousticMediumSerializationAdapter.js +27 -27
  1299. package/src/engine/sound/simulation/core/AcousticOccluderIndex.js +312 -312
  1300. package/src/engine/sound/simulation/core/AcousticRegionIndex.js +159 -159
  1301. package/src/engine/sound/simulation/core/AcousticSolution.js +90 -90
  1302. package/src/engine/sound/simulation/core/AcousticSourceState.js +109 -109
  1303. package/src/engine/sound/simulation/core/AcousticVolumeRegion.js +73 -73
  1304. package/src/engine/sound/simulation/core/OcclusionSolver.js +112 -112
  1305. package/src/engine/sound/simulation/core/RayHit.js +50 -50
  1306. package/src/engine/sound/simulation/core/VolumeField.js +149 -149
  1307. package/src/engine/sound/simulation/definition/AcousticDirectivity.js +114 -114
  1308. package/src/engine/sound/simulation/definition/AcousticDirectivitySerializationAdapter.js +30 -30
  1309. package/src/engine/sound/simulation/definition/AcousticMaterial.js +122 -122
  1310. package/src/engine/sound/simulation/definition/AcousticMaterialSerializationAdapter.js +32 -32
  1311. package/src/engine/sound/simulation/ecs/AcousticBody.js +48 -48
  1312. package/src/engine/sound/simulation/ecs/AcousticBodySerializationAdapter.js +34 -34
  1313. package/src/engine/sound/simulation/ecs/AcousticProbeFieldSystem.js +64 -64
  1314. package/src/engine/sound/simulation/ecs/AcousticSimulationSystem.js +75 -75
  1315. package/src/engine/sound/simulation/ecs/AcousticVolume.js +74 -74
  1316. package/src/engine/sound/simulation/ecs/AcousticVolumeSerializationAdapter.js +37 -37
  1317. package/src/engine/sound/simulation/ecs/AcousticVolumeSystem.js +66 -66
  1318. package/src/engine/sound/simulation/probe/AcousticProbeField.js +578 -578
  1319. package/src/engine/sound/simulation/probe/AcousticProbeFieldSerializationAdapter.js +60 -60
  1320. package/src/engine/sound/simulation/probe/ProbeFieldPathingSolution.js +64 -64
  1321. package/src/engine/sound/simulation/probe/ProbeVisibilityRecheck.js +132 -132
  1322. package/src/engine/sound/simulation/probe/acoustic_probe_transfer.js +103 -103
  1323. package/src/engine/sound/simulation/probe/acoustic_trace_energy.js +136 -136
  1324. package/src/engine/sound/simulation/probe/bakeProbeField.js +169 -169
  1325. package/src/engine/sound/simulation/probe/bakeProbeReflectors.js +110 -110
  1326. package/src/engine/sound/simulation/probe/bakeReverbBands.js +110 -110
  1327. package/src/engine/sound/simulation/probe/bakeVisibility.js +148 -148
  1328. package/src/engine/sound/simulation/probe/echogram_band_rt60.js +83 -83
  1329. package/src/engine/sound/simulation/probe/probe_delaunay_edges.js +215 -215
  1330. package/src/engine/sound/simulation/probe/probe_densify_portals.js +140 -140
  1331. package/src/engine/sound/simulation/probe/probe_detect_portals.js +196 -196
  1332. package/src/engine/sound/simulation/probe/probe_edge_key.js +38 -38
  1333. package/src/engine/sound/simulation/probe/probe_edge_visible.js +41 -41
  1334. package/src/engine/sound/simulation/probe/probe_place_sdf_cover.js +82 -82
  1335. package/src/engine/sound/simulation/probe/probe_refine_connectivity.js +181 -181
  1336. package/src/engine/sound/simulation/probe/probe_sdf_grid.js +171 -171
  1337. package/src/engine/sound/simulation/probe/probe_visibility_augment.js +146 -146
  1338. package/src/engine/sound/simulation/probe/probe_visibility_bridge_components.js +167 -167
  1339. package/src/engine/sound/simulation/render/FoaProbeReverbRenderer.js +354 -354
  1340. package/src/engine/sound/simulation/render/ProbeReverbRenderer.js +231 -231
  1341. package/src/engine/sound/simulation/render/foaReverbImpulseResponse.js +105 -105
  1342. package/src/engine/sound/simulation/render/reverbImpulseResponse.js +66 -66
  1343. package/src/engine/sound/simulation/render/reverb_band_tails.js +93 -93
  1344. package/src/engine/sound/simulation/serialization/populateAcousticSerializationRegistry.js +25 -25
  1345. package/src/engine/sound/sopra/README.md +643 -643
  1346. package/src/engine/sound/sopra/SopraEngine.js +432 -432
  1347. package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -71
  1348. package/src/engine/sound/sopra/asset/BufferProvider.js +29 -29
  1349. package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -58
  1350. package/src/engine/sound/sopra/definition/BusDefinition.js +142 -142
  1351. package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -54
  1352. package/src/engine/sound/sopra/definition/DuckingRule.js +106 -106
  1353. package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -31
  1354. package/src/engine/sound/sopra/definition/EventDescription.js +262 -262
  1355. package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +55 -55
  1356. package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -83
  1357. package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -39
  1358. package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -117
  1359. package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -31
  1360. package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -18
  1361. package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -109
  1362. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +185 -185
  1363. package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +59 -59
  1364. package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +128 -128
  1365. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +226 -226
  1366. package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -54
  1367. package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -191
  1368. package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -39
  1369. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +79 -79
  1370. package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -42
  1371. package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -77
  1372. package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -27
  1373. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +5 -0
  1374. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -1
  1375. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +125 -125
  1376. package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -41
  1377. package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -24
  1378. package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -120
  1379. package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -31
  1380. package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -128
  1381. package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -29
  1382. package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +113 -113
  1383. package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -25
  1384. package/src/engine/sound/sopra/runtime/BusGraph.js +231 -231
  1385. package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -98
  1386. package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -68
  1387. package/src/engine/sound/sopra/runtime/Voice.js +145 -145
  1388. package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -136
  1389. package/src/engine/sound/sopra/runtime/VoicePool.js +17 -17
  1390. package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -42
  1391. package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -99
  1392. package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -49
  1393. package/src/engine/sound/sopra/util/MockAudioContext.js +224 -224
  1394. package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -60
  1395. package/src/engine/ui/DraggableAspect.js +115 -115
  1396. package/src/engine/ui/apply_pointer_tilt_3d.js +50 -50
  1397. package/src/generation/COORDINATES.md +54 -54
  1398. package/src/generation/GridGenerationResource.js +24 -24
  1399. package/src/generation/GridTaskGroup.js +214 -214
  1400. package/src/generation/REVIEW_01_ACTION_PLAN.md +628 -628
  1401. package/src/generation/automata/CaveGeneratorCellularAutomata.js +79 -79
  1402. package/src/generation/automata/CellularAutomata.js +22 -22
  1403. package/src/generation/compute_writes_union.js +25 -25
  1404. package/src/generation/filtering/CellFilter.js +117 -117
  1405. package/src/generation/filtering/core/CellFilterBinaryOperation.js +37 -37
  1406. package/src/generation/filtering/core/CellFilterOperationTertiary.js +43 -43
  1407. package/src/generation/filtering/core/CellFilterUnaryOperation.js +29 -29
  1408. package/src/generation/filtering/numeric/process/computeCellFilterSobel.js +36 -36
  1409. package/src/generation/filtering/numeric/process/computeFilterSurfaceNormal.js +42 -42
  1410. package/src/generation/filtering/numeric/util/populateSampler2DFromCellFilter.js +18 -18
  1411. package/src/generation/filtering/numeric/util/write_cell_filter_to_sampler2d.js +47 -47
  1412. package/src/generation/filtering/util/cell_filter_weights_pick.js +54 -54
  1413. package/src/generation/grid/GridData.js +233 -233
  1414. package/src/generation/grid/coords/grid_to_texel.js +10 -10
  1415. package/src/generation/grid/coords/texel_to_grid.js +10 -10
  1416. package/src/generation/grid/generation/GridTaskApplyActionToCells.js +90 -90
  1417. package/src/generation/grid/generation/GridTaskDensityMarkerDistribution.js +388 -388
  1418. package/src/generation/grid/generation/GridTaskExecuteRuleTimes.js +127 -127
  1419. package/src/generation/grid/generation/GridTaskSequence.js +58 -58
  1420. package/src/generation/grid/generation/NoopGridTaskGenerator.js +12 -12
  1421. package/src/generation/grid/generation/discrete/GridTaskActionRuleSet.js +48 -48
  1422. package/src/generation/grid/generation/discrete/GridTaskCellularAutomata.js +146 -146
  1423. package/src/generation/grid/generation/discrete/GridTaskConnectRooms.js +540 -540
  1424. package/src/generation/grid/generation/discrete/layer/GridTaskBuildSourceDistanceMap.js +222 -222
  1425. package/src/generation/grid/generation/grid/GridTaskAddNodesFixed.js +44 -44
  1426. package/src/generation/grid/generation/road/GridTaskGenerateRoads.js +614 -614
  1427. package/src/generation/markers/GridCellActionPlaceMarker.js +244 -244
  1428. package/src/generation/markers/GridCellActionPlaceMarkerGroup.js +86 -86
  1429. package/src/generation/markers/actions/terrain/MarkerNodeActionPaintTerrain.js +266 -266
  1430. package/src/generation/markers/emitter/MarkerNodeEmitterGridCellAction.js +49 -49
  1431. package/src/generation/placement/GridCellActionTransformNearbyMarkers.js +120 -120
  1432. package/src/generation/placement/action/GridCellAction.js +45 -45
  1433. package/src/generation/placement/action/GridCellActionPlaceTags.js +140 -140
  1434. package/src/generation/placement/action/GridCellActionWriteFilterToLayer.js +89 -89
  1435. package/src/generation/placement/action/random/CellActionSelectRandom.js +69 -69
  1436. package/src/generation/placement/action/random/weighted/CellActionSelectWeightedRandom.js +91 -91
  1437. package/src/generation/placement/action/util/GridCellActionSequence.js +61 -61
  1438. package/src/generation/placement/action/util/GridCellDisplacedAction.js +61 -61
  1439. package/src/generation/rules/cell/CellMatcherRemovalPreservesConnectivity.js +183 -183
  1440. package/src/generation/theme/ThemeEngine.js +666 -666
  1441. package/src/shade/RENDERER_CONTRACT.md +568 -0
  1442. package/src/shade/descriptor/texture/format/gpu_texture_format_info.js +267 -267
  1443. package/src/shade/device/ShadeDeviceFailure.js +104 -104
  1444. package/src/shade/device/ShadeDeviceFailureReason.js +41 -41
  1445. package/src/shade/device/mock/IMPLEMENTATION_PLAN.md +255 -255
  1446. package/src/shade/device/mock/REVIEW_2026_08_22.md +527 -527
  1447. package/src/shade/device/mock/SoftwareGPUBindGroup.js +23 -23
  1448. package/src/shade/device/mock/SoftwareGPUBindGroupLayout.js +22 -22
  1449. package/src/shade/device/mock/SoftwareGPUError.js +63 -63
  1450. package/src/shade/device/mock/SoftwareGPUPipeline.js +75 -75
  1451. package/src/shade/device/mock/SoftwareGPUQuerySet.js +113 -113
  1452. package/src/shade/device/mock/SoftwareGPUSampler.js +30 -30
  1453. package/src/shade/device/mock/SoftwareGPUShaderModule.js +32 -32
  1454. package/src/shade/device/mock/SoftwareGPUTextureView.js +22 -22
  1455. package/src/shade/device/mock/SoftwareGPUUncapturedErrorEvent.js +23 -23
  1456. package/src/shade/device/mock/normalize_dynamic_offsets.js +26 -26
  1457. package/src/shade/device/mock/texture_copy.js +287 -287
  1458. package/src/shade/device/timing/GPUTimer.js +285 -285
  1459. package/src/shade/device/timing/GPUTimerArray.js +205 -205
  1460. package/src/shade/playground/add_random_lights.js +49 -49
  1461. package/src/shade/playground/add_random_meshes.js +83 -83
  1462. package/src/shade/playground/animation_prototype.js +508 -508
  1463. package/src/shade/playground/animation_prototype_skinning.js +647 -647
  1464. package/src/shade/playground/animation_prototype_stress.js +765 -765
  1465. package/src/shade/playground/enable_picker.js +59 -59
  1466. package/src/shade/playground/gltf_skinned_prototype.js +560 -560
  1467. package/src/shade/playground/gltf_skinned_stress_prototype.js +387 -387
  1468. package/src/shade/playground/gltf_viewer.js +282 -282
  1469. package/src/shade/playground/load_default_environment.js +19 -19
  1470. package/src/shade/playground/main.js +2727 -2727
  1471. package/src/shade/playground/make_animated_instances.js +117 -117
  1472. package/src/shade/playground/prototype_virtual_texture.js +274 -274
  1473. package/src/shade/playground/prototype_volumetrics_01.js +206 -206
  1474. package/src/shade/playground/sample.js +234 -234
  1475. package/src/shade/playground/sample_load_gltf.js +102 -102
  1476. package/src/shade/playground/spread_node3d_grid.js +19 -19
  1477. package/src/shade/renderer/Renderer.js +2436 -2436
  1478. package/src/shade/renderer/ShadeIndirectLightingMode.js +14 -14
  1479. package/src/shade/renderer/animation/ShadeAnimationChannel.js +414 -414
  1480. package/src/shade/renderer/animation/compute_skinned_mesh_clip_bounding_sphere.js +531 -531
  1481. package/src/shade/renderer/animation/pose/PosePlayback.js +55 -55
  1482. package/src/shade/renderer/animation/pose/pose_evaluate_local.js +179 -179
  1483. package/src/shade/renderer/animation/pose/pose_evaluate_world.js +76 -76
  1484. package/src/shade/renderer/animation/skinning/GPUMeshSkinningContext.js +1024 -1024
  1485. package/src/shade/renderer/animation/skinning/SKINNING_BINDING_STRUCT.js +79 -79
  1486. package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +56 -56
  1487. package/src/shade/renderer/binding/BindGroupManager.js +282 -282
  1488. package/src/shade/renderer/binding/adapt_cache_capacity.js +25 -25
  1489. package/src/shade/renderer/buffer/GPUBufferAllocator.js +116 -116
  1490. package/src/shade/renderer/buffer/buffer_pool_ops.js +123 -123
  1491. package/src/shade/renderer/buffer/table/GPU_DATABASE_PAGE_SIZE.js +9 -9
  1492. package/src/shade/renderer/buffer/table/GPU_DATABASE_UPLOAD_BUFFER_LIMIT.js +8 -8
  1493. package/src/shade/renderer/buffer/table/PageBufferPool.js +71 -71
  1494. package/src/shade/renderer/buffer/table/PageSlotAllocator.js +95 -95
  1495. package/src/shade/renderer/camera/Camera.js +281 -281
  1496. package/src/shade/renderer/camera/GPUCameraContext.js +233 -233
  1497. package/src/shade/renderer/camera/orbital/README.md +84 -84
  1498. package/src/shade/renderer/deferred/shader_composite_volumetrics.js +75 -75
  1499. package/src/shade/renderer/dynamic/DYNAMIC_MESH_VERTEX.js +25 -25
  1500. package/src/shade/renderer/dynamic/GPUDynamicMeshRenderer.js +342 -342
  1501. package/src/shade/renderer/dynamic/GPUDynamicMeshResidency.js +167 -167
  1502. package/src/shade/renderer/dynamic/shader_dynamic_mesh.js +170 -170
  1503. package/src/shade/renderer/extension/FrameContext.js +263 -263
  1504. package/src/shade/renderer/extension/FramePhase.js +104 -104
  1505. package/src/shade/renderer/extension/FrameRecord.js +126 -126
  1506. package/src/shade/renderer/extension/GBufferTextures.js +83 -83
  1507. package/src/shade/renderer/extension/PresentTarget.js +34 -34
  1508. package/src/shade/renderer/extension/RENDER_EXTENSION_DESIGN.md +918 -918
  1509. package/src/shade/renderer/extension/RenderExtension.js +69 -69
  1510. package/src/shade/renderer/extension/RenderExtensionRegistry.js +319 -319
  1511. package/src/shade/renderer/extension/SceneColor.js +40 -40
  1512. package/src/shade/renderer/extension/ViewTextures.js +78 -78
  1513. package/src/shade/renderer/fow/GPUFogOfWarRenderer.js +182 -182
  1514. package/src/shade/renderer/geometry/Geometry.js +608 -608
  1515. package/src/shade/renderer/geometry/geometry_build_from_meshlet_geometry.js +342 -342
  1516. package/src/shade/renderer/geometry/meshlet_geometry_transfer.js +41 -41
  1517. package/src/shade/renderer/geometry/primitives/make_box_geometry.js +153 -153
  1518. package/src/shade/renderer/geometry/primitives/make_cylinder_geometry.js +240 -240
  1519. package/src/shade/renderer/geometry/primitives/make_plane_geometry.js +145 -145
  1520. package/src/shade/renderer/geometry/primitives/make_polyhedron_geometry.js +327 -327
  1521. package/src/shade/renderer/geometry/primitives/make_torus_geometry.js +107 -107
  1522. package/src/shade/renderer/geometry/primitives/make_torus_knot_geometry.js +165 -165
  1523. package/src/shade/renderer/geometry/virtual/VGEO_FORMAT.md +607 -607
  1524. package/src/shade/renderer/geometry/virtual/build/VGeoBuildOptions.js +66 -66
  1525. package/src/shade/renderer/geometry/virtual/build/VGeoBuildResult.js +32 -32
  1526. package/src/shade/renderer/geometry/virtual/build/VGeoFaceGraph.js +103 -103
  1527. package/src/shade/renderer/geometry/virtual/build/VGeoGatheredFaces.js +25 -25
  1528. package/src/shade/renderer/geometry/virtual/build/VGeoLevel.js +33 -33
  1529. package/src/shade/renderer/geometry/virtual/build/VGeoLevelOptions.js +62 -62
  1530. package/src/shade/renderer/geometry/virtual/build/VGeoLevelSummary.js +49 -49
  1531. package/src/shade/renderer/geometry/virtual/build/VGeoSubmesh.js +20 -20
  1532. package/src/shade/renderer/geometry/virtual/build/VirtualGeometryMesh.js +214 -214
  1533. package/src/shade/renderer/geometry/virtual/build/gltf/GltfByteSource.js +122 -122
  1534. package/src/shade/renderer/geometry/virtual/build/gltf/GltfCollectReport.js +29 -29
  1535. package/src/shade/renderer/geometry/virtual/build/gltf/GltfDocument.js +210 -210
  1536. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometry.js +48 -48
  1537. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometryInstance.js +25 -25
  1538. package/src/shade/renderer/geometry/virtual/build/gltf/GltfGeometrySelection.js +28 -28
  1539. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_collect_geometries.js +296 -296
  1540. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_open_document.js +166 -166
  1541. package/src/shade/renderer/geometry/virtual/build/gltf/gltf_read_geometry.js +199 -199
  1542. package/src/shade/renderer/geometry/virtual/build/vgeo_assemble_pages.js +173 -173
  1543. package/src/shade/renderer/geometry/virtual/build/vgeo_bounding_sphere.js +28 -28
  1544. package/src/shade/renderer/geometry/virtual/build/vgeo_build.js +200 -200
  1545. package/src/shade/renderer/geometry/virtual/build/vgeo_build_levels.js +1786 -1786
  1546. package/src/shade/renderer/geometry/virtual/build/vgeo_build_page_nodes.js +157 -157
  1547. package/src/shade/renderer/geometry/virtual/build/vgeo_build_root_group.js +26 -26
  1548. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_normals.js +75 -75
  1549. package/src/shade/renderer/geometry/virtual/build/vgeo_compute_vertex_tangents.js +143 -143
  1550. package/src/shade/renderer/geometry/virtual/build/vgeo_containing_sphere.js +44 -44
  1551. package/src/shade/renderer/geometry/virtual/build/vgeo_extract_submesh.js +41 -41
  1552. package/src/shade/renderer/geometry/virtual/build/vgeo_gather_faces.js +71 -71
  1553. package/src/shade/renderer/geometry/virtual/build/vgeo_graph_components.js +123 -123
  1554. package/src/shade/renderer/geometry/virtual/build/vgeo_narrow_indices.js +26 -26
  1555. package/src/shade/renderer/geometry/virtual/build/vgeo_normalize_source.js +112 -112
  1556. package/src/shade/renderer/geometry/virtual/build/vgeo_partition_graph.js +56 -56
  1557. package/src/shade/renderer/geometry/virtual/build/vgeo_sphere_inflate_to_contain.js +70 -70
  1558. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set.js +187 -187
  1559. package/src/shade/renderer/geometry/virtual/build/vgeo_split_face_set_packed.js +207 -207
  1560. package/src/shade/renderer/geometry/virtual/format/VGEO_DEFECT.js +18 -18
  1561. package/src/shade/renderer/geometry/virtual/format/VGeoDefect.js +38 -38
  1562. package/src/shade/renderer/geometry/virtual/format/VGeoWriteOptions.js +64 -64
  1563. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTE.js +13 -13
  1564. package/src/shade/renderer/geometry/virtual/format/attribute/VGEO_ATTRIBUTES.js +50 -50
  1565. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_allocate.js +28 -28
  1566. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attribute_mask.js +23 -23
  1567. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_attributes_read.js +31 -31
  1568. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_copy_vertex.js +32 -32
  1569. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_declare.js +46 -46
  1570. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_like.js +19 -19
  1571. package/src/shade/renderer/geometry/virtual/format/attribute/vgeo_layers_test_fixtures.js +27 -27
  1572. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_asset_id_preimage.js +62 -62
  1573. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_asset_id.js +13 -13
  1574. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_directory_block_checksum.js +41 -41
  1575. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_frame_checksum.js +41 -41
  1576. package/src/shade/renderer/geometry/virtual/format/checksum/vgeo_compute_header_checksum.js +25 -25
  1577. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_OFFSET.js +20 -20
  1578. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_CLUSTER_RECORD_SIZE.js +5 -5
  1579. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CHILD_REF.js +5 -5
  1580. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_TRIANGLE_COUNT.js +5 -5
  1581. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_MAX_CLUSTER_VERTEX_COUNT.js +5 -5
  1582. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_CHILD.js +5 -5
  1583. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_NO_GROUP.js +5 -5
  1584. package/src/shade/renderer/geometry/virtual/format/cluster/VGEO_SAME_PAGE.js +6 -6
  1585. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryCluster.js +81 -81
  1586. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryGroup.js +37 -37
  1587. package/src/shade/renderer/geometry/virtual/format/dag/VirtualGeometryPageNode.js +55 -55
  1588. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_BLOCK_RECORDS.js +5 -5
  1589. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_FLAG_ROOT_PAGE.js +5 -5
  1590. package/src/shade/renderer/geometry/virtual/format/directory/VGEO_DIRECTORY_RECORD_SIZE.js +5 -5
  1591. package/src/shade/renderer/geometry/virtual/format/directory/vgeo_directory_block_count.js +12 -12
  1592. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_OFFSET.js +11 -11
  1593. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CHILD_TABLE_ENTRY_SIZE.js +5 -5
  1594. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_LZ4.js +11 -11
  1595. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_CODEC_RAW.js +5 -5
  1596. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_ALIGNMENT.js +5 -5
  1597. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_HEADER_SIZE.js +5 -5
  1598. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_FRAME_OFFSET.js +13 -13
  1599. package/src/shade/renderer/geometry/virtual/format/frame/VGEO_PAGE_MAGIC.js +5 -5
  1600. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameBlob.js +21 -21
  1601. package/src/shade/renderer/geometry/virtual/format/frame/VGeoFrameExtent.js +59 -59
  1602. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_codec_name.js +27 -27
  1603. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_decode_frame_blob.js +84 -84
  1604. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_encode_frame_blob.js +56 -56
  1605. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_frame_decode_bound.js +35 -35
  1606. package/src/shade/renderer/geometry/virtual/format/frame/vgeo_scan_frames.js +74 -74
  1607. package/src/shade/renderer/geometry/virtual/format/header/VGEO_FORMAT_VERSION.js +6 -6
  1608. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HASH_ALGO_XXH3.js +5 -5
  1609. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_OFFSET.js +31 -31
  1610. package/src/shade/renderer/geometry/virtual/format/header/VGEO_HEADER_SIZE.js +5 -5
  1611. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_OFFSET.js +20 -20
  1612. package/src/shade/renderer/geometry/virtual/format/header/VGEO_LEVEL_RECORD_SIZE.js +9 -9
  1613. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MAGIC.js +5 -5
  1614. package/src/shade/renderer/geometry/virtual/format/header/VGEO_MIN_READER_VERSION.js +5 -5
  1615. package/src/shade/renderer/geometry/virtual/format/header/VGEO_PAYLOAD_ENCODING_SHADE_MESHLET_V1.js +5 -5
  1616. package/src/shade/renderer/geometry/virtual/format/header/VGeoLevelRecord.js +41 -41
  1617. package/src/shade/renderer/geometry/virtual/format/page/VGEO_GROUP_RECORD_SIZE.js +5 -5
  1618. package/src/shade/renderer/geometry/virtual/format/page/VGEO_MAX_GROUP_CLUSTER_COUNT.js +6 -6
  1619. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_OFFSET.js +21 -21
  1620. package/src/shade/renderer/geometry/virtual/format/page/VGEO_METRIC_SIZE.js +5 -5
  1621. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FANOUT.js +9 -9
  1622. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_FLAG_LEAF.js +5 -5
  1623. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_OFFSET.js +10 -10
  1624. package/src/shade/renderer/geometry/virtual/format/page/VGEO_NODE_RECORD_SIZE.js +5 -5
  1625. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_HEADER_SIZE.js +5 -5
  1626. package/src/shade/renderer/geometry/virtual/format/page/VGEO_PAGE_OFFSET.js +16 -16
  1627. package/src/shade/renderer/geometry/virtual/format/payload/VGEO_ENCODING_FLAGS_MASK.js +6 -6
  1628. package/src/shade/renderer/geometry/virtual/format/payload/VGeoEncodedPayload.js +18 -18
  1629. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_encode_cluster_payload.js +200 -200
  1630. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_attribute_offset.js +39 -39
  1631. package/src/shade/renderer/geometry/virtual/format/payload/vgeo_payload_size.js +20 -20
  1632. package/src/shade/renderer/geometry/virtual/format/prototypeVGEOFormatViewer.js +2063 -2063
  1633. package/src/shade/renderer/geometry/virtual/format/vgeo_validate_container.js +1622 -1622
  1634. package/src/shade/renderer/geometry/virtual/format/vgeo_write_container.js +485 -485
  1635. package/src/shade/renderer/gpu_primitive/sort/ffx/graph_radix_sort.js +115 -115
  1636. package/src/shade/renderer/gpu_primitive/sort/one_sweep/verify_radix_sort_one_sweep.js +110 -110
  1637. package/src/shade/renderer/light/environment/octahedral_resample_kernel.js +50 -50
  1638. package/src/shade/renderer/light/environment/octahedral_uv_to_direction.js +30 -30
  1639. package/src/shade/renderer/light/environment/resample_cube_to_octahedral.js +157 -157
  1640. package/src/shade/renderer/light/make_sunlight.js +35 -35
  1641. package/src/shade/renderer/light/model/io/light_record_point.js +39 -39
  1642. package/src/shade/renderer/loader/gltf/GLTF_PRIMITIVE_MODE.js +23 -23
  1643. package/src/shade/renderer/loader/gltf/MESHOPT_COMPRESSION_PLAN.md +793 -793
  1644. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtension.js +54 -54
  1645. package/src/shade/renderer/loader/gltf/ext/GltfBufferViewExtensionSet.js +109 -109
  1646. package/src/shade/renderer/loader/gltf/ext/gltf_apply_buffer_view_extensions.js +69 -69
  1647. package/src/shade/renderer/loader/gltf/ext/gltf_buffer_view_extensions.js +22 -22
  1648. package/src/shade/renderer/loader/gltf/ext/gltf_geometry_extensions.js +39 -39
  1649. package/src/shade/renderer/loader/gltf/ext/meshopt/MeshoptBufferViewExtension.js +301 -301
  1650. package/src/shade/renderer/loader/gltf/gltf_component_type.js +223 -223
  1651. package/src/shade/renderer/loader/gltf/gltf_node_world_matrices.js +137 -137
  1652. package/src/shade/renderer/loader/gltf/gltf_parse_container.js +241 -241
  1653. package/src/shade/renderer/loader/gltf/gltf_read_accessor.js +294 -294
  1654. package/src/shade/renderer/loader/gltf/gltf_used_image_indices.js +49 -49
  1655. package/src/shade/renderer/loader/gltf/load_gltf.js +1253 -1253
  1656. package/src/shade/renderer/loader/gltf/tiny-gltf.js +646 -646
  1657. package/src/shade/renderer/loader/usd/UsdError.js +33 -33
  1658. package/src/shade/renderer/loader/usd/UsdExtensionRegistry.js +69 -69
  1659. package/src/shade/renderer/loader/usd/load_usd.js +226 -226
  1660. package/src/shade/renderer/loader/usd/parse_usda.js +931 -931
  1661. package/src/shade/renderer/loader/usd/unpack_usdz.js +106 -106
  1662. package/src/shade/renderer/loader/usd/usd_build_scene.js +658 -658
  1663. package/src/shade/renderer/loader/usd/usd_compose_transform.js +258 -258
  1664. package/src/shade/renderer/loader/usd/usd_decode_image.js +384 -384
  1665. package/src/shade/renderer/loader/usd/usd_lz4.js +182 -182
  1666. package/src/shade/renderer/loader/usd/usd_triangulate.js +159 -159
  1667. package/src/shade/renderer/material/ShadeMaterial.d.ts +2 -2
  1668. package/src/shade/renderer/material/ShadeMaterial.js +2 -2
  1669. package/src/shade/renderer/material/standard/fragment_gbuffer.js +334 -334
  1670. package/src/shade/renderer/material/standard/fragment_gbuffer_vt.js +169 -169
  1671. package/src/shade/renderer/material/standard/material_gbuffer_vertex.js +45 -45
  1672. package/src/shade/renderer/material/standard_material_to_struct.js +23 -23
  1673. package/src/shade/renderer/particles/prototypeParticleSystem.js +450 -450
  1674. package/src/shade/renderer/particles/runtime/spawn_request_encoding.js +52 -52
  1675. package/src/shade/renderer/pool/TransientResourcePool.js +392 -392
  1676. package/src/shade/renderer/postprocess/bloom/bloom_chain_resolution.js +29 -29
  1677. package/src/shade/renderer/postprocess/bloom/graph_bloom_downsample.js +109 -109
  1678. package/src/shade/renderer/postprocess/bloom/graph_postprocess_bloom.js +155 -155
  1679. package/src/shade/renderer/postprocess/cas/shader_ffx_rcas.js +114 -114
  1680. package/src/shade/renderer/postprocess/dof/DOF_AUTOFOCUS_PARAMS_STRUCT.js +25 -25
  1681. package/src/shade/renderer/postprocess/dof/DOF_FOCUS_STATE_STRUCT.js +23 -23
  1682. package/src/shade/renderer/postprocess/dof/DOF_PRESETS.js +27 -27
  1683. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_diameter.js +41 -41
  1684. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_band_radius.js +32 -32
  1685. package/src/shade/renderer/postprocess/dof/chunk_dof_coc_coefficient.js +26 -26
  1686. package/src/shade/renderer/postprocess/dof/compute_dof_autofocus_params.js +64 -64
  1687. package/src/shade/renderer/postprocess/dof/compute_dof_uniform.js +54 -54
  1688. package/src/shade/renderer/postprocess/dof/gather/DOF2_CONFIG.js +91 -91
  1689. package/src/shade/renderer/postprocess/dof/gather/DOF2_PRESETS.js +9 -9
  1690. package/src/shade/renderer/postprocess/dof/gather/DOF2_SETTINGS_STRUCT.js +66 -66
  1691. package/src/shade/renderer/postprocess/dof/gather/DOF2_TEXTURE_FORMAT.js +22 -22
  1692. package/src/shade/renderer/postprocess/dof/gather/DepthOfFieldU.js +292 -292
  1693. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_coc_to_near_far.js +25 -25
  1694. package/src/shade/renderer/postprocess/dof/gather/chunk_dof2_gather_weight.js +30 -30
  1695. package/src/shade/renderer/postprocess/dof/gather/compute_dof2_uniform.js +54 -54
  1696. package/src/shade/renderer/postprocess/dof/gather/graph_postprocess_dof2.js +194 -194
  1697. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_fill.js +68 -68
  1698. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_far.js +140 -140
  1699. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_gather_near.js +142 -142
  1700. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_recombine.js +99 -99
  1701. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_setup.js +102 -102
  1702. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_dilate.js +75 -75
  1703. package/src/shade/renderer/postprocess/dof/gather/shader_dof2_tile_max.js +73 -73
  1704. package/src/shade/renderer/postprocess/dof/raymarch/DOF_SETTINGS_STRUCT.js +49 -49
  1705. package/src/shade/renderer/postprocess/dof/raymarch/DepthOfField.js +298 -298
  1706. package/src/shade/renderer/postprocess/dof/raymarch/graph_postprocess_dof.js +244 -244
  1707. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_composite.js +91 -91
  1708. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_firefly_cleanup.js +61 -61
  1709. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_prefilter.js +90 -90
  1710. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_raymarch_compute.js +267 -267
  1711. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_slice_compute.js +271 -271
  1712. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_dilate.js +70 -70
  1713. package/src/shade/renderer/postprocess/dof/raymarch/shader_dof_tile_max.js +72 -72
  1714. package/src/shade/renderer/postprocess/dof/shader_dof_autofocus.js +99 -99
  1715. package/src/shade/renderer/postprocess/gtao/generate_magic_square_noise.js +51 -51
  1716. package/src/shade/renderer/postprocess/motion_blur/MotionBlur.js +56 -56
  1717. package/src/shade/renderer/postprocess/upscale-dither/NOTES.md +78 -78
  1718. package/src/shade/renderer/postprocess/upscale-dither/UpscaleDither.js +95 -95
  1719. package/src/shade/renderer/postprocess/upscale-dither/fragment_shader_upscale_dither.js +204 -204
  1720. package/src/shade/renderer/rasterize/bucket/construct_primitive_state.js +58 -58
  1721. package/src/shade/renderer/rasterize/fast/graph_rasterize_scene_fast.js +230 -230
  1722. package/src/shade/renderer/rasterize/native/graph_rasterize_meshes_alpha_tested.js +278 -278
  1723. package/src/shade/renderer/rasterize/native/oit/chunk_material_side_should_draw.js +26 -26
  1724. package/src/shade/renderer/rasterize/native/oit/shader_oit_resolve_moments_ibl.js +178 -178
  1725. package/src/shade/renderer/rasterize/standard/graph_rasterize_partial_opaque.js +235 -235
  1726. package/src/shade/renderer/rasterize/standard/graph_rasterize_remaining_opaque.js +208 -208
  1727. package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +206 -206
  1728. package/src/shade/renderer/scene/DynamicMesh.js +70 -70
  1729. package/src/shade/renderer/scene/DynamicMeshBatch.js +90 -90
  1730. package/src/shade/renderer/scene/GPUSceneContext.js +901 -901
  1731. package/src/shade/renderer/scene/SceneNodeBinding.js +56 -56
  1732. package/src/shade/renderer/scene/SkinnedMesh.js +55 -55
  1733. package/src/shade/renderer/scene/TransformAuthority.js +33 -33
  1734. package/src/shade/renderer/scene/optimization/deduplicate_geometries.js +38 -38
  1735. package/src/shade/renderer/scene/optimization/deduplicate_images.js +93 -93
  1736. package/src/shade/renderer/scene/optimization/geometry/internal/match_refit_validate.js +127 -127
  1737. package/src/shade/renderer/scene/optimization/geometry/internal/match_solve_transform.js +492 -492
  1738. package/src/shade/renderer/scene/optimization/optimize_texture_set.js +81 -81
  1739. package/src/shade/renderer/scene/optimization/source_to_sampler2d.js +21 -21
  1740. package/src/shade/renderer/scene/serialization/STANDARD_MATERIAL_SERIALIZED_BYTE_SIZE.js +12 -12
  1741. package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_transform_point.js +26 -26
  1742. package/src/shade/renderer/shader/chunk/noise/chunk_interleaved_gradient_noise.js +20 -20
  1743. package/src/shade/renderer/shader/chunk/spherical_harmonics/dering/chunk_sh2_dering_optimize_positive.js +61 -61
  1744. package/src/shade/renderer/shader/chunk/texture/chunk_texture_filter_fxaa_lite.js +225 -225
  1745. package/src/shade/renderer/shader/chunk/texture/dither/chunk_dither_color_8bit_gradient_noise.js +25 -25
  1746. package/src/shade/renderer/shadow/map/DirectionalLightShadowmap.js +384 -384
  1747. package/src/shade/renderer/shadow/map/PointLightShadowmap.js +105 -105
  1748. package/src/shade/renderer/shadow/map/SpotLightShadowmap.js +63 -63
  1749. package/src/shade/renderer/shadow/map/TetrahedronPointLightShadowmap.js +89 -89
  1750. package/src/shade/renderer/shadow/map/shadowmap_update_score.js +232 -232
  1751. package/src/shade/renderer/texture/GPUTextureAllocator.js +85 -85
  1752. package/src/shade/renderer/texture/texture_pool_ops.js +171 -171
  1753. package/src/shade/renderer/texture/virtual/PageTableQuadAllocator.js +253 -253
  1754. package/src/shade/renderer/texture/virtual/README.md +276 -276
  1755. package/src/shade/renderer/texture/virtual/VTPageTable.js +330 -330
  1756. package/src/shade/renderer/texture/virtual/VTPhysicalCache.js +349 -349
  1757. package/src/shade/renderer/texture/virtual/VT_CONSTANTS.js +154 -154
  1758. package/src/shade/renderer/texture/virtual/VT_STACK_INFO_STRUCT.js +20 -20
  1759. package/src/shade/renderer/texture/virtual/VirtualTextureManager.js +1276 -1276
  1760. package/src/shade/renderer/texture/virtual/VirtualTextureStack.js +106 -106
  1761. package/src/shade/renderer/texture/virtual/build_chunk_vt_lookup.js +150 -150
  1762. package/src/shade/renderer/texture/virtual/build_shader_vt_feedback.js +232 -232
  1763. package/src/shade/renderer/texture/virtual/chunk_vt_request_key.js +28 -28
  1764. package/src/shade/renderer/texture/virtual/source/VTSourceImage.js +207 -207
  1765. package/src/shade/renderer/texture/virtual/source/VTSourceProcedural.js +135 -135
  1766. package/src/shade/renderer/texture/virtual/source/VTSourceTiled.js +132 -132
  1767. package/src/shade/renderer/water/GPUWaterRenderer.js +167 -167
  1768. package/src/shade/renderer/water/shader_water.js +246 -246
  1769. package/src/shade/renderer/water/water_height_texture_from_sampler.js +56 -56
  1770. package/src/shade/vitest.setup.mjs +89 -89
  1771. package/src/shade/wgsl/validate_wgsl_source.js +316 -316
  1772. package/src/view/controller/controls/NumericIntervalControl.js +90 -90
  1773. package/src/view/controller/controls/Vector2Control.js +83 -83
  1774. package/src/view/controller/controls/Vector3Control.js +90 -90
  1775. package/src/view/elements/Group.js +56 -56
  1776. package/src/view/elements/drawCheckers.js +33 -33
  1777. package/src/view/elements/windrose/WindRoseDiagram.js +369 -369
  1778. package/src/view/graphics/WebGLContextFailureView.js +76 -76
  1779. package/src/view/layout/DockPanel.js +51 -51
  1780. package/src/view/layout/SplitView.js +365 -365
  1781. package/src/view/layout/show_popup_menu.js +81 -81
  1782. package/src/view/minimap/MinimapCanvasView.js +411 -411
  1783. package/src/view/minimap/MinimapImageCache.js +88 -88
  1784. package/src/view/minimap/MinimapMarkerCollection.d.ts +0 -1
  1785. package/src/view/minimap/MinimapMarkerCollection.d.ts.map +1 -1
  1786. package/src/view/minimap/MinimapMarkerCollection.js +180 -180
  1787. package/src/view/minimap/dom/MinimapCameraView.js +206 -206
  1788. package/src/view/minimap/minimap_focus_area.js +97 -97
  1789. package/editor/process/symbolic/SocketsSymbolicDisplay.js +0 -192
  1790. package/src/core/geom/3d/topology/struct/binary/io/moved_tests.txt +0 -37
  1791. package/src/engine/ecs/attachment/Attachment.d.ts +0 -40
  1792. package/src/engine/ecs/attachment/Attachment.d.ts.map +0 -1
  1793. package/src/engine/ecs/attachment/Attachment.js +0 -57
  1794. package/src/engine/ecs/attachment/AttachmentBinding.d.ts +0 -28
  1795. package/src/engine/ecs/attachment/AttachmentBinding.d.ts.map +0 -1
  1796. package/src/engine/ecs/attachment/AttachmentBinding.js +0 -41
  1797. package/src/engine/ecs/attachment/AttachmentSystem.d.ts +0 -61
  1798. package/src/engine/ecs/attachment/AttachmentSystem.d.ts.map +0 -1
  1799. package/src/engine/ecs/attachment/AttachmentSystem.js +0 -307
  1800. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts +0 -27
  1801. package/src/engine/ecs/attachment/BoneAttachmentBinding.d.ts.map +0 -1
  1802. package/src/engine/ecs/attachment/BoneAttachmentBinding.js +0 -69
  1803. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts +0 -4
  1804. package/src/engine/ecs/attachment/TransformAttachmentBinding.d.ts.map +0 -1
  1805. package/src/engine/ecs/attachment/TransformAttachmentBinding.js +0 -51
  1806. package/src/engine/ecs/sockets/AttachmentSocket.d.ts +0 -32
  1807. package/src/engine/ecs/sockets/AttachmentSocket.d.ts.map +0 -1
  1808. package/src/engine/ecs/sockets/AttachmentSocket.js +0 -60
  1809. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts +0 -6
  1810. package/src/engine/ecs/sockets/AttachmentSocketType.d.ts.map +0 -1
  1811. package/src/engine/ecs/sockets/AttachmentSocketType.js +0 -8
  1812. package/src/engine/ecs/sockets/AttachmentSockets.d.ts +0 -57
  1813. package/src/engine/ecs/sockets/AttachmentSockets.d.ts.map +0 -1
  1814. package/src/engine/ecs/sockets/AttachmentSockets.js +0 -130
  1815. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts +0 -29
  1816. package/src/engine/ecs/sockets/BoneAttachmentSocket.d.ts.map +0 -1
  1817. package/src/engine/ecs/sockets/BoneAttachmentSocket.js +0 -56
  1818. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts +0 -6
  1819. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.d.ts.map +0 -1
  1820. package/src/engine/ecs/sockets/serialization/AttachmentSocketsAssetLoader.js +0 -19
  1821. package/src/shade/renderer/animation/_dump_resolve.d.mts +0 -2
  1822. package/src/shade/renderer/animation/_dump_resolve.d.mts.map +0 -1
  1823. package/src/shade/renderer/animation/_dump_resolve.mjs +0 -5
@@ -1,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
+ }