@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,1300 +1,1300 @@
1
- # Physics Engine Review: meep vs. cannon-es
2
-
3
- A deep technical comparison of the meep in-house rigid-body engine
4
- (`H:/git/moh/app/src/mir-engine/meep/src/engine/physics/`) against
5
- **cannon-es** (https://github.com/pmndrs/cannon-es), the
6
- community-maintained fork of Stefan Hedman's **cannon.js**
7
- (https://github.com/schteppe/cannon.js).
8
-
9
- Cannon is uniquely interesting as a reference because it shares every
10
- hard constraint we work under: **pure JavaScript, single-threaded, no
11
- SIMD, no SharedArrayBuffer, GC-sensitive hot paths**. Anywhere Cannon
12
- discovered a JS-specific workaround, it translates directly to us.
13
- Anywhere we beat Cannon, it's a fair fight on engineering merit — not
14
- "they had SIMD intrinsics and we didn't".
15
-
16
- This review respects PLAN.md's documented out-of-scope decisions (pure
17
- JS, no SAB, no multi-threaded solver, action-log netcode). Cannon
18
- citations use `src/<dir>/<File>.ts` paths; our citations use
19
- `path:line` relative to the package root.
20
-
21
- ---
22
-
23
- ## 1. Overall Architecture
24
-
25
- ### Pipeline shape
26
-
27
- | Stage | meep `PhysicsSystem.fixedUpdate` (`ecs/PhysicsSystem.js:1075`) | cannon-es `World.internalStep` (`src/world/World.ts`) |
28
- |---|---|---|
29
- | Apply gravity | Folded into Stage 1 `integrate_velocity` (`integration/integrate_velocity.js`) | Standalone "gravity" loop over all bodies |
30
- | Forces from user constraints | n/a (no joints in v1) | `for (const c of constraints) c.update()` adds equations |
31
- | Subsystem callbacks | n/a | `subsystems` user-extension hook |
32
- | Velocity integration | Stage 1, awake-only | Last (after solver); leap-frog |
33
- | Broadphase refit | Stage 2, awake-only, fat-AABB `compute_fat_world_aabb.js` | Per-body `updateAABB()` inside `broadphase.collisionPairs` |
34
- | Pair generation | Stage 3, `generate_pairs.js` — leaf query into both BVHs, dedup via touched bit | `broadphase.collisionPairs(this, p1, p2)` |
35
- | Wake propagation | Stage 4, explicit `__wake_pairs` over the pair list | Implicit — narrowphase sets `wakeUpAfterNarrowphase = true` when relative speed exceeds threshold |
36
- | Narrowphase | Stage 5, `narrowphase_step.js` cascade `if (a_kind && b_kind) ...` | `Narrowphase.getContacts(p1, p2, ...)` giant switch dispatch |
37
- | Island build | Stage 6, `IslandBuilder` union-find CSR | Lazy in `SplitSolver` (BFS over equation graph), or no partitioning if using bare `GSSolver` |
38
- | Solver | Stage 7, `solve_contacts.js` per island, 10 iters | `solver.solve(dt, this)` over the flat equation list |
39
- | Position integration | Stage 8 | Same as velocity (leap-frog `pos += velo * dt`) |
40
- | Sleep | Stage 9, per-island atomic | Stage 14, per-body `body.sleepTick(time)` |
41
- | Damping | Folded into integrators | Stage 11, explicit multiplicative decay |
42
- | Event diff | Stage 10, `diff_manifolds` → Begin/Stay/End | `collisionMatrix` flip + `collide`/`endContact` events from narrowphase |
43
- | End-of-step | Stage 11, `manifolds.advance_frame()` (roll touched, evict grace-expired) | `clearForces()` — accumulators wiped |
44
-
45
- The single most consequential structural divergence is **what each step
46
- iterates over**. Cannon's documented `internalStep` walks **every body**
47
- on every pass (`for (let i = 0; i !== N; i++) { const bi = bodies[i] }`
48
- appears repeatedly in `World.ts`), with sleep state being a property
49
- checked inline rather than a filter. We instead iterate
50
- `storage.awake_at(i)` for `i in [0, awake_count)` — the awake set is a
51
- dense `Uint32Array` (`body/BodyStorage.js:101-110`) and grows / shrinks
52
- via swap-with-last (`body/BodyStorage.js:330-337`). At million-body
53
- scale where 99% of bodies are mostly sleeping (the design target in
54
- PLAN.md), we pay O(awake) per stage while Cannon pays O(total). This
55
- is **the** scalability story between the two engines.
56
-
57
- ### Data layout — body pool
58
-
59
- | Aspect | meep `BodyStorage` | cannon-es `Body` |
60
- |---|---|---|
61
- | ID encoding | 24-bit index ǁ 8-bit generation (`body/BodyStorage.js:11-19`) | Bare object reference (`Body` instance) + monotonically increasing `id` |
62
- | Slot reuse policy | Min-heap of free indices for deterministic reuse (`body/BodyStorage.js:154-176`, `383-426`) | No reuse — destroyed bodies are GC'd |
63
- | Active list | Dense `Uint32Array __awake_list` + reverse map `__awake_pos` (`body/BodyStorage.js:101-103`) | None — bodies live in `World.bodies: Body[]` regardless of sleep state |
64
- | Body state storage | SoA — `__entities`, `__generations`, `__kinds`, `__flags` as parallel typed arrays. Hot mutable state (velocity / accumulators) on `RigidBody` instances | AoS — every field on the `Body` class instance (`position: Vec3`, `velocity: Vec3`, `force: Vec3`, `torque: Vec3`, `sleepState: number`, etc.) |
65
- | Iteration | `for (let i = 0; i < awake_count; i++) { const idx = awake_at(i); const rb = __bodies[idx]; ... }` | `for (let i = 0; i !== N; i++) { const bi = bodies[i]; if (bi.sleepState === Body.SLEEPING) continue; ... }` |
66
- | Generation safety | Stale packed id detected via `is_valid()` mask + generation check (`body/BodyStorage.js:210-219`) | Stale references possible (caller is responsible) |
67
-
68
- **Where we win:** SoA identity tables + awake-list filtering. Hot
69
- streaming reads of cold flags / kinds / generations don't pull
70
- unrelated body fields into cache, and disconnected sleeping bodies
71
- contribute literally zero work per step. Cannon's per-body
72
- `sleepState` check is cheap-but-not-free, and on a scene of 1M static
73
- chairs in a town the difference is dramatic.
74
-
75
- **Where Cannon wins (or rather, "is more idiomatic"):** Cannon's
76
- `Body` is a single class with every field on it. V8 monomorphises
77
- this beautifully — every `body.velocity` access is the same hidden
78
- class lookup. There's no parallel-array index arithmetic, no
79
- `body_id_index(packed)` mask-and-shift. Debugging is plain
80
- object inspection. Our split (RigidBody for the JS-side hot state,
81
- BodyStorage for identity SoA, Collider list as a separate per-body
82
- array, transforms on a Transform component) costs a few extra
83
- indirections per body in the solver inner loop — but those are array
84
- indexed loads, not pointer-chases, so still cache-friendly.
85
-
86
- The crucial nuance: **even Cannon's `Body` AoS is "AoS per body but
87
- cache-unfriendly per pass"**. When the integrator wants only
88
- `position`, `velocity`, and `force`, it still pulls all 25-ish other
89
- fields of `Body` into cache. Pure SoA (one `Float64Array` per
90
- attribute) would be better, but we don't go fully SoA on the hot
91
- fields either — `RigidBody.linearVelocity` is a `Vector3` extending
92
- `Float64Array`, accessed as `lv[0]/[1]/[2]` (`solver/solve_contacts.js:427`).
93
- Our SoA win is therefore narrower than the layout suggests; the
94
- honest comparison is "we got the identity / scheduling pieces into
95
- SoA, the per-body kinematic state is still AoS like Cannon's, just
96
- with typed-array storage". The Jolt/Bullet style of fully SoA kinematic
97
- state remains a future optimisation.
98
-
99
- ### Broadphase
100
-
101
- | Engine | Strategies | Default |
102
- |---|---|---|
103
- | meep | One: BVH × 2 (`staticBvh` + `dynamicBvh`), with fat-AABB refit (`broadphase/compute_fat_world_aabb.js`) | The only choice |
104
- | cannon-es | Three: `NaiveBroadphase` (O(N²)), `GridBroadphase` (uniform grid, spheres+planes only), `SAPBroadphase` (single-axis sweep & prune) | `NaiveBroadphase` |
105
-
106
- `NaiveBroadphase.collisionPairs` is literally:
107
- ```js
108
- for (let i = 0; i !== n; i++) {
109
- for (let j = 0; j !== i; j++) {
110
- if (!this.needBroadphaseCollision(bi, bj)) continue;
111
- this.intersectionTest(bi, bj, pairs1, pairs2);
112
- }
113
- }
114
- ```
115
- The class docstring even concedes the algorithm's complexity is "N²
116
- (which is bad)". Cannon's expectation is that users will swap in
117
- `SAPBroadphase` for scenes with more than a few hundred bodies. SAP
118
- uses insertion-sort on one axis (chosen via `autoDetectAxis()` —
119
- variance maximisation), which handles small frame-to-frame motion in
120
- ~O(N) but degrades to O(N²) on sudden mass motion. Cannon makes no
121
- attempt at a tree.
122
-
123
- **Our BVH split with awake-list-driven pair generation is
124
- unambiguously better at our target scale.** At 1M bodies the
125
- NaiveBroadphase is 10¹² pair checks per frame — completely
126
- unusable. Our `generate_pairs.js:50-99` does one BVH query per
127
- awake-leaf into each of two BVHs and dedups via the manifold
128
- touched flag, scaling O(awake · log(total)) at worst. This is a
129
- *million-fold* difference at the design-target scale.
130
-
131
- ### Island structure
132
-
133
- Cannon ships **`SplitSolver`** as an opt-in wrapper around `GSSolver`.
134
- On each `solve()` call it builds a graph (every body is a node, every
135
- equation an edge), runs BFS to find connected components, then calls
136
- the inner solver per island. Each rebuild allocates fresh `nodes`,
137
- `children`, and intermediate arrays — there's an internal pool but
138
- graph-shape changes between frames defeat it. Cannon's island
139
- construction is also coupled to the solver, not to the contact
140
- generator, so it's purely a solver-side optimisation; sleep and
141
- events use unrelated machinery (per-body sleep tick, per-pair
142
- narrowphase-driven events).
143
-
144
- We pull islands up to a first-class pipeline stage with `IslandBuilder`
145
- (`island/IslandBuilder.js`) producing CSR-shaped output that the
146
- solver and sleep test *both* consume. Bodies are sorted ascending
147
- within an island and islands ascending by root index, giving us
148
- **determinism by construction** without sorting at the solver
149
- boundary. The PLAN.md atomic-sleep + atomic-wake story (sleep groups
150
- threaded via `sleep_group_next/prev` so waking any member walks the
151
- chain) is structurally impossible in Cannon's per-body model — see §3.
152
-
153
- ### Solver
154
-
155
- Cannon's `GSSolver` is a beautifully general sequential-impulse
156
- solver over a flat equation list (contacts, friction, joints all the
157
- same shape: an `Equation` with `computeB`, `computeC`, lambda
158
- bounds). It implements the **SPOOK formulation** (Lacoursière /
159
- Erleben) of constraint regularisation: `a = 4/(h*(1+4*d))`, `b =
160
- 4d/(1+4d)`, `eps = 4/(h*h*k*(1+4*d))` parameterised by stiffness `k`
161
- and relaxation `d` (`src/equations/Equation.ts`). Per-iteration:
162
-
163
- ```js
164
- deltalambda = invC * (B - GWlambda - c.eps * lambdaj);
165
- ```
166
- The `c.eps * lambdaj` term is what makes SPOOK different from naive
167
- Baumgarte — it's **continuous regularisation**, applied at every
168
- iteration to soften the constraint, rather than a one-shot velocity
169
- bias. Defaults: `stiffness = 1e7, relaxation = 4, iterations = 10`.
170
-
171
- Our `solve_contacts.js` is a more specialised contacts-only sequential
172
- impulse solver: warm-start replay (`solver/solve_contacts.js:466-477`),
173
- Baumgarte position correction folded into the velocity bias
174
- (`solver/solve_contacts.js:442-450`), Coulomb friction with disk-clamp
175
- (`solver/solve_contacts.js:566-571`), per-island iteration
176
- (`solver/solve_contacts.js:308-313`). 10 iterations default
177
- (matches Cannon). The split-impulse / split-position-pass story is
178
- discussed in §3.
179
-
180
- ### Sleep system
181
-
182
- Cannon's per-body sleep is the chatter-prone classic algorithm.
183
- `Body.sleepTick(time)` (verbatim from `src/objects/Body.ts`):
184
- ```js
185
- if (this.allowSleep) {
186
- const sleepState = this.sleepState;
187
- const speedSquared = velocity² + angularVelocity²;
188
- const speedLimitSquared = this.sleepSpeedLimit ** 2;
189
- if (sleepState === Body.AWAKE && speedSquared < speedLimitSquared) {
190
- this.sleepState = Body.SLEEPY;
191
- this.timeLastSleepy = time;
192
- } else if (sleepState === Body.SLEEPY && speedSquared > speedLimitSquared) {
193
- this.wakeUp();
194
- } else if (sleepState === Body.SLEEPY && time - this.timeLastSleepy > this.sleepTimeLimit) {
195
- this.sleep();
196
- }
197
- }
198
- ```
199
- This is the failure mode that **specifically motivates** our atomic
200
- sleep: in a stack of 100 blocks, frame N the bottom block falls
201
- asleep, frame N+1 a top block's micro-jitter wakes a middle block via
202
- broadphase pair propagation, frame N+2 the wake cascades down,
203
- frame N+3 the bottom re-wakes. The whole stack chatters indefinitely.
204
- PLAN.md "Sleep + events" calls this out explicitly:
205
-
206
- > "Per-island atomic sleep: an island sleeps when `max(|v|² + |ω|²)`
207
- > across all members stays below the threshold long enough; the whole
208
- > island sleeps in the same frame. Replaces the per-body chatter on
209
- > weakly-connected piles."
210
-
211
- Our `__sleep_test` (`ecs/PhysicsSystem.js:942-1006`) walks each
212
- island, takes `max(v² + ω²)` over all members, increments every
213
- member's timer if-and-only-if the **island as a whole** is below
214
- threshold, and atomically sleeps all members the same frame
215
- (`__atomic_sleep_island_range` at `:754-786`). The dual atomic-wake
216
- walks the `sleep_group_next` circular DLL (`:705-737`).
217
-
218
- Cannon has no equivalent. The `wakeUpAfterNarrowphase` flag is a
219
- one-frame deferral, not an island-aware operation.
220
-
221
- ### Threading
222
-
223
- Both single-threaded by deliberate design.
224
-
225
- ---
226
-
227
- ## 2. Specific Algorithms and Tradeoffs
228
-
229
- ### Body storage — class instances vs SoA + stable IDs
230
-
231
- Cannon's `Body` declares ~30 mutable public fields including
232
- `position`, `velocity`, `quaternion`, `angularVelocity`, `force`,
233
- `torque`, `mass`, `linearDamping`, `angularDamping`, `linearFactor`,
234
- `angularFactor`, `sleepState`, `allowSleep`, `collisionFilterGroup`,
235
- `collisionFilterMask`, `collisionResponse`, `isTrigger`, `shapes`,
236
- `shapeOffsets`, `shapeOrientations`, `aabb`, `boundingRadius`,
237
- `inertia`, `invInertia`, `invInertiaWorld`. They're set in the
238
- constructor with `||` default coalescing — typical Cannon idiom is
239
- `this.mass = typeof options.mass === 'number' ? options.mass : 0`.
240
- V8 monomorphises `Body` instances aggressively because the
241
- initialisation order is fixed and every field gets a typed default.
242
-
243
- Our split:
244
- - `RigidBody` (`ecs/RigidBody.js`) — JS-side hot state (velocity,
245
- accumulators, damping, mass, gravity scale, sleep state). Class
246
- fields declared at class-body scope (`= new Vector3(0,0,0)` etc.),
247
- same V8 hidden-class win.
248
- - `BodyStorage` — identity SoA (`__entities`, `__generations`,
249
- `__kinds`, `__flags`, `__alive` as parallel typed arrays) and
250
- scheduling (`__awake_list`).
251
- - `Collider` (component, attached separately, possibly compound).
252
- - `Transform` (independent, shared with rendering / IK / etc.).
253
-
254
- **GC pressure.** Cannon allocates one `Body` per body, plus the
255
- `Vec3` / `Quaternion` instance for each field. A scene of 100k bodies
256
- is ~100k bodies × ~10 Vec3/Quat ≈ 1M small objects on the heap, all
257
- of which the GC has to walk on every major collection. We allocate
258
- ~100k bodies × 4 Vector3s (linear/angular vel, accumulated
259
- force/torque, inertia) ≈ 400k small objects — better, but still on
260
- the heap. Where we materially win is the **per-step work**: our SoA
261
- identity tables are TypedArray slabs that don't appear in GC
262
- traversal at all, and we don't allocate per-step.
263
-
264
- **Iteration speed.** For the solver inner loop the comparison is a
265
- wash: both engines access `rb.linearVelocity[0..2]` (us, since
266
- Vector3 extends Float64Array) or `body.velocity.x/y/z` (Cannon). V8
267
- should compile both to the same machine code. The difference appears
268
- at the broadphase / wake phase, where Cannon's per-body iteration
269
- pulls the whole `Body` into cache for a `sleepState` check while ours
270
- reads a single byte from `__alive` / `__awake_pos` and jumps over the
271
- slot.
272
-
273
- **Observability.** Cannon wins here — `console.log(world.bodies[42])`
274
- shows you everything. Our `system.__bodies[idx]` shows RigidBody but
275
- not its colliders, transform, or storage entry; you have to know to
276
- look in three places. This is the cost of decomposition; it's a
277
- trade we deliberately made for ECS clarity and we shouldn't undo it.
278
- But adding a `dump(body_idx)` helper that aggregates the four
279
- sources would be cheap and would close the gap.
280
-
281
- ### Broadphase
282
-
283
- Already covered above; the headline is that **Cannon ships three
284
- broadphases and defaults to the worst one**. The `NaiveBroadphase`
285
- default exists for tutorial-grade simplicity ("first 50 bodies just
286
- work, then read the docs"). For a real physics engine `SAPBroadphase`
287
- is the standard choice and it's still single-axis SAP, which is
288
- state-of-the-art-1990s.
289
-
290
- A subtle point worth flagging: cannon-es's `SAPBroadphase` uses
291
- event listeners on the World (`world.addEventListener('addBody',
292
- ...)`) to keep its `axisList` in sync. This is allocation-free per
293
- step but has a hidden cost — the `addBody` / `removeBody` events fire
294
- synchronously on dataset mutations, and adding / removing many bodies
295
- mid-step is **explicitly fragile in Cannon**. We sidestep this
296
- entirely because our broadphase reads `storage.awake_at(i)` directly
297
- and the BVH allocates/frees its own nodes on
298
- `attach_collider`/`detach_collider`. No event-listener indirection.
299
-
300
- ### Narrowphase pairs — SAT vs GJK/EPA + closed-form
301
-
302
- This is the **deepest structural divergence**. Cannon has
303
- **no GJK and no EPA**. Its entire narrowphase is built on:
304
-
305
- 1. Closed-form per-pair handlers (`sphereSphere`, `spherePlane`,
306
- `sphereBox`, `boxBox`, etc. — a large method-per-pair switch on
307
- `Narrowphase.getContacts`)
308
- 2. **SAT** for arbitrary `ConvexPolyhedron` shapes (face normals + edge
309
- crosses), followed by
310
- 3. **Sutherland-Hodgman clipping** of the incident face against the
311
- reference face's bounding planes to extract a multi-point manifold
312
-
313
- Our cascade in `narrowphase/narrowphase_step.js:296-782`:
314
-
315
- | Pair | Path | Cannon equivalent |
316
- |---|---|---|
317
- | sphere-sphere | `sphere_sphere_contact` closed-form (`:309-325`) | `Narrowphase.sphereSphere` |
318
- | sphere-box | `sphere_box_contact` closed-form, handles centre-inside-box (`:328-359`) | `Narrowphase.sphereBox` |
319
- | capsule-X | `capsule_*` closed-form family (`:397-483`) | Cannon has no capsule shape |
320
- | box-box | SAT + Sutherland-Hodgman in `box_box_manifold.js` (`:362-394`) | `ConvexPolyhedron` via `Narrowphase.convexConvex` — same algorithm, more general implementation |
321
- | convex × concave (heightmap/mesh) | Per-triangle GJK+EPA via decomposition dispatcher (`:498-713`) | `convexTrimesh` / `convexHeightfield` — Trimesh BVH-queries triangles, each treated as a `ConvexPolyhedron` for SAT |
322
- | anything else convex | GJK + EPA fallback (`:715-781`) | **No fallback exists** — must be `ConvexPolyhedron` with explicit faces |
323
-
324
- **The structural tradeoff:** SAT works only on shapes with explicit
325
- face / edge lists. Cannon can't represent a smooth shape (sphere is
326
- its own special case; capsule, cylinder, cone, ellipsoid, generic
327
- convex-hull-from-point-cloud — all impossible without an explicit
328
- polyhedral mesh). We can, via GJK on the support function. But our
329
- EPA degenerates on smooth shapes (PLAN.md "Limitations / Known
330
- caveats") so the *practical* shape coverage of the two engines is
331
- closer than the algorithm choice suggests.
332
-
333
- **Cannon's `ConvexPolyhedron.clipFaceAgainstHull`** is its quality
334
- crown jewel for convex-convex contacts. The pipeline:
335
-
336
- 1. `findSeparatingAxis` iterates face normals of A, then face normals
337
- of B, then 9 (or `uniqueAxesA × uniqueAxesB`) edge-cross axes.
338
- Tracks the minimum penetration depth across all axes. Returns the
339
- normal of the deepest axis or `false` (no overlap).
340
- 2. Choose the witness face on B whose normal is most parallel to the
341
- separating normal — this is the *incident face*.
342
- 3. Clip the incident face polygon against every neighbouring face of
343
- A's witness face, treated as a half-space. After all clips, the
344
- surviving vertices that lie *behind* the witness face become
345
- contact points. Snippet (paraphrased from the source):
346
-
347
- ```js
348
- for (let i = 0; i < numVerticesA; i++) {
349
- localPlaneNormal.copy(this.faceNormals[otherFace]);
350
- this.clipFaceAgainstPlane(pVtxIn, pVtxOut, planeNormalWS, planeEqWS);
351
- // swap buffers
352
- }
353
- // finally:
354
- for (let i = 0; i < pVtxIn.length; i++) {
355
- let depth = planeNormalWS.dot(pVtxIn[i]) + planeEqWS;
356
- if (depth <= maxDist && depth <= 1e-6) {
357
- result.push({ point, normal, depth });
358
- }
359
- }
360
- ```
361
-
362
- Our `box_box_manifold.js` does exactly the same thing but specialised
363
- to two boxes (3 face normals × 2 + 9 edge crosses = 15 axes, hard-coded).
364
- The result is 0..4 contact points per box-box pair, same as Cannon.
365
- **The major gap:** we have no general convex-hull shape, only `BoxShape3D`.
366
- If we add `ConvexHullShape3D` (on PLAN.md backlog), we'll want
367
- basically Cannon's `ConvexPolyhedron.clipFaceAgainstHull` ported. The
368
- algorithm is already in our codebase as the inner clipping pass of
369
- `box_box_manifold.js`; the missing piece is the SAT outer loop
370
- that iterates an arbitrary face/edge list rather than the hard-coded 15.
371
-
372
- ### GJK / EPA — what each engine can actually handle
373
-
374
- | Shape pair | meep | cannon-es |
375
- |---|---|---|
376
- | sphere–sphere | closed-form | closed-form |
377
- | sphere–box | closed-form (interior-handling) | closed-form |
378
- | sphere–plane | closed-form (via box flat path) | closed-form |
379
- | sphere–convex polyhedron | GJK + EPA (works) | `sphereConvex` via vertex-iteration; works for explicit hulls |
380
- | sphere–trimesh | per-triangle GJK + EPA via decomp | `sphereTrimesh` via BVH triangle query |
381
- | capsule–anything | closed-form for cap-sphere / cap-cap / cap-box | n/a (no capsule shape) |
382
- | convex–convex | GJK + EPA fallback (may degenerate on smooth) | SAT + clip via `ConvexPolyhedron` |
383
- | convex–trimesh | per-triangle GJK + EPA (PLAN.md "drop and settle" tests skipped) | per-triangle SAT + clip (more reliable for polyhedral cases) |
384
- | concave–concave | refused | not supported |
385
- | anything smooth | GJK works for overlap test; EPA degenerates on depth recovery | impossible — needs polyhedral approximation |
386
-
387
- The structural picture is: **Cannon trades expressivity for
388
- robustness on its supported shape set**. We trade robustness on
389
- smooth shapes for general support-function-based collision. The cost
390
- of our choice is the EPA-on-smooth-shapes failure mode, which we
391
- mitigate by routing all common pairs through closed-form handlers.
392
- PLAN.md "Limitations / Known caveats" calls this out:
393
-
394
- > "EPA on smooth shapes: degenerates (no flat face to converge on).
395
- > Mitigated by closed-form paths for sphere/cube/capsule pairs; exotic
396
- > convex shapes vs spheres can still fail."
397
-
398
- ### MPR (Minkowski Portal Refinement)
399
-
400
- Cannon has nothing comparable. We have `gjk/mpr.js` (XenoCollide,
401
- Snethen GDC 2009) but it's not yet wired into `narrowphase_step` —
402
- PLAN.md "Alternative narrowphase: MPR" calls it a candidate for
403
- falling back when EPA stalls. **Wiring it as the fallback for
404
- EPA non-convergence is a clear next step** — concrete payoff: torus-knot
405
- test (currently skipped), ball-on-curved-surface contact stability.
406
-
407
- ### Solver — SPOOK vs Baumgarte
408
-
409
- Cannon's solver is built on the **SPOOK formulation** described in
410
- Lacoursière's PhD thesis and Erleben's papers. The key equations
411
- (verbatim from `src/equations/Equation.ts.setSpookParams`):
412
-
413
- ```js
414
- a = 4.0 / (h * (1 + 4 * d)); // bias gain (analogous to Baumgarte β/h)
415
- b = (4.0 * d) / (1 + 4 * d); // velocity-error gain
416
- eps = 4.0 / (h * h * k * (1 + 4 * d)); // regularisation
417
- ```
418
-
419
- And the per-iteration update (`GSSolver.solve`):
420
- ```js
421
- deltalambda = invC * (B - GWlambda - c.eps * lambdaj);
422
- // then clamped to [minForce*h, maxForce*h]
423
- ```
424
-
425
- The interesting term is **`c.eps * lambdaj`** — the regularisation
426
- softens the constraint at every iteration in a continuous way, not as
427
- a one-shot velocity bias. This is fundamentally different from
428
- Baumgarte: instead of "if penetration > slop, add a corrective
429
- velocity bias", SPOOK adds a per-iteration drag on the lambda that
430
- makes the constraint behave like a stiff spring with controlled
431
- damping. `eps` scales as `1 / (h² k)` — at our 60 Hz timestep
432
- (`h ≈ 0.0167`) with `k = 1e7`, `eps ≈ 0.024` for `d = 4`. Cannon's
433
- default produces a stable, soft constraint.
434
-
435
- Our `solver/solve_contacts.js:442-450`:
436
- ```js
437
- let bias = 0;
438
- if (depth > PENETRATION_SLOP) {
439
- bias = -BAUMGARTE_BETA / dt * (depth - PENETRATION_SLOP);
440
- if (bias < -MAX_BAUMGARTE_BIAS) bias = -MAX_BAUMGARTE_BIAS;
441
- }
442
- ```
443
- This is classic Catto / Box2D-Lite Baumgarte: one-shot velocity bias
444
- proportional to penetration depth minus slop, with a hard cap to stop
445
- EPA-misreported deep penetrations from launching bodies. The cap is
446
- itself an admission that pure Baumgarte is fragile against bad depth
447
- readings — SPOOK's `eps` doesn't have this failure mode because it
448
- doesn't try to fully correct in one tick.
449
-
450
- **SPOOK is genuinely more principled and should be considered as a
451
- swap-in.** Concretely:
452
-
453
- 1. Replace `bias = -BAUMGARTE_BETA / dt * (depth - PENETRATION_SLOP)`
454
- with `bias = -a * (depth - PENETRATION_SLOP)` where `a` comes from
455
- `setSpookParams(k, d, dt)`. Choose `k = 1e7` and `d = 4` as
456
- Cannon does (these are the field-tested defaults).
457
- 2. Add the regularisation term to the iteration update — currently
458
- `lambda_n = -m_eff_n * (vn + bias_n)`, becomes
459
- `lambda_n = -m_eff_n * (vn + bias_n + eps * j_n_accum)` and
460
- `m_eff_n = 1 / (k_n + eps)`.
461
- 3. The `MAX_BAUMGARTE_BIAS` cap can probably be removed — SPOOK's
462
- softness handles inflated-depth EPA outputs gracefully because
463
- the regularisation prevents lambda from running away.
464
-
465
- This is the most concrete and highest-confidence improvement
466
- opportunity in the review. **Estimated impact:** tall-stack
467
- stability comparable to Cannon (which handles tens of bodies without
468
- substepping using SPOOK + 10 iterations), removal of the inflated-EPA
469
- launch failure mode, more robust feel without changing the public
470
- API. **Risk:** SPOOK changes contact feel slightly — bodies "settle"
471
- rather than "snap" to non-penetration. PLAN.md's note about TGS
472
- substepping being blocked on split-impulse becomes much less urgent.
473
-
474
- ### Manifold caching — persistent vs per-frame rebuild
475
-
476
- Cannon has **no persistent manifold cache**. Every step,
477
- `Narrowphase.getContacts` rebuilds the contact list from scratch:
478
-
479
- ```
480
- const oldcontacts = World_step_oldContacts;
481
- for (i = 0; i !== NoldContacts; i++) { oldcontacts.push(contacts[i]); }
482
- contacts.length = 0;
483
- this.narrowphase.getContacts(p1, p2, ..., oldcontacts, ...);
484
- ```
485
-
486
- The "oldcontacts" array is a **pool of allocated `ContactEquation`
487
- objects** to recycle — pool-via-array-of-instances, not warm-start
488
- information. The per-pair contact identity is rebuilt every frame,
489
- which means **Cannon cannot do per-pair warm-start at all**. It can
490
- warm-start within the solver iteration (lambda persists across
491
- iterations within one frame's solve call) but not across frames.
492
-
493
- Our `contact/ManifoldStore.js` is a real persistent manifold cache,
494
- designed exactly like Bullet's `btPersistentManifold`:
495
- - Keyed by canonical body pair via `PairUint32Map`
496
- (`ManifoldStore.js:114`).
497
- - Up to 4 contacts per slot (`MAX_CONTACTS_PER_MANIFOLD = 4`,
498
- `ManifoldStore.js:10`).
499
- - Per-contact stride includes `j_n`, `j_t1`, `j_t2` accumulated
500
- impulses (`ManifoldStore.js:13-33`) that survive across frames.
501
- - Touched / prev_touched / grace flag scheme so transient one-frame
502
- separations don't evict the manifold (`ManifoldStore.js:57-69`,
503
- `416-445`).
504
- - `begin_refill(slot)` explicitly preserves impulse fields while
505
- overwriting geometry (`ManifoldStore.js:265-268`).
506
-
507
- And `narrowphase_step.js:837-931` implements **match-and-merge**:
508
- candidates from this frame are matched to previous-frame contacts by
509
- **feature_id first, position-fallback within 2 cm second**, with the
510
- matched prev-contact's impulses copied to the corresponding new slot
511
- index. This is a genuine warm-start that survives:
512
- - Sphere-on-box sliding (feature_id is the box voronoi region,
513
- `narrowphase_step.js:226-242`, stable while the sphere stays on the
514
- same face/edge).
515
- - Triangle-decomposition contacts (feature_id is the triangle index,
516
- stable per-triangle, `narrowphase_step.js:590`).
517
- - Box-box (feature_id = 0, falls back to position matching within
518
- `MATCH_TOL_SQR = 0.0004 m²`).
519
-
520
- **Status correction.** Prior reviews (Bullet, Jolt, Rapier) flagged
521
- "warm-start impulses wiped every frame" — that was true at the time
522
- of those reviews. Reading `ManifoldStore.js` and `narrowphase_step.js`
523
- as they stand today, **warm-start is wired and working** on the
524
- steady-state contact path. The only branches that wipe impulses are
525
- `clear_contacts(slot)` calls (when narrowphase determines no contact
526
- or empty collider lists), which is correct behaviour — separated
527
- contacts should not warm-start. The `clear_impulses(slot, k)` for
528
- unmatched-by-match-and-merge candidates is also correct: a fresh
529
- contact at a slot index with stale data shouldn't inherit it.
530
-
531
- This is **a substantial structural advantage over Cannon**. Cannon's
532
- solver pays 10 iterations of convergence from a cold start every
533
- frame; ours starts from the previous frame's solution and typically
534
- converges in 2-3 iterations (with the remaining 7-8 absorbing
535
- geometry changes). For tall stacks specifically, this is the
536
- difference between PGS-with-warm-start being usable and needing TGS
537
- substepping.
538
-
539
- **Improvement opportunity:** consider lowering iteration count from
540
- 10 to e.g. 6 for steady-state, with a wake-event re-bump to 10 for
541
- the first few frames after wake. The savings would scale with the
542
- warm-start hit rate.
543
-
544
- ### Sleep — chatter vs atomic
545
-
546
- Already covered above. Cannon's per-body sleep chatters on
547
- weakly-connected piles; ours is atomic per island. The improvement
548
- opportunity is in the **other** direction — Cannon's `sleepTick`
549
- hooks `Body.dispatchEvent(Body.sleepyEvent)` /
550
- `Body.dispatchEvent(Body.wakeupEvent)` so user code can subscribe.
551
- We have `Signal onContactBegin/Stay/End` but no `onBodySleep` /
552
- `onBodyWake` events. For game code (turn music off when player
553
- stops, despawn enemies that have been asleep for N seconds), this is
554
- a missing affordance.
555
-
556
- ### CCD (Continuous Collision Detection)
557
-
558
- Cannon has no meaningful CCD — no per-body sweep, no speculative
559
- margin. Fast-moving bodies tunnel.
560
-
561
- We have **speculative margin** via the fat-AABB swept extent:
562
- `compute_fat_world_aabb` pads the broadphase AABB by `linearVelocity
563
- * dt`, so the broadphase catches imminent collisions and the
564
- narrowphase sees the future-position pair early. PLAN.md
565
- "Limitations / Known caveats" notes this is "CCD floor only" — the
566
- falling-tower repro at 1 km drop onto 1 cm floor still tunnels 180
567
- out of 1000 bodies because a single-step sweep isn't a true
568
- swept-shape cast. PLAN.md backlog has "Per-body linear CCD
569
- shape-cast" as the planned fix.
570
-
571
- **We are categorically ahead of Cannon here.** Even our "CCD floor"
572
- catches everything Cannon misses.
573
-
574
- ### Concave / triangle mesh
575
-
576
- Cannon's `Trimesh` stores vertices in a `Float32Array`, indices in
577
- `Int16Array`, normals in `Float32Array`. The narrowphase uses an
578
- `Octree` to query triangles overlapping the convex's AABB. Each
579
- returned triangle is **treated as a `ConvexPolyhedron`** for the
580
- convex-convex SAT + clip path, which is correctness-wise solid for
581
- polyhedral convex shapes but doesn't help with smooth shapes (which
582
- Cannon doesn't have anyway).
583
-
584
- Our `MeshShape3D` + `mesh_enumerate_triangles` does an O(N) linear
585
- scan with tight per-triangle AABB filtering. No BVH per mesh.
586
- **This is genuinely a regression compared to Cannon's Octree** for
587
- large meshes (>10k triangles). PLAN.md doesn't list this as a
588
- backlog item, but should — a per-mesh BVH built once at attach time
589
- is exactly the same machinery as the global broadphase BVH, just
590
- indexed by triangle. **Improvement opportunity: build a per-mesh
591
- triangle BVH in `MeshShape3D` construction (or first-query), query
592
- it instead of linear scanning.**
593
-
594
- Both engines have boundary-edge correctness issues at internal
595
- triangle junctions. Cannon's polyhedron-per-triangle treats each
596
- triangle's edges as collision-active, producing the well-known
597
- "phantom contacts on internal edges" bug. Ours has the same problem
598
- plus the EPA-on-degenerate-triangle issue (PLAN.md "Limitations").
599
- Neither engine implements Catto's "voronoi region triangle culling"
600
- fix. **PLAN.md "Closed-form triangle-vs-primitive solvers"** is the
601
- correct prescription here.
602
-
603
- ### Heightmaps
604
-
605
- Cannon's `Heightfield` constructs an explicit convex polyhedron per
606
- cell via `getConvexTrianglePillar(i, j, isUpper)` and caches them in
607
- a `_cachedPillars` dictionary. Cache key is `"i_j_isUpper"` (string!
608
- allocates on every lookup). Each pillar is a 5-faced extruded
609
- triangle that goes from the upper surface down to a fixed depth —
610
- giving the heightfield a "thickness" rather than the infinite-thin
611
- surface our heightmap has.
612
-
613
- Our `HeightMapShape3D` is a `Sampler2D`-backed surface; we sample
614
- heights via `sampleChannelCatmullRomUV` and emit triangles
615
- on-demand via `heightmap_enumerate_triangles`. No per-cell convex
616
- caching, no extrusion — the heightmap is a one-sided surface and
617
- "falling through" from below is undefined behaviour (we explicitly
618
- reject contacts where the body is on the wrong side via the
619
- one-sided face-normal check at `narrowphase_step.js:654`).
620
-
621
- **Tradeoffs:**
622
-
623
- | Aspect | meep heightmap | cannon-es Heightfield |
624
- |---|---|---|
625
- | Memory | O(1) — `Sampler2D` reference + orientation vector | O(cells × cached_pillars) — each pillar is a `ConvexPolyhedron` with 6 vertices, 5 faces |
626
- | Per-step cost | Catmull-Rom sample × 4 per cell × triangles touched | Polyhedron support × triangle count touched (SAT) |
627
- | Smoothness | Catmull-Rom interpolation matches the terrain renderer's geometry | Linear interpolation (raw heights) |
628
- | Two-sidedness | One-sided (per design) | Two-sided (extruded pillar) |
629
- | Allocation hot-path | None (rebound `Triangle3D` flyweight) | `_cachedPillars` string-key dict + Object spread on miss |
630
-
631
- The per-step performance is roughly comparable. The Catmull-Rom
632
- sampling we do is more expensive per-cell than Cannon's linear
633
- read, but Cannon's polyhedron-per-cell SAT inflates the constant
634
- factor on the narrowphase side. **Concrete win for us:** zero
635
- allocation on the heightmap hot path; Cannon's string-key dict
636
- allocation is a visible GC pressure source.
637
-
638
- ### Joints / Constraints
639
-
640
- Cannon has a rich constraint library: `PointToPointConstraint`,
641
- `DistanceConstraint`, `HingeConstraint`, `LockConstraint`,
642
- `ConeTwistConstraint`. All are built on `Constraint` (base class)
643
- that holds an array of `Equation` objects, plus a polymorphic
644
- `update()` that recomputes the equations' Jacobians from the current
645
- body poses. The solver iterates contacts AND joint equations
646
- uniformly because they all share the SPOOK formulation.
647
-
648
- We have **none**. PLAN.md "Backlog → Features → Joints" lists this
649
- as planned ("distance, hinge, ball-socket, prismatic") and notes
650
- "the solver loop is already set up to iterate contacts ∪ joints;
651
- only constraint pre-step + warm-start hook is missing."
652
-
653
- **Reading Cannon's `Constraint` API is the right move for the
654
- joint implementation**. Specifically the SPOOK-equation pattern
655
- makes it trivial to add new joints — each new joint type just
656
- specifies how to compute G, what bounds the lambda has, and what `B`
657
- should be at this pose. If we adopt SPOOK (see solver section) we
658
- get this "joints and contacts are uniform" property for free.
659
-
660
- ### Queries
661
-
662
- Cannon has `World.raycastClosest`, `raycastAny`, `raycastAll` — each
663
- walks all bodies, then per-body iterates `shapes[i]`, then calls the
664
- shape's `raycast` method. The shape-side `raycast` is implemented
665
- per-shape (`Sphere.raycast`, `Box.raycast` via `ConvexPolyhedron`,
666
- `Plane.raycast`, `Heightfield.raycast`, `Trimesh.raycast`). No BVH
667
- traversal — the broadphase isn't consulted for raycasts at all.
668
-
669
- Our `queries/raycast.js` traverses both BVHs by AABB-ray test and
670
- reports the nearest hit. Currently the result is the leaf AABB hit,
671
- not refined to the actual shape geometry — so a raycast against a
672
- sphere reports a hit on the bounding cube. PLAN.md "Public queries"
673
- notes this: "narrowphase refinement against the actual shape
674
- geometry is a follow-up — for now `result.t` is the distance to the
675
- leaf's inflated AABB". This is a **legitimate gap vs Cannon** —
676
- ours is broadphase-fast but coarse, Cannon's is exact-shape but
677
- linear-scan. **Improvement opportunity: add per-shape exact-raycast
678
- refinement** after broadphase narrows candidates. The closed-form
679
- sphere/box/capsule cases are trivial; the convex case can use
680
- shape-cast machinery already in place.
681
-
682
- `shapeCast` is something **Cannon does not have** in any form. Ours
683
- sweeps a convex shape via swept-AABB broadphase + GJK bisection,
684
- reporting time-of-impact and contact normal recovered via EPA. This
685
- is what character controllers need; Cannon users have to roll their
686
- own from raycasts.
687
-
688
- `overlap` similarly has no Cannon equivalent. Ours filters
689
- broadphase candidates by GJK and writes body ids into a caller-sized
690
- `Uint32Array`. Useful for AOE selection, kinematic probes.
691
-
692
- ---
693
-
694
- ## 3. In-depth comparison of our code against Cannon
695
-
696
- I'll pick eight touchpoints and walk through them.
697
-
698
- ### 3.1 Per-step body iteration — O(awake) vs O(total)
699
-
700
- **Our code, `ecs/PhysicsSystem.js:1083`:**
701
- ```js
702
- const count = this.storage.awake_count;
703
- for (let i = 0; i < count; i++) {
704
- const idx = this.storage.awake_at(i);
705
- const rb = this.__bodies[idx];
706
- const tr = this.__transforms[idx];
707
- integrate_velocity(rb, tr, gx, gy, gz, dt);
708
- }
709
- ```
710
-
711
- **Cannon's pattern, `src/world/World.ts`:**
712
- ```js
713
- for (let i = 0; i !== N; i++) {
714
- const bi = bodies[i];
715
- bi.sleepTick(this.time);
716
- // ... unconditional work happens here, sleep state checked inline
717
- }
718
- ```
719
-
720
- Cannon's loop visits all `N` bodies regardless of sleep state. The
721
- per-iteration cost is dominated by the field access pattern (cache
722
- miss on cold bodies) + sleep state branch. For a scene with 1M
723
- bodies and 100 awake, Cannon does 1M unconditional loads + 1M
724
- branches; we do 100 indexed loads.
725
-
726
- This is **the single most important scalability difference between
727
- the two engines**. At our design target of "millions of mostly
728
- sleeping bodies" Cannon's per-step cost grows linearly with the
729
- total body count, ours with the awake count. A 1000:1 ratio
730
- (realistic for a populated town) is a 1000× speedup.
731
-
732
- **Improvement on our side:** none — the structure is correct. **The
733
- risk to keep an eye on:** the per-body collider list lives in
734
- `this.__body_collider_lists[idx]`, a JS array. Each
735
- `compute_fat_world_aabb` call inside Stage 2 indirects through
736
- `list[k].collider.shape.compute_bounding_box(...)`. That's three
737
- pointer-chases per leaf. For a fully-monomorphic hot loop we could
738
- flatten the leaves into a per-body Float64Array of `(node_id,
739
- shape_local_aabb)`. Probably not worth the structural cost until
740
- benchmarks show it dominating.
741
-
742
- ### 3.2 SPOOK constraint formulation
743
-
744
- Side-by-side comparison of the per-iteration update:
745
-
746
- **Cannon `GSSolver`:**
747
- ```js
748
- deltalambda = invC * (B - GWlambda - c.eps * lambdaj);
749
- ```
750
- Where `invC = 1 / (G·M⁻¹·Gᵀ + eps)`, `B = -g*a - GW*b - h*GiMf`, and
751
- `eps` is the SPOOK regularisation.
752
-
753
- **Ours `solve_contacts.js:531`:**
754
- ```js
755
- const lambda_n = -m_eff_n * (vn + bias_n);
756
- ```
757
- Where `m_eff_n = 1 / k_n` (no eps), `bias_n = -BAUMGARTE_BETA/dt *
758
- (depth - slop)` capped at `MAX_BAUMGARTE_BIAS = 3 m/s`, plus
759
- restitution velocity-target if `vn_pre < -RESTITUTION_VELOCITY_THRESHOLD`.
760
-
761
- **The qualitative difference:**
762
-
763
- - **SPOOK is unconditional and continuous.** The `eps * lambdaj` term
764
- is present every iteration, regardless of depth. It acts like a
765
- spring damping. As lambda grows, the regularisation eats into it,
766
- preventing runaway.
767
- - **Baumgarte is conditional and one-shot.** The bias kicks in only
768
- above the slop threshold, applies a velocity correction once per
769
- step, and the `MAX_BAUMGARTE_BIAS` cap is a panic guard against
770
- EPA returning bad depths.
771
-
772
- **Concrete failure mode of our code:** when EPA misreports depth on a
773
- torus knot (PLAN.md mentions this as one of the motivating cases for
774
- MAX_BAUMGARTE_BIAS), our solver caps the bias at 3 m/s. That's
775
- enough to stop the body launching but not enough to actually
776
- resolve a 10cm phantom penetration — the body sinks. SPOOK doesn't
777
- have this dichotomy: the regularisation moderates the response
778
- naturally.
779
-
780
- **Recommended action:** when joints land (they need SPOOK or SPOOK-like
781
- structure anyway for the constraint/joint uniformity), refactor the
782
- contact solver to use SPOOK. The pre-step changes:
783
-
784
- ```js
785
- // In pre-step (where bias is currently computed):
786
- const k_spook = 1e7; // stiffness
787
- const d_spook = 4; // relaxation
788
- const a_spook = 4 / (dt * (1 + 4 * d_spook));
789
- const b_spook = (4 * d_spook) / (1 + 4 * d_spook);
790
- const eps = 4 / (dt * dt * k_spook * (1 + 4 * d_spook));
791
- pre[pre_off + 12] = 1 / (k_n + eps); // includes regularisation
792
- pre[pre_off + 15] = -a_spook * Math.max(0, depth - PENETRATION_SLOP);
793
- ```
794
-
795
- (With a corresponding inner-loop change to subtract `eps * j_n_accum`
796
- from the lambda before clamping.)
797
-
798
- This single change probably resolves several existing pain points
799
- (MAX_BAUMGARTE_BIAS cap, restitution-vs-warm-start interaction in the
800
- TGS attempt, mesh-contact phantom-depth launching).
801
-
802
- ### 3.3 Friction model
803
-
804
- **Cannon's `FrictionEquation`:** one-dimensional, along a single
805
- tangent direction. The slipForce passes to `Equation` as bounds
806
- `[-slipForce, slipForce]` where `slipForce = μ * F_normal`.
807
- Cannon generates **two** FrictionEquations per contact (two tangent
808
- directions) so the friction is effectively a polyhedral cone, not a
809
- disk.
810
-
811
- **Ours `solve_contacts.js:566-571`:**
812
- ```js
813
- const want_t1 = j_t1_accum + lambda_t1;
814
- const want_t2 = j_t2_accum + lambda_t2;
815
- const max_friction = mus[ci] * new_j_n;
816
- friction_cone_clamp(scratch_clamp, 0, want_t1, want_t2, max_friction);
817
- ```
818
- Disk clamp in the (t1, t2) tangent plane. Mathematically a true
819
- circular friction cone — strictly more accurate than Cannon's box
820
- clamp.
821
-
822
- **Win for us.** The box-vs-disk distinction matters for objects
823
- sliding in directions not aligned with the tangent basis — Cannon
824
- allows up to √2 × μ tangential force on a 45°-sliding object, ours
825
- correctly clamps to μ. The difference is small but observable on
826
- spinning objects on a flat surface.
827
-
828
- ### 3.4 Restitution + warm-start interaction
829
-
830
- **Ours `solve_contacts.js:452-456`:**
831
- ```js
832
- if (vn_pre < -RESTITUTION_VELOCITY_THRESHOLD) {
833
- bias += restitution_combined * vn_pre;
834
- }
835
- ```
836
- Restitution is **added to the velocity bias inside the iterative
837
- loop**. Combined with the `j_n ≥ 0` clamp, this is what PLAN.md
838
- identifies as one of three reasons the TGS substep attempt failed:
839
-
840
- > "Restitution × warm-start clamp. The j_n ≥ 0 clamp lets substep 1+
841
- > shrink the impulse applied in substep 0, so a restitution=0 ball
842
- > that should stop instead picks up a phantom upward velocity."
843
-
844
- **Cannon's `ContactEquation`:**
845
- ```js
846
- GW = ePlusOne * vj.dot(n) - ePlusOne * vi.dot(n) + ...
847
- B = -g * a - GW * b - h * GiMf
848
- ```
849
- Restitution scales the **closing velocity** in `GW` before computing
850
- the bias. Mathematically identical to ours in the simple case but
851
- structurally different — Cannon's restitution is baked into
852
- `computeB` and rebuilt every step, ours is added on top of
853
- Baumgarte every step. Cannon's GS solver doesn't have the
854
- warm-start-shrinkage problem because there's no warm-start.
855
-
856
- **A genuine improvement opportunity:** the Box2D / Box2D-Lite
857
- "split-impulse" pattern that PLAN.md mentions as a prerequisite for
858
- TGS is also the right structure for clean restitution. Restitution
859
- should be a **one-shot impulse at first contact**, not a per-iteration
860
- bias. Bullet does this too. Cannon's approach is less correct (it
861
- applies on every iteration, but starts cold each frame so the
862
- warm-start interaction doesn't bite). When we add SPOOK, this is a
863
- good time to switch to a real restitution model.
864
-
865
- ### 3.5 ConvexPolyhedron.clipFaceAgainstHull vs box_box_manifold
866
-
867
- Both implement Sutherland-Hodgman polygon clipping against a series
868
- of half-spaces. The structural difference:
869
-
870
- Cannon's `clipFaceAgainstHull` is **N-gon → N-gon** (handles any
871
- polygon size, output bounded by `numClipFaces × original_size`).
872
- The clip buffer is a JavaScript `Array<Vec3>` that grows / shrinks
873
- with `.push()` and `.shift()` — both allocate-and-copy.
874
-
875
- Our `box_box_manifold.js` clips a quad against 4 edges, hard-coded.
876
- Output buffer is a `Float64Array(8 * 3)` allocated once at module load
877
- (`clip_in`, `clip_out` at `box_box_manifold.js:53-54`). Zero
878
- allocation per call.
879
-
880
- **Win for us on the box-box case.** For the future ConvexHullShape3D
881
- we'll want to port Cannon's general case, but with our typed-array
882
- allocation discipline rather than `Array.push/shift`. The
883
- key insight from Cannon that we'd otherwise miss: **the inner clip
884
- loop reuses two ping-pong buffers and a swap-on-output pattern**
885
- that minimises the polygon-buffer reallocations to two fixed
886
- `Vec3[]` arrays per clip pass. That pattern translates directly to
887
- two fixed `Float64Array` slabs for us.
888
-
889
- ### 3.6 Sleep — atomic island vs per-body chatter
890
-
891
- Already covered in §1 and §2. Concrete walk-through of the failure:
892
-
893
- **Cannon's chatter (paraphrased trace):**
894
- ```
895
- Frame N: bottom block sleepState=SLEEPY, timeLastSleepy=N*dt
896
- Frame N+1: top block jitters; broadphase pair list includes block above it
897
- both blocks `wakeUpAfterNarrowphase=true`
898
- bottom wakes via velocity propagation
899
- Frame N+2: bottom resleeps (now SLEEPY again)
900
- Frame N+3: middle blocks similarly wake from a different jitter
901
- ...
902
- ```
903
-
904
- A 100-block stack chatters at ~3-5 fps of sleep-state churn for
905
- seconds before fully settling, if it ever does. The user-observable
906
- effect is GC spikes from per-frame event dispatch plus visible
907
- micro-jitter.
908
-
909
- **Our atomic sleep (`ecs/PhysicsSystem.js:942-1006`):**
910
- ```js
911
- if (max_v_sqr < threshold_sqr) {
912
- let min_timer = Infinity;
913
- for (let i = start; i < end; i++) {
914
- rb.sleep_timer += dt;
915
- if (rb.sleep_timer < min_timer) min_timer = rb.sleep_timer;
916
- }
917
- if (min_timer >= time_threshold) {
918
- this.__atomic_sleep_island_range(body_data, start, end);
919
- }
920
- }
921
- ```
922
- The whole island either accumulates time or doesn't. When it crosses
923
- the threshold, every member sleeps the same step, and the
924
- sleep-group circular DLL connects them so a single wake event
925
- walks the chain.
926
-
927
- **Verbatim from PLAN.md's "Done" section:** "Per-island atomic sleep:
928
- ... Replaces the per-body chatter on weakly-connected piles." This
929
- is one of the engineering claims most clearly validated by the
930
- Cannon comparison — the algorithm Cannon ships has the exact failure
931
- mode we explicitly engineered around.
932
-
933
- ### 3.7 Per-pair narrowphase dispatch
934
-
935
- Both engines look superficially similar — a cascade of pair-type
936
- checks dispatching to per-pair contact methods. The differences:
937
-
938
- **Cannon `Narrowphase.getContacts`:** dispatch table keyed by shape
939
- type enum (Sphere=1, Box=4, Plane=8, etc.). Lookup pattern:
940
- ```js
941
- const resolverIndex = (si.type | sj.type);
942
- // uses bit-OR of shape type enums as table key
943
- ```
944
- Method-per-pair on the `Narrowphase` class (`sphereSphere`,
945
- `sphereBox`, `sphereConvex`, `convexConvex`, `planeBox`,
946
- `planeConvex`, `planeTrimesh`, `sphereTrimesh`, `convexTrimesh`,
947
- `sphereHeightfield`, `convexHeightfield`, `sphereParticle`,
948
- `planeParticle`, `convexParticle`, `boxConvex`, ...).
949
-
950
- **Ours `narrowphase_step.js:dispatch_pair`:** cascade of
951
- `if (isSphereA && isSphereB)`, `if ((isSphereA && isBoxB) || ...)`,
952
- etc. — handled as boolean checks on `shape.isXxx === true` flags.
953
-
954
- **Tradeoff:**
955
-
956
- - Cannon's table is O(1) dispatch but allocates an integer combination
957
- per call and has unmaintainable enum-OR collision risk. It also
958
- forces all narrowphase to live on one class instance.
959
- - Ours is O(K) cascade where K = pair categories handled (currently
960
- ~7). At small K cascade is faster than table lookup; the
961
- isShapeType branch predicts well after the first hit. At large K
962
- the table wins.
963
-
964
- **Both lose to a real double-dispatch pattern**, but neither is bad
965
- enough to fix yet.
966
-
967
- **Improvement opportunity for us:** the cascade in `dispatch_pair`
968
- checks `isSphereA && isBoxB || isBoxA && isSphereB` which always
969
- runs both halves of the `||` even after a hit. Rearranging by
970
- expected frequency (sphere-sphere first if that's the dominant pair,
971
- which it is for ball-based games) gets us closer to one-branch fast
972
- path for the common case. Probably <1% real-world win; not urgent.
973
-
974
- ### 3.8 Allocation patterns
975
-
976
- **Cannon's allocation hot-spots that cannon-es fixed vs original
977
- cannon.js:**
978
-
979
- cannon-es is the result of years of de-allocation work compared to
980
- the original. Notable fixes documented in the changelog / source
981
- comments:
982
-
983
- - Module-level `tmpVec`, `tmpQuat`, `Body_applyForce_rotForce`,
984
- `Body_applyImpulse_velo` etc. scratch instances declared once per
985
- module, reused everywhere. Original cannon.js allocated these
986
- per-call.
987
- - `World_step_oldContacts` and `World_step_oldFrictionEquations`
988
- arrays reused frame-to-frame, with `.length = 0` + push pattern.
989
- - `SAPBroadphase` insertion-sort with persistent `axisList`.
990
-
991
- **Where Cannon STILL allocates per-step** (and we don't):
992
- - `Narrowphase.getContacts` uses `Array.push` extensively for the
993
- output contact list. Even with the `contactPointPool` recycling
994
- the `ContactEquation` instances, the outer array grows via `push`
995
- and resets via `.length = 0`. This is **the** cannon-es per-frame
996
- allocation hot path. Our equivalent — writing into a fixed
997
- `Float64Array` slab via `set_contact(slot, idx, ...)` — has zero
998
- steady-state allocation.
999
- - `SplitSolver` BFS over equations builds a fresh `nodes` /
1000
- `children` graph each call. Pool exists but graph-shape changes
1001
- defeat it.
1002
- - `Heightfield._cachedPillars` uses string keys (`"i_j_isUpper"`) —
1003
- every lookup allocates a string. Hot path on heightmap collision.
1004
-
1005
- **JS-specific optimisation insights from cannon-es worth absorbing:**
1006
-
1007
- 1. **Module-level scratch instances declared in a uniform pattern.**
1008
- The Cannon convention is `const Body_methodName_purpose = new
1009
- Vec3()`. Naming makes the scratch's owner and intent obvious. We
1010
- do the same with prefixed `scratch_*` names; cannon's discipline
1011
- is more enforced. The convention is good — names like
1012
- `scratch_inertia_a` / `scratch_inertia_b` in our solver follow
1013
- the pattern correctly. A grep for `const scratch_` shows we're
1014
- consistent.
1015
-
1016
- 2. **Body field initialisation order matters for V8 hidden classes.**
1017
- Both engines initialise fields in the constructor (or as class
1018
- fields) in a fixed order, ensuring all `RigidBody` / `Body`
1019
- instances share the same hidden class. Our class-field syntax
1020
- (`linearVelocity = new Vector3(0, 0, 0)` at class-body scope) is
1021
- the modern equivalent of Cannon's constructor assignments. Both
1022
- approaches achieve hidden-class consistency. **Risk:** if any
1023
- call site does `body.somethingNew = value`, V8 transitions to a
1024
- new hidden class. We should keep RigidBody field-set strictly
1025
- closed; users wanting extra state should attach side components.
1026
-
1027
- 3. **Avoid Float32Array for hot constraint data.** cannon-es uses
1028
- `Vec3` (3 numbers as object fields, not typed array). Vec3 access
1029
- is faster than Float32Array indexing on V8 (no bounds check). But
1030
- for **bulk solver state** that lives in slabs (our manifold
1031
- `data_buffer`, our `scratch_pre` per-contact stride), Float64Array
1032
- is correct — bulk reads / writes by offset compile to direct
1033
- memory operations. The lesson is "use Vec3-like for individual
1034
- vectors, TypedArray for bulk slabs". We follow this pattern
1035
- correctly.
1036
-
1037
- 4. **Avoid object-spread / Object.assign on hot path.** Cannon has a
1038
- couple of places (in Equation reset code) that do `Object.assign`
1039
- on equation instances — measurable per-frame cost. We don't have
1040
- any such patterns; worth a periodic audit.
1041
-
1042
- 5. **Use shift-by-N on bit-packed flags rather than separate
1043
- booleans.** Our `ManifoldStore` packs `count | touched |
1044
- prev_touched | grace` into one Uint32 word
1045
- (`ManifoldStore.js:57-61`). Cannon uses separate boolean fields
1046
- on `Body` (`allowSleep`, `wakeUpAfterNarrowphase`, etc.) — fine
1047
- for code clarity but more memory per body. At million-body
1048
- scale our bit-packing saves 4 bytes per slot × N slots.
1049
-
1050
- ### 3.9 Numerical stability — degenerate cases
1051
-
1052
- **Cannon's degenerate-case handling:**
1053
-
1054
- - `findSeparatingAxis` skips edge-cross axes where the cross is
1055
- almost zero (`if (!Cross.almostZero())`). Prevents NaN normals on
1056
- parallel edge configurations.
1057
- - `ConvexPolyhedron` constructor warns if a face normal points
1058
- inward (`console.error` — doesn't fix).
1059
- - `Trimesh` validates index range when computing per-triangle
1060
- normals.
1061
-
1062
- **Ours:**
1063
-
1064
- - `narrowphase_step.js:728`: `if (!(depth > 0) || !Number.isFinite(depth))
1065
- return count;` — rejects NaN/Inf/zero depths from EPA. Good.
1066
- - `narrowphase_step.js:620`: same check inside the concave loop.
1067
- - `solve_contacts.js:151, 261`: `inv_mass_of` and
1068
- `angular_jacobian_contribution` guard against zero mass /
1069
- inertia. Good.
1070
- - **Missing:** no zero-area triangle guard in `mesh_enumerate_triangles`
1071
- or `heightmap_enumerate_triangles`. A degenerate triangle (collinear
1072
- vertices) would feed a zero face normal into GJK / EPA and the
1073
- one-sided rejection at `narrowphase_step.js:654` would silently
1074
- skip it. **Probably fine in practice** — degenerate triangles in a
1075
- source mesh are usually filtered at import — but worth a paranoid
1076
- guard.
1077
- - `solver/solve_contacts.js:418-420` guards K=0 with
1078
- `k_n > 0 ? 1 / k_n : 0` — prevents NaN m_eff on two
1079
- zero-mass bodies (kinematic-vs-kinematic, which the solver
1080
- should never see, but defensively).
1081
-
1082
- **Concrete improvement opportunity:** add `if (Math.abs(face_normal_sq) <
1083
- EPS) continue;` at `narrowphase_step.js:599` (after computing
1084
- `fnx_l/fny_l/fnz_l` in the triangle loop). This rejects degenerate
1085
- triangles before they reach EPA.
1086
-
1087
- ---
1088
-
1089
- ## 4. Simplicity & uniformity
1090
-
1091
- ### Code organisation
1092
-
1093
- | Aspect | meep | cannon-es |
1094
- |---|---|---|
1095
- | Module layout | One concept per file, hierarchical directories (`body/`, `broadphase/`, `solver/`, `gjk/`, `narrowphase/`, ...) | Mostly one class per file, flat-ish (`src/objects/`, `src/collision/`, `src/equations/`, `src/solver/`, `src/shapes/`) |
1096
- | Class structure | Mostly free functions + a few classes (Storage, ManifoldStore, IslandBuilder, PhysicsSystem) | Class hierarchy everywhere (Body, Shape→Sphere/Box/..., Equation→ContactEquation/FrictionEquation, Constraint→PointToPoint/Distance/...) |
1097
- | Public API surface | Concentrated on `PhysicsSystem` | Spread across World + Body + Shape + Constraint subclasses |
1098
- | File count | ~50 files in `physics/` (excluding tests) | ~50 files in cannon-es `src/` |
1099
- | LOC | Roughly comparable; we have more in the broadphase + decomposition machinery |
1100
-
1101
- Both engines are flat and avoid deep hierarchies (unlike Bullet's
1102
- `btCollisionObject < btRigidBody < btSoftBody` etc.). Cannon does
1103
- have one inheritance chain that matters: `Shape` → `Sphere`, `Box`
1104
- (which is a `ConvexPolyhedron` subclass), `Plane`, `Heightfield`,
1105
- `Particle`, `ConvexPolyhedron`, `Trimesh`. The `Shape` base class
1106
- exposes `volume()`, `boundingSphereRadius`, `updateBoundingSphereRadius()`,
1107
- `calculateLocalInertia()`, `calculateWorldAABB()` — virtual methods
1108
- that every shape overrides. Adding a new shape means subclassing
1109
- `Shape` and implementing 5 virtuals.
1110
-
1111
- Our `AbstractShape3D` plays the equivalent role. New shape = same
1112
- contract.
1113
-
1114
- ### Adding a new shape pair — workflow
1115
-
1116
- **Cannon:** add a new method on `Narrowphase`, register it in the
1117
- dispatch table. Add the shape class as a `Shape` subclass.
1118
-
1119
- **Ours:** add a new closed-form file in `narrowphase/`, add a branch
1120
- to the `dispatch_pair` cascade in `narrowphase_step.js`. Add the
1121
- shape class as an `AbstractShape3D` implementation.
1122
-
1123
- **Equivalent friction.** Adding capsule-mesh would be the same
1124
- amount of work in either engine.
1125
-
1126
- ### Adding a new constraint
1127
-
1128
- **Cannon:** subclass `Constraint`, instantiate the Equations it
1129
- owns, implement `update()` to recompute Jacobians. SPOOK + GSSolver
1130
- handle the rest.
1131
-
1132
- **Ours:** **not currently supported**. The solver knows about
1133
- contacts only; adding joints requires extending `solve_island` to
1134
- iterate a `joints` list parallel to contacts. PLAN.md says this is
1135
- planned but not done.
1136
-
1137
- **Cannon's design is the right reference.** The "constraints and
1138
- contacts are both Equations" abstraction is what makes Cannon's
1139
- joint library so flexible. Adopting SPOOK structurally enables
1140
- copying this design.
1141
-
1142
- ### Adding a new query
1143
-
1144
- **Cannon:** implement per-shape `raycast` method; `World.raycastClosest`
1145
- iterates all bodies + shapes.
1146
-
1147
- **Ours:** add a query function in `queries/`. Use the BVH for
1148
- broadphase, dispatch per-shape narrowphase.
1149
-
1150
- **We win on structure** — queries live in their own subsystem and
1151
- share the broadphase. Cannon's per-shape `raycast` is duplicated
1152
- across every shape class and bypasses the broadphase entirely.
1153
-
1154
- ### Where Cannon's `Body` god-class wins
1155
-
1156
- Despite our decomposition argument, **Cannon's single-class `Body`
1157
- makes some things simpler**:
1158
- - One reference to pass around (`body`).
1159
- - `addEventListener('sleep', ...)` etc. for application code.
1160
- - `body.applyForce(force, worldPoint)` reads naturally.
1161
-
1162
- Our `system.applyForceAt(rigidBody, transform, force, worldPoint)`
1163
- needs four arguments because force application needs the transform
1164
- (for the r = worldPoint - position calculation) and the system
1165
- (for the wake call), and the rigid body itself. The system-level
1166
- API is necessarily more verbose. **This is the unavoidable cost of
1167
- ECS decomposition** — we picked it for good reasons (transform is
1168
- shared with rendering, components are independently
1169
- serialisable / observable) and the price is API verbosity.
1170
-
1171
- ### Where Cannon's API is a cautionary tale
1172
-
1173
- 1. **`world.bodies` is a public mutable array.** Users mutate it
1174
- directly, which violates every encapsulation invariant. We
1175
- correctly hide `__bodies` and expose only `entityOf(packed_id)`.
1176
-
1177
- 2. **Body events fire via `dispatchEvent` on the Body itself.** This
1178
- means every Body has its own listener list. At 100k bodies that's
1179
- 100k tiny listener arrays. We use Signal-per-system + per-entity
1180
- event channel (`PhysicsEvents`), which scales better.
1181
-
1182
- 3. **`World.contacts` rebuilt every frame as a fresh array.** Visible
1183
- GC pressure. Our `ManifoldStore` is persistent and slab-based.
1184
-
1185
- 4. **`Heightfield._cachedPillars` string-key dict.** Allocates per
1186
- lookup. Our `HeightMapShape3D` has no equivalent caching layer —
1187
- it samples and emits triangles on-demand into a flyweight, zero
1188
- allocation.
1189
-
1190
- 5. **`ConvexPolyhedron.findSeparatingAxis` allocates `Vec3`
1191
- intermediates** despite cannon-es's de-allocation pass. Our
1192
- `box_box_manifold.js` is entirely typed-array, zero allocation.
1193
-
1194
- ### Where we could simplify further
1195
-
1196
- - The cascade of `is*A && is*B || is*A && is*B` in `dispatch_pair`
1197
- has each pair-type check duplicated for ordering. A small enum +
1198
- pair-table would compress this without sacrificing inlining. But
1199
- it's not urgent — the cascade is still readable at 7 pair types.
1200
-
1201
- - `narrowphase_step.js` has the SAT/clip box-box code linked but the
1202
- general convex-hull case TODO. When we add `ConvexHullShape3D`,
1203
- the box-box code wants to be subsumed into the general path
1204
- rather than maintained as a parallel implementation. Don't add
1205
- ConvexHull without refactoring box-box at the same time, or we'll
1206
- have two copies of essentially the same algorithm.
1207
-
1208
- - `solve_contacts.js` has separate friction and normal blocks that
1209
- could be unified under SPOOK's "every equation is the same shape"
1210
- model. This is the joint-system prerequisite anyway.
1211
-
1212
- ---
1213
-
1214
- ## Top recommendations
1215
-
1216
- In rough priority order:
1217
-
1218
- 1. **Adopt SPOOK constraint regularisation** in the solver
1219
- (`solver/solve_contacts.js`). Removes the `MAX_BAUMGARTE_BIAS`
1220
- hack, improves tall-stack stability comparable to Cannon, and is
1221
- the structural prerequisite for clean joint implementation.
1222
- Estimated effort: medium (one solver rewrite, no public API
1223
- change). Estimated payoff: high.
1224
-
1225
- 2. **Wire MPR as the EPA-non-convergence fallback** in
1226
- `narrowphase_step.js`. Code exists at `gjk/mpr.js` per PLAN.md;
1227
- one branch on EPA's NaN/zero-depth path swaps it in. Unblocks the
1228
- torus-knot test and the smooth-shape contact stability issue.
1229
- Effort: low. Payoff: high for smooth-shape cases.
1230
-
1231
- 3. **Add per-shape exact raycast refinement** after BVH broadphase
1232
- (`queries/raycast.js`). Currently returns leaf-AABB hit which
1233
- underestimates `t` for non-AABB shapes. Cannon's per-shape
1234
- raycast methods are the reference. Effort: medium. Payoff:
1235
- makes the raycast query genuinely useful for character
1236
- controllers / weapon hit detection.
1237
-
1238
- 4. **Build per-mesh triangle BVH for `MeshShape3D`**. Cannon's
1239
- `Trimesh.tree` is the reference (Octree there; BVH for us since
1240
- we have the infrastructure). Replace the O(N) linear scan in
1241
- `mesh_enumerate_triangles`. Effort: low (BVH is in
1242
- `core/bvh2/`). Payoff: linear-to-log mesh scaling, opens the door
1243
- to genuinely large static-mesh worlds.
1244
-
1245
- 5. **Closed-form triangle-vs-primitive** narrowphase (already on
1246
- PLAN.md backlog — re-emphasising because it's the gating issue
1247
- for several skipped tests). Cannon's "treat triangle as
1248
- ConvexPolyhedron and SAT it" works but has the same internal-edge
1249
- phantom contact issue we have. Catto's voronoi triangle
1250
- classification is the standard fix.
1251
-
1252
- 6. **Add `onBodySleep` / `onBodyWake` signals** on `PhysicsSystem`.
1253
- Cannon's per-body events are the wrong scale (event listener per
1254
- body), but a system-level signal carrying island membership is
1255
- useful for game code. Effort: trivial. Payoff: gameplay-quality
1256
- of life.
1257
-
1258
- 7. **Consider lowering default solver iterations from 10 to 6 once
1259
- warm-start is verified stable in production.** Cannon defaults
1260
- to 10 because it can't warm-start. Ours can; the savings scale
1261
- with warm-start hit rate (typically >80% in steady-state
1262
- contacts). Effort: trivial (one constant). Validation: medium
1263
- (need a stack-stability benchmark sweep).
1264
-
1265
- 8. **Audit `mesh_enumerate_triangles` for zero-area triangle
1266
- handling.** Add an `Math.abs(face_normal_sq) < EPS` skip. Cheap
1267
- defensive guard. Effort: trivial.
1268
-
1269
- ---
1270
-
1271
- ## Summary
1272
-
1273
- Cannon is our closest peer on engineering constraints (pure JS,
1274
- single-threaded, no SIMD) and the comparison is consistently
1275
- favourable. **Where we are clearly ahead:**
1276
- - Broadphase (BVH × 2 vs default NaiveBroadphase O(N²))
1277
- - Body iteration scaling (O(awake) vs O(total))
1278
- - Atomic island sleep + atomic wake (vs per-body chatter on weakly-connected piles)
1279
- - Persistent manifold cache with cross-frame warm-start (vs frame-rebuilt contact list)
1280
- - CCD floor via speculative margin (vs nothing)
1281
- - Disk-clamped friction cone (vs polyhedral box clamp)
1282
- - Zero-allocation hot paths in narrowphase + heightmap (vs string-key dict, Array.push)
1283
- - shapeCast + overlap queries (Cannon has neither)
1284
- - ECS decomposition + observability hooks
1285
-
1286
- **Where Cannon is ahead or has the better idea:**
1287
- - SPOOK constraint formulation (vs our Baumgarte + cap hack)
1288
- - Joints / Constraints library (we have none)
1289
- - General convex polyhedron shape (we have only `BoxShape3D`)
1290
- - Per-shape exact raycast (vs our AABB-only result)
1291
- - Per-mesh BVH (Octree) for large triangle meshes (vs our O(N) linear scan)
1292
- - Per-body sleep/wake event listeners (we have signals at system level only)
1293
-
1294
- **Headline structural insight:** Cannon is "what JS rigid-body
1295
- physics looked like in 2014, slowly de-allocated by community
1296
- maintenance to 2023." We are "what JS rigid-body physics could be in
1297
- 2026 if you start from the modern reference architectures
1298
- (Jolt/Bullet/Box2D) and then constrain to pure JS." The
1299
- ecosystem-position is genuinely interesting — there's no other
1300
- pure-JS engine attempting our design ambitions.
1
+ # Physics Engine Review: meep vs. cannon-es
2
+
3
+ A deep technical comparison of the meep in-house rigid-body engine
4
+ (`H:/git/moh/app/src/mir-engine/meep/src/engine/physics/`) against
5
+ **cannon-es** (https://github.com/pmndrs/cannon-es), the
6
+ community-maintained fork of Stefan Hedman's **cannon.js**
7
+ (https://github.com/schteppe/cannon.js).
8
+
9
+ Cannon is uniquely interesting as a reference because it shares every
10
+ hard constraint we work under: **pure JavaScript, single-threaded, no
11
+ SIMD, no SharedArrayBuffer, GC-sensitive hot paths**. Anywhere Cannon
12
+ discovered a JS-specific workaround, it translates directly to us.
13
+ Anywhere we beat Cannon, it's a fair fight on engineering merit — not
14
+ "they had SIMD intrinsics and we didn't".
15
+
16
+ This review respects PLAN.md's documented out-of-scope decisions (pure
17
+ JS, no SAB, no multi-threaded solver, action-log netcode). Cannon
18
+ citations use `src/<dir>/<File>.ts` paths; our citations use
19
+ `path:line` relative to the package root.
20
+
21
+ ---
22
+
23
+ ## 1. Overall Architecture
24
+
25
+ ### Pipeline shape
26
+
27
+ | Stage | meep `PhysicsSystem.fixedUpdate` (`ecs/PhysicsSystem.js:1075`) | cannon-es `World.internalStep` (`src/world/World.ts`) |
28
+ |---|---|---|
29
+ | Apply gravity | Folded into Stage 1 `integrate_velocity` (`integration/integrate_velocity.js`) | Standalone "gravity" loop over all bodies |
30
+ | Forces from user constraints | n/a (no joints in v1) | `for (const c of constraints) c.update()` adds equations |
31
+ | Subsystem callbacks | n/a | `subsystems` user-extension hook |
32
+ | Velocity integration | Stage 1, awake-only | Last (after solver); leap-frog |
33
+ | Broadphase refit | Stage 2, awake-only, fat-AABB `compute_fat_world_aabb.js` | Per-body `updateAABB()` inside `broadphase.collisionPairs` |
34
+ | Pair generation | Stage 3, `generate_pairs.js` — leaf query into both BVHs, dedup via touched bit | `broadphase.collisionPairs(this, p1, p2)` |
35
+ | Wake propagation | Stage 4, explicit `__wake_pairs` over the pair list | Implicit — narrowphase sets `wakeUpAfterNarrowphase = true` when relative speed exceeds threshold |
36
+ | Narrowphase | Stage 5, `narrowphase_step.js` cascade `if (a_kind && b_kind) ...` | `Narrowphase.getContacts(p1, p2, ...)` giant switch dispatch |
37
+ | Island build | Stage 6, `IslandBuilder` union-find CSR | Lazy in `SplitSolver` (BFS over equation graph), or no partitioning if using bare `GSSolver` |
38
+ | Solver | Stage 7, `solve_contacts.js` per island, 10 iters | `solver.solve(dt, this)` over the flat equation list |
39
+ | Position integration | Stage 8 | Same as velocity (leap-frog `pos += velo * dt`) |
40
+ | Sleep | Stage 9, per-island atomic | Stage 14, per-body `body.sleepTick(time)` |
41
+ | Damping | Folded into integrators | Stage 11, explicit multiplicative decay |
42
+ | Event diff | Stage 10, `diff_manifolds` → Begin/Stay/End | `collisionMatrix` flip + `collide`/`endContact` events from narrowphase |
43
+ | End-of-step | Stage 11, `manifolds.advance_frame()` (roll touched, evict grace-expired) | `clearForces()` — accumulators wiped |
44
+
45
+ The single most consequential structural divergence is **what each step
46
+ iterates over**. Cannon's documented `internalStep` walks **every body**
47
+ on every pass (`for (let i = 0; i !== N; i++) { const bi = bodies[i] }`
48
+ appears repeatedly in `World.ts`), with sleep state being a property
49
+ checked inline rather than a filter. We instead iterate
50
+ `storage.awake_at(i)` for `i in [0, awake_count)` — the awake set is a
51
+ dense `Uint32Array` (`body/BodyStorage.js:101-110`) and grows / shrinks
52
+ via swap-with-last (`body/BodyStorage.js:330-337`). At million-body
53
+ scale where 99% of bodies are mostly sleeping (the design target in
54
+ PLAN.md), we pay O(awake) per stage while Cannon pays O(total). This
55
+ is **the** scalability story between the two engines.
56
+
57
+ ### Data layout — body pool
58
+
59
+ | Aspect | meep `BodyStorage` | cannon-es `Body` |
60
+ |---|---|---|
61
+ | ID encoding | 24-bit index ǁ 8-bit generation (`body/BodyStorage.js:11-19`) | Bare object reference (`Body` instance) + monotonically increasing `id` |
62
+ | Slot reuse policy | Min-heap of free indices for deterministic reuse (`body/BodyStorage.js:154-176`, `383-426`) | No reuse — destroyed bodies are GC'd |
63
+ | Active list | Dense `Uint32Array __awake_list` + reverse map `__awake_pos` (`body/BodyStorage.js:101-103`) | None — bodies live in `World.bodies: Body[]` regardless of sleep state |
64
+ | Body state storage | SoA — `__entities`, `__generations`, `__kinds`, `__flags` as parallel typed arrays. Hot mutable state (velocity / accumulators) on `RigidBody` instances | AoS — every field on the `Body` class instance (`position: Vec3`, `velocity: Vec3`, `force: Vec3`, `torque: Vec3`, `sleepState: number`, etc.) |
65
+ | Iteration | `for (let i = 0; i < awake_count; i++) { const idx = awake_at(i); const rb = __bodies[idx]; ... }` | `for (let i = 0; i !== N; i++) { const bi = bodies[i]; if (bi.sleepState === Body.SLEEPING) continue; ... }` |
66
+ | Generation safety | Stale packed id detected via `is_valid()` mask + generation check (`body/BodyStorage.js:210-219`) | Stale references possible (caller is responsible) |
67
+
68
+ **Where we win:** SoA identity tables + awake-list filtering. Hot
69
+ streaming reads of cold flags / kinds / generations don't pull
70
+ unrelated body fields into cache, and disconnected sleeping bodies
71
+ contribute literally zero work per step. Cannon's per-body
72
+ `sleepState` check is cheap-but-not-free, and on a scene of 1M static
73
+ chairs in a town the difference is dramatic.
74
+
75
+ **Where Cannon wins (or rather, "is more idiomatic"):** Cannon's
76
+ `Body` is a single class with every field on it. V8 monomorphises
77
+ this beautifully — every `body.velocity` access is the same hidden
78
+ class lookup. There's no parallel-array index arithmetic, no
79
+ `body_id_index(packed)` mask-and-shift. Debugging is plain
80
+ object inspection. Our split (RigidBody for the JS-side hot state,
81
+ BodyStorage for identity SoA, Collider list as a separate per-body
82
+ array, transforms on a Transform component) costs a few extra
83
+ indirections per body in the solver inner loop — but those are array
84
+ indexed loads, not pointer-chases, so still cache-friendly.
85
+
86
+ The crucial nuance: **even Cannon's `Body` AoS is "AoS per body but
87
+ cache-unfriendly per pass"**. When the integrator wants only
88
+ `position`, `velocity`, and `force`, it still pulls all 25-ish other
89
+ fields of `Body` into cache. Pure SoA (one `Float64Array` per
90
+ attribute) would be better, but we don't go fully SoA on the hot
91
+ fields either — `RigidBody.linearVelocity` is a `Vector3` extending
92
+ `Float64Array`, accessed as `lv[0]/[1]/[2]` (`solver/solve_contacts.js:427`).
93
+ Our SoA win is therefore narrower than the layout suggests; the
94
+ honest comparison is "we got the identity / scheduling pieces into
95
+ SoA, the per-body kinematic state is still AoS like Cannon's, just
96
+ with typed-array storage". The Jolt/Bullet style of fully SoA kinematic
97
+ state remains a future optimisation.
98
+
99
+ ### Broadphase
100
+
101
+ | Engine | Strategies | Default |
102
+ |---|---|---|
103
+ | meep | One: BVH × 2 (`staticBvh` + `dynamicBvh`), with fat-AABB refit (`broadphase/compute_fat_world_aabb.js`) | The only choice |
104
+ | cannon-es | Three: `NaiveBroadphase` (O(N²)), `GridBroadphase` (uniform grid, spheres+planes only), `SAPBroadphase` (single-axis sweep & prune) | `NaiveBroadphase` |
105
+
106
+ `NaiveBroadphase.collisionPairs` is literally:
107
+ ```js
108
+ for (let i = 0; i !== n; i++) {
109
+ for (let j = 0; j !== i; j++) {
110
+ if (!this.needBroadphaseCollision(bi, bj)) continue;
111
+ this.intersectionTest(bi, bj, pairs1, pairs2);
112
+ }
113
+ }
114
+ ```
115
+ The class docstring even concedes the algorithm's complexity is "N²
116
+ (which is bad)". Cannon's expectation is that users will swap in
117
+ `SAPBroadphase` for scenes with more than a few hundred bodies. SAP
118
+ uses insertion-sort on one axis (chosen via `autoDetectAxis()` —
119
+ variance maximisation), which handles small frame-to-frame motion in
120
+ ~O(N) but degrades to O(N²) on sudden mass motion. Cannon makes no
121
+ attempt at a tree.
122
+
123
+ **Our BVH split with awake-list-driven pair generation is
124
+ unambiguously better at our target scale.** At 1M bodies the
125
+ NaiveBroadphase is 10¹² pair checks per frame — completely
126
+ unusable. Our `generate_pairs.js:50-99` does one BVH query per
127
+ awake-leaf into each of two BVHs and dedups via the manifold
128
+ touched flag, scaling O(awake · log(total)) at worst. This is a
129
+ *million-fold* difference at the design-target scale.
130
+
131
+ ### Island structure
132
+
133
+ Cannon ships **`SplitSolver`** as an opt-in wrapper around `GSSolver`.
134
+ On each `solve()` call it builds a graph (every body is a node, every
135
+ equation an edge), runs BFS to find connected components, then calls
136
+ the inner solver per island. Each rebuild allocates fresh `nodes`,
137
+ `children`, and intermediate arrays — there's an internal pool but
138
+ graph-shape changes between frames defeat it. Cannon's island
139
+ construction is also coupled to the solver, not to the contact
140
+ generator, so it's purely a solver-side optimisation; sleep and
141
+ events use unrelated machinery (per-body sleep tick, per-pair
142
+ narrowphase-driven events).
143
+
144
+ We pull islands up to a first-class pipeline stage with `IslandBuilder`
145
+ (`island/IslandBuilder.js`) producing CSR-shaped output that the
146
+ solver and sleep test *both* consume. Bodies are sorted ascending
147
+ within an island and islands ascending by root index, giving us
148
+ **determinism by construction** without sorting at the solver
149
+ boundary. The PLAN.md atomic-sleep + atomic-wake story (sleep groups
150
+ threaded via `sleep_group_next/prev` so waking any member walks the
151
+ chain) is structurally impossible in Cannon's per-body model — see §3.
152
+
153
+ ### Solver
154
+
155
+ Cannon's `GSSolver` is a beautifully general sequential-impulse
156
+ solver over a flat equation list (contacts, friction, joints all the
157
+ same shape: an `Equation` with `computeB`, `computeC`, lambda
158
+ bounds). It implements the **SPOOK formulation** (Lacoursière /
159
+ Erleben) of constraint regularisation: `a = 4/(h*(1+4*d))`, `b =
160
+ 4d/(1+4d)`, `eps = 4/(h*h*k*(1+4*d))` parameterised by stiffness `k`
161
+ and relaxation `d` (`src/equations/Equation.ts`). Per-iteration:
162
+
163
+ ```js
164
+ deltalambda = invC * (B - GWlambda - c.eps * lambdaj);
165
+ ```
166
+ The `c.eps * lambdaj` term is what makes SPOOK different from naive
167
+ Baumgarte — it's **continuous regularisation**, applied at every
168
+ iteration to soften the constraint, rather than a one-shot velocity
169
+ bias. Defaults: `stiffness = 1e7, relaxation = 4, iterations = 10`.
170
+
171
+ Our `solve_contacts.js` is a more specialised contacts-only sequential
172
+ impulse solver: warm-start replay (`solver/solve_contacts.js:466-477`),
173
+ Baumgarte position correction folded into the velocity bias
174
+ (`solver/solve_contacts.js:442-450`), Coulomb friction with disk-clamp
175
+ (`solver/solve_contacts.js:566-571`), per-island iteration
176
+ (`solver/solve_contacts.js:308-313`). 10 iterations default
177
+ (matches Cannon). The split-impulse / split-position-pass story is
178
+ discussed in §3.
179
+
180
+ ### Sleep system
181
+
182
+ Cannon's per-body sleep is the chatter-prone classic algorithm.
183
+ `Body.sleepTick(time)` (verbatim from `src/objects/Body.ts`):
184
+ ```js
185
+ if (this.allowSleep) {
186
+ const sleepState = this.sleepState;
187
+ const speedSquared = velocity² + angularVelocity²;
188
+ const speedLimitSquared = this.sleepSpeedLimit ** 2;
189
+ if (sleepState === Body.AWAKE && speedSquared < speedLimitSquared) {
190
+ this.sleepState = Body.SLEEPY;
191
+ this.timeLastSleepy = time;
192
+ } else if (sleepState === Body.SLEEPY && speedSquared > speedLimitSquared) {
193
+ this.wakeUp();
194
+ } else if (sleepState === Body.SLEEPY && time - this.timeLastSleepy > this.sleepTimeLimit) {
195
+ this.sleep();
196
+ }
197
+ }
198
+ ```
199
+ This is the failure mode that **specifically motivates** our atomic
200
+ sleep: in a stack of 100 blocks, frame N the bottom block falls
201
+ asleep, frame N+1 a top block's micro-jitter wakes a middle block via
202
+ broadphase pair propagation, frame N+2 the wake cascades down,
203
+ frame N+3 the bottom re-wakes. The whole stack chatters indefinitely.
204
+ PLAN.md "Sleep + events" calls this out explicitly:
205
+
206
+ > "Per-island atomic sleep: an island sleeps when `max(|v|² + |ω|²)`
207
+ > across all members stays below the threshold long enough; the whole
208
+ > island sleeps in the same frame. Replaces the per-body chatter on
209
+ > weakly-connected piles."
210
+
211
+ Our `__sleep_test` (`ecs/PhysicsSystem.js:942-1006`) walks each
212
+ island, takes `max(v² + ω²)` over all members, increments every
213
+ member's timer if-and-only-if the **island as a whole** is below
214
+ threshold, and atomically sleeps all members the same frame
215
+ (`__atomic_sleep_island_range` at `:754-786`). The dual atomic-wake
216
+ walks the `sleep_group_next` circular DLL (`:705-737`).
217
+
218
+ Cannon has no equivalent. The `wakeUpAfterNarrowphase` flag is a
219
+ one-frame deferral, not an island-aware operation.
220
+
221
+ ### Threading
222
+
223
+ Both single-threaded by deliberate design.
224
+
225
+ ---
226
+
227
+ ## 2. Specific Algorithms and Tradeoffs
228
+
229
+ ### Body storage — class instances vs SoA + stable IDs
230
+
231
+ Cannon's `Body` declares ~30 mutable public fields including
232
+ `position`, `velocity`, `quaternion`, `angularVelocity`, `force`,
233
+ `torque`, `mass`, `linearDamping`, `angularDamping`, `linearFactor`,
234
+ `angularFactor`, `sleepState`, `allowSleep`, `collisionFilterGroup`,
235
+ `collisionFilterMask`, `collisionResponse`, `isTrigger`, `shapes`,
236
+ `shapeOffsets`, `shapeOrientations`, `aabb`, `boundingRadius`,
237
+ `inertia`, `invInertia`, `invInertiaWorld`. They're set in the
238
+ constructor with `||` default coalescing — typical Cannon idiom is
239
+ `this.mass = typeof options.mass === 'number' ? options.mass : 0`.
240
+ V8 monomorphises `Body` instances aggressively because the
241
+ initialisation order is fixed and every field gets a typed default.
242
+
243
+ Our split:
244
+ - `RigidBody` (`ecs/RigidBody.js`) — JS-side hot state (velocity,
245
+ accumulators, damping, mass, gravity scale, sleep state). Class
246
+ fields declared at class-body scope (`= new Vector3(0,0,0)` etc.),
247
+ same V8 hidden-class win.
248
+ - `BodyStorage` — identity SoA (`__entities`, `__generations`,
249
+ `__kinds`, `__flags`, `__alive` as parallel typed arrays) and
250
+ scheduling (`__awake_list`).
251
+ - `Collider` (component, attached separately, possibly compound).
252
+ - `Transform` (independent, shared with rendering / IK / etc.).
253
+
254
+ **GC pressure.** Cannon allocates one `Body` per body, plus the
255
+ `Vec3` / `Quaternion` instance for each field. A scene of 100k bodies
256
+ is ~100k bodies × ~10 Vec3/Quat ≈ 1M small objects on the heap, all
257
+ of which the GC has to walk on every major collection. We allocate
258
+ ~100k bodies × 4 Vector3s (linear/angular vel, accumulated
259
+ force/torque, inertia) ≈ 400k small objects — better, but still on
260
+ the heap. Where we materially win is the **per-step work**: our SoA
261
+ identity tables are TypedArray slabs that don't appear in GC
262
+ traversal at all, and we don't allocate per-step.
263
+
264
+ **Iteration speed.** For the solver inner loop the comparison is a
265
+ wash: both engines access `rb.linearVelocity[0..2]` (us, since
266
+ Vector3 extends Float64Array) or `body.velocity.x/y/z` (Cannon). V8
267
+ should compile both to the same machine code. The difference appears
268
+ at the broadphase / wake phase, where Cannon's per-body iteration
269
+ pulls the whole `Body` into cache for a `sleepState` check while ours
270
+ reads a single byte from `__alive` / `__awake_pos` and jumps over the
271
+ slot.
272
+
273
+ **Observability.** Cannon wins here — `console.log(world.bodies[42])`
274
+ shows you everything. Our `system.__bodies[idx]` shows RigidBody but
275
+ not its colliders, transform, or storage entry; you have to know to
276
+ look in three places. This is the cost of decomposition; it's a
277
+ trade we deliberately made for ECS clarity and we shouldn't undo it.
278
+ But adding a `dump(body_idx)` helper that aggregates the four
279
+ sources would be cheap and would close the gap.
280
+
281
+ ### Broadphase
282
+
283
+ Already covered above; the headline is that **Cannon ships three
284
+ broadphases and defaults to the worst one**. The `NaiveBroadphase`
285
+ default exists for tutorial-grade simplicity ("first 50 bodies just
286
+ work, then read the docs"). For a real physics engine `SAPBroadphase`
287
+ is the standard choice and it's still single-axis SAP, which is
288
+ state-of-the-art-1990s.
289
+
290
+ A subtle point worth flagging: cannon-es's `SAPBroadphase` uses
291
+ event listeners on the World (`world.addEventListener('addBody',
292
+ ...)`) to keep its `axisList` in sync. This is allocation-free per
293
+ step but has a hidden cost — the `addBody` / `removeBody` events fire
294
+ synchronously on dataset mutations, and adding / removing many bodies
295
+ mid-step is **explicitly fragile in Cannon**. We sidestep this
296
+ entirely because our broadphase reads `storage.awake_at(i)` directly
297
+ and the BVH allocates/frees its own nodes on
298
+ `attach_collider`/`detach_collider`. No event-listener indirection.
299
+
300
+ ### Narrowphase pairs — SAT vs GJK/EPA + closed-form
301
+
302
+ This is the **deepest structural divergence**. Cannon has
303
+ **no GJK and no EPA**. Its entire narrowphase is built on:
304
+
305
+ 1. Closed-form per-pair handlers (`sphereSphere`, `spherePlane`,
306
+ `sphereBox`, `boxBox`, etc. — a large method-per-pair switch on
307
+ `Narrowphase.getContacts`)
308
+ 2. **SAT** for arbitrary `ConvexPolyhedron` shapes (face normals + edge
309
+ crosses), followed by
310
+ 3. **Sutherland-Hodgman clipping** of the incident face against the
311
+ reference face's bounding planes to extract a multi-point manifold
312
+
313
+ Our cascade in `narrowphase/narrowphase_step.js:296-782`:
314
+
315
+ | Pair | Path | Cannon equivalent |
316
+ |---|---|---|
317
+ | sphere-sphere | `sphere_sphere_contact` closed-form (`:309-325`) | `Narrowphase.sphereSphere` |
318
+ | sphere-box | `sphere_box_contact` closed-form, handles centre-inside-box (`:328-359`) | `Narrowphase.sphereBox` |
319
+ | capsule-X | `capsule_*` closed-form family (`:397-483`) | Cannon has no capsule shape |
320
+ | box-box | SAT + Sutherland-Hodgman in `box_box_manifold.js` (`:362-394`) | `ConvexPolyhedron` via `Narrowphase.convexConvex` — same algorithm, more general implementation |
321
+ | convex × concave (heightmap/mesh) | Per-triangle GJK+EPA via decomposition dispatcher (`:498-713`) | `convexTrimesh` / `convexHeightfield` — Trimesh BVH-queries triangles, each treated as a `ConvexPolyhedron` for SAT |
322
+ | anything else convex | GJK + EPA fallback (`:715-781`) | **No fallback exists** — must be `ConvexPolyhedron` with explicit faces |
323
+
324
+ **The structural tradeoff:** SAT works only on shapes with explicit
325
+ face / edge lists. Cannon can't represent a smooth shape (sphere is
326
+ its own special case; capsule, cylinder, cone, ellipsoid, generic
327
+ convex-hull-from-point-cloud — all impossible without an explicit
328
+ polyhedral mesh). We can, via GJK on the support function. But our
329
+ EPA degenerates on smooth shapes (PLAN.md "Limitations / Known
330
+ caveats") so the *practical* shape coverage of the two engines is
331
+ closer than the algorithm choice suggests.
332
+
333
+ **Cannon's `ConvexPolyhedron.clipFaceAgainstHull`** is its quality
334
+ crown jewel for convex-convex contacts. The pipeline:
335
+
336
+ 1. `findSeparatingAxis` iterates face normals of A, then face normals
337
+ of B, then 9 (or `uniqueAxesA × uniqueAxesB`) edge-cross axes.
338
+ Tracks the minimum penetration depth across all axes. Returns the
339
+ normal of the deepest axis or `false` (no overlap).
340
+ 2. Choose the witness face on B whose normal is most parallel to the
341
+ separating normal — this is the *incident face*.
342
+ 3. Clip the incident face polygon against every neighbouring face of
343
+ A's witness face, treated as a half-space. After all clips, the
344
+ surviving vertices that lie *behind* the witness face become
345
+ contact points. Snippet (paraphrased from the source):
346
+
347
+ ```js
348
+ for (let i = 0; i < numVerticesA; i++) {
349
+ localPlaneNormal.copy(this.faceNormals[otherFace]);
350
+ this.clipFaceAgainstPlane(pVtxIn, pVtxOut, planeNormalWS, planeEqWS);
351
+ // swap buffers
352
+ }
353
+ // finally:
354
+ for (let i = 0; i < pVtxIn.length; i++) {
355
+ let depth = planeNormalWS.dot(pVtxIn[i]) + planeEqWS;
356
+ if (depth <= maxDist && depth <= 1e-6) {
357
+ result.push({ point, normal, depth });
358
+ }
359
+ }
360
+ ```
361
+
362
+ Our `box_box_manifold.js` does exactly the same thing but specialised
363
+ to two boxes (3 face normals × 2 + 9 edge crosses = 15 axes, hard-coded).
364
+ The result is 0..4 contact points per box-box pair, same as Cannon.
365
+ **The major gap:** we have no general convex-hull shape, only `BoxShape3D`.
366
+ If we add `ConvexHullShape3D` (on PLAN.md backlog), we'll want
367
+ basically Cannon's `ConvexPolyhedron.clipFaceAgainstHull` ported. The
368
+ algorithm is already in our codebase as the inner clipping pass of
369
+ `box_box_manifold.js`; the missing piece is the SAT outer loop
370
+ that iterates an arbitrary face/edge list rather than the hard-coded 15.
371
+
372
+ ### GJK / EPA — what each engine can actually handle
373
+
374
+ | Shape pair | meep | cannon-es |
375
+ |---|---|---|
376
+ | sphere–sphere | closed-form | closed-form |
377
+ | sphere–box | closed-form (interior-handling) | closed-form |
378
+ | sphere–plane | closed-form (via box flat path) | closed-form |
379
+ | sphere–convex polyhedron | GJK + EPA (works) | `sphereConvex` via vertex-iteration; works for explicit hulls |
380
+ | sphere–trimesh | per-triangle GJK + EPA via decomp | `sphereTrimesh` via BVH triangle query |
381
+ | capsule–anything | closed-form for cap-sphere / cap-cap / cap-box | n/a (no capsule shape) |
382
+ | convex–convex | GJK + EPA fallback (may degenerate on smooth) | SAT + clip via `ConvexPolyhedron` |
383
+ | convex–trimesh | per-triangle GJK + EPA (PLAN.md "drop and settle" tests skipped) | per-triangle SAT + clip (more reliable for polyhedral cases) |
384
+ | concave–concave | refused | not supported |
385
+ | anything smooth | GJK works for overlap test; EPA degenerates on depth recovery | impossible — needs polyhedral approximation |
386
+
387
+ The structural picture is: **Cannon trades expressivity for
388
+ robustness on its supported shape set**. We trade robustness on
389
+ smooth shapes for general support-function-based collision. The cost
390
+ of our choice is the EPA-on-smooth-shapes failure mode, which we
391
+ mitigate by routing all common pairs through closed-form handlers.
392
+ PLAN.md "Limitations / Known caveats" calls this out:
393
+
394
+ > "EPA on smooth shapes: degenerates (no flat face to converge on).
395
+ > Mitigated by closed-form paths for sphere/cube/capsule pairs; exotic
396
+ > convex shapes vs spheres can still fail."
397
+
398
+ ### MPR (Minkowski Portal Refinement)
399
+
400
+ Cannon has nothing comparable. We have `gjk/mpr.js` (XenoCollide,
401
+ Snethen GDC 2009) but it's not yet wired into `narrowphase_step` —
402
+ PLAN.md "Alternative narrowphase: MPR" calls it a candidate for
403
+ falling back when EPA stalls. **Wiring it as the fallback for
404
+ EPA non-convergence is a clear next step** — concrete payoff: torus-knot
405
+ test (currently skipped), ball-on-curved-surface contact stability.
406
+
407
+ ### Solver — SPOOK vs Baumgarte
408
+
409
+ Cannon's solver is built on the **SPOOK formulation** described in
410
+ Lacoursière's PhD thesis and Erleben's papers. The key equations
411
+ (verbatim from `src/equations/Equation.ts.setSpookParams`):
412
+
413
+ ```js
414
+ a = 4.0 / (h * (1 + 4 * d)); // bias gain (analogous to Baumgarte β/h)
415
+ b = (4.0 * d) / (1 + 4 * d); // velocity-error gain
416
+ eps = 4.0 / (h * h * k * (1 + 4 * d)); // regularisation
417
+ ```
418
+
419
+ And the per-iteration update (`GSSolver.solve`):
420
+ ```js
421
+ deltalambda = invC * (B - GWlambda - c.eps * lambdaj);
422
+ // then clamped to [minForce*h, maxForce*h]
423
+ ```
424
+
425
+ The interesting term is **`c.eps * lambdaj`** — the regularisation
426
+ softens the constraint at every iteration in a continuous way, not as
427
+ a one-shot velocity bias. This is fundamentally different from
428
+ Baumgarte: instead of "if penetration > slop, add a corrective
429
+ velocity bias", SPOOK adds a per-iteration drag on the lambda that
430
+ makes the constraint behave like a stiff spring with controlled
431
+ damping. `eps` scales as `1 / (h² k)` — at our 60 Hz timestep
432
+ (`h ≈ 0.0167`) with `k = 1e7`, `eps ≈ 0.024` for `d = 4`. Cannon's
433
+ default produces a stable, soft constraint.
434
+
435
+ Our `solver/solve_contacts.js:442-450`:
436
+ ```js
437
+ let bias = 0;
438
+ if (depth > PENETRATION_SLOP) {
439
+ bias = -BAUMGARTE_BETA / dt * (depth - PENETRATION_SLOP);
440
+ if (bias < -MAX_BAUMGARTE_BIAS) bias = -MAX_BAUMGARTE_BIAS;
441
+ }
442
+ ```
443
+ This is classic Catto / Box2D-Lite Baumgarte: one-shot velocity bias
444
+ proportional to penetration depth minus slop, with a hard cap to stop
445
+ EPA-misreported deep penetrations from launching bodies. The cap is
446
+ itself an admission that pure Baumgarte is fragile against bad depth
447
+ readings — SPOOK's `eps` doesn't have this failure mode because it
448
+ doesn't try to fully correct in one tick.
449
+
450
+ **SPOOK is genuinely more principled and should be considered as a
451
+ swap-in.** Concretely:
452
+
453
+ 1. Replace `bias = -BAUMGARTE_BETA / dt * (depth - PENETRATION_SLOP)`
454
+ with `bias = -a * (depth - PENETRATION_SLOP)` where `a` comes from
455
+ `setSpookParams(k, d, dt)`. Choose `k = 1e7` and `d = 4` as
456
+ Cannon does (these are the field-tested defaults).
457
+ 2. Add the regularisation term to the iteration update — currently
458
+ `lambda_n = -m_eff_n * (vn + bias_n)`, becomes
459
+ `lambda_n = -m_eff_n * (vn + bias_n + eps * j_n_accum)` and
460
+ `m_eff_n = 1 / (k_n + eps)`.
461
+ 3. The `MAX_BAUMGARTE_BIAS` cap can probably be removed — SPOOK's
462
+ softness handles inflated-depth EPA outputs gracefully because
463
+ the regularisation prevents lambda from running away.
464
+
465
+ This is the most concrete and highest-confidence improvement
466
+ opportunity in the review. **Estimated impact:** tall-stack
467
+ stability comparable to Cannon (which handles tens of bodies without
468
+ substepping using SPOOK + 10 iterations), removal of the inflated-EPA
469
+ launch failure mode, more robust feel without changing the public
470
+ API. **Risk:** SPOOK changes contact feel slightly — bodies "settle"
471
+ rather than "snap" to non-penetration. PLAN.md's note about TGS
472
+ substepping being blocked on split-impulse becomes much less urgent.
473
+
474
+ ### Manifold caching — persistent vs per-frame rebuild
475
+
476
+ Cannon has **no persistent manifold cache**. Every step,
477
+ `Narrowphase.getContacts` rebuilds the contact list from scratch:
478
+
479
+ ```
480
+ const oldcontacts = World_step_oldContacts;
481
+ for (i = 0; i !== NoldContacts; i++) { oldcontacts.push(contacts[i]); }
482
+ contacts.length = 0;
483
+ this.narrowphase.getContacts(p1, p2, ..., oldcontacts, ...);
484
+ ```
485
+
486
+ The "oldcontacts" array is a **pool of allocated `ContactEquation`
487
+ objects** to recycle — pool-via-array-of-instances, not warm-start
488
+ information. The per-pair contact identity is rebuilt every frame,
489
+ which means **Cannon cannot do per-pair warm-start at all**. It can
490
+ warm-start within the solver iteration (lambda persists across
491
+ iterations within one frame's solve call) but not across frames.
492
+
493
+ Our `contact/ManifoldStore.js` is a real persistent manifold cache,
494
+ designed exactly like Bullet's `btPersistentManifold`:
495
+ - Keyed by canonical body pair via `PairUint32Map`
496
+ (`ManifoldStore.js:114`).
497
+ - Up to 4 contacts per slot (`MAX_CONTACTS_PER_MANIFOLD = 4`,
498
+ `ManifoldStore.js:10`).
499
+ - Per-contact stride includes `j_n`, `j_t1`, `j_t2` accumulated
500
+ impulses (`ManifoldStore.js:13-33`) that survive across frames.
501
+ - Touched / prev_touched / grace flag scheme so transient one-frame
502
+ separations don't evict the manifold (`ManifoldStore.js:57-69`,
503
+ `416-445`).
504
+ - `begin_refill(slot)` explicitly preserves impulse fields while
505
+ overwriting geometry (`ManifoldStore.js:265-268`).
506
+
507
+ And `narrowphase_step.js:837-931` implements **match-and-merge**:
508
+ candidates from this frame are matched to previous-frame contacts by
509
+ **feature_id first, position-fallback within 2 cm second**, with the
510
+ matched prev-contact's impulses copied to the corresponding new slot
511
+ index. This is a genuine warm-start that survives:
512
+ - Sphere-on-box sliding (feature_id is the box voronoi region,
513
+ `narrowphase_step.js:226-242`, stable while the sphere stays on the
514
+ same face/edge).
515
+ - Triangle-decomposition contacts (feature_id is the triangle index,
516
+ stable per-triangle, `narrowphase_step.js:590`).
517
+ - Box-box (feature_id = 0, falls back to position matching within
518
+ `MATCH_TOL_SQR = 0.0004 m²`).
519
+
520
+ **Status correction.** Prior reviews (Bullet, Jolt, Rapier) flagged
521
+ "warm-start impulses wiped every frame" — that was true at the time
522
+ of those reviews. Reading `ManifoldStore.js` and `narrowphase_step.js`
523
+ as they stand today, **warm-start is wired and working** on the
524
+ steady-state contact path. The only branches that wipe impulses are
525
+ `clear_contacts(slot)` calls (when narrowphase determines no contact
526
+ or empty collider lists), which is correct behaviour — separated
527
+ contacts should not warm-start. The `clear_impulses(slot, k)` for
528
+ unmatched-by-match-and-merge candidates is also correct: a fresh
529
+ contact at a slot index with stale data shouldn't inherit it.
530
+
531
+ This is **a substantial structural advantage over Cannon**. Cannon's
532
+ solver pays 10 iterations of convergence from a cold start every
533
+ frame; ours starts from the previous frame's solution and typically
534
+ converges in 2-3 iterations (with the remaining 7-8 absorbing
535
+ geometry changes). For tall stacks specifically, this is the
536
+ difference between PGS-with-warm-start being usable and needing TGS
537
+ substepping.
538
+
539
+ **Improvement opportunity:** consider lowering iteration count from
540
+ 10 to e.g. 6 for steady-state, with a wake-event re-bump to 10 for
541
+ the first few frames after wake. The savings would scale with the
542
+ warm-start hit rate.
543
+
544
+ ### Sleep — chatter vs atomic
545
+
546
+ Already covered above. Cannon's per-body sleep chatters on
547
+ weakly-connected piles; ours is atomic per island. The improvement
548
+ opportunity is in the **other** direction — Cannon's `sleepTick`
549
+ hooks `Body.dispatchEvent(Body.sleepyEvent)` /
550
+ `Body.dispatchEvent(Body.wakeupEvent)` so user code can subscribe.
551
+ We have `Signal onContactBegin/Stay/End` but no `onBodySleep` /
552
+ `onBodyWake` events. For game code (turn music off when player
553
+ stops, despawn enemies that have been asleep for N seconds), this is
554
+ a missing affordance.
555
+
556
+ ### CCD (Continuous Collision Detection)
557
+
558
+ Cannon has no meaningful CCD — no per-body sweep, no speculative
559
+ margin. Fast-moving bodies tunnel.
560
+
561
+ We have **speculative margin** via the fat-AABB swept extent:
562
+ `compute_fat_world_aabb` pads the broadphase AABB by `linearVelocity
563
+ * dt`, so the broadphase catches imminent collisions and the
564
+ narrowphase sees the future-position pair early. PLAN.md
565
+ "Limitations / Known caveats" notes this is "CCD floor only" — the
566
+ falling-tower repro at 1 km drop onto 1 cm floor still tunnels 180
567
+ out of 1000 bodies because a single-step sweep isn't a true
568
+ swept-shape cast. PLAN.md backlog has "Per-body linear CCD
569
+ shape-cast" as the planned fix.
570
+
571
+ **We are categorically ahead of Cannon here.** Even our "CCD floor"
572
+ catches everything Cannon misses.
573
+
574
+ ### Concave / triangle mesh
575
+
576
+ Cannon's `Trimesh` stores vertices in a `Float32Array`, indices in
577
+ `Int16Array`, normals in `Float32Array`. The narrowphase uses an
578
+ `Octree` to query triangles overlapping the convex's AABB. Each
579
+ returned triangle is **treated as a `ConvexPolyhedron`** for the
580
+ convex-convex SAT + clip path, which is correctness-wise solid for
581
+ polyhedral convex shapes but doesn't help with smooth shapes (which
582
+ Cannon doesn't have anyway).
583
+
584
+ Our `MeshShape3D` + `mesh_enumerate_triangles` does an O(N) linear
585
+ scan with tight per-triangle AABB filtering. No BVH per mesh.
586
+ **This is genuinely a regression compared to Cannon's Octree** for
587
+ large meshes (>10k triangles). PLAN.md doesn't list this as a
588
+ backlog item, but should — a per-mesh BVH built once at attach time
589
+ is exactly the same machinery as the global broadphase BVH, just
590
+ indexed by triangle. **Improvement opportunity: build a per-mesh
591
+ triangle BVH in `MeshShape3D` construction (or first-query), query
592
+ it instead of linear scanning.**
593
+
594
+ Both engines have boundary-edge correctness issues at internal
595
+ triangle junctions. Cannon's polyhedron-per-triangle treats each
596
+ triangle's edges as collision-active, producing the well-known
597
+ "phantom contacts on internal edges" bug. Ours has the same problem
598
+ plus the EPA-on-degenerate-triangle issue (PLAN.md "Limitations").
599
+ Neither engine implements Catto's "voronoi region triangle culling"
600
+ fix. **PLAN.md "Closed-form triangle-vs-primitive solvers"** is the
601
+ correct prescription here.
602
+
603
+ ### Heightmaps
604
+
605
+ Cannon's `Heightfield` constructs an explicit convex polyhedron per
606
+ cell via `getConvexTrianglePillar(i, j, isUpper)` and caches them in
607
+ a `_cachedPillars` dictionary. Cache key is `"i_j_isUpper"` (string!
608
+ allocates on every lookup). Each pillar is a 5-faced extruded
609
+ triangle that goes from the upper surface down to a fixed depth —
610
+ giving the heightfield a "thickness" rather than the infinite-thin
611
+ surface our heightmap has.
612
+
613
+ Our `HeightMapShape3D` is a `Sampler2D`-backed surface; we sample
614
+ heights via `sampleChannelCatmullRomUV` and emit triangles
615
+ on-demand via `heightmap_enumerate_triangles`. No per-cell convex
616
+ caching, no extrusion — the heightmap is a one-sided surface and
617
+ "falling through" from below is undefined behaviour (we explicitly
618
+ reject contacts where the body is on the wrong side via the
619
+ one-sided face-normal check at `narrowphase_step.js:654`).
620
+
621
+ **Tradeoffs:**
622
+
623
+ | Aspect | meep heightmap | cannon-es Heightfield |
624
+ |---|---|---|
625
+ | Memory | O(1) — `Sampler2D` reference + orientation vector | O(cells × cached_pillars) — each pillar is a `ConvexPolyhedron` with 6 vertices, 5 faces |
626
+ | Per-step cost | Catmull-Rom sample × 4 per cell × triangles touched | Polyhedron support × triangle count touched (SAT) |
627
+ | Smoothness | Catmull-Rom interpolation matches the terrain renderer's geometry | Linear interpolation (raw heights) |
628
+ | Two-sidedness | One-sided (per design) | Two-sided (extruded pillar) |
629
+ | Allocation hot-path | None (rebound `Triangle3D` flyweight) | `_cachedPillars` string-key dict + Object spread on miss |
630
+
631
+ The per-step performance is roughly comparable. The Catmull-Rom
632
+ sampling we do is more expensive per-cell than Cannon's linear
633
+ read, but Cannon's polyhedron-per-cell SAT inflates the constant
634
+ factor on the narrowphase side. **Concrete win for us:** zero
635
+ allocation on the heightmap hot path; Cannon's string-key dict
636
+ allocation is a visible GC pressure source.
637
+
638
+ ### Joints / Constraints
639
+
640
+ Cannon has a rich constraint library: `PointToPointConstraint`,
641
+ `DistanceConstraint`, `HingeConstraint`, `LockConstraint`,
642
+ `ConeTwistConstraint`. All are built on `Constraint` (base class)
643
+ that holds an array of `Equation` objects, plus a polymorphic
644
+ `update()` that recomputes the equations' Jacobians from the current
645
+ body poses. The solver iterates contacts AND joint equations
646
+ uniformly because they all share the SPOOK formulation.
647
+
648
+ We have **none**. PLAN.md "Backlog → Features → Joints" lists this
649
+ as planned ("distance, hinge, ball-socket, prismatic") and notes
650
+ "the solver loop is already set up to iterate contacts ∪ joints;
651
+ only constraint pre-step + warm-start hook is missing."
652
+
653
+ **Reading Cannon's `Constraint` API is the right move for the
654
+ joint implementation**. Specifically the SPOOK-equation pattern
655
+ makes it trivial to add new joints — each new joint type just
656
+ specifies how to compute G, what bounds the lambda has, and what `B`
657
+ should be at this pose. If we adopt SPOOK (see solver section) we
658
+ get this "joints and contacts are uniform" property for free.
659
+
660
+ ### Queries
661
+
662
+ Cannon has `World.raycastClosest`, `raycastAny`, `raycastAll` — each
663
+ walks all bodies, then per-body iterates `shapes[i]`, then calls the
664
+ shape's `raycast` method. The shape-side `raycast` is implemented
665
+ per-shape (`Sphere.raycast`, `Box.raycast` via `ConvexPolyhedron`,
666
+ `Plane.raycast`, `Heightfield.raycast`, `Trimesh.raycast`). No BVH
667
+ traversal — the broadphase isn't consulted for raycasts at all.
668
+
669
+ Our `queries/raycast.js` traverses both BVHs by AABB-ray test and
670
+ reports the nearest hit. Currently the result is the leaf AABB hit,
671
+ not refined to the actual shape geometry — so a raycast against a
672
+ sphere reports a hit on the bounding cube. PLAN.md "Public queries"
673
+ notes this: "narrowphase refinement against the actual shape
674
+ geometry is a follow-up — for now `result.t` is the distance to the
675
+ leaf's inflated AABB". This is a **legitimate gap vs Cannon** —
676
+ ours is broadphase-fast but coarse, Cannon's is exact-shape but
677
+ linear-scan. **Improvement opportunity: add per-shape exact-raycast
678
+ refinement** after broadphase narrows candidates. The closed-form
679
+ sphere/box/capsule cases are trivial; the convex case can use
680
+ shape-cast machinery already in place.
681
+
682
+ `shapeCast` is something **Cannon does not have** in any form. Ours
683
+ sweeps a convex shape via swept-AABB broadphase + GJK bisection,
684
+ reporting time-of-impact and contact normal recovered via EPA. This
685
+ is what character controllers need; Cannon users have to roll their
686
+ own from raycasts.
687
+
688
+ `overlap` similarly has no Cannon equivalent. Ours filters
689
+ broadphase candidates by GJK and writes body ids into a caller-sized
690
+ `Uint32Array`. Useful for AOE selection, kinematic probes.
691
+
692
+ ---
693
+
694
+ ## 3. In-depth comparison of our code against Cannon
695
+
696
+ I'll pick eight touchpoints and walk through them.
697
+
698
+ ### 3.1 Per-step body iteration — O(awake) vs O(total)
699
+
700
+ **Our code, `ecs/PhysicsSystem.js:1083`:**
701
+ ```js
702
+ const count = this.storage.awake_count;
703
+ for (let i = 0; i < count; i++) {
704
+ const idx = this.storage.awake_at(i);
705
+ const rb = this.__bodies[idx];
706
+ const tr = this.__transforms[idx];
707
+ integrate_velocity(rb, tr, gx, gy, gz, dt);
708
+ }
709
+ ```
710
+
711
+ **Cannon's pattern, `src/world/World.ts`:**
712
+ ```js
713
+ for (let i = 0; i !== N; i++) {
714
+ const bi = bodies[i];
715
+ bi.sleepTick(this.time);
716
+ // ... unconditional work happens here, sleep state checked inline
717
+ }
718
+ ```
719
+
720
+ Cannon's loop visits all `N` bodies regardless of sleep state. The
721
+ per-iteration cost is dominated by the field access pattern (cache
722
+ miss on cold bodies) + sleep state branch. For a scene with 1M
723
+ bodies and 100 awake, Cannon does 1M unconditional loads + 1M
724
+ branches; we do 100 indexed loads.
725
+
726
+ This is **the single most important scalability difference between
727
+ the two engines**. At our design target of "millions of mostly
728
+ sleeping bodies" Cannon's per-step cost grows linearly with the
729
+ total body count, ours with the awake count. A 1000:1 ratio
730
+ (realistic for a populated town) is a 1000× speedup.
731
+
732
+ **Improvement on our side:** none — the structure is correct. **The
733
+ risk to keep an eye on:** the per-body collider list lives in
734
+ `this.__body_collider_lists[idx]`, a JS array. Each
735
+ `compute_fat_world_aabb` call inside Stage 2 indirects through
736
+ `list[k].collider.shape.compute_bounding_box(...)`. That's three
737
+ pointer-chases per leaf. For a fully-monomorphic hot loop we could
738
+ flatten the leaves into a per-body Float64Array of `(node_id,
739
+ shape_local_aabb)`. Probably not worth the structural cost until
740
+ benchmarks show it dominating.
741
+
742
+ ### 3.2 SPOOK constraint formulation
743
+
744
+ Side-by-side comparison of the per-iteration update:
745
+
746
+ **Cannon `GSSolver`:**
747
+ ```js
748
+ deltalambda = invC * (B - GWlambda - c.eps * lambdaj);
749
+ ```
750
+ Where `invC = 1 / (G·M⁻¹·Gᵀ + eps)`, `B = -g*a - GW*b - h*GiMf`, and
751
+ `eps` is the SPOOK regularisation.
752
+
753
+ **Ours `solve_contacts.js:531`:**
754
+ ```js
755
+ const lambda_n = -m_eff_n * (vn + bias_n);
756
+ ```
757
+ Where `m_eff_n = 1 / k_n` (no eps), `bias_n = -BAUMGARTE_BETA/dt *
758
+ (depth - slop)` capped at `MAX_BAUMGARTE_BIAS = 3 m/s`, plus
759
+ restitution velocity-target if `vn_pre < -RESTITUTION_VELOCITY_THRESHOLD`.
760
+
761
+ **The qualitative difference:**
762
+
763
+ - **SPOOK is unconditional and continuous.** The `eps * lambdaj` term
764
+ is present every iteration, regardless of depth. It acts like a
765
+ spring damping. As lambda grows, the regularisation eats into it,
766
+ preventing runaway.
767
+ - **Baumgarte is conditional and one-shot.** The bias kicks in only
768
+ above the slop threshold, applies a velocity correction once per
769
+ step, and the `MAX_BAUMGARTE_BIAS` cap is a panic guard against
770
+ EPA returning bad depths.
771
+
772
+ **Concrete failure mode of our code:** when EPA misreports depth on a
773
+ torus knot (PLAN.md mentions this as one of the motivating cases for
774
+ MAX_BAUMGARTE_BIAS), our solver caps the bias at 3 m/s. That's
775
+ enough to stop the body launching but not enough to actually
776
+ resolve a 10cm phantom penetration — the body sinks. SPOOK doesn't
777
+ have this dichotomy: the regularisation moderates the response
778
+ naturally.
779
+
780
+ **Recommended action:** when joints land (they need SPOOK or SPOOK-like
781
+ structure anyway for the constraint/joint uniformity), refactor the
782
+ contact solver to use SPOOK. The pre-step changes:
783
+
784
+ ```js
785
+ // In pre-step (where bias is currently computed):
786
+ const k_spook = 1e7; // stiffness
787
+ const d_spook = 4; // relaxation
788
+ const a_spook = 4 / (dt * (1 + 4 * d_spook));
789
+ const b_spook = (4 * d_spook) / (1 + 4 * d_spook);
790
+ const eps = 4 / (dt * dt * k_spook * (1 + 4 * d_spook));
791
+ pre[pre_off + 12] = 1 / (k_n + eps); // includes regularisation
792
+ pre[pre_off + 15] = -a_spook * Math.max(0, depth - PENETRATION_SLOP);
793
+ ```
794
+
795
+ (With a corresponding inner-loop change to subtract `eps * j_n_accum`
796
+ from the lambda before clamping.)
797
+
798
+ This single change probably resolves several existing pain points
799
+ (MAX_BAUMGARTE_BIAS cap, restitution-vs-warm-start interaction in the
800
+ TGS attempt, mesh-contact phantom-depth launching).
801
+
802
+ ### 3.3 Friction model
803
+
804
+ **Cannon's `FrictionEquation`:** one-dimensional, along a single
805
+ tangent direction. The slipForce passes to `Equation` as bounds
806
+ `[-slipForce, slipForce]` where `slipForce = μ * F_normal`.
807
+ Cannon generates **two** FrictionEquations per contact (two tangent
808
+ directions) so the friction is effectively a polyhedral cone, not a
809
+ disk.
810
+
811
+ **Ours `solve_contacts.js:566-571`:**
812
+ ```js
813
+ const want_t1 = j_t1_accum + lambda_t1;
814
+ const want_t2 = j_t2_accum + lambda_t2;
815
+ const max_friction = mus[ci] * new_j_n;
816
+ friction_cone_clamp(scratch_clamp, 0, want_t1, want_t2, max_friction);
817
+ ```
818
+ Disk clamp in the (t1, t2) tangent plane. Mathematically a true
819
+ circular friction cone — strictly more accurate than Cannon's box
820
+ clamp.
821
+
822
+ **Win for us.** The box-vs-disk distinction matters for objects
823
+ sliding in directions not aligned with the tangent basis — Cannon
824
+ allows up to √2 × μ tangential force on a 45°-sliding object, ours
825
+ correctly clamps to μ. The difference is small but observable on
826
+ spinning objects on a flat surface.
827
+
828
+ ### 3.4 Restitution + warm-start interaction
829
+
830
+ **Ours `solve_contacts.js:452-456`:**
831
+ ```js
832
+ if (vn_pre < -RESTITUTION_VELOCITY_THRESHOLD) {
833
+ bias += restitution_combined * vn_pre;
834
+ }
835
+ ```
836
+ Restitution is **added to the velocity bias inside the iterative
837
+ loop**. Combined with the `j_n ≥ 0` clamp, this is what PLAN.md
838
+ identifies as one of three reasons the TGS substep attempt failed:
839
+
840
+ > "Restitution × warm-start clamp. The j_n ≥ 0 clamp lets substep 1+
841
+ > shrink the impulse applied in substep 0, so a restitution=0 ball
842
+ > that should stop instead picks up a phantom upward velocity."
843
+
844
+ **Cannon's `ContactEquation`:**
845
+ ```js
846
+ GW = ePlusOne * vj.dot(n) - ePlusOne * vi.dot(n) + ...
847
+ B = -g * a - GW * b - h * GiMf
848
+ ```
849
+ Restitution scales the **closing velocity** in `GW` before computing
850
+ the bias. Mathematically identical to ours in the simple case but
851
+ structurally different — Cannon's restitution is baked into
852
+ `computeB` and rebuilt every step, ours is added on top of
853
+ Baumgarte every step. Cannon's GS solver doesn't have the
854
+ warm-start-shrinkage problem because there's no warm-start.
855
+
856
+ **A genuine improvement opportunity:** the Box2D / Box2D-Lite
857
+ "split-impulse" pattern that PLAN.md mentions as a prerequisite for
858
+ TGS is also the right structure for clean restitution. Restitution
859
+ should be a **one-shot impulse at first contact**, not a per-iteration
860
+ bias. Bullet does this too. Cannon's approach is less correct (it
861
+ applies on every iteration, but starts cold each frame so the
862
+ warm-start interaction doesn't bite). When we add SPOOK, this is a
863
+ good time to switch to a real restitution model.
864
+
865
+ ### 3.5 ConvexPolyhedron.clipFaceAgainstHull vs box_box_manifold
866
+
867
+ Both implement Sutherland-Hodgman polygon clipping against a series
868
+ of half-spaces. The structural difference:
869
+
870
+ Cannon's `clipFaceAgainstHull` is **N-gon → N-gon** (handles any
871
+ polygon size, output bounded by `numClipFaces × original_size`).
872
+ The clip buffer is a JavaScript `Array<Vec3>` that grows / shrinks
873
+ with `.push()` and `.shift()` — both allocate-and-copy.
874
+
875
+ Our `box_box_manifold.js` clips a quad against 4 edges, hard-coded.
876
+ Output buffer is a `Float64Array(8 * 3)` allocated once at module load
877
+ (`clip_in`, `clip_out` at `box_box_manifold.js:53-54`). Zero
878
+ allocation per call.
879
+
880
+ **Win for us on the box-box case.** For the future ConvexHullShape3D
881
+ we'll want to port Cannon's general case, but with our typed-array
882
+ allocation discipline rather than `Array.push/shift`. The
883
+ key insight from Cannon that we'd otherwise miss: **the inner clip
884
+ loop reuses two ping-pong buffers and a swap-on-output pattern**
885
+ that minimises the polygon-buffer reallocations to two fixed
886
+ `Vec3[]` arrays per clip pass. That pattern translates directly to
887
+ two fixed `Float64Array` slabs for us.
888
+
889
+ ### 3.6 Sleep — atomic island vs per-body chatter
890
+
891
+ Already covered in §1 and §2. Concrete walk-through of the failure:
892
+
893
+ **Cannon's chatter (paraphrased trace):**
894
+ ```
895
+ Frame N: bottom block sleepState=SLEEPY, timeLastSleepy=N*dt
896
+ Frame N+1: top block jitters; broadphase pair list includes block above it
897
+ both blocks `wakeUpAfterNarrowphase=true`
898
+ bottom wakes via velocity propagation
899
+ Frame N+2: bottom resleeps (now SLEEPY again)
900
+ Frame N+3: middle blocks similarly wake from a different jitter
901
+ ...
902
+ ```
903
+
904
+ A 100-block stack chatters at ~3-5 fps of sleep-state churn for
905
+ seconds before fully settling, if it ever does. The user-observable
906
+ effect is GC spikes from per-frame event dispatch plus visible
907
+ micro-jitter.
908
+
909
+ **Our atomic sleep (`ecs/PhysicsSystem.js:942-1006`):**
910
+ ```js
911
+ if (max_v_sqr < threshold_sqr) {
912
+ let min_timer = Infinity;
913
+ for (let i = start; i < end; i++) {
914
+ rb.sleep_timer += dt;
915
+ if (rb.sleep_timer < min_timer) min_timer = rb.sleep_timer;
916
+ }
917
+ if (min_timer >= time_threshold) {
918
+ this.__atomic_sleep_island_range(body_data, start, end);
919
+ }
920
+ }
921
+ ```
922
+ The whole island either accumulates time or doesn't. When it crosses
923
+ the threshold, every member sleeps the same step, and the
924
+ sleep-group circular DLL connects them so a single wake event
925
+ walks the chain.
926
+
927
+ **Verbatim from PLAN.md's "Done" section:** "Per-island atomic sleep:
928
+ ... Replaces the per-body chatter on weakly-connected piles." This
929
+ is one of the engineering claims most clearly validated by the
930
+ Cannon comparison — the algorithm Cannon ships has the exact failure
931
+ mode we explicitly engineered around.
932
+
933
+ ### 3.7 Per-pair narrowphase dispatch
934
+
935
+ Both engines look superficially similar — a cascade of pair-type
936
+ checks dispatching to per-pair contact methods. The differences:
937
+
938
+ **Cannon `Narrowphase.getContacts`:** dispatch table keyed by shape
939
+ type enum (Sphere=1, Box=4, Plane=8, etc.). Lookup pattern:
940
+ ```js
941
+ const resolverIndex = (si.type | sj.type);
942
+ // uses bit-OR of shape type enums as table key
943
+ ```
944
+ Method-per-pair on the `Narrowphase` class (`sphereSphere`,
945
+ `sphereBox`, `sphereConvex`, `convexConvex`, `planeBox`,
946
+ `planeConvex`, `planeTrimesh`, `sphereTrimesh`, `convexTrimesh`,
947
+ `sphereHeightfield`, `convexHeightfield`, `sphereParticle`,
948
+ `planeParticle`, `convexParticle`, `boxConvex`, ...).
949
+
950
+ **Ours `narrowphase_step.js:dispatch_pair`:** cascade of
951
+ `if (isSphereA && isSphereB)`, `if ((isSphereA && isBoxB) || ...)`,
952
+ etc. — handled as boolean checks on `shape.isXxx === true` flags.
953
+
954
+ **Tradeoff:**
955
+
956
+ - Cannon's table is O(1) dispatch but allocates an integer combination
957
+ per call and has unmaintainable enum-OR collision risk. It also
958
+ forces all narrowphase to live on one class instance.
959
+ - Ours is O(K) cascade where K = pair categories handled (currently
960
+ ~7). At small K cascade is faster than table lookup; the
961
+ isShapeType branch predicts well after the first hit. At large K
962
+ the table wins.
963
+
964
+ **Both lose to a real double-dispatch pattern**, but neither is bad
965
+ enough to fix yet.
966
+
967
+ **Improvement opportunity for us:** the cascade in `dispatch_pair`
968
+ checks `isSphereA && isBoxB || isBoxA && isSphereB` which always
969
+ runs both halves of the `||` even after a hit. Rearranging by
970
+ expected frequency (sphere-sphere first if that's the dominant pair,
971
+ which it is for ball-based games) gets us closer to one-branch fast
972
+ path for the common case. Probably <1% real-world win; not urgent.
973
+
974
+ ### 3.8 Allocation patterns
975
+
976
+ **Cannon's allocation hot-spots that cannon-es fixed vs original
977
+ cannon.js:**
978
+
979
+ cannon-es is the result of years of de-allocation work compared to
980
+ the original. Notable fixes documented in the changelog / source
981
+ comments:
982
+
983
+ - Module-level `tmpVec`, `tmpQuat`, `Body_applyForce_rotForce`,
984
+ `Body_applyImpulse_velo` etc. scratch instances declared once per
985
+ module, reused everywhere. Original cannon.js allocated these
986
+ per-call.
987
+ - `World_step_oldContacts` and `World_step_oldFrictionEquations`
988
+ arrays reused frame-to-frame, with `.length = 0` + push pattern.
989
+ - `SAPBroadphase` insertion-sort with persistent `axisList`.
990
+
991
+ **Where Cannon STILL allocates per-step** (and we don't):
992
+ - `Narrowphase.getContacts` uses `Array.push` extensively for the
993
+ output contact list. Even with the `contactPointPool` recycling
994
+ the `ContactEquation` instances, the outer array grows via `push`
995
+ and resets via `.length = 0`. This is **the** cannon-es per-frame
996
+ allocation hot path. Our equivalent — writing into a fixed
997
+ `Float64Array` slab via `set_contact(slot, idx, ...)` — has zero
998
+ steady-state allocation.
999
+ - `SplitSolver` BFS over equations builds a fresh `nodes` /
1000
+ `children` graph each call. Pool exists but graph-shape changes
1001
+ defeat it.
1002
+ - `Heightfield._cachedPillars` uses string keys (`"i_j_isUpper"`) —
1003
+ every lookup allocates a string. Hot path on heightmap collision.
1004
+
1005
+ **JS-specific optimisation insights from cannon-es worth absorbing:**
1006
+
1007
+ 1. **Module-level scratch instances declared in a uniform pattern.**
1008
+ The Cannon convention is `const Body_methodName_purpose = new
1009
+ Vec3()`. Naming makes the scratch's owner and intent obvious. We
1010
+ do the same with prefixed `scratch_*` names; cannon's discipline
1011
+ is more enforced. The convention is good — names like
1012
+ `scratch_inertia_a` / `scratch_inertia_b` in our solver follow
1013
+ the pattern correctly. A grep for `const scratch_` shows we're
1014
+ consistent.
1015
+
1016
+ 2. **Body field initialisation order matters for V8 hidden classes.**
1017
+ Both engines initialise fields in the constructor (or as class
1018
+ fields) in a fixed order, ensuring all `RigidBody` / `Body`
1019
+ instances share the same hidden class. Our class-field syntax
1020
+ (`linearVelocity = new Vector3(0, 0, 0)` at class-body scope) is
1021
+ the modern equivalent of Cannon's constructor assignments. Both
1022
+ approaches achieve hidden-class consistency. **Risk:** if any
1023
+ call site does `body.somethingNew = value`, V8 transitions to a
1024
+ new hidden class. We should keep RigidBody field-set strictly
1025
+ closed; users wanting extra state should attach side components.
1026
+
1027
+ 3. **Avoid Float32Array for hot constraint data.** cannon-es uses
1028
+ `Vec3` (3 numbers as object fields, not typed array). Vec3 access
1029
+ is faster than Float32Array indexing on V8 (no bounds check). But
1030
+ for **bulk solver state** that lives in slabs (our manifold
1031
+ `data_buffer`, our `scratch_pre` per-contact stride), Float64Array
1032
+ is correct — bulk reads / writes by offset compile to direct
1033
+ memory operations. The lesson is "use Vec3-like for individual
1034
+ vectors, TypedArray for bulk slabs". We follow this pattern
1035
+ correctly.
1036
+
1037
+ 4. **Avoid object-spread / Object.assign on hot path.** Cannon has a
1038
+ couple of places (in Equation reset code) that do `Object.assign`
1039
+ on equation instances — measurable per-frame cost. We don't have
1040
+ any such patterns; worth a periodic audit.
1041
+
1042
+ 5. **Use shift-by-N on bit-packed flags rather than separate
1043
+ booleans.** Our `ManifoldStore` packs `count | touched |
1044
+ prev_touched | grace` into one Uint32 word
1045
+ (`ManifoldStore.js:57-61`). Cannon uses separate boolean fields
1046
+ on `Body` (`allowSleep`, `wakeUpAfterNarrowphase`, etc.) — fine
1047
+ for code clarity but more memory per body. At million-body
1048
+ scale our bit-packing saves 4 bytes per slot × N slots.
1049
+
1050
+ ### 3.9 Numerical stability — degenerate cases
1051
+
1052
+ **Cannon's degenerate-case handling:**
1053
+
1054
+ - `findSeparatingAxis` skips edge-cross axes where the cross is
1055
+ almost zero (`if (!Cross.almostZero())`). Prevents NaN normals on
1056
+ parallel edge configurations.
1057
+ - `ConvexPolyhedron` constructor warns if a face normal points
1058
+ inward (`console.error` — doesn't fix).
1059
+ - `Trimesh` validates index range when computing per-triangle
1060
+ normals.
1061
+
1062
+ **Ours:**
1063
+
1064
+ - `narrowphase_step.js:728`: `if (!(depth > 0) || !Number.isFinite(depth))
1065
+ return count;` — rejects NaN/Inf/zero depths from EPA. Good.
1066
+ - `narrowphase_step.js:620`: same check inside the concave loop.
1067
+ - `solve_contacts.js:151, 261`: `inv_mass_of` and
1068
+ `angular_jacobian_contribution` guard against zero mass /
1069
+ inertia. Good.
1070
+ - **Missing:** no zero-area triangle guard in `mesh_enumerate_triangles`
1071
+ or `heightmap_enumerate_triangles`. A degenerate triangle (collinear
1072
+ vertices) would feed a zero face normal into GJK / EPA and the
1073
+ one-sided rejection at `narrowphase_step.js:654` would silently
1074
+ skip it. **Probably fine in practice** — degenerate triangles in a
1075
+ source mesh are usually filtered at import — but worth a paranoid
1076
+ guard.
1077
+ - `solver/solve_contacts.js:418-420` guards K=0 with
1078
+ `k_n > 0 ? 1 / k_n : 0` — prevents NaN m_eff on two
1079
+ zero-mass bodies (kinematic-vs-kinematic, which the solver
1080
+ should never see, but defensively).
1081
+
1082
+ **Concrete improvement opportunity:** add `if (Math.abs(face_normal_sq) <
1083
+ EPS) continue;` at `narrowphase_step.js:599` (after computing
1084
+ `fnx_l/fny_l/fnz_l` in the triangle loop). This rejects degenerate
1085
+ triangles before they reach EPA.
1086
+
1087
+ ---
1088
+
1089
+ ## 4. Simplicity & uniformity
1090
+
1091
+ ### Code organisation
1092
+
1093
+ | Aspect | meep | cannon-es |
1094
+ |---|---|---|
1095
+ | Module layout | One concept per file, hierarchical directories (`body/`, `broadphase/`, `solver/`, `gjk/`, `narrowphase/`, ...) | Mostly one class per file, flat-ish (`src/objects/`, `src/collision/`, `src/equations/`, `src/solver/`, `src/shapes/`) |
1096
+ | Class structure | Mostly free functions + a few classes (Storage, ManifoldStore, IslandBuilder, PhysicsSystem) | Class hierarchy everywhere (Body, Shape→Sphere/Box/..., Equation→ContactEquation/FrictionEquation, Constraint→PointToPoint/Distance/...) |
1097
+ | Public API surface | Concentrated on `PhysicsSystem` | Spread across World + Body + Shape + Constraint subclasses |
1098
+ | File count | ~50 files in `physics/` (excluding tests) | ~50 files in cannon-es `src/` |
1099
+ | LOC | Roughly comparable; we have more in the broadphase + decomposition machinery |
1100
+
1101
+ Both engines are flat and avoid deep hierarchies (unlike Bullet's
1102
+ `btCollisionObject < btRigidBody < btSoftBody` etc.). Cannon does
1103
+ have one inheritance chain that matters: `Shape` → `Sphere`, `Box`
1104
+ (which is a `ConvexPolyhedron` subclass), `Plane`, `Heightfield`,
1105
+ `Particle`, `ConvexPolyhedron`, `Trimesh`. The `Shape` base class
1106
+ exposes `volume()`, `boundingSphereRadius`, `updateBoundingSphereRadius()`,
1107
+ `calculateLocalInertia()`, `calculateWorldAABB()` — virtual methods
1108
+ that every shape overrides. Adding a new shape means subclassing
1109
+ `Shape` and implementing 5 virtuals.
1110
+
1111
+ Our `AbstractShape3D` plays the equivalent role. New shape = same
1112
+ contract.
1113
+
1114
+ ### Adding a new shape pair — workflow
1115
+
1116
+ **Cannon:** add a new method on `Narrowphase`, register it in the
1117
+ dispatch table. Add the shape class as a `Shape` subclass.
1118
+
1119
+ **Ours:** add a new closed-form file in `narrowphase/`, add a branch
1120
+ to the `dispatch_pair` cascade in `narrowphase_step.js`. Add the
1121
+ shape class as an `AbstractShape3D` implementation.
1122
+
1123
+ **Equivalent friction.** Adding capsule-mesh would be the same
1124
+ amount of work in either engine.
1125
+
1126
+ ### Adding a new constraint
1127
+
1128
+ **Cannon:** subclass `Constraint`, instantiate the Equations it
1129
+ owns, implement `update()` to recompute Jacobians. SPOOK + GSSolver
1130
+ handle the rest.
1131
+
1132
+ **Ours:** **not currently supported**. The solver knows about
1133
+ contacts only; adding joints requires extending `solve_island` to
1134
+ iterate a `joints` list parallel to contacts. PLAN.md says this is
1135
+ planned but not done.
1136
+
1137
+ **Cannon's design is the right reference.** The "constraints and
1138
+ contacts are both Equations" abstraction is what makes Cannon's
1139
+ joint library so flexible. Adopting SPOOK structurally enables
1140
+ copying this design.
1141
+
1142
+ ### Adding a new query
1143
+
1144
+ **Cannon:** implement per-shape `raycast` method; `World.raycastClosest`
1145
+ iterates all bodies + shapes.
1146
+
1147
+ **Ours:** add a query function in `queries/`. Use the BVH for
1148
+ broadphase, dispatch per-shape narrowphase.
1149
+
1150
+ **We win on structure** — queries live in their own subsystem and
1151
+ share the broadphase. Cannon's per-shape `raycast` is duplicated
1152
+ across every shape class and bypasses the broadphase entirely.
1153
+
1154
+ ### Where Cannon's `Body` god-class wins
1155
+
1156
+ Despite our decomposition argument, **Cannon's single-class `Body`
1157
+ makes some things simpler**:
1158
+ - One reference to pass around (`body`).
1159
+ - `addEventListener('sleep', ...)` etc. for application code.
1160
+ - `body.applyForce(force, worldPoint)` reads naturally.
1161
+
1162
+ Our `system.applyForceAt(rigidBody, transform, force, worldPoint)`
1163
+ needs four arguments because force application needs the transform
1164
+ (for the r = worldPoint - position calculation) and the system
1165
+ (for the wake call), and the rigid body itself. The system-level
1166
+ API is necessarily more verbose. **This is the unavoidable cost of
1167
+ ECS decomposition** — we picked it for good reasons (transform is
1168
+ shared with rendering, components are independently
1169
+ serialisable / observable) and the price is API verbosity.
1170
+
1171
+ ### Where Cannon's API is a cautionary tale
1172
+
1173
+ 1. **`world.bodies` is a public mutable array.** Users mutate it
1174
+ directly, which violates every encapsulation invariant. We
1175
+ correctly hide `__bodies` and expose only `entityOf(packed_id)`.
1176
+
1177
+ 2. **Body events fire via `dispatchEvent` on the Body itself.** This
1178
+ means every Body has its own listener list. At 100k bodies that's
1179
+ 100k tiny listener arrays. We use Signal-per-system + per-entity
1180
+ event channel (`PhysicsEvents`), which scales better.
1181
+
1182
+ 3. **`World.contacts` rebuilt every frame as a fresh array.** Visible
1183
+ GC pressure. Our `ManifoldStore` is persistent and slab-based.
1184
+
1185
+ 4. **`Heightfield._cachedPillars` string-key dict.** Allocates per
1186
+ lookup. Our `HeightMapShape3D` has no equivalent caching layer —
1187
+ it samples and emits triangles on-demand into a flyweight, zero
1188
+ allocation.
1189
+
1190
+ 5. **`ConvexPolyhedron.findSeparatingAxis` allocates `Vec3`
1191
+ intermediates** despite cannon-es's de-allocation pass. Our
1192
+ `box_box_manifold.js` is entirely typed-array, zero allocation.
1193
+
1194
+ ### Where we could simplify further
1195
+
1196
+ - The cascade of `is*A && is*B || is*A && is*B` in `dispatch_pair`
1197
+ has each pair-type check duplicated for ordering. A small enum +
1198
+ pair-table would compress this without sacrificing inlining. But
1199
+ it's not urgent — the cascade is still readable at 7 pair types.
1200
+
1201
+ - `narrowphase_step.js` has the SAT/clip box-box code linked but the
1202
+ general convex-hull case TODO. When we add `ConvexHullShape3D`,
1203
+ the box-box code wants to be subsumed into the general path
1204
+ rather than maintained as a parallel implementation. Don't add
1205
+ ConvexHull without refactoring box-box at the same time, or we'll
1206
+ have two copies of essentially the same algorithm.
1207
+
1208
+ - `solve_contacts.js` has separate friction and normal blocks that
1209
+ could be unified under SPOOK's "every equation is the same shape"
1210
+ model. This is the joint-system prerequisite anyway.
1211
+
1212
+ ---
1213
+
1214
+ ## Top recommendations
1215
+
1216
+ In rough priority order:
1217
+
1218
+ 1. **Adopt SPOOK constraint regularisation** in the solver
1219
+ (`solver/solve_contacts.js`). Removes the `MAX_BAUMGARTE_BIAS`
1220
+ hack, improves tall-stack stability comparable to Cannon, and is
1221
+ the structural prerequisite for clean joint implementation.
1222
+ Estimated effort: medium (one solver rewrite, no public API
1223
+ change). Estimated payoff: high.
1224
+
1225
+ 2. **Wire MPR as the EPA-non-convergence fallback** in
1226
+ `narrowphase_step.js`. Code exists at `gjk/mpr.js` per PLAN.md;
1227
+ one branch on EPA's NaN/zero-depth path swaps it in. Unblocks the
1228
+ torus-knot test and the smooth-shape contact stability issue.
1229
+ Effort: low. Payoff: high for smooth-shape cases.
1230
+
1231
+ 3. **Add per-shape exact raycast refinement** after BVH broadphase
1232
+ (`queries/raycast.js`). Currently returns leaf-AABB hit which
1233
+ underestimates `t` for non-AABB shapes. Cannon's per-shape
1234
+ raycast methods are the reference. Effort: medium. Payoff:
1235
+ makes the raycast query genuinely useful for character
1236
+ controllers / weapon hit detection.
1237
+
1238
+ 4. **Build per-mesh triangle BVH for `MeshShape3D`**. Cannon's
1239
+ `Trimesh.tree` is the reference (Octree there; BVH for us since
1240
+ we have the infrastructure). Replace the O(N) linear scan in
1241
+ `mesh_enumerate_triangles`. Effort: low (BVH is in
1242
+ `core/bvh2/`). Payoff: linear-to-log mesh scaling, opens the door
1243
+ to genuinely large static-mesh worlds.
1244
+
1245
+ 5. **Closed-form triangle-vs-primitive** narrowphase (already on
1246
+ PLAN.md backlog — re-emphasising because it's the gating issue
1247
+ for several skipped tests). Cannon's "treat triangle as
1248
+ ConvexPolyhedron and SAT it" works but has the same internal-edge
1249
+ phantom contact issue we have. Catto's voronoi triangle
1250
+ classification is the standard fix.
1251
+
1252
+ 6. **Add `onBodySleep` / `onBodyWake` signals** on `PhysicsSystem`.
1253
+ Cannon's per-body events are the wrong scale (event listener per
1254
+ body), but a system-level signal carrying island membership is
1255
+ useful for game code. Effort: trivial. Payoff: gameplay-quality
1256
+ of life.
1257
+
1258
+ 7. **Consider lowering default solver iterations from 10 to 6 once
1259
+ warm-start is verified stable in production.** Cannon defaults
1260
+ to 10 because it can't warm-start. Ours can; the savings scale
1261
+ with warm-start hit rate (typically >80% in steady-state
1262
+ contacts). Effort: trivial (one constant). Validation: medium
1263
+ (need a stack-stability benchmark sweep).
1264
+
1265
+ 8. **Audit `mesh_enumerate_triangles` for zero-area triangle
1266
+ handling.** Add an `Math.abs(face_normal_sq) < EPS` skip. Cheap
1267
+ defensive guard. Effort: trivial.
1268
+
1269
+ ---
1270
+
1271
+ ## Summary
1272
+
1273
+ Cannon is our closest peer on engineering constraints (pure JS,
1274
+ single-threaded, no SIMD) and the comparison is consistently
1275
+ favourable. **Where we are clearly ahead:**
1276
+ - Broadphase (BVH × 2 vs default NaiveBroadphase O(N²))
1277
+ - Body iteration scaling (O(awake) vs O(total))
1278
+ - Atomic island sleep + atomic wake (vs per-body chatter on weakly-connected piles)
1279
+ - Persistent manifold cache with cross-frame warm-start (vs frame-rebuilt contact list)
1280
+ - CCD floor via speculative margin (vs nothing)
1281
+ - Disk-clamped friction cone (vs polyhedral box clamp)
1282
+ - Zero-allocation hot paths in narrowphase + heightmap (vs string-key dict, Array.push)
1283
+ - shapeCast + overlap queries (Cannon has neither)
1284
+ - ECS decomposition + observability hooks
1285
+
1286
+ **Where Cannon is ahead or has the better idea:**
1287
+ - SPOOK constraint formulation (vs our Baumgarte + cap hack)
1288
+ - Joints / Constraints library (we have none)
1289
+ - General convex polyhedron shape (we have only `BoxShape3D`)
1290
+ - Per-shape exact raycast (vs our AABB-only result)
1291
+ - Per-mesh BVH (Octree) for large triangle meshes (vs our O(N) linear scan)
1292
+ - Per-body sleep/wake event listeners (we have signals at system level only)
1293
+
1294
+ **Headline structural insight:** Cannon is "what JS rigid-body
1295
+ physics looked like in 2014, slowly de-allocated by community
1296
+ maintenance to 2023." We are "what JS rigid-body physics could be in
1297
+ 2026 if you start from the modern reference architectures
1298
+ (Jolt/Bullet/Box2D) and then constrain to pure JS." The
1299
+ ecosystem-position is genuinely interesting — there's no other
1300
+ pure-JS engine attempting our design ambitions.