@woosh/meep-engine 2.155.0 → 2.156.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.
- package/README.md +1 -1
- package/build/bundle-worker-image-decoder.js +1 -1
- package/build/bundle-worker-terrain.js +1 -1
- package/editor/view/ecs/ComponentControlView.d.ts +0 -9
- package/editor/view/ecs/ComponentControlView.js +2 -98
- package/package.json +1 -1
- package/src/core/binary/32BitEncoder.js +1 -1
- package/src/core/binary/to_half_float_uint16.js +3 -3
- package/src/core/bvh2/bvh3/ebvh_build_hierarchy_radix.d.ts.map +1 -1
- package/src/core/bvh2/bvh3/ebvh_build_hierarchy_radix.js +275 -253
- package/src/core/cache/Cache.d.ts.map +1 -1
- package/src/core/cache/Cache.js +7 -0
- package/src/core/cache/FrequencySketch.d.ts.map +1 -1
- package/src/core/cache/FrequencySketch.js +8 -4
- package/src/core/clipboard/obtainClipBoard.d.ts +6 -0
- package/src/core/clipboard/obtainClipBoard.d.ts.map +1 -0
- package/src/core/clipboard/obtainClipBoard.js +29 -0
- package/src/core/clipboard/safeClipboardReadText.d.ts +6 -0
- package/src/core/clipboard/safeClipboardReadText.d.ts.map +1 -0
- package/src/core/clipboard/safeClipboardReadText.js +55 -0
- package/src/core/clipboard/safeClipboardWriteText.d.ts +8 -0
- package/src/core/clipboard/safeClipboardWriteText.d.ts.map +1 -0
- package/src/core/clipboard/safeClipboardWriteText.js +23 -0
- package/src/core/collection/array/array_quick_sort_by_lookup_map.js +1 -1
- package/src/core/collection/array/array_set_diff_sorting.d.ts.map +1 -1
- package/src/core/collection/array/array_set_diff_sorting.js +4 -1
- package/src/core/collection/array/array_shuffle.d.ts.map +1 -1
- package/src/core/collection/array/array_shuffle.js +30 -27
- package/src/core/collection/array/binarySearchLowIndex.d.ts.map +1 -1
- package/src/core/collection/array/binarySearchLowIndex.js +4 -3
- package/src/core/collection/array/typed/array_buffer_hash.js +1 -1
- package/src/core/collection/array/typed/is_typed_array_equals.d.ts.map +1 -1
- package/src/core/collection/array/typed/is_typed_array_equals.js +12 -2
- package/src/core/collection/heap/BinaryHeap.d.ts.map +1 -1
- package/src/core/collection/heap/BinaryHeap.js +12 -2
- package/src/core/collection/queue/Deque.d.ts.map +1 -1
- package/src/core/collection/queue/Deque.js +10 -8
- package/src/core/collection/table/RowFirstTable.d.ts.map +1 -1
- package/src/core/collection/table/RowFirstTable.js +4 -2
- package/src/core/collection/table/RowFirstTableSpec.js +2 -2
- package/src/core/color/operations/color_lerp.d.ts.map +1 -1
- package/src/core/color/operations/color_lerp.js +10 -3
- package/src/core/color/rgb2uint32.js +1 -1
- package/src/core/color/rgbe9995_to_rgb.js +1 -1
- package/src/core/function/objectsEqual.d.ts.map +1 -1
- package/src/core/function/objectsEqual.js +2 -1
- package/src/core/geom/2d/aabb/AABB2.d.ts.map +1 -1
- package/src/core/geom/2d/aabb/AABB2.js +12 -11
- package/src/core/geom/2d/convex-hull/convex_hull_jarvis_2d.d.ts.map +1 -1
- package/src/core/geom/2d/convex-hull/convex_hull_jarvis_2d.js +30 -4
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.d.ts.map +1 -1
- package/src/core/geom/2d/convex-hull/fixed_convex_hull_relaxation.js +6 -2
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.d.ts.map +1 -1
- package/src/core/geom/2d/hash-grid/SpatialHashGrid.js +388 -386
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.d.ts.map +1 -1
- package/src/core/geom/2d/hash-grid/shg_query_elements_line.js +8 -3
- package/src/core/geom/2d/quad-tree/QuadTreeDatum.d.ts.map +1 -1
- package/src/core/geom/2d/quad-tree/QuadTreeDatum.js +9 -1
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.d.ts +3 -1
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.d.ts.map +1 -1
- package/src/core/geom/2d/quad-tree/qt_query_data_nearest_to_point.js +3 -1
- package/src/core/geom/2d/quad-tree-binary/QuadTree.js +714 -714
- package/src/core/geom/2d/r-tree/StaticR2Tree.d.ts.map +1 -1
- package/src/core/geom/2d/r-tree/StaticR2Tree.js +5 -4
- package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/aabb3_detailed_volume_intersection.js +33 -29
- package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/aabb3_near_distance_to_intersection_ray_segment.js +3 -1
- package/src/core/geom/3d/aabb/aabb3_signed_distance_to_aabb3.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/aabb3_signed_distance_to_aabb3.js +10 -7
- package/src/core/geom/3d/aabb/aabb3_transformed_compute_plane_side.d.ts.map +1 -1
- package/src/core/geom/3d/aabb/aabb3_transformed_compute_plane_side.js +30 -9
- package/src/core/geom/3d/aabb/compute_aabb_from_points.js +3 -3
- package/src/core/geom/3d/box/box3_raycast.d.ts +37 -0
- package/src/core/geom/3d/box/box3_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/box/box3_raycast.js +81 -0
- package/src/core/geom/3d/capsule/capsule_raycast.d.ts +35 -0
- package/src/core/geom/3d/capsule/capsule_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/capsule/capsule_raycast.js +93 -0
- package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.d.ts.map +1 -1
- package/src/core/geom/3d/cone/compute_bounding_cone_of_2_cones.js +4 -0
- package/src/core/geom/3d/frustum/frustum3_computeNearestPointToPoint.js +1 -1
- package/src/core/geom/3d/line/line3_compute_segment_point_distance_eikonal.d.ts.map +1 -1
- package/src/core/geom/3d/line/line3_compute_segment_point_distance_eikonal.js +3 -2
- package/src/core/geom/3d/mat4/decompose_matrix_4_array.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/decompose_matrix_4_array.js +12 -2
- package/src/core/geom/3d/mat4/eulerAnglesFromMatrix.js +2 -2
- package/src/core/geom/3d/mat4/m4_multiply_alphatensor.d.ts +1 -1
- package/src/core/geom/3d/mat4/m4_multiply_alphatensor.d.ts.map +1 -1
- package/src/core/geom/3d/mat4/m4_multiply_alphatensor.js +19 -13
- package/src/core/geom/3d/octahedra/octahedral_direction_to_uv.d.ts.map +1 -1
- package/src/core/geom/3d/octahedra/octahedral_direction_to_uv.js +3 -2
- package/src/core/geom/3d/plane/plane3_compute_plane_intersection.js +3 -2
- package/src/core/geom/3d/shape/MeshShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/MeshShape3D.js +7 -0
- package/src/core/geom/3d/shape/UnionShape3D.d.ts.map +1 -1
- package/src/core/geom/3d/shape/UnionShape3D.js +3 -2
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.d.ts.map +1 -1
- package/src/core/geom/3d/shape/util/shape3d_voxelize_to_grid.js +153 -148
- package/src/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.d.ts.map +1 -1
- package/src/core/geom/3d/sphere/harmonics/sh3_dering_optimize_positive.js +7 -0
- package/src/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.d.ts.map +1 -1
- package/src/core/geom/3d/sphere/harmonics/sh3_sample_by_direction.js +13 -10
- package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.d.ts +1 -1
- package/src/core/geom/3d/sphere/sphere_projected_sphere_radius_sqr.js +2 -2
- package/src/core/geom/3d/sphere/sphere_raycast.d.ts +33 -0
- package/src/core/geom/3d/sphere/sphere_raycast.d.ts.map +1 -0
- package/src/core/geom/3d/sphere/sphere_raycast.js +47 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.d.ts +24 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.d.ts.map +1 -0
- package/src/core/geom/3d/tetrahedra/tetrahedral_mesh_tet_get_neighbours.js +39 -0
- package/src/core/geom/3d/tetrahedra/triangle/trace_triangular_depth_map.d.ts.map +1 -1
- package/src/core/geom/3d/tetrahedra/triangle/trace_triangular_depth_map.js +4 -2
- package/src/core/geom/3d/topology/bounds/computeTriangleClusterNormalBoundingCone.js +3 -3
- package/src/core/geom/3d/topology/simplify/decimate_edge_collapse_snap.js +1 -1
- package/src/core/geom/3d/topology/tm_vertex_compute_normal.d.ts.map +1 -1
- package/src/core/geom/3d/topology/tm_vertex_compute_normal.js +4 -2
- package/src/core/geom/3d/util/make_justified_point_grid.d.ts.map +1 -1
- package/src/core/geom/3d/util/make_justified_point_grid.js +18 -10
- package/src/core/geom/ConicRay.d.ts.map +1 -1
- package/src/core/geom/ConicRay.js +11 -13
- package/src/core/geom/packing/max-rect/removeRedundantBoxes.d.ts.map +1 -1
- package/src/core/geom/packing/max-rect/removeRedundantBoxes.js +19 -4
- package/src/core/geom/vec3/v3_orthonormal_matrix_from_normal.d.ts.map +1 -0
- package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.js +1 -1
- package/src/core/graph/coloring/colorizeGraph.js +2 -2
- package/src/core/graph/csr/CSRGraph.d.ts.map +1 -1
- package/src/core/graph/csr/CSRGraph.js +325 -319
- package/src/core/graph/layout/CircleLayout.d.ts.map +1 -1
- package/src/core/graph/layout/CircleLayout.js +8 -6
- package/src/core/graph/metis/native/refine/compute_kway_params.d.ts.map +1 -1
- package/src/core/graph/metis/native/refine/compute_kway_params.js +139 -138
- package/src/core/graph/mn_graph_coarsen.d.ts.map +1 -1
- package/src/core/graph/mn_graph_coarsen.js +4 -2
- package/src/core/graph/v2/NodeContainer.js +7 -7
- package/src/core/localization/LocalizationEngine.js +1 -1
- package/src/core/math/bell_membership_function.d.ts.map +1 -1
- package/src/core/math/bell_membership_function.js +3 -1
- package/src/core/math/complex/complex_add.d.ts +4 -4
- package/src/core/math/complex/complex_add.d.ts.map +1 -1
- package/src/core/math/complex/complex_add.js +3 -3
- package/src/core/math/complex/complex_div.d.ts +4 -4
- package/src/core/math/complex/complex_div.d.ts.map +1 -1
- package/src/core/math/complex/complex_div.js +3 -3
- package/src/core/math/complex/complex_mul.d.ts +4 -4
- package/src/core/math/complex/complex_mul.d.ts.map +1 -1
- package/src/core/math/complex/complex_mul.js +3 -3
- package/src/core/math/complex/complex_sub.d.ts +4 -4
- package/src/core/math/complex/complex_sub.d.ts.map +1 -1
- package/src/core/math/complex/complex_sub.js +3 -3
- package/src/core/math/idct_1d.d.ts +4 -4
- package/src/core/math/idct_1d.d.ts.map +1 -1
- package/src/core/math/idct_1d.js +3 -3
- package/src/core/math/noise/create_simplex_noise_2d.d.ts.map +1 -1
- package/src/core/math/noise/create_simplex_noise_2d.js +4 -2
- package/src/core/math/noise/sdnoise.d.ts.map +1 -1
- package/src/core/math/noise/sdnoise.js +12 -9
- package/src/core/math/physics/mie/compute_bhmie_optical_properties.d.ts.map +1 -1
- package/src/core/math/physics/mie/compute_bhmie_optical_properties.js +94 -50
- package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts +3 -6
- package/src/core/math/physics/mie/lorenz_mie_coefs.d.ts.map +1 -1
- package/src/core/math/physics/mie/lorenz_mie_coefs.js +180 -157
- package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts +3 -4
- package/src/core/math/physics/mie/mie_ab_to_optical_properties.d.ts.map +1 -1
- package/src/core/math/physics/mie/mie_ab_to_optical_properties.js +47 -21
- package/src/core/math/random/randomIntegerBetween.d.ts.map +1 -1
- package/src/core/math/random/randomIntegerBetween.js +4 -1
- package/src/core/math/solveCubic.d.ts.map +1 -1
- package/src/core/math/solveCubic.js +95 -82
- package/src/core/math/spline/computeCatmullRomSplineUniformDistance.d.ts.map +1 -1
- package/src/core/math/spline/computeCatmullRomSplineUniformDistance.js +13 -0
- package/src/core/math/statistics/softmax.js +1 -1
- package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts +1 -0
- package/src/core/model/node-graph/visual/NodeGraphVisualData.d.ts.map +1 -1
- package/src/core/model/node-graph/visual/NodeGraphVisualData.js +2 -1
- package/src/core/model/node-graph/visual/NodeVisualData.js +1 -1
- package/src/core/model/object/ImmutableObjectPool.d.ts +7 -0
- package/src/core/model/object/ImmutableObjectPool.d.ts.map +1 -1
- package/src/core/model/object/ImmutableObjectPool.js +20 -10
- package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.d.ts.map +1 -1
- package/src/core/model/reactive/evaluation/MultiPredicateEvaluator.js +39 -2
- package/src/core/model/reactive/model/terminal/ReactiveReference.d.ts.map +1 -1
- package/src/core/model/reactive/model/terminal/ReactiveReference.js +2 -0
- package/src/core/parser/simple/readHexToken.d.ts.map +1 -1
- package/src/core/parser/simple/readHexToken.js +6 -0
- package/src/core/primitives/numbers/number_pretty_print.d.ts.map +1 -1
- package/src/core/primitives/numbers/number_pretty_print.js +4 -1
- package/src/core/primitives/strings/string_jaro_winkler.js +1 -1
- package/src/core/process/CompositeProcess.js +1 -1
- package/src/core/process/action/AsynchronousDelayAction.d.ts.map +1 -1
- package/src/core/process/action/AsynchronousDelayAction.js +3 -0
- package/src/core/process/executor/ConcurrentExecutor.d.ts.map +1 -1
- package/src/core/process/executor/ConcurrentExecutor.js +3 -2
- package/src/core/process/task/util/randomCountTask.d.ts.map +1 -1
- package/src/core/process/task/util/randomCountTask.js +3 -1
- package/src/core/process/undo/ActionProcessor.d.ts.map +1 -1
- package/src/core/process/undo/ActionProcessor.js +5 -3
- package/src/core/process/worker/WorkerBuilder.js +3 -3
- package/src/engine/animation/curve/AnimationCurve.d.ts.map +1 -1
- package/src/engine/animation/curve/AnimationCurve.js +4 -2
- package/src/engine/control/first-person/DESIGN.md +1 -1
- package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts +55 -0
- package/src/engine/control/first-person/FirstPersonMotionPhase.d.ts.map +1 -0
- package/src/engine/control/first-person/FirstPersonMotionPhase.js +134 -0
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +23 -2
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerController.js +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +168 -0
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +115 -0
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts +71 -0
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerSystem.js +255 -55
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +82 -43
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/LedgeGrab.js +405 -213
- package/src/engine/control/first-person/abilities/Mantle.d.ts +6 -0
- package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/Mantle.js +104 -45
- package/src/engine/control/first-person/abilities/ScrambleUp.d.ts +61 -0
- package/src/engine/control/first-person/abilities/ScrambleUp.d.ts.map +1 -0
- package/src/engine/control/first-person/abilities/ScrambleUp.js +182 -0
- package/src/engine/control/first-person/math/jumpDynamics.d.ts +84 -0
- package/src/engine/control/first-person/math/jumpDynamics.d.ts.map +1 -0
- package/src/engine/control/first-person/math/jumpDynamics.js +108 -0
- package/src/engine/control/first-person/prototype_first_person_controller.js +45 -1
- package/src/engine/graphics/camera/testClippingPlaneComputation.js +1 -1
- package/src/engine/graphics/ecs/path/tube/prototypeAnimatedPathMask.js +1 -1
- package/src/engine/graphics/particles/particular/engine/utils/volume/prototypeParticleVolume.js +1 -1
- package/src/engine/graphics/render/buffer/buffers/prototypeNormalFrameBuffer.js +1 -1
- package/src/engine/graphics/render/forward_plus/plugin/ptototypeFPPlugin.js +1 -1
- package/src/engine/graphics/render/visibility/hiz/prototypeHiZ.js +1 -1
- package/src/engine/graphics/sh3/path_tracer/texture/sample_material.js +1 -1
- package/src/engine/graphics/shadows/testShadowMapRendering.js +1 -1
- package/src/engine/physics/CONSTRAINT_SOLVER_BENCH_LOG.md +208 -0
- package/src/engine/physics/CONSTRAINT_SOLVER_IMPROVEMENTS_PLAN.md +364 -0
- package/src/engine/physics/PLAN.md +6 -5
- package/src/engine/physics/constraint/solve_constraints.d.ts +4 -1
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
- package/src/engine/physics/constraint/solve_constraints.js +147 -33
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +1750 -1747
- package/src/engine/physics/fluid/ecs/FluidSystem.d.ts +3 -3
- package/src/engine/physics/gjk/gjk_epa_penetration.d.ts.map +1 -1
- package/src/engine/physics/gjk/gjk_epa_penetration.js +5 -9
- package/src/engine/physics/narrowphase/convex_convex_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/convex_convex_manifold.js +22 -25
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts +32 -13
- package/src/engine/physics/narrowphase/convex_decomposition.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/convex_decomposition.js +61 -65
- package/src/engine/physics/narrowphase/mesh_convex_hull.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/mesh_convex_hull.js +13 -8
- package/src/engine/physics/narrowphase/refine_ray_concave.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_concave.js +5 -3
- package/src/engine/physics/narrowphase/refine_ray_hit.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/refine_ray_hit.js +81 -78
- package/src/engine/sound/SoundEngine.d.ts.map +1 -1
- package/src/engine/sound/SoundEngine.js +28 -0
- package/src/engine/sound/dB2Volume.d.ts +1 -1
- package/src/engine/sound/dB2Volume.d.ts.map +1 -1
- package/src/engine/sound/dB2Volume.js +1 -1
- package/src/engine/sound/ecs/SoundController.d.ts +4 -0
- package/src/engine/sound/ecs/SoundController.d.ts.map +1 -1
- package/src/engine/sound/ecs/SoundController.js +4 -0
- package/src/engine/sound/ecs/SoundControllerSystem.d.ts +5 -0
- package/src/engine/sound/ecs/SoundControllerSystem.d.ts.map +1 -1
- package/src/engine/sound/ecs/SoundControllerSystem.js +5 -0
- package/src/engine/sound/ecs/audio/AudioEmitter.d.ts +69 -0
- package/src/engine/sound/ecs/audio/AudioEmitter.d.ts.map +1 -0
- package/src/engine/sound/ecs/audio/AudioEmitter.js +83 -0
- package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts +97 -0
- package/src/engine/sound/ecs/audio/AudioEmitterSystem.d.ts.map +1 -0
- package/src/engine/sound/ecs/audio/AudioEmitterSystem.js +238 -0
- package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts +90 -0
- package/src/engine/sound/ecs/audio/LiveEmitterSet.d.ts.map +1 -0
- package/src/engine/sound/ecs/audio/LiveEmitterSet.js +324 -0
- package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts +59 -0
- package/src/engine/sound/ecs/audio/SpatialAudioIndex.d.ts.map +1 -0
- package/src/engine/sound/ecs/audio/SpatialAudioIndex.js +140 -0
- package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts +16 -65
- package/src/engine/sound/ecs/emitter/SoundEmitter.d.ts.map +1 -1
- package/src/engine/sound/ecs/emitter/SoundEmitter.js +19 -224
- package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts +26 -29
- package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.d.ts.map +1 -1
- package/src/engine/sound/ecs/emitter/SoundEmitterComponentContext.js +168 -135
- package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts +36 -59
- package/src/engine/sound/ecs/emitter/SoundEmitterSystem.d.ts.map +1 -1
- package/src/engine/sound/ecs/emitter/SoundEmitterSystem.js +154 -390
- package/src/engine/sound/ecs/emitter/SoundTrack.d.ts +20 -23
- package/src/engine/sound/ecs/emitter/SoundTrack.d.ts.map +1 -1
- package/src/engine/sound/ecs/emitter/SoundTrack.js +34 -152
- package/src/engine/sound/sopra/IMPLEMENTATION_PLAN.md +993 -0
- package/src/engine/sound/sopra/README.md +643 -7
- package/src/engine/sound/sopra/SopraEngine.d.ts +229 -0
- package/src/engine/sound/sopra/SopraEngine.d.ts.map +1 -0
- package/src/engine/sound/sopra/SopraEngine.js +423 -0
- package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts +26 -0
- package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.d.ts.map +1 -0
- package/src/engine/sound/sopra/asset/AssetManagerBufferProvider.js +71 -0
- package/src/engine/sound/sopra/asset/BufferProvider.d.ts +24 -0
- package/src/engine/sound/sopra/asset/BufferProvider.d.ts.map +1 -0
- package/src/engine/sound/sopra/asset/BufferProvider.js +29 -0
- package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts +31 -0
- package/src/engine/sound/sopra/asset/StubBufferProvider.d.ts.map +1 -0
- package/src/engine/sound/sopra/asset/StubBufferProvider.js +58 -0
- package/src/engine/sound/sopra/definition/BusDefinition.d.ts +83 -0
- package/src/engine/sound/sopra/definition/BusDefinition.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/BusDefinition.js +142 -0
- package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/BusDefinitionSerializationAdapter.js +54 -0
- package/src/engine/sound/sopra/definition/DuckingRule.d.ts +71 -0
- package/src/engine/sound/sopra/definition/DuckingRule.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/DuckingRule.js +106 -0
- package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/DuckingRuleSerializationAdapter.js +31 -0
- package/src/engine/sound/sopra/definition/EventDescription.d.ts +132 -0
- package/src/engine/sound/sopra/definition/EventDescription.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/EventDescription.js +259 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/EventDescriptionSerializationAdapter.js +71 -0
- package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts +51 -0
- package/src/engine/sound/sopra/definition/MixerSnapshot.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/MixerSnapshot.js +83 -0
- package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/MixerSnapshotSerializationAdapter.js +39 -0
- package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts +72 -0
- package/src/engine/sound/sopra/definition/ParameterDefinition.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/ParameterDefinition.js +117 -0
- package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/ParameterDefinitionSerializationAdapter.js +31 -0
- package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts +14 -0
- package/src/engine/sound/sopra/definition/SopraPanningModel.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/SopraPanningModel.js +20 -0
- package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts +10 -0
- package/src/engine/sound/sopra/definition/VoiceStealMode.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/VoiceStealMode.js +18 -0
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts +93 -0
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/AbstractAudioClip.js +109 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts +80 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClip.js +181 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/BlendContainerAudioClipSerializationAdapter.js +74 -0
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts +34 -0
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/ContainerAudioClip.js +100 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts +101 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClip.js +230 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/RandomContainerAudioClipSerializationAdapter.js +54 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts +103 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClip.js +191 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SampleAudioClipSerializationAdapter.js +39 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts +40 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClip.js +91 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SequenceContainerAudioClipSerializationAdapter.js +42 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts +44 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClip.js +77 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SilenceAudioClipSerializationAdapter.js +27 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts +65 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClip.js +131 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts +17 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/clip/SwitchContainerAudioClipSerializationAdapter.js +41 -0
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts +24 -0
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/AbstractAudioEffect.js +24 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts +70 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffect.js +120 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/CompressorEffectSerializationAdapter.js +31 -0
- package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts +74 -0
- package/src/engine/sound/sopra/definition/effect/EqEffect.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/EqEffect.js +128 -0
- package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/EqEffectSerializationAdapter.js +29 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts +49 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffect.js +101 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts +18 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.d.ts.map +1 -0
- package/src/engine/sound/sopra/definition/effect/ReverbEffectSerializationAdapter.js +25 -0
- package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts +31 -0
- package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.d.ts.map +1 -0
- package/src/engine/sound/sopra/legacy/soundEmitterToEventDescription.js +106 -0
- package/src/engine/sound/sopra/runtime/BusGraph.d.ts +79 -0
- package/src/engine/sound/sopra/runtime/BusGraph.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/BusGraph.js +227 -0
- package/src/engine/sound/sopra/runtime/EventInstance.d.ts +144 -0
- package/src/engine/sound/sopra/runtime/EventInstance.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/EventInstance.js +579 -0
- package/src/engine/sound/sopra/runtime/ParameterStore.d.ts +42 -0
- package/src/engine/sound/sopra/runtime/ParameterStore.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/ParameterStore.js +98 -0
- package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts +42 -0
- package/src/engine/sound/sopra/runtime/SopraPlaybackContext.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/SopraPlaybackContext.js +68 -0
- package/src/engine/sound/sopra/runtime/Voice.d.ts +67 -0
- package/src/engine/sound/sopra/runtime/Voice.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/Voice.js +145 -0
- package/src/engine/sound/sopra/runtime/VoiceManager.d.ts +38 -0
- package/src/engine/sound/sopra/runtime/VoiceManager.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/VoiceManager.js +136 -0
- package/src/engine/sound/sopra/runtime/VoicePool.d.ts +12 -0
- package/src/engine/sound/sopra/runtime/VoicePool.d.ts.map +1 -0
- package/src/engine/sound/sopra/runtime/VoicePool.js +17 -0
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts +11 -0
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.d.ts.map +1 -0
- package/src/engine/sound/sopra/serialization/populateSopraSerializationRegistry.js +42 -0
- package/src/engine/sound/sopra/serialization/sopraJSON.d.ts +33 -0
- package/src/engine/sound/sopra/serialization/sopraJSON.d.ts.map +1 -0
- package/src/engine/sound/sopra/serialization/sopraJSON.js +99 -0
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts +27 -0
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.d.ts.map +1 -0
- package/src/engine/sound/sopra/serialization/sopraSerializationHarness.js +49 -0
- package/src/engine/sound/sopra/util/MockAudioContext.d.ts +74 -0
- package/src/engine/sound/sopra/util/MockAudioContext.d.ts.map +1 -0
- package/src/engine/sound/sopra/util/MockAudioContext.js +215 -0
- package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts +15 -0
- package/src/engine/sound/sopra/util/buildAttenuationCurve.d.ts.map +1 -0
- package/src/engine/sound/sopra/util/buildAttenuationCurve.js +40 -0
- package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts +34 -0
- package/src/engine/sound/sopra/util/fadeOutAndStop.d.ts.map +1 -0
- package/src/engine/sound/sopra/util/fadeOutAndStop.js +60 -0
- package/src/engine/sound/volume2dB.d.ts +1 -1
- package/src/engine/sound/volume2dB.d.ts.map +1 -1
- package/src/engine/sound/volume2dB.js +1 -1
- package/src/engine/graphics/sh3/path_tracer/sampling/v3_orthonormal_matrix_from_normal.d.ts.map +0 -1
- package/src/engine/physics/narrowphase/ray_shapes.d.ts +0 -66
- package/src/engine/physics/narrowphase/ray_shapes.d.ts.map +0 -1
- package/src/engine/physics/narrowphase/ray_shapes.js +0 -187
- package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts +0 -23
- package/src/engine/sound/ecs/emitter/SoundEmitterChannel.d.ts.map +0 -1
- package/src/engine/sound/ecs/emitter/SoundEmitterChannel.js +0 -32
- package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts +0 -18
- package/src/engine/sound/ecs/emitter/SoundTrackNodes.d.ts.map +0 -1
- package/src/engine/sound/ecs/emitter/SoundTrackNodes.js +0 -18
- package/src/engine/sound/sopra/AbstractAudioClip.d.ts +0 -26
- package/src/engine/sound/sopra/AbstractAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/AbstractAudioClip.js +0 -29
- package/src/engine/sound/sopra/ContainerAudioClip.d.ts +0 -12
- package/src/engine/sound/sopra/ContainerAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/ContainerAudioClip.js +0 -13
- package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts +0 -12
- package/src/engine/sound/sopra/RandomContainerAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/RandomContainerAudioClip.js +0 -15
- package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts +0 -7
- package/src/engine/sound/sopra/SequenceContainerAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/SequenceContainerAudioClip.js +0 -8
- package/src/engine/sound/sopra/SilenceAudioClip.d.ts +0 -13
- package/src/engine/sound/sopra/SilenceAudioClip.d.ts.map +0 -1
- package/src/engine/sound/sopra/SilenceAudioClip.js +0 -15
- /package/src/{engine/graphics/sh3/path_tracer/sampling → core/geom/vec3}/v3_orthonormal_matrix_from_normal.d.ts +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# Constraint-Solver Improvements Plan — A2 / A3 / A4
|
|
2
|
+
|
|
3
|
+
**Status:** complete — A2 baked (no flags); A3/A4 dropped on evidence; jitter goal
|
|
4
|
+
redirected to sleep · **Date:** 2026-06-09 · **Area:** `engine/physics` solver
|
|
5
|
+
|
|
6
|
+
> **Outcome (numbers + reasoning in `CONSTRAINT_SOLVER_BENCH_LOG.md`).** Burned in with
|
|
7
|
+
> **no flags/toggles** — one solver path. The Monte-Carlo **median over 150 seeds** was
|
|
8
|
+
> the decider (single-seed results were basin-luck):
|
|
9
|
+
> - **A2** (block 3×3 point-solve + symmetric sweep) — **BAKED**: −9–16% joint-solve
|
|
10
|
+
> time, exact ball-socket, mass-ratio-robust; residual-neutral in the median. The one
|
|
11
|
+
> robust win.
|
|
12
|
+
> - **A3** (ω under-relaxation) — **REMOVED**: raises the *median* residual, chaotic /
|
|
13
|
+
> bimodal in ω on the metastable scene, +46% transient penetration. A disproven
|
|
14
|
+
> bandaid, not shipped.
|
|
15
|
+
> - **A4** (contact-bias smoothing) — **REVERTED**: neutral, benefit unproven; not worth
|
|
16
|
+
> churning contact determinism.
|
|
17
|
+
>
|
|
18
|
+
> All config fields removed. Full physics suite **865 pass / 0 fail**; one golden band
|
|
19
|
+
> re-baselined for A2's (honest) slightly-livelier settle.
|
|
20
|
+
>
|
|
21
|
+
> **The settled-jitter goal is not closed by the solver — and the data shows why:** the
|
|
22
|
+
> residual is **basin/sleep-dominated, not convergence-dominated** (per-seed variance ≫
|
|
23
|
+
> any config difference), so no convergence/relaxation lever moves the median. The real
|
|
24
|
+
> lever is **island sleep** (item **B** below: aggregate-energy + hysteresis criterion),
|
|
25
|
+
> then **A1** (reduced coordinates) to remove the maximal-coordinate residual at source.
|
|
26
|
+
> A2 ships as a faster/more-correct foundation; B is the recommended next step.
|
|
27
|
+
|
|
28
|
+
## 0. Goal & prime directive
|
|
29
|
+
|
|
30
|
+
Close the residual-jitter / "never sleeps" gap on coupled, over-constrained islands
|
|
31
|
+
(ragdolls, jointed chains) **without** resorting to "bump the iteration count". Three
|
|
32
|
+
systemic changes, all staying inside the existing maximal-coordinate TGS solver:
|
|
33
|
+
|
|
34
|
+
- **A4 — smooth the split-impulse position bias** near the penetration slop (kill the
|
|
35
|
+
bang-bang relay that drives contact-side chatter).
|
|
36
|
+
- **A2 — block joint point-solve + root-ordered symmetric Gauss-Seidel** (remove
|
|
37
|
+
intra-joint coupling error and the directional bias of one-way PGS).
|
|
38
|
+
- **A3 — constraint-level dissipation**: under-relaxation (SOR ω) on the joint solve +
|
|
39
|
+
optional compliance/damping on the locked joint DOFs (so the limit cycle *decays*
|
|
40
|
+
instead of persisting; replaces leaning on global velocity damping).
|
|
41
|
+
|
|
42
|
+
**Prime directive for this work:** *every significant step is gated by the benchmark
|
|
43
|
+
suite.* The engine is real-time and single-thread JS — a stray allocation, a polymorphic
|
|
44
|
+
deopt, or an extra hot-loop branch can erase the win. We do **not** discover performance
|
|
45
|
+
regressions after the fact: each change ships with an **a-priori expected perf delta**
|
|
46
|
+
and a **hard gate**. A measured regression beyond its anticipated budget **halts the
|
|
47
|
+
phase** until it is either optimized away or signed off with a recorded rationale.
|
|
48
|
+
|
|
49
|
+
This is the engineering counterpart to the reduced-coordinate option (A1), which is
|
|
50
|
+
explicitly **out of scope** here (see §8). A2/A3/A4 narrow the gap inside the current
|
|
51
|
+
architecture; A1 is a separate strategic bet.
|
|
52
|
+
|
|
53
|
+
## 1. Background
|
|
54
|
+
|
|
55
|
+
The solver is already modern (see `JOLT_REVIEW.md`, `RAPIER_REVIEW.md`, `BULLET_REVIEW.md`,
|
|
56
|
+
`PLAN.md`): TGS, 4 substeps with per-substep position re-integration, per-substep
|
|
57
|
+
warm-starting for **both** contacts and joints, Catto split-impulse, persistent
|
|
58
|
+
feature-ID manifolds, atomic island sleeping. The jitter is therefore **not** a missing
|
|
59
|
+
technique — it is:
|
|
60
|
+
|
|
61
|
+
1. **An unconverged coupled-island limit cycle.** 10 maximal-coordinate cone-twist joints
|
|
62
|
+
+ self/ground contacts + ~4:1 mass ratio, solved by sequential impulse, leave a
|
|
63
|
+
residual that → 0 only in the iteration limit. The split-impulse position bias
|
|
64
|
+
re-derives from penetration around the 5 mm slop every substep — a relay forcing term —
|
|
65
|
+
while the only dissipation is blunt global velocity damping. Forcing + weak damping =
|
|
66
|
+
a low-amplitude limit cycle.
|
|
67
|
+
2. **Above the sleep gate.** The residual spikes to ~0.7 rad/s, 7× the
|
|
68
|
+
`√sleepVelocitySqrThreshold ≈ 0.1` gate, so the island's sleep timer keeps resetting
|
|
69
|
+
and it never deactivates. (Same phenomenon the KEVA bench documents: a 5,320-body
|
|
70
|
+
island that "never fully quiesces, so atomic-island sleep rarely triggers".)
|
|
71
|
+
|
|
72
|
+
The quality signal is already pinned by `PhysicsSystem.ragdoll.spec.js`:
|
|
73
|
+
`GOLDEN.settledLinSpeed` (0.4–1.2 m/s, obs 0.73) and `GOLDEN.settledAngSpeed`
|
|
74
|
+
(0.4–1.2 rad/s, obs 0.75), plus the 1000-seed Monte-Carlo sweep. Those are our quality
|
|
75
|
+
acceptance instruments; this plan adds the *performance* acceptance instruments alongside.
|
|
76
|
+
|
|
77
|
+
## 2. The three changes (scope summary)
|
|
78
|
+
|
|
79
|
+
| ID | Change | Primary files | Risk | A-priori perf delta (gate) |
|
|
80
|
+
|----|--------|---------------|------|----------------------------|
|
|
81
|
+
| **A4** | Smooth contact position bias near slop | `solver/solve_contacts.js` | Low | ≤ **+2%** mean/p99 on contact-heavy benches; ~0% ragdoll |
|
|
82
|
+
| **A2** | Block 3×3 joint point-solve; root-order joints; symmetric (alternating) sweep | `constraint/solve_constraints.js`, `island/*`, `ecs/PhysicsSystem.js` | Medium | ≤ **+10%** joint-heavy at *equal* `jointIterations`; **target net-neutral / negative** after iteration tuning; ~0% on contact-only benches |
|
|
83
|
+
| **A3** | SOR under-relaxation + locked-DOF compliance/damping | `constraint/solve_constraints.js`, `ecs/Joint.js`, `ecs/PhysicsSystem.js` | Low–Med | ≤ **+2%** joint-heavy |
|
|
84
|
+
|
|
85
|
+
Cumulative worst-case before tuning: **≈ +8–14%** on the joint-heavy ragdoll-field bench,
|
|
86
|
+
**≈ +2–3%** contact-heavy, **≈ 0%** contact-only-no-joint. After A2 enables a
|
|
87
|
+
`jointIterations` reduction at equal-or-better convergence (Phase 4), the joint-heavy
|
|
88
|
+
target is **≤ +3% or net-negative**. These are the numbers the gates enforce.
|
|
89
|
+
|
|
90
|
+
## 3. Measurement methodology & acceptance gates
|
|
91
|
+
|
|
92
|
+
> This section is the backbone of the plan. No code change is merged until its
|
|
93
|
+
> measurement step is green.
|
|
94
|
+
|
|
95
|
+
### 3.1 Benchmark suite (existing — the perf canaries)
|
|
96
|
+
|
|
97
|
+
Run from the moh root (`npx jest --config jest.conf.json -t "<name>"`, flip `test.skip`→`test`):
|
|
98
|
+
|
|
99
|
+
- `ecs/PhysicsSystem.bench.spec.js`
|
|
100
|
+
- *falling tower* (1000 random shapes) — broad mixed contact load
|
|
101
|
+
- *settling grid* (1024) — **sleep canary** (must still sleep fast)
|
|
102
|
+
- *KEVA tower* (5,320) — large-island contact stress (heaviest; run alone)
|
|
103
|
+
- *high-churn lifecycle* (10k ticks) — alloc/leak/drift detector
|
|
104
|
+
- *reproducibility* (8 trials bit-identical) — **determinism gate**
|
|
105
|
+
- `constraint/swing_twist.bench.spec.js` — joint math microbench (A2/A3 canary)
|
|
106
|
+
- `ecs/PhysicsMeshStack.bench.spec.js`, `ecs/PhysicsNarrowphaseRain.bench.spec.js`,
|
|
107
|
+
`queries/raycast.bench.spec.js` — **control benches**: should be ~0% on all three
|
|
108
|
+
changes; non-zero here means we touched code we didn't mean to.
|
|
109
|
+
|
|
110
|
+
### 3.2 New benches to add (Phase 0)
|
|
111
|
+
|
|
112
|
+
`ecs/PhysicsSystem.ragdoll.bench.spec.js` (reuse `buildScene` from the ragdoll spec):
|
|
113
|
+
|
|
114
|
+
- **micro** — 1 ragdoll × 600 steps: per-tick joint-solve cost in isolation (A2/A3 signal,
|
|
115
|
+
low contact noise).
|
|
116
|
+
- **macro / field** — *N* ragdolls (start N=64, also report N=256) dropped into a shared
|
|
117
|
+
pile: realistic joint+contact+self-collision load. This is the **headline A2/A3 perf
|
|
118
|
+
bench**.
|
|
119
|
+
- **settle metric** — for the field scene, report **(a)** ticks-until-island-sleep and
|
|
120
|
+
**(b)** fraction of bodies asleep at t=10 s, plus the settled-speed peak. This is the
|
|
121
|
+
*quality-of-settling* number the whole effort is about; it must **improve** (more sleep,
|
|
122
|
+
less residual), never regress.
|
|
123
|
+
|
|
124
|
+
Follow the existing bench conventions exactly: `test.skip` by default, `performance.now()`
|
|
125
|
+
per `fixedUpdate`, `compute_stats`, fixed seeds, report `sys.storage.awake_count`.
|
|
126
|
+
|
|
127
|
+
### 3.3 Quality / stability gates (must stay green on every change)
|
|
128
|
+
|
|
129
|
+
1. `PhysicsSystem.ragdoll.spec.js` — all 3 active tests pass; `GOLDEN.settledLinSpeed`
|
|
130
|
+
/ `settledAngSpeed` bands hold (or **improve**, with a deliberate re-baseline recorded).
|
|
131
|
+
2. **Determinism** — bit-identical: the ragdoll determinism test (256 steps) **and** the
|
|
132
|
+
bench reproducibility trial. No `toBeCloseTo`; `===` only.
|
|
133
|
+
3. **No NaN/Inf**, no fall-through (`count_fall_through == 0` on the thick-slab benches),
|
|
134
|
+
KEVA stack holds.
|
|
135
|
+
4. **Sleep not broken** — settling grid still reaches near-full sleep in the last second.
|
|
136
|
+
5. Monte-Carlo sweep (`RAGDOLL_SWEEP_RUNS=200`) — no NaN seeds; worst settled speed within
|
|
137
|
+
band across the seed space.
|
|
138
|
+
|
|
139
|
+
### 3.4 Performance protocol (JS noise control)
|
|
140
|
+
|
|
141
|
+
JS timing is noisy; measurements are only trustworthy under a fixed protocol:
|
|
142
|
+
|
|
143
|
+
- **Prefer same-process A/B via config flags** (§3.6): run each bench twice in one process,
|
|
144
|
+
flag-off then flag-on, and report the **delta**. This cancels machine/JIT/thermal
|
|
145
|
+
variance — the cleanest signal and the primary acceptance measurement.
|
|
146
|
+
- **Warm-up**: discard the first full bench run (JIT tier-up) before timing.
|
|
147
|
+
- **Repetition**: 5 runs; report **median of the per-run medians** for p50 and the
|
|
148
|
+
**max of per-run p99** for tail.
|
|
149
|
+
- **Noise band**: deltas **< 3%** are reported but **not** gated (within run-to-run noise).
|
|
150
|
+
Gates trigger only on deltas that exceed both 3% **and** the budget.
|
|
151
|
+
- **Environment**: record `node -v`, OS, machine; same machine for baseline and
|
|
152
|
+
comparison; AC power, no competing load. Pin the Node version for the whole effort.
|
|
153
|
+
- KEVA (~15 min/run) is run **once per phase** (pre/post), not per-commit; the
|
|
154
|
+
lightweight benches (ragdoll micro/field, settling grid, swing_twist) run per-commit.
|
|
155
|
+
|
|
156
|
+
### 3.5 Regression policy & anticipated-budget gate
|
|
157
|
+
|
|
158
|
+
The user requirement — *"if performance regresses it must be anticipated and within
|
|
159
|
+
expected limits"* — is operationalized as:
|
|
160
|
+
|
|
161
|
+
- Each phase has a **budget** (the per-bench numbers in §2 / the table below).
|
|
162
|
+
- After implementing a phase, run the protocol (§3.4). For every bench:
|
|
163
|
+
- delta **≤ budget** → **accept**, record the number.
|
|
164
|
+
- budget **< delta** within a documented stretch (e.g. before the Phase-4 iteration
|
|
165
|
+
cut) → **accept only with a one-line signed rationale** in the results log naming why
|
|
166
|
+
and the recovery plan.
|
|
167
|
+
- delta **> budget** with no rationale → **STOP**. Optimize (profile for alloc/deopt/
|
|
168
|
+
branch cost) or revert. Do not proceed to the next phase on an unexplained regression.
|
|
169
|
+
- Quality gate failing → **STOP** regardless of perf (correctness-first; no loosening
|
|
170
|
+
tolerances to pass — see project policy).
|
|
171
|
+
|
|
172
|
+
**Consolidated budget table** (filled with measured baselines in Phase 0):
|
|
173
|
+
|
|
174
|
+
| Bench | Metric | Baseline (Phase 0) | After A4 | After A2 (equal iters) | After A3 | After Phase-4 tune |
|
|
175
|
+
|-------|--------|--------------------|----------|------------------------|----------|--------------------|
|
|
176
|
+
| ragdoll field (N=64) | p50 / p99 tick | TBD | budget +0% | +≤10% | +≤2% | **≤ +3% or ↓** |
|
|
177
|
+
| ragdoll field — sleep% @10s | higher=better | TBD | ≥ baseline | **↑** | **↑** | **↑** |
|
|
178
|
+
| settling grid | p50; last-1s | TBD | +≤2% | +≤1% | +≤1% | ≤ +3% |
|
|
179
|
+
| KEVA tower | mean tick | TBD | +≤2% | +≤1% | +≤1% | ≤ +3% |
|
|
180
|
+
| falling tower | p50 / p99 | TBD | +≤2% | +≤1% | +≤1% | ≤ +3% |
|
|
181
|
+
| swing_twist micro | mean | TBD | ~0% | +≤15%* | +≤3% | ≤ +5% |
|
|
182
|
+
| MeshStack / Rain / raycast | p50 | TBD | ~0% | ~0% | ~0% | ~0% |
|
|
183
|
+
|
|
184
|
+
\* swing_twist is a pure joint-math microbench with no iteration-count amortization, so it
|
|
185
|
+
shows A2's per-iteration cost most starkly; the field bench (with iteration tuning) is the
|
|
186
|
+
real-world number.
|
|
187
|
+
|
|
188
|
+
### 3.6 Config flags for clean A/B (and uniform-flow burn-in)
|
|
189
|
+
|
|
190
|
+
Add solver config fields (defaults preserve current behavior so Phase 0 baseline ==
|
|
191
|
+
current `master`), letting each change be measured in isolation and rolled back instantly:
|
|
192
|
+
|
|
193
|
+
- `contactBiasSmoothing: boolean` (A4)
|
|
194
|
+
- `jointBlockSolve: boolean`, `jointSweepSymmetric: boolean` (A2)
|
|
195
|
+
- `jointRelaxation: number` (A3, ω; default **1.0** = no relaxation)
|
|
196
|
+
- `jointComplianceLin: number` / `jointDampingLin: number` (A3; default **0** = rigid)
|
|
197
|
+
|
|
198
|
+
These follow the existing config-knob style (`velocityIterations`, `jointIterations`, …),
|
|
199
|
+
not sentinels. **Burn-in (Phase 4):** once a *boolean* is validated as strictly better,
|
|
200
|
+
**collapse to the single path and delete the flag** (uniform control flow — we don't keep
|
|
201
|
+
two joint solvers forever). The *numeric* knobs (`jointRelaxation`, compliance/damping)
|
|
202
|
+
stay as real tuning config with sensible defaults.
|
|
203
|
+
|
|
204
|
+
### 3.7 Results log
|
|
205
|
+
|
|
206
|
+
Commit `CONSTRAINT_SOLVER_BENCH_LOG.md` next to this plan: one table per phase with
|
|
207
|
+
machine/Node, baseline vs after, delta, pass/fail, and any signed rationale. The plan is
|
|
208
|
+
the contract; the log is the evidence.
|
|
209
|
+
|
|
210
|
+
## 4. Phases
|
|
211
|
+
|
|
212
|
+
### Phase 0 — Baseline & harness (no solver changes)
|
|
213
|
+
|
|
214
|
+
**Objective:** lock a trustworthy baseline and the new benches before touching the solver.
|
|
215
|
+
|
|
216
|
+
- [ ] Add `ecs/PhysicsSystem.ragdoll.bench.spec.js` (micro + field + settle metric, §3.2).
|
|
217
|
+
- [ ] Add the config flags (§3.6) wired as **no-ops at default** (pure plumbing; verify
|
|
218
|
+
determinism + golden bands unchanged → proves the plumbing is inert).
|
|
219
|
+
- [ ] Run the **full** suite under the protocol (§3.4); fill the §3.5 baseline column and
|
|
220
|
+
seed `CONSTRAINT_SOLVER_BENCH_LOG.md`. Record `node -v`, machine.
|
|
221
|
+
- [ ] Confirm all §3.3 quality gates green on baseline.
|
|
222
|
+
- **Exit:** baseline numbers committed; flags inert; determinism intact.
|
|
223
|
+
|
|
224
|
+
### Phase 1 — A4: smooth contact position bias
|
|
225
|
+
|
|
226
|
+
**Why first:** smallest, most contained change (one function, contact-side only), so it
|
|
227
|
+
validates the gate discipline before we touch the joint hot path. Independent of A2/A3.
|
|
228
|
+
|
|
229
|
+
- [ ] In `solve_contacts.js` position pass, replace the hard slop gate
|
|
230
|
+
(`if depth>slop: bias=-spook_a*(depth-slop)` then `MAX_POSITION_BIAS` clamp) with a
|
|
231
|
+
**smooth ramp** across a small band above the slop (e.g. quadratic ease-in to the
|
|
232
|
+
linear region, or softplus), removing the C0 kink and the saturation corner that the
|
|
233
|
+
contact rings on. Keep the asymptotic linear gain identical so steady-state push-out
|
|
234
|
+
is unchanged.
|
|
235
|
+
- [ ] Behind `contactBiasSmoothing` flag.
|
|
236
|
+
- **Expected perf:** +2–4 flops/contact/position-iter (×4 substeps). Budget **≤ +2%** on
|
|
237
|
+
KEVA / grid / falling tower; **~0%** ragdoll & control benches.
|
|
238
|
+
- [ ] **Bench gate:** flag-off vs flag-on A/B on KEVA, settling grid, falling tower,
|
|
239
|
+
ragdoll field, + control benches. Confirm within budget; record in log.
|
|
240
|
+
- [ ] **Quality gate:** §3.3, with attention to fall-through (bias change must not let
|
|
241
|
+
penetration leak) and settled penetration band in the ragdoll spec.
|
|
242
|
+
- **Exit:** within budget, quality green, logged. (Decision to default-on deferred to
|
|
243
|
+
Phase 4 burn-in.)
|
|
244
|
+
|
|
245
|
+
### Phase 2 — A2: block point-solve + root-ordered symmetric Gauss-Seidel
|
|
246
|
+
|
|
247
|
+
**Objective:** remove intra-joint coupling error and one-way-sweep directional bias — the
|
|
248
|
+
structural joint-convergence win.
|
|
249
|
+
|
|
250
|
+
- [ ] **Block the 3 locked-linear DOFs** of each joint: form the 3×3 effective-mass
|
|
251
|
+
`K = (invMₐ+invM_b)I + skew(rₐ)ᵀinvIₐskew(rₐ) + skew(r_b)ᵀinvI_b skew(r_b)`,
|
|
252
|
+
solve the coupled impulse via a **closed-form, zero-alloc, regularized** 3×3 inverse
|
|
253
|
+
(guard near-singular → ε or per-DOF fallback). Keep `LIMITED`/`MOTOR`/`SPRING`
|
|
254
|
+
angular DOFs per-DOF (inequalities don't block cleanly). Behind `jointBlockSolve`.
|
|
255
|
+
*(Standard Catto block-solver technique applied to the ball-socket point.)*
|
|
256
|
+
- [ ] **Root-order joints** per island: BFS from a chosen root over island connectivity
|
|
257
|
+
(reuse `island/` data); store one pooled ordered-index array per island.
|
|
258
|
+
- [ ] **Symmetric sweep**: alternate forward (root→tip) / backward each joint iteration.
|
|
259
|
+
Behind `jointSweepSymmetric`.
|
|
260
|
+
- [ ] Preserve **warm-start** semantics exactly (per-substep replay); the block solve must
|
|
261
|
+
accumulate the same `dofImpulse` representation so warm-start and determinism hold.
|
|
262
|
+
- **Expected perf:** per-joint-iteration linear-block arithmetic +40–80% vs 3 scalars,
|
|
263
|
+
×`jointIterations`(8)×substeps(4). Budget **≤ +10%** joint-heavy / swing_twist **≤ +15%**
|
|
264
|
+
at equal iterations; **~0%** on contact-only & control benches (joint path not hit — a key
|
|
265
|
+
canary that the change is properly scoped).
|
|
266
|
+
- [ ] **Bench gate:** A/B each flag *independently* then together, on ragdoll micro/field +
|
|
267
|
+
swing_twist + KEVA (KEVA has few joints → confirms ~0% there) + controls.
|
|
268
|
+
- [ ] **Quality gate:** §3.3 + **convergence improvement evidence**: settled speed should
|
|
269
|
+
drop and/or sleep% rise on the ragdoll field bench at equal `jointIterations`.
|
|
270
|
+
Determinism re-baselined deliberately (the solve order/maths changed → exact values
|
|
271
|
+
change; update determinism reference + golden bands in one commit, recorded).
|
|
272
|
+
- **Exit:** within budget, measurable convergence/sleep improvement, quality green, logged.
|
|
273
|
+
|
|
274
|
+
### Phase 3 — A3: constraint-level dissipation
|
|
275
|
+
|
|
276
|
+
**Objective:** make the residual *decay* (targeted dissipation), built on A2's solver.
|
|
277
|
+
|
|
278
|
+
- [ ] **Under-relaxation (SOR ω<1)** on the joint velocity solve: scale the per-iteration
|
|
279
|
+
impulse delta by `jointRelaxation` (default 1.0). One multiply per DOF/iter.
|
|
280
|
+
- [ ] **Locked-DOF compliance + damping**: give the locked joint DOFs a small SPOOK
|
|
281
|
+
compliance with damping by **reusing the existing `SPRING`/γ regularization path**
|
|
282
|
+
(`solve_constraints.js`), driven by `jointComplianceLin`/`jointDampingLin`
|
|
283
|
+
(default 0 = rigid). Soft *with damping* (not soft alone — soft alone rings).
|
|
284
|
+
- [ ] Add per-joint override fields on `Joint` only if a global default proves insufficient
|
|
285
|
+
(prefer one global default; avoid per-joint sentinel sprawl).
|
|
286
|
+
- **Expected perf:** ω ~free (<0.5%); compliance adds a few flops/locked-DOF/iter. Budget
|
|
287
|
+
**≤ +2%** joint-heavy; ~0% elsewhere.
|
|
288
|
+
- [ ] **Tuning sweep:** grid `jointRelaxation ∈ {1.0, 0.9, 0.8, 0.7}` ×
|
|
289
|
+
`compliance/damping ∈ {off, light, medium}` measured against the **settled-speed /
|
|
290
|
+
sleep%** metric on the ragdoll field bench (and the Monte-Carlo sweep for robustness
|
|
291
|
+
across seeds). Pick the point that minimizes residual **without** making the ragdoll
|
|
292
|
+
visibly spongy (watch joint-anchor error / limit overshoot).
|
|
293
|
+
- [ ] **Bench gate** + **quality gate** (§3.3); confirm the chosen ω/compliance lands the
|
|
294
|
+
ragdoll into **sleep within 10 s** (the headline outcome) and tighten the golden
|
|
295
|
+
bands to the new, lower settled speed (recorded re-baseline).
|
|
296
|
+
- **Exit:** ragdoll sleeps / residual materially reduced; within budget; logged.
|
|
297
|
+
|
|
298
|
+
### Phase 4 — Iteration tuning, burn-in, final regression sweep
|
|
299
|
+
|
|
300
|
+
**Objective:** convert A2's better convergence into a perf *win*, and clean up.
|
|
301
|
+
|
|
302
|
+
- [ ] With A2+A3 on, **reduce `jointIterations`** (8 → trial 6, 5, 4) and re-measure: find
|
|
303
|
+
the lowest count that holds the quality gates. This is where the joint-heavy budget
|
|
304
|
+
returns to **≤ +3% or net-negative**. (Note: this is *not* "bump iterations" — it's
|
|
305
|
+
spending A2's convergence headroom to **cut** work.)
|
|
306
|
+
- [ ] **Burn-in**: for each validated boolean flag, collapse to the single code path and
|
|
307
|
+
delete the flag (uniform control flow). Keep numeric knobs as documented config with
|
|
308
|
+
the chosen defaults.
|
|
309
|
+
- [ ] **Full final regression sweep** including KEVA and the high-churn + reproducibility
|
|
310
|
+
lifecycle benches; fill the final budget column; sign off any residual overage.
|
|
311
|
+
- [ ] Update `PLAN.md` / relevant `*_REVIEW.md` cross-references; note remaining gap vs A1.
|
|
312
|
+
- **Exit:** all gates green, log complete, flags burned in, defaults set.
|
|
313
|
+
|
|
314
|
+
## 5. Risk register & rollback
|
|
315
|
+
|
|
316
|
+
| Risk | Mitigation |
|
|
317
|
+
|------|------------|
|
|
318
|
+
| Hot-loop **allocation** in the 3×3 block path (GC → tail-latency spike) | All scratch in pooled typed arrays / locals; verify via p99 + high-churn drift bench; profile allocations |
|
|
319
|
+
| **Deopt / megamorphism** from new branches | Keep flags monomorphic; burn-in removes branches; control benches must stay ~0% |
|
|
320
|
+
| Block **near-singular** `K` (degenerate config) | Regularized inverse + per-DOF fallback; covered by a degenerate-config unit test |
|
|
321
|
+
| **Determinism** drift (cross-platform FP from new math/order) | Re-baseline deterministically in one commit; reproducibility bench is a gate |
|
|
322
|
+
| A3 **over-damping** → spongy ragdoll / joint drift | Tuning sweep gates on joint-anchor error + limit overshoot, not just residual |
|
|
323
|
+
| Accidental scope creep into contacts (A2) | Contact-only & control benches must read ~0%; any movement = STOP & investigate |
|
|
324
|
+
| Each change is independently **revertible** | Config flags until Phase-4 burn-in; one phase per commit series |
|
|
325
|
+
|
|
326
|
+
## 6. Definition of done
|
|
327
|
+
|
|
328
|
+
- Ragdoll **sleeps within 10 s** (or residual settled speed materially reduced and golden
|
|
329
|
+
bands re-baselined lower); Monte-Carlo sweep robust across seeds.
|
|
330
|
+
- Every bench within its final budget (§3.5); all quality gates (§3.3) green; determinism
|
|
331
|
+
bit-identical; `CONSTRAINT_SOLVER_BENCH_LOG.md` complete with machine/Node and any
|
|
332
|
+
signed rationales.
|
|
333
|
+
- Flags burned in; tuning knobs documented with defaults.
|
|
334
|
+
|
|
335
|
+
## 7. Out of scope (referenced, not done here)
|
|
336
|
+
|
|
337
|
+
- **A1 — reduced-coordinate (Featherstone) articulation.** The exact-joint formulation;
|
|
338
|
+
a separate strategic pillar (parallel solver + contact-coupling layer + two-formulation
|
|
339
|
+
maintenance tax). A2/A3/A4 narrow the gap inside the current architecture first; A1 is
|
|
340
|
+
evaluated on its own once these land and are measured.
|
|
341
|
+
- **B — joint-aware island sleep** (aggregate-energy + hysteresis sleep criterion). A
|
|
342
|
+
complementary, mostly-orthogonal change to `__sleep_test` in `PhysicsSystem.js`. If
|
|
343
|
+
A3 already gets the ragdoll under the existing gate, B may be unnecessary; if not, B is
|
|
344
|
+
the cheap finisher. Track separately.
|
|
345
|
+
|
|
346
|
+
## 8. Appendix — touch points (current `master`)
|
|
347
|
+
|
|
348
|
+
- **Config knobs** — `ecs/PhysicsSystem.js`: `substeps`(4), `velocityIterations`(4),
|
|
349
|
+
`positionIterations`(1), `jointIterations`(8), `sleepVelocitySqrThreshold`(0.01),
|
|
350
|
+
`sleepTimeThreshold`(0.5). Add A2/A3/A4 flags here.
|
|
351
|
+
- **A4** — `solver/solve_contacts.js`: `PENETRATION_SLOP`(0.005), `CONTACT_RELAXATION`(4.75),
|
|
352
|
+
`MAX_POSITION_BIAS`(3), SPOOK gains (`g_spook_a`), per-contact position-bias block.
|
|
353
|
+
- **A2/A3** — `constraint/solve_constraints.js`: joint velocity solve, per-DOF loop,
|
|
354
|
+
per-substep warm-start, `LIMITED` inequality handling, `SPRING`/γ compliance path
|
|
355
|
+
(reused by A3), `swing_twist_error`. Joint ordering: `island/` (union-find + builder).
|
|
356
|
+
- **A3 joint fields** — `ecs/Joint.js` (`DofMode`, `dofImpulse`), `constraint/DofMode.js`.
|
|
357
|
+
- **Benches** — `ecs/PhysicsSystem.bench.spec.js`, `constraint/swing_twist.bench.spec.js`,
|
|
358
|
+
`ecs/PhysicsMeshStack.bench.spec.js`, `ecs/PhysicsNarrowphaseRain.bench.spec.js`,
|
|
359
|
+
`queries/raycast.bench.spec.js`; **new** `ecs/PhysicsSystem.ragdoll.bench.spec.js`.
|
|
360
|
+
- **Quality** — `ecs/PhysicsSystem.ragdoll.spec.js` (`GOLDEN` bands, determinism,
|
|
361
|
+
Monte-Carlo sweep), `ecs/PhysicsSystem.bench.spec.js` reproducibility trial.
|
|
362
|
+
|
|
363
|
+
> Line numbers intentionally omitted in favor of symbol names — they drift; grep the
|
|
364
|
+
> symbols. Verify each touch point before editing.
|
|
@@ -834,11 +834,12 @@ correctness gain.
|
|
|
834
834
|
|
|
835
835
|
Phasing (each phase: implement → spec → run from `H:/git/moh` → commit):
|
|
836
836
|
|
|
837
|
-
1. [x] **Ray-primitive helpers** — landed as `narrowphase/ray_shapes.js
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
837
|
+
1. [x] **Ray-primitive helpers** — originally landed as `narrowphase/ray_shapes.js`,
|
|
838
|
+
later EXTRACTED to `core/geom/3d/{sphere,box,capsule}/{sphere3,box3,capsule3}_raycast.js`
|
|
839
|
+
(the (`t`, normal, miss = `Infinity`, first-hit-from-outside) convention is a
|
|
840
|
+
fine general raycast contract, and a ray-vs-capsule primitive was otherwise
|
|
841
|
+
missing engine-wide; the dispatch still shares one ray→local transform across
|
|
842
|
+
them). Built local-frame (unit direction ⇒ `t` preserved;
|
|
842
843
|
rotate the local normal back). Triangle MT is inlined in the concave path
|
|
843
844
|
(the existing `computeTriangleRayIntersection` writes a `SurfacePoint3` and
|
|
844
845
|
returns no `t` — unsuited to the buffer-flyweight loop). Colocated specs.
|
|
@@ -33,6 +33,9 @@ export function swing_twist_error(dx: number, dy: number, dz: number, dw: number
|
|
|
33
33
|
* @param {number} dt_sub substep size in seconds (the SPOOK gain is derived
|
|
34
34
|
* from it, matching the contact solver's per-substep position stiffness)
|
|
35
35
|
* @param {number} iters velocity iterations
|
|
36
|
+
* @param {boolean} [reverse] A2: traverse the joint array tip→root this call
|
|
37
|
+
* (the caller alternates it per substep for a symmetric Gauss-Seidel sweep, so
|
|
38
|
+
* chain impulses propagate both ways). Default false ⇒ original root→tip order.
|
|
36
39
|
*/
|
|
37
|
-
export function solve_joints(joints: Joint[], system: PhysicsSystem, dt_sub: number, iters: number): void;
|
|
40
|
+
export function solve_joints(joints: Joint[], system: PhysicsSystem, dt_sub: number, iters: number, reverse?: boolean): void;
|
|
38
41
|
//# sourceMappingURL=solve_constraints.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"solve_constraints.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/constraint/solve_constraints.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"solve_constraints.d.ts","sourceRoot":"","sources":["../../../../../src/engine/physics/constraint/solve_constraints.js"],"names":[],"mappings":"AA6RA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,sCAHW,MAAM,MAAa,MAAM,MAAa,MAAM,MAAa,MAAM,OAC/D,YAAY,QAkCtB;AA0JD;;;;;;;;;;;;;;;;GAgBG;AACH,qCATW,OAAO,iCAEP,MAAM,SAEN,MAAM,YACN,OAAO,QA2SjB"}
|
|
@@ -123,6 +123,15 @@ const ang_clo = new Float64Array(3);
|
|
|
123
123
|
const ang_chi = new Float64Array(3);
|
|
124
124
|
const ang_gamma = new Float64Array(3);
|
|
125
125
|
|
|
126
|
+
// A2 (block solve) scratch: the coupled 3×3 effective-mass of a fully-LOCKED
|
|
127
|
+
// linear point constraint and its inverse, stored as the 6 symmetric entries
|
|
128
|
+
// [00, 01, 02, 11, 12, 22]; plus the per-body cross products `r×aₖ` and their
|
|
129
|
+
// inverse-inertia images `Iw⁻¹·(r×aₖ)`.
|
|
130
|
+
const lin_K = new Float64Array(6);
|
|
131
|
+
const lin_Kinv = new Float64Array(6);
|
|
132
|
+
const block_c = new Float64Array(9);
|
|
133
|
+
const block_w = new Float64Array(9);
|
|
134
|
+
|
|
126
135
|
/**
|
|
127
136
|
* Angular effective-mass contribution of one body about a unit world axis:
|
|
128
137
|
* `a · Iw⁻¹ · a`. Zero for non-dynamic / rotation-locked bodies (whose
|
|
@@ -223,6 +232,57 @@ function clamp_bias(b) {
|
|
|
223
232
|
return b;
|
|
224
233
|
}
|
|
225
234
|
|
|
235
|
+
/**
|
|
236
|
+
* A2 — accumulate one body's angular contribution to the 3×3 point-constraint
|
|
237
|
+
* effective-mass block `K` (frame axes): for each frame axis `aₖ`,
|
|
238
|
+
* `cₖ = r×aₖ` and `wₖ = Iw⁻¹·cₖ`, then `K_kl += cₖ·w_l`. Symmetric, so only the
|
|
239
|
+
* upper triangle is written. Rotation-locked / non-dynamic bodies (zero inverse
|
|
240
|
+
* inertia) add nothing. The mass term `(invMA+invMB)·I` is seeded by the caller.
|
|
241
|
+
*
|
|
242
|
+
* @param {Float64Array} ss @param {number} base
|
|
243
|
+
* @param {number} rx @param {number} ry @param {number} rz lever arm
|
|
244
|
+
* @param {Float64Array} fa frame-A world basis (rows = axes)
|
|
245
|
+
* @param {Float64Array} K length-6 symmetric accumulator [00,01,02,11,12,22]
|
|
246
|
+
*/
|
|
247
|
+
function accumulate_point_block(ss, base, rx, ry, rz, fa, K) {
|
|
248
|
+
const iix = ss[base + SBS_INV_I_X], iiy = ss[base + SBS_INV_I_Y], iiz = ss[base + SBS_INV_I_Z];
|
|
249
|
+
if (iix === 0 && iiy === 0 && iiz === 0) return;
|
|
250
|
+
const qx = ss[base + SBS_QX], qy = ss[base + SBS_QY], qz = ss[base + SBS_QZ], qw = ss[base + SBS_QW];
|
|
251
|
+
for (let k = 0; k < 3; k++) {
|
|
252
|
+
const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
|
|
253
|
+
const cx = ry * az - rz * ay, cy = rz * ax - rx * az, cz = rx * ay - ry * ax;
|
|
254
|
+
block_c[3 * k] = cx; block_c[3 * k + 1] = cy; block_c[3 * k + 2] = cz;
|
|
255
|
+
world_inverse_inertia_apply_raw(block_w, 3 * k, iix, iiy, iiz, qx, qy, qz, qw, cx, cy, cz);
|
|
256
|
+
}
|
|
257
|
+
K[0] += block_c[0] * block_w[0] + block_c[1] * block_w[1] + block_c[2] * block_w[2];
|
|
258
|
+
K[1] += block_c[0] * block_w[3] + block_c[1] * block_w[4] + block_c[2] * block_w[5];
|
|
259
|
+
K[2] += block_c[0] * block_w[6] + block_c[1] * block_w[7] + block_c[2] * block_w[8];
|
|
260
|
+
K[3] += block_c[3] * block_w[3] + block_c[4] * block_w[4] + block_c[5] * block_w[5];
|
|
261
|
+
K[4] += block_c[3] * block_w[6] + block_c[4] * block_w[7] + block_c[5] * block_w[8];
|
|
262
|
+
K[5] += block_c[6] * block_w[6] + block_c[7] * block_w[7] + block_c[8] * block_w[8];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Invert a symmetric positive-definite 3×3 (6-entry form) in closed form,
|
|
267
|
+
* writing the inverse (same 6-entry form) to `out`. Returns the determinant; a
|
|
268
|
+
* near-singular block (|det| < 1e-12) returns 0 and leaves `out` untouched so
|
|
269
|
+
* the caller falls back to the per-axis solve.
|
|
270
|
+
*
|
|
271
|
+
* @param {Float64Array} K [00,01,02,11,12,22] @param {Float64Array} out
|
|
272
|
+
* @returns {number} determinant
|
|
273
|
+
*/
|
|
274
|
+
function invert_sym3(K, out) {
|
|
275
|
+
const a = K[0], b = K[1], c = K[2], d = K[3], e = K[4], f = K[5];
|
|
276
|
+
const A00 = d * f - e * e, A01 = c * e - b * f, A02 = b * e - c * d;
|
|
277
|
+
const A11 = a * f - c * c, A12 = b * c - a * e, A22 = a * d - b * b;
|
|
278
|
+
const det = a * A00 + b * A01 + c * A02;
|
|
279
|
+
if (det < 1e-12 && det > -1e-12) return 0;
|
|
280
|
+
const inv = 1 / det;
|
|
281
|
+
out[0] = A00 * inv; out[1] = A01 * inv; out[2] = A02 * inv;
|
|
282
|
+
out[3] = A11 * inv; out[4] = A12 * inv; out[5] = A22 * inv;
|
|
283
|
+
return det;
|
|
284
|
+
}
|
|
285
|
+
|
|
226
286
|
/**
|
|
227
287
|
* Swing-twist decomposition of the relative rotation `qD = conj(qA)⊗qB`, giving
|
|
228
288
|
* the per-frame-axis angular positions used by wide-cone angular DOFs.
|
|
@@ -443,8 +503,11 @@ function fill_row(joint, dofIndex, k, keff, pos, vel, spook_locked, spook_spec,
|
|
|
443
503
|
* @param {number} dt_sub substep size in seconds (the SPOOK gain is derived
|
|
444
504
|
* from it, matching the contact solver's per-substep position stiffness)
|
|
445
505
|
* @param {number} iters velocity iterations
|
|
506
|
+
* @param {boolean} [reverse] A2: traverse the joint array tip→root this call
|
|
507
|
+
* (the caller alternates it per substep for a symmetric Gauss-Seidel sweep, so
|
|
508
|
+
* chain impulses propagate both ways). Default false ⇒ original root→tip order.
|
|
446
509
|
*/
|
|
447
|
-
export function solve_joints(joints, system, dt_sub, iters) {
|
|
510
|
+
export function solve_joints(joints, system, dt_sub, iters, reverse) {
|
|
448
511
|
const n = joints.length;
|
|
449
512
|
if (n === 0 || dt_sub <= 0) return;
|
|
450
513
|
|
|
@@ -454,7 +517,8 @@ export function solve_joints(joints, system, dt_sub, iters) {
|
|
|
454
517
|
const storage = system.storage;
|
|
455
518
|
const ss = system.__solver_state.data;
|
|
456
519
|
|
|
457
|
-
for (let
|
|
520
|
+
for (let jj = 0; jj < n; jj++) {
|
|
521
|
+
const ji = reverse ? (n - 1 - jj) : jj;
|
|
458
522
|
const joint = joints[ji];
|
|
459
523
|
if (joint === undefined || joint === null) continue;
|
|
460
524
|
|
|
@@ -541,26 +605,48 @@ export function solve_joints(joints, system, dt_sub, iters) {
|
|
|
541
605
|
|
|
542
606
|
// --- Linear DOF row setup (frame axes). Position along axis k is
|
|
543
607
|
// `C · axis_k`; convention is A−B (impulse +to A / −to B). ---
|
|
608
|
+
let useLinBlock = false;
|
|
544
609
|
if (linConstrained) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
610
|
+
// A2: a fully-LOCKED linear triple is a ball-socket point — solve it
|
|
611
|
+
// as one coupled 3×3 block (frame axes) instead of axis-by-axis,
|
|
612
|
+
// removing the intra-joint coupling error the sequential per-axis
|
|
613
|
+
// Gauss-Seidel leaves behind. Falls back to per-axis if the block is
|
|
614
|
+
// near-singular.
|
|
615
|
+
if (mode[0] === DofMode.LOCKED && mode[1] === DofMode.LOCKED && mode[2] === DofMode.LOCKED) {
|
|
616
|
+
lin_K[0] = lin_K[3] = lin_K[5] = invMA + invMB;
|
|
617
|
+
lin_K[1] = lin_K[2] = lin_K[4] = 0;
|
|
618
|
+
accumulate_point_block(ss, baseA, rAx, rAy, rAz, fa, lin_K);
|
|
619
|
+
if (!to_world) accumulate_point_block(ss, baseB, rBx, rBy, rBz, fa, lin_K);
|
|
620
|
+
if (invert_sym3(lin_K, lin_Kinv) !== 0) {
|
|
621
|
+
for (let k = 0; k < 3; k++) {
|
|
622
|
+
const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
|
|
623
|
+
lin_bias[k] = clamp_bias(spook_locked * (Cx * ax + Cy * ay + Cz * az));
|
|
624
|
+
lin_active[k] = 1;
|
|
625
|
+
}
|
|
626
|
+
useLinBlock = true;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (!useLinBlock) {
|
|
630
|
+
for (let k = 0; k < 3; k++) {
|
|
631
|
+
lin_active[k] = 0;
|
|
632
|
+
const m = mode[k];
|
|
633
|
+
if (m === DofMode.FREE) { imp[k] = 0; continue; }
|
|
634
|
+
const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
|
|
635
|
+
const keff = axis_effective_mass(ss, baseA, invMA, rAx, rAy, rAz, ax, ay, az)
|
|
636
|
+
+ (to_world ? 0 : axis_effective_mass(ss, baseB, invMB, rBx, rBy, rBz, ax, ay, az));
|
|
637
|
+
const pos = Cx * ax + Cy * ay + Cz * az;
|
|
638
|
+
// Relative anchor velocity along the axis (A−B), for LIMITED
|
|
639
|
+
// bound selection. Cheap; only the projection is used.
|
|
640
|
+
let vel = 0;
|
|
641
|
+
if (m === DofMode.LIMITED) {
|
|
642
|
+
const rvx = (ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * rAz - ss[baseA + SBS_AV_Z] * rAy) - (to_world ? 0 : (ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rBz - ss[baseB + SBS_AV_Z] * rBy));
|
|
643
|
+
const rvy = (ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rAx - ss[baseA + SBS_AV_X] * rAz) - (to_world ? 0 : (ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rBx - ss[baseB + SBS_AV_X] * rBz));
|
|
644
|
+
const rvz = (ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * rAy - ss[baseA + SBS_AV_Y] * rAx) - (to_world ? 0 : (ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rBy - ss[baseB + SBS_AV_Y] * rBx));
|
|
645
|
+
vel = rvx * ax + rvy * ay + rvz * az;
|
|
646
|
+
}
|
|
647
|
+
fill_row(joint, k, k, keff, pos, vel, spook_locked, spook_spec, dt_sub,
|
|
648
|
+
lin_active, lin_eff, lin_bias, lin_clo, lin_chi, lin_gamma);
|
|
561
649
|
}
|
|
562
|
-
fill_row(joint, k, k, keff, pos, vel, spook_locked, spook_spec, dt_sub,
|
|
563
|
-
lin_active, lin_eff, lin_bias, lin_clo, lin_chi, lin_gamma);
|
|
564
650
|
}
|
|
565
651
|
} else {
|
|
566
652
|
lin_active[0] = lin_active[1] = lin_active[2] = 0;
|
|
@@ -650,22 +736,50 @@ export function solve_joints(joints, system, dt_sub, iters) {
|
|
|
650
736
|
// Linear rows (frame axes). The relative anchor velocity is
|
|
651
737
|
// recomputed before each row so successive rows see the prior
|
|
652
738
|
// impulses (Gauss-Seidel coupling).
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
739
|
+
if (useLinBlock) {
|
|
740
|
+
// A2: coupled 3×3 point solve — one relative anchor velocity,
|
|
741
|
+
// projected onto the frame axes, solved against Kⁱⁿᵛ in a single
|
|
742
|
+
// Newton step (bilateral, no clamp). The point constraint is
|
|
743
|
+
// satisfied exactly each iteration; only its coupling with the
|
|
744
|
+
// angular rows remains to iterate.
|
|
656
745
|
const rvx = (ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * rAz - ss[baseA + SBS_AV_Z] * rAy) - (to_world ? 0 : (ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rBz - ss[baseB + SBS_AV_Z] * rBy));
|
|
657
746
|
const rvy = (ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rAx - ss[baseA + SBS_AV_X] * rAz) - (to_world ? 0 : (ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rBx - ss[baseB + SBS_AV_X] * rBz));
|
|
658
747
|
const rvz = (ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * rAy - ss[baseA + SBS_AV_Y] * rAx) - (to_world ? 0 : (ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rBy - ss[baseB + SBS_AV_Y] * rBx));
|
|
659
|
-
const
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
748
|
+
const a0x = fa[0], a0y = fa[1], a0z = fa[2];
|
|
749
|
+
const a1x = fa[3], a1y = fa[4], a1z = fa[5];
|
|
750
|
+
const a2x = fa[6], a2y = fa[7], a2z = fa[8];
|
|
751
|
+
const b0 = rvx * a0x + rvy * a0y + rvz * a0z + lin_bias[0];
|
|
752
|
+
const b1 = rvx * a1x + rvy * a1y + rvz * a1z + lin_bias[1];
|
|
753
|
+
const b2 = rvx * a2x + rvy * a2y + rvz * a2z + lin_bias[2];
|
|
754
|
+
const d0 = -(lin_Kinv[0] * b0 + lin_Kinv[1] * b1 + lin_Kinv[2] * b2);
|
|
755
|
+
const d1 = -(lin_Kinv[1] * b0 + lin_Kinv[3] * b1 + lin_Kinv[4] * b2);
|
|
756
|
+
const d2 = -(lin_Kinv[2] * b0 + lin_Kinv[4] * b1 + lin_Kinv[5] * b2);
|
|
757
|
+
imp[0] += d0; imp[1] += d1; imp[2] += d2;
|
|
758
|
+
const Px = d0 * a0x + d1 * a1x + d2 * a2x;
|
|
759
|
+
const Py = d0 * a0y + d1 * a1y + d2 * a2y;
|
|
760
|
+
const Pz = d0 * a0z + d1 * a1z + d2 * a2z;
|
|
761
|
+
if (Px !== 0 || Py !== 0 || Pz !== 0) {
|
|
762
|
+
apply_impulse(ss, baseA, invMA, rAx, rAy, rAz, Px, Py, Pz, +1);
|
|
763
|
+
if (!to_world) apply_impulse(ss, baseB, invMB, rBx, rBy, rBz, Px, Py, Pz, -1);
|
|
764
|
+
}
|
|
765
|
+
} else {
|
|
766
|
+
for (let k = 0; k < 3; k++) {
|
|
767
|
+
if (!lin_active[k]) continue;
|
|
768
|
+
const ax = fa[3 * k], ay = fa[3 * k + 1], az = fa[3 * k + 2];
|
|
769
|
+
const rvx = (ss[baseA + SBS_LV_X] + ss[baseA + SBS_AV_Y] * rAz - ss[baseA + SBS_AV_Z] * rAy) - (to_world ? 0 : (ss[baseB + SBS_LV_X] + ss[baseB + SBS_AV_Y] * rBz - ss[baseB + SBS_AV_Z] * rBy));
|
|
770
|
+
const rvy = (ss[baseA + SBS_LV_Y] + ss[baseA + SBS_AV_Z] * rAx - ss[baseA + SBS_AV_X] * rAz) - (to_world ? 0 : (ss[baseB + SBS_LV_Y] + ss[baseB + SBS_AV_Z] * rBx - ss[baseB + SBS_AV_X] * rBz));
|
|
771
|
+
const rvz = (ss[baseA + SBS_LV_Z] + ss[baseA + SBS_AV_X] * rAy - ss[baseA + SBS_AV_Y] * rAx) - (to_world ? 0 : (ss[baseB + SBS_LV_Z] + ss[baseB + SBS_AV_X] * rBy - ss[baseB + SBS_AV_Y] * rBx));
|
|
772
|
+
const vrel = rvx * ax + rvy * ay + rvz * az;
|
|
773
|
+
const old = imp[k];
|
|
774
|
+
// `+ γ·old` regularises a spring row (γ = 0 otherwise → exact).
|
|
775
|
+
let nv = old - lin_eff[k] * (vrel + lin_bias[k] + lin_gamma[k] * old);
|
|
776
|
+
if (nv < lin_clo[k]) nv = lin_clo[k]; else if (nv > lin_chi[k]) nv = lin_chi[k];
|
|
777
|
+
imp[k] = nv;
|
|
778
|
+
const d = nv - old;
|
|
779
|
+
if (d !== 0) {
|
|
780
|
+
apply_impulse(ss, baseA, invMA, rAx, rAy, rAz, d * ax, d * ay, d * az, +1);
|
|
781
|
+
if (!to_world) apply_impulse(ss, baseB, invMB, rBx, rBy, rBz, d * ax, d * ay, d * az, -1);
|
|
782
|
+
}
|
|
669
783
|
}
|
|
670
784
|
}
|
|
671
785
|
|