What the safe API binds and which tests check it; then which tests check the magnitude rules of
oxijolt::limits, and what they leave open. limits.md derives the bounds themselves.
Test names refer to the integration tests in crates/oxijolt/tests and the unit tests in
crates/oxijolt/src (src/... below). Line numbers refer to the vendored Jolt 5.6 sources
(crates/oxijolt-sys/vendor/JoltPhysics/Jolt/Physics/).
| Feature | Safe API | Tests |
|---|---|---|
| World with settings and validation | PhysicsWorld::new, WorldSettings |
world.rs: default_world_steps, invalid_settings_are_rejected, gravity_round_trips_including_zero |
| Solver step counts per world | WorldSettings::velocity_steps, position_steps, PhysicsWorld::velocity_steps, position_steps |
solver_steps.rs: step_counts_outside_jolts_range_are_refused, step_counts_read_back_from_jolt, a_stack_without_position_steps_stays_finite, fewer_solver_steps_let_a_hinge_chain_stretch_more, chain_at_four_steps_is_identical_with_1_and_4_workers; src/world/tests.rs: world_settings_leave_every_other_physics_setting_at_jolts_default |
| Contact tuning per world | WorldSettings::penetration_slop, speculative_contact_distance, PhysicsWorld::penetration_slop, speculative_contact_distance |
contact_tuning.rs: contact_distances_outside_their_range_are_refused, contact_distances_read_back_from_jolt, a_deep_impact_is_corrected_down_to_the_slop, a_gently_placed_sphere_rests_within_the_slop_not_at_it, speculative_contacts_reach_bodies_within_the_distance, contact_distances_at_their_bounds_keep_a_small_scene_finite, far_bodies_within_the_speculative_distance_stay_finite, tuned_stacks_are_identical_with_1_and_4_workers; src/world/tests.rs: contact_distances_are_validated, world_settings_leave_every_other_physics_setting_at_jolts_default |
| Step with a report of dropped work, time step bounds, collision steps per update | PhysicsWorld::step, step_with_collision_steps, StepReport, StepError, MIN_DELTA_TIME, MAX_DELTA_TIME, MAX_COLLISION_STEPS |
world.rs: step_rejects_bad_delta_time, step_rejects_delta_time_below_the_bound, step_rejects_delta_time_above_the_bound, full_contact_constraint_buffer_is_reported_and_the_world_advances, full_body_pair_cache_is_reported_and_the_world_advances; collision_steps.rs: collision_step_counts_outside_the_bound_are_refused, bad_total_delta_times_are_refused_for_every_count, collision_steps_shorter_than_min_delta_time_are_refused, a_refused_call_consumes_nothing, a_refused_call_keeps_the_queued_events, collision_steps_cover_the_whole_delta_time, the_collision_step_count_reaches_jolt, a_rigid_force_acts_over_all_collision_steps_and_clears_after_the_call, a_soft_body_force_acts_in_the_first_collision_step_only, a_vehicle_gravity_override_acts_like_world_gravity_in_every_collision_step, one_collision_step_equals_step, sub_stepped_events_come_in_one_canonical_batch; job_system.rs: a_saturated_update_at_max_collision_steps_fits_jolts_job_pool; the doctest of step_with_collision_steps |
| Crate-wide error and result, outside the crate root | oxijolt::error::Error, oxijolt::error::Result, From for every area error |
src/error/tests.rs: every_area_error_converts_into_its_variant, display_is_the_wrapped_errors, source_is_the_wrapped_errors_source, sources_are_reported, error_is_send_sync_static_and_copy; root_glob.rs: the_crate_root_leaves_result_and_error_to_other_globs, the_crate_wide_result_is_named_by_the_error_module; the doctest of Error |
Prelude next to an engine prelude that exports Vec3, Quat, Real and Result |
oxijolt::prelude, oxijolt::prelude::math |
prelude.rs: the_prelude_leaves_math_and_result_to_the_engine_prelude, the_math_types_come_from_the_prelude_math_module; the doctest of prelude |
| The trait rules of the API guidelines (R6, R7) | ids, area errors, settings and their Default |
api_traits.rs: ids_compare_order_and_hash, errors_are_copy_and_comparable, settings_are_clone_and_comparable, settings_without_required_inputs_have_defaults (compile-time) |
| Collision layers | CollisionLayers, ObjectLayer, BroadPhaseLayer |
dynamics.rs: collision_layers_decide_which_bodies_touch; src/layers.rs: validate_rejects_inconsistent_tables |
| Several worlds, threads | PhysicsWorld: Send + Sync, WorldSettings::worker_threads |
dynamics.rs: two_worlds_side_by_side_do_not_interfere, worlds_step_in_parallel_threads; world.rs: world_and_shape_are_send_and_sync, world_is_readable_from_many_threads |
| Caller and single-threaded job systems | JobSystem, Job, WorldSettings::job_system, WorldSettings::MAX_CONCURRENCY, WorldSettings::single_threaded |
job_system.rs: rayon_job_system_matches_the_native_pool, inline_job_system_matches_the_native_pool, step_from_the_only_thread_of_a_rayon_pool_completes, jobs_may_outlive_their_world, a_deferred_job_may_finish_on_another_thread_while_the_world_drops, a_job_system_that_keeps_every_job_does_not_run_out_of_jolt_jobs, a_panicking_job_system_panics_out_of_step_and_the_world_stays_usable, max_concurrency_is_bounded, worlds_sharing_one_job_system_step_in_parallel, the_last_of_job_system_and_worker_threads_wins, a_sleeping_world_queues_no_jobs, single_threaded_matches_the_native_pool, the_last_of_three_job_system_choices_wins, a_single_threaded_world_steps_on_another_thread, worlds_with_different_native_job_systems_step_in_parallel; src/world/tests.rs: single_threaded_is_chosen_last_wins_and_has_concurrency_one; src/job_system.rs: run_and_drop_each_run_the_job_once, jobs_run_inside_a_queue_callback_wait_for_the_end_of_the_update, a_job_released_by_its_update_does_nothing_when_run_later, concurrent_panics_keep_one_payload_and_drop_the_others; job_system_leaks.rs: caller_job_systems_do_not_leak; the Rayon doctest of JobSystem |
| Static, kinematic and dynamic bodies, ids | BodySettings, create_body, BodyId |
bodies.rs: ids_follow_insertion_order, foreign_and_stale_ids_are_rejected, invalid_body_settings_are_rejected, full_world_rejects_another_body |
| Every body of a world, in id order | PhysicsWorld::body_ids, contains_body |
bodies.rs: body_ids_list_every_body_in_id_order (after a removal and an index reuse, with a character's inner body); world.rs: ids_are_iterators; the playground's digest tests (examples/playground/src/digest.rs) |
| Pose and velocity, read and write | BodyRef, BodyMut, Activation |
bodies.rs: pose_and_velocity_read_back_as_exact_bits, activation_decides_whether_a_pose_write_wakes_the_body |
| Forces, torque, reset | BodyMut::add_force, add_force_at_point, add_torque, reset_forces |
bodies.rs: forces, reset_forces_ignores_static_and_kinematic_bodies |
| Sleeping and active state | BodyRef::is_sleeping, is_active |
bodies.rs: sleeping_flag_is_readable |
Poses of every awake body, in BodyId order |
PhysicsWorld::active_body_poses, active_body_poses_into, BodyPose |
src/body/poses/tests.rs: poses_come_in_body_id_order_whatever_order_jolt_woke_them, poses_match_the_per_body_readout_bit_for_bit, poses_into_clears_and_reuses_the_buffer, each_call_reads_the_awake_set_again_into_the_same_buffer, a_world_with_only_soft_bodies_awake_reads_them, bodies_that_fall_asleep_are_synced_through_their_deactivation_event, concurrent_shared_reads_and_saves_agree; body_poses.rs: poses_are_identical_across_thread_counts, poses_are_identical_with_caller_job_systems |
| Impulses | BodyMut::add_impulse, add_angular_impulse, add_impulse_at_point |
body_controls.rs: impulses_change_velocity_at_once_and_wake, impulse_at_point_spins_the_body, impulses_respect_locked_axes, impulses_ignore_static_and_kinematic_bodies_and_refuse_soft_bodies |
| Buoyancy and drag | BodyMut::apply_buoyancy_impulse, BuoyancySettings, extension JPH_Body_GetSubmergedVolume, JPH_Body_ApplyBuoyancyImpulse2 |
buoyancy.rs: a_light_box_floats_at_its_density_line, a_sphere_twice_as_light_floats_half_under, a_dense_box_sinks_to_the_floor, drag_slows_a_submerged_body, drag_never_reverses_a_body_in_one_call, a_current_leaves_a_body_exactly_at_rest_in_zero_gravity, a_current_carries_a_floating_body, the_buoyant_lever_matches_the_geometry, a_fully_submerged_offset_body_turns, velocities_are_clamped_right_after_the_call, locked_axes_stay_still, only_submerged_dynamic_bodies_are_pushed; body_ext_smoke.rs (raw); helpers_determinism.rs: helpers_match_across_processes, a_rollback_replays_buoyancy_queries_and_estimates_exactly; helpers_leaks.rs |
| Kinematic moves | BodyMut::move_kinematic |
body_controls.rs: a_kinematic_platform_moves_toward_its_target, a_kinematic_move_keeps_its_velocity_after_the_step, a_tiny_move_does_not_wake_a_sleeping_kinematic_body, kinematic_moves_respect_locked_axes, a_kinematic_platform_carries_a_resting_cube |
| Activation on demand | BodyMut::activate, deactivate, PhysicsWorld::activate_bodies_in_box |
body_controls.rs: deactivate_zeroes_velocities_and_reports_an_event, deactivate_zeroes_the_velocities_of_a_body_already_asleep, repeated_deactivation_reports_once, activate_wakes_a_sleeping_body_and_reports_it, deactivate_refuses_soft_bodies, activate_bodies_in_box_wakes_exact_overlaps_in_body_id_order; src/body/tests.rs: the_broad_phase_box_encloses_the_callers_box |
| Sensors | BodySettings::sensor, BodyRef::is_sensor |
sensors.rs: a_sensor_reports_a_falling_cube_without_stopping_it, a_listener_cannot_turn_a_sensor_contact_into_an_ordinary_one, a_kinematic_body_entering_a_static_sensor_is_reported, a_static_sensor_loses_a_body_that_falls_asleep, an_active_kinematic_sensor_sees_a_sleeping_body, sensors_never_fall_asleep, a_deactivated_sensor_does_not_see_a_sleeping_body, an_awake_body_still_triggers_a_deactivated_sensor, a_character_inner_body_triggers_a_sensor, a_soft_body_reports_a_sensor |
| User data | BodySettings::user_data, BodyRef::user_data |
body_settings.rs: user_data_round_trips, ragdoll_parts_refuse_user_data, changing_settings_after_creation_changes_no_body |
| Locked axes | BodySettings::allowed_dofs, BodyRef::allowed_dofs, AllowedDofs, BodyError::RestrictedDofs |
body_settings.rs: a_plane_2d_body_stays_in_its_plane, axes_stay_world_axes_for_a_rotated_body, allowed_dofs_read_back, bodies_with_restricted_dofs_are_refused_by_every_constraint_kind_vehicles_and_ragdolls, rotated_rebase_refuses_restricted_dofs_and_changes_nothing, translation_rebase_keeps_restricted_dofs; src/body/tests.rs: allowed_dofs_match_jolts_flags |
| Movement capability and motion type changes | BodySettings::allow_dynamic_or_kinematic, BodyRef::can_be_kinematic_or_dynamic, BodyMut::set_motion_type, BodyError::CannotMove |
body_structure.rs: a_static_body_that_may_move_falls_when_made_dynamic, a_static_body_without_the_flag_cannot_move, making_a_body_static_stops_and_deactivates_it, dynamic_to_kinematic_drops_forces, same_motion_type_changes_nothing, motion_type_changes_refuse_owned_bodies, removing_the_constraint_releases_the_guard, a_static_body_that_may_move_joins_a_fixed_constraint_with_an_automatic_point |
| Shape changes | BodyMut::set_shape |
shape_changes.rs: set_shape_gives_the_mass_properties_of_a_fresh_body, set_shape_with_the_same_shape_and_a_new_mass_changes_the_mass_and_refuses_earlier_states, set_shape_with_none_restores_the_shapes_own_mass, set_shape_keeps_the_origin_and_moves_the_centre_of_mass, set_shape_keeps_the_allowed_dofs, a_growing_shape_pushes_a_resting_cube_up, a_shrinking_static_floor_wakes_the_cube_on_it, set_shape_wakes_bodies_between_old_and_new_bounds, set_shape_wakes_the_same_bodies_whether_or_not_the_broad_phase_was_optimized, set_shape_refuses_owned_bodies, a_dropped_shape_lives_on_in_its_body; body_controls_leaks.rs: body_controls_do_not_leak |
| Body controls replay | the controls above with save_state and restore_state |
body_controls_state.rs: momentary_inputs_replay_after_a_detour, the_inputs_change_the_run, creation_only_configuration_survives_detours_and_structural_edits, shape_and_motion_type_changes_refuse_earlier_states, restore_after_deactivate_reports_no_activation_event; body_controls_determinism.rs: controls_match_with_1_and_4_workers, controls_match_across_processes; the compile_fail examples of docs/bodies.md |
| Removal wakes the bodies around it | PhysicsWorld::remove_body |
bodies.rs: removal_wakes_bodies_resting_on_it, removal_wakes_the_same_bodies_whether_or_not_the_broad_phase_was_optimized |
| Mass override, continuous collision, gravity factor | BodySettings::mass, motion_quality, gravity_factor |
bodies.rs: forces, mass_too_small_to_invert_is_rejected, gravity_factor_scales_the_fall; dynamics.rs: linear_cast_body_does_not_tunnel_through_thin_static |
| Provided mass and inertia, the shape's ignored | BodySettings::mass_and_inertia, Inertia |
src/body/tests.rs: provided_principal_inertia_reaches_the_motion_properties, provided_tensor_inertia_reaches_the_motion_properties; body_settings.rs: provided_inertia_spins_the_body_as_given, invalid_provided_inertia_is_refused, the_last_mass_setter_wins, set_shape_recomputes_provided_inertia_from_the_shape, provided_inertia_at_its_bounds_steps_finitely; ragdoll.rs: provided_inertia_on_a_ragdoll_part; vehicle.rs: a_chassis_with_provided_inertia_drives_finitely; api_traits.rs: settings_are_clone_and_comparable; the doctest of mass_and_inertia |
| Damping, mass readout | BodySettings::linear_damping, angular_damping, BodyRef::mass |
src/body.rs: damping_reaches_the_body, invalid_damping_is_rejected, mass_is_reported_for_dynamic_bodies_only; ragdoll.rs: plain_settings_keep_the_masses_and_stabilized_ones_the_total |
| Contacts of the last step | PhysicsWorld::were_bodies_in_contact |
ragdoll.rs: the_same_parts_as_plain_bodies_collide, two_ragdolls_collide_with_each_other_but_not_with_themselves; listeners.rs: added_and_persisted_pairs_were_in_contact |
| Enhanced internal edge removal | BodySettings::enhanced_internal_edge_removal |
src/body.rs: enhanced_internal_edge_removal_reaches_the_body (reads the flag back from the Jolt body); dynamics.rs: enhanced_internal_edge_removal_smooths_sliding_over_a_compound |
| Box, sphere, Y-cylinder, Y-capsule, convex radius | Shape::new_box, new_box_with_convex_radius, new_sphere, new_cylinder, new_capsule |
shapes.rs: sharp_box_edge_ray_hits_the_exact_corner, convex_radius_rounds_box_edges_for_contacts; src/shape.rs: cylinder_and_capsule_dimensions_reach_jolt, invalid_dimensions_are_rejected |
| Heightfield (n = 33, holes, block size, active-edge threshold) | Shape::new_height_field, HeightFieldSettings |
shapes.rs: height_field_33_builds_and_matches_samples_at_nodes, height_field_rising_along_z_matches_analytic_surface, height_field_of_holes_has_no_collision, height_field_with_custom_active_edge_threshold_builds, active_edge_threshold_decides_the_normal_on_a_gentle_ridge, static_only_shapes_are_rejected_for_moving_bodies |
| Offset centre of mass | Shape::new_offset_center_of_mass |
shapes.rs: low_center_of_mass_rights_a_tilted_box; src/shape.rs: offset_center_of_mass_moves_only_the_center |
| Compound with per-child pose and user data | Shape::new_compound, CompoundChild, CompoundSubShape |
shapes.rs: compound_children_report_their_user_data, child_pose_is_applied, single_child_compound_keeps_its_user_data |
Compound edits at run time: add, remove, move and replace children, publish shapes that set_shape installs |
MutableCompound, ShapeError::NoSubShape |
src/shape/mutable/tests.rs: to_shape_builds_what_new_compound_builds, the_holder_stays_index_aligned, removing_to_zero_and_adding_again_works, extent_is_checked_after_the_centre_of_mass_moves, mass_free_children_leave_the_centre_where_mass_puts_it; mutable_compounds.rs: removing_a_child_drops_the_cube_it_carried, wake_order_does_not_depend_on_broad_phase_optimisation, an_added_child_catches_a_falling_cube, a_moved_child_pushes_a_resting_cube_up, a_replaced_child_keeps_its_slot_user_data, later_children_move_down_after_a_removal, a_commit_reaches_only_the_body_it_is_set_on, a_dynamic_compound_losing_a_child_gets_fresh_mass_properties, commits_follow_the_body_rules_of_set_shape, editing_and_publishing_do_not_touch_the_world, a_state_before_a_commit_is_refused_and_one_after_replays_exactly, removed_contacts_after_a_commit_carry_ids_of_the_old_shape, a_character_on_a_removed_child_keeps_updating, a_committed_shape_nests_and_scales; mutable_compounds_determinism.rs: mutable_compounds_match_with_1_and_4_workers, mutable_compounds_match_across_processes, a_rollback_after_the_last_edit_replays_exactly; shape_stress.rs (the mutable family, also in the asserts leg); mutable_compounds_leaks.rs (Windows): compound_edits_do_not_leak |
| Convex hull | Shape::new_convex_hull, new_convex_hull_with_convex_radius, new_convex_hull_with_material, ConvexHullError |
src/shape/hull/tests.rs: cube_corners_make_a_six_faced_hull, points_without_a_triangle_are_degenerate, points_in_one_plane_are_coplanar, hull_carries_its_material; hull_shapes.rs: irregular_rotated_hull_falls_and_rests_on_a_floor, thin_box_hull_is_a_dynamic_body, rotated_needle_hull_is_refused_as_dynamic_and_accepted_as_static, queries_hit_a_static_hull_from_above, default_hull_radius_equals_explicit, clouds_the_hull_builder_asserts_on_are_refused_or_built_without_asserts (without asserts only, see limits.md) |
| Triangle mesh with per-triangle materials, and the triangles it drops | Shape::new_mesh, new_mesh_with_settings, MeshSettings, MeshBuildQuality, DroppedTriangles, MeshError |
src/shape/mesh/tests.rs: unit_quad_builds_a_mesh, triangles_carry_their_materials, mesh_settings_reach_jolt, meshes_without_usable_triangles_are_refused, dropped_triangles_are_reported_with_their_area, materials_follow_the_input_triangles_past_dropped_ones; mesh_shapes.rs: rays_follow_the_triangle_surface, shape_queries_see_the_mesh_from_above, dynamic_shapes_rest_on_a_mesh, contacts_report_the_triangle_material, thin_strips_of_a_wide_level_mesh_carry_a_cube, the_thinnest_kept_slivers_collide_with_convex_shapes_up_to_the_extent, the_smallest_kept_triangles_collide_with_convex_shapes_up_to_the_extent |
| Kinematic mesh bodies; meshes refused for dynamic bodies, queries, characters and ragdolls | PhysicsWorld::create_body with BodySettings::mass |
mesh_shapes.rs: kinematic_mesh_platform_carries_a_box, kinematic_pure_mesh_steps_without_inertia, bodies_that_may_not_use_a_mesh_are_refused, meshes_and_heightfields_never_collide_with_each_other; src/shape/mesh/tests.rs: kinematic_eligibility_looks_through_compounds_and_decorators |
| Scaled shapes | Shape::new_scaled |
src/shape/scaled/tests.rs: box_bounds_follow_the_scale, jolt_scale_rules_per_shape_kind, scaled_box_has_the_mass_of_the_equivalent_box, materials_survive_scaling, scaled_meshes_stay_kinematic_and_collidable, placements_match_jolts_centre_of_mass_spaces, scaled_meshes_are_checked_for_the_extent_they_were_built_for; scaled_shapes.rs: non_uniformly_scaled_hull_and_box_rest_on_a_mesh, scaled_mesh_with_a_far_centre_of_mass_carries_a_cube, mirrored_mesh_faces_the_other_way, kinematic_scaled_mesh_carries_a_box, scaled_mesh_is_refused_as_dynamic, scaled_height_field_catches_a_falling_sphere, mirrored_height_field_faces_down; oxijolt-sys shape_ext_smoke.rs (the triangle readback it uses: triangles_are_read_across_batches_up_to_the_capacity, height_field_holes_have_no_triangles, compound_and_decorated_shapes_have_no_triangles) |
| Tapered capsule and tapered cylinder (cone) | Shape::new_tapered_capsule, new_tapered_cylinder |
src/shape/tapered/tests.rs: tapered_capsule_reaches_jolt, tapered_cylinder_and_cone_reach_jolt, tapered_shapes_follow_jolts_scale_rules; tapered_shapes.rs: tapered_capsule_and_cone_rest_on_a_mesh, queries_hit_tapered_shapes |
| Jolt's message for refused shape settings | ShapeError::Rejected, oxijolt::error::JoltMessage |
src/error/tests.rs: shape_error_variants_display_their_payload, jolt_message_reads_up_to_the_nul, jolt_message_without_nul_is_cut_to_capacity, jolt_message_drops_a_character_cut_at_the_end; oxijolt-sys shape_ext_smoke.rs |
| Seeded stress of the new shapes in an asserts-safe child process (3000 sparse hulls, outside the clouds the hull builder asserts on, among them clouds with a nearly collinear spike, a nearly flat cap or near-tied extreme points, 600 meshes among them level-like grids with thin strips next to far triangles, 1200 scaled shapes, 1000 tapered shapes, a share of them collided and queried, the meshes also by a box as large as the default convex extent) | the constructors above | shape_stress.rs: shapes_survive_seeded_stress |
| Determinism and rollback of the new shapes | the shapes above in one scene | mesh_shapes_determinism.rs: new_shapes_run_identically_with_1_and_4_workers, rollback_after_a_detour_replays_the_straight_run |
| Shape cooking: shapes with children and materials to bytes and back, refused when of another build or damaged | Shape::save_binary_state, Shape::restore_binary_state, BinaryStateError, ShapeError::BinaryState |
shape_binary_state.rs: every_shape_kind_round_trips, materials_round_trip_with_their_user_data, heightfields_without_material_indices_round_trip, a_restored_mesh_keeps_its_convex_extent, a_world_on_a_restored_mesh_steps_like_one_on_the_original, saves_in_two_processes_are_equal; src/shape/binary_state/tests.rs: the_format_version_is_the_extension_s, the_header_names_this_build, bytes_of_another_build_are_refused, every_header_bit_flip_is_refused, payload_bit_flips_are_refused_as_corrupt, every_truncation_is_refused_as_truncated, bytes_after_the_payload_are_refused, bytes_without_the_header_are_not_binary_state, a_record_joltc_refuses_behind_a_valid_checksum_is_rejected, a_restored_shape_beyond_the_extent_is_malformed, the_shape_error_reports_its_source; src/shape/binary_state/hash.rs: matches_the_reference_vectors, pieces_hash_like_the_whole; oxijolt-sys shape_state.rs (a box and a compound of a mesh and a shared hull round-trip, an empty shape keeps its centre of mass, and each record check refuses by its own message) |
| Leak gate for shape cooking (Windows) | save and restore of a compound with a mesh, a hull and materials, a refusal by the checksum and one by joltc | shape_binary_state_leaks.rs: saving_and_restoring_shapes_does_not_leak |
| Real models from open sources (report) | Shape::new_mesh, new_convex_hull, cooking, bodies and a character on the meshes |
real_meshes.rs: radio, kitchen_fridge, bathtub, bookcase, track_tile, dungeon_corridor, oloid_mesh_and_hull; real_meshes_downloaded.rs (with scripts/fetch_models.py): spot, scattering_skull, scattering_skull_statue, a_changed_skull_buffer_is_refused; real_meshes_determinism.rs: track_tile_runs_identically_with_1_and_4_workers, dungeon_corridor_runs_identically_with_1_and_4_workers, skull_runs_identically_with_1_and_4_workers |
| Thin dynamic hulls on a floor (limits) | Shape::new_convex_hull on dynamic bodies |
thin_hulls.rs: thin_hulls_set_down_rest_and_sleep, a_thin_dome_slapping_down_sinks_into_a_box_and_through_a_mesh |
| Shapes shared between bodies and worlds | Shape |
shapes.rs: shapes_are_shared_across_bodies_and_worlds; bodies.rs: shape_can_be_dropped_after_body_creation; ragdoll.rs: humanoid_settles_on_a_heightfield_in_a_second_world (the same terrain and compound shapes in two worlds, identical ray hits) |
| Physics materials with user data | PhysicsMaterial, Shape::new_box_with_material and its siblings, HeightFieldSettings::materials, ContactManifold::materials |
src/material.rs: user_data_round_trips, convex_constructors_keep_the_plain_shape_and_carry_the_material, compound_children_resolve_their_own_materials, padded_height_field_cells_resolve_their_materials, single_material_height_field_resolves_everywhere; listeners.rs: materials_are_reported_for_each_side; crates/oxijolt-sys/tests/material_smoke.rs |
| Ray cast | PhysicsWorld::cast_ray, RayCast, RayCastHit |
queries.rs: ray_cast_builder_defaults, ray_hits_chunk_and_terrain_from_twenty_metres, ray_starting_inside_hits_at_exactly_zero, ray_normals_point_out_of_the_hit_surface; shapes.rs: height_field_is_hit_from_below |
| Shape cast | PhysicsWorld::cast_shape, ShapeCast, ShapeCastHit |
queries.rs: capsule_cast_down_reports_floor_normal_and_distance, rotated_capsule_cast_uses_its_rotation, target_distance_stops_short, start_penetrating_is_reported_moving_in_not_out |
| Collide shape | PhysicsWorld::collide_shape, CollideShape, CollideShapeHit |
queries.rs: collide_capsule_with_floor_reports_geometric_depth, collide_ceiling_normal_points_down, max_separation_reports_negative_depth |
| Query filters: layers, child groups, excluded body | QueryFilter |
queries.rs: spawn_ground_ignores_canopy_by_group, object_layer_selection_skips_other_layers, excluded_body_is_skipped, nested_compound_group_governs_shape_casts_and_collide, filter_rejects_unknown_layer_and_foreign_body |
| Queries without a step, broad-phase optimisation | PhysicsWorld::optimize_broad_phase |
queries.rs: queries_see_created_moved_and_removed_bodies_without_a_step, optimize_broad_phase_keeps_query_results |
| Queries from many threads | queries on &PhysicsWorld |
queries.rs: filtered_queries_run_in_parallel; world.rs: rays_are_cast_from_many_threads |
| Floating origin | PhysicsWorld::rebase |
rebase.rs: vehicle_drives_across_a_rotating_rebase, resting_item_stays_across_a_rebase, sleeping_body_stays_asleep_across_a_rebase, drift_rebase_equals_the_scene_built_in_the_new_frame, rays_answer_the_same_across_a_rebase, invalid_rebase_changes_nothing; ragdoll.rs: rebase_moves_a_settled_ragdoll_rigidly; constraints.rs: rebase_moves_constraints_rigidly, rebase_with_a_pulley_is_atomic, rebase_with_pulleys_is_repeatable |
| Same results with 1 and 4 worker threads, with caller job systems and with the single-threaded job system, also with several collision steps, for the scenes the tests run (determinism.md) | WorldSettings::worker_threads, WorldSettings::job_system, WorldSettings::single_threaded, PhysicsWorld::step_with_collision_steps |
determinism.rs: stacks_digest_is_identical_across_thread_counts, chunk_digest_is_identical_across_thread_counts, walker_digest_is_identical_across_thread_counts, vehicle_digest_is_identical_across_thread_counts, fleet_digest_is_identical_across_thread_counts, fleet_digest_detects_a_changed_input, ragdoll_pile_digest_is_identical_across_thread_counts, constraint_digest_is_identical_across_thread_counts, soft_body_digest_is_identical_across_thread_counts, permuted_insertion_order_fails_the_gate, stacks_digest_is_identical_across_thread_counts_with_collision_steps, fleet_digest_is_identical_across_thread_counts_with_collision_steps, the nine *_digest_is_identical_with_caller_job_systems gates and the nine *_digest_is_identical_with_the_single_threaded_job_system gates; constraint_forces_determinism.rs: constraint_impulses_are_identical_with_1_and_4_workers; event_determinism.rs: events_are_identical_with_1_and_4_workers, sub_stepped_events_are_identical_with_1_and_4_workers, events_are_identical_with_caller_job_systems, events_are_identical_with_the_single_threaded_job_system, observing_every_event_leaves_the_simulation_unchanged; state.rs: rollback_replay_is_identical_across_processes; vehicle.rs: drive_a_route_twice_gives_identical_bits; skeleton_mapper_determinism.rs: skeleton_mapper_match_with_1_and_4_workers, skeleton_mapper_match_across_processes, a_changed_mapped_input_changes_the_digest (its mapped poses and its bodies); contact_tuning.rs: tuned_stacks_are_identical_with_1_and_4_workers; crates/oxijolt-sys/tests/determinism.rs: digest_is_identical_across_thread_counts |
| World state save and restore | PhysicsWorld::save_state, save_state_of, restore_state, WorldState, StateError |
state.rs: rollback_replays_every_tick_bit_for_bit, a_state_restores_any_number_of_times, restore_undoes_saved_constraint_targets, restore_does_not_undo_unsaved_configuration, restore_after_a_structural_change_is_refused_and_changes_nothing, a_rejected_structural_call_keeps_a_state_restorable, a_failed_create_on_a_full_world_keeps_a_state_restorable, a_vehicle_in_zero_gravity_keeps_its_world_up_across_a_restore, a_state_of_another_world_is_refused, bodies_created_after_the_restore_point_get_the_original_ids, a_selected_bodies_state_replays_when_the_rest_is_static, bodies_left_out_of_a_state_keep_their_current_state, soft_bodies_replay_after_a_detour_restore, restore_does_not_undo_a_vertex_inverse_mass and the other tests in the file; constraint_forces_state.rs: readouts_restore_with_the_state_for_every_kind; state_leaks.rs: world_states_do_not_leak; crates/oxijolt-sys/tests/state_smoke.rs |
| Rollback helpers: reused state buffers, body selections, filtered restore, state size | PhysicsWorld::save_state_into, restore_state_of, BodySelection, WorldState::new, WorldState::data_size, StateError::Body |
state_buffers.rs: a_state_saved_into_a_used_buffer_replays_like_save_state, a_buffer_belongs_to_the_world_that_saved_into_it_last, a_new_state_restores_into_no_world, a_refused_selection_leaves_the_buffer_as_it_was, a_ring_of_buffers_rolls_back_over_a_detour_bit_for_bit, a_movable_state_leaves_static_bodies_out, a_movable_state_holds_kinematic_and_inner_bodies, a_movable_state_replays_like_a_full_one, restoring_some_bodies_leaves_the_others_bit_for_bit, restoring_movable_bodies_keeps_static_bodies_where_they_are, a_selected_body_the_state_does_not_hold_keeps_its_state, inner_bodies_follow_their_characters, a_refused_filtered_restore_changes_nothing, a_filtered_restore_after_a_structural_change_is_refused, restoring_all_bodies_is_restore_state, a_filtered_restore_replays_identically_across_processes; state_filtered_parts.rs: a_filtered_restore_keeps_unselected_cloths_forces_and_wakes; state_buffer_allocations.rs: saving_into_a_used_buffer_allocates_nothing (every selection, selected saves after only full saves, worlds of two, one and two characters); state_sizes.rs: an_empty_world_holds_the_counts, a_static_body_adds_its_id_flag_and_pose, every_body_that_can_move_adds_the_same_bytes, a_resting_cube_adds_one_contact_pair_with_four_points, a_hinge_adds_its_index_and_solver_state, a_character_adds_its_inner_body_and_keeps_its_contacts_in_its_own_state; state_buffer_leaks.rs: state_buffers_and_filtered_restores_do_not_leak (Windows) |
Debug wireframe as line data (debug-renderer) |
PhysicsWorld::debug_lines, debug_lines_into, DebugLines, DebugLineSettings |
debug_lines.rs: near_colliders_present_far_absent_terrain_present, same_calls_give_identical_lines, line_cap_gives_exactly_the_cap_and_truncated, hidden_groups_vanish, compound_child_pose_applies_child_rotation_before_body_rotation; debug_lines_leaks.rs; crates/oxijolt-sys/tests/debug_renderer_bindings.rs |
| Leak gate for per-call joltc objects (Windows; bounded memory growth, catches only leaks above its threshold) | queries, rebase | leaks.rs: per_call_joltc_objects_do_not_leak |
| Character settings with Jolt's defaults, validation | PhysicsWorld::create_character, CharacterSettings, CharacterError, CharacterRef::penetration_recovery_speed, CharacterMut::set_penetration_recovery_speed |
character.rs: invalid_settings_and_poses_are_rejected_without_side_effects; src/character/tests.rs: default_settings_match_jolt; character_recovery.rs: penetration_recovery_speed_set_at_runtime_decides_the_push |
| Humanoid preset: a capsule of a height and radius standing on the position, owned by the settings and shared by their clones | CharacterSettings::humanoid |
src/character/tests.rs: humanoid_puts_the_capsule_bottom_at_the_position, humanoid_refuses_bad_dimensions; presets.rs: a_humanoid_stands_on_a_floor_and_walks, humanoid_settings_outlive_their_clones, humanoid_settings_cross_threads (Send + Sync, a clone used and dropped on another thread), a_humanoid_accepts_a_borrowed_inner_body; character_leaks.rs (Windows): characters_do_not_leak |
| Character ids per world, removal | CharacterId, character_ids, character_count, contains_character, remove_character |
character.rs: ids_count_from_one_are_never_reused_and_belong_to_their_world, removing_a_character_mid_run_leaves_the_others_sound |
| Character update: move by velocity, ground state and normal | update_character, CharacterMut::set_linear_velocity, CharacterRef::ground_state, ground_normal, ground_body, GroundState |
character.rs: a_character_lands_on_a_floor_and_reports_it, linear_velocity_round_trips_and_moves_a_free_character, a_slope_limit_near_zero_turns_the_limit_off; src/character/tests.rs: ground_states_convert_and_report_support |
| Stick to floor and walk stairs | ExtendedUpdateSettings |
character.rs: walk_stairs_climbs_a_step_that_stops_a_character_without_it; walker.rs: walking_down_a_30_degree_slope_has_no_hops (stick to floor as the floor snap) |
| Character study: which character laws the built-ins carry, per configuration (character-study.md) | ExtendedUpdateSettings, CharacterSettings, CharacterMut::set_penetration_recovery_speed, refresh_character_contacts, the queries |
character_study.rs: law_1_slope_limit, law_2_stays_on_the_floor, law_3_steps_sharp, law_3_steps_rounded, law_4_no_hops_downhill, law_5v_push_out_is_not_velocity, law_5r_push_out_recovers, law_6_seams_are_one_surface, radial_acceptance, the_controls_are_seen, the_study_table_in_the_docs_matches_the_tests |
| Character study instruments | the study harness (tests/study/) |
character_study.rs: decoded_heightfields_match_their_profiles, decoded_face_angles_match_the_scene, a_resting_character_has_zero_gap, seam_pair_matches_the_continuous_field, frames_round_trip, the_walker_row_reproduces_the_reference_near_step_bit_for_bit, tick_reports_account_for_every_displacement, law_predicates_reject_corrupted_traces |
| Character study determinism: repeated, across processes with 1 and 4 workers, restored after a detour, in a tilted frame | the study caller | character_study_determinism.rs: a_study_run_is_bit_exact_when_repeated, study_runs_match_across_processes_and_worker_counts, a_restored_study_run_continues_bit_for_bit_after_a_detour, the_study_rows_give_the_same_cells_in_a_tilted_frame, tilt_accounting_counts_flipped_and_divergent_cases |
| Character study rule 7: steep terrain slides, a steep structure holds, the terrain veto stands after the autostep and the snap, a wall beside a high ledge does not hold the character, the structure-edge snap takes a ledge edge and not a wall | the study caller | character_study_rules.rs: the_terrain_veto_stands_after_the_autostep_and_the_snap, a_steep_structure_holds_the_character_and_steep_terrain_does_not, walking_off_a_high_ledge_beside_a_wall_falls_to_the_floor, the_structure_edge_snap_takes_a_ledge_edge_and_not_a_wall_leaning_back (variants: high_ledge_beside_wall_survey) |
| Up and rotation per update (radial up) | CharacterMut::set_up, set_rotation, set_position |
character.rs: up_and_rotation_set_per_update_give_the_same_walk_in_another_frame; walker.rs: a_rotated_floor_far_from_the_anchor_keeps_the_path, a_chunk_seam_is_crossed |
| Character contacts: body, layer, compound child group, contact normal; filters | CharacterRef::active_contacts, CharacterContact, contact_compound_child, contact_object_layer, QueryFilter |
character.rs: a_character_lands_on_a_floor_and_reports_it, compound_children_report_their_group_and_filters_select_them |
| Inner body | CharacterSettings::inner_body, InnerBody, is_inner_body, BodyError::OwnedByCharacter |
character.rs: an_inner_body_is_a_body_of_the_world_owned_by_its_character, dropping_a_world_with_characters_releases_them |
| Collisions between characters | CharacterSettings::collide_with_characters |
character.rs: characters_that_collide_with_characters_keep_apart, removing_a_character_mid_run_leaves_the_others_sound |
| Character save and restore, chained replay | CharacterRef::save_state, CharacterMut::restore_state, CharacterState |
character.rs: a_chained_replay_of_one_character_is_bit_exact, a_chained_replay_of_colliding_characters_is_bit_exact, a_restored_state_saves_the_same_bytes, a_restored_state_moves_the_inner_body_with_the_character; crates/oxijolt-sys/tests/character_smoke.rs: character_lands_and_its_restored_state_continues_bit_for_bit |
| Characters across a rebase | PhysicsWorld::rebase, refresh_character_contacts |
character.rs: a_character_crosses_a_rebase_with_the_world |
The game's near step on a radial planet (reference controller in test support, tests/common/walker.rs) |
built on the rows above | walker.rs: step_law_climbs_up_to_045_but_not_05, a_step_under_a_low_ceiling_is_not_taken, the_autostep_never_climbs_a_dynamic_body_in_the_filter, slope_law_climbs_30_degrees_and_slides_back_from_60, steep_slopes_always_bring_the_walker_down, a_buried_walker_is_put_back_on_the_terrain, an_overlapping_wall_pushes_the_walker_out, a_jump_reaches_the_ballistic_apex_and_lands, rising_into_a_ceiling_resets_vel_up, chained_near_step_replay_is_bit_exact and the other tests in the file |
| Leak gate for characters (Windows) | create, update, save and restore, remove | character_leaks.rs: characters_do_not_leak |
| Character contact material lifetime: a character keeps the materials of its cached contacts alive, also when an update reuses contacts with a body whose shape was replaced or removed | update_character, refresh_character_contacts, PhysicsWorld::restore_state |
character_materials.rs: a_character_whose_contact_body_was_removed_keeps_updating, a_character_whose_contact_shape_was_replaced_keeps_updating, a_listener_panic_still_retains_the_new_contacts_materials; src/character/tests.rs: cached_contact_materials_are_owned_until_the_contacts_change, a_world_restore_releases_the_contact_materials, a_character_restore_keeps_the_old_materials_until_the_next_update, a_filter_panic_still_releases_the_materials_of_dropped_contacts, contacts_sharing_a_material_hold_one_reference, a_character_keeps_updating_after_a_character_it_touched_is_removed; character_materials_leaks.rs (Windows): character_contact_materials_do_not_leak; oxijolt-sys material_smoke.rs: add_ref_and_count_follow_jolt |
| Vehicle settings with Jolt's defaults, validation | WheeledVehicleSettings, WheelSettings, SpringSettings, VehicleEngineSettings, VehicleTransmissionSettings, VehicleDifferentialSettings, VehicleAntiRollBar |
src/vehicle/settings.rs: wheel_defaults_are_jolts, controller_defaults_are_jolts, constraint_differential_and_anti_roll_bar_defaults_are_jolts, built_settings_reach_jolt, step_coefficients_of_wheels_must_be_finite, step_coefficients_of_the_drivetrain_must_be_finite and the other validation tests in the file; vehicle.rs: invalid_vehicles_create_nothing. Anti-roll bars and the pitch and roll limit are tested for their defaults and validation only, not for their effect |
| Car preset: four mirrored wheels, front steering and drive, rear hand brake, anti-roll bars | WheeledVehicleSettings::car |
src/vehicle/settings/presets.rs: car_preset_equals_the_explicit_settings; presets.rs: a_car_preset_drives_brakes_steers_and_shifts, car_preset_values_are_validated |
| Vehicle creation, ids, removal | PhysicsWorld::create_wheeled_vehicle, remove_vehicle, VehicleId, vehicle_ids, vehicle_count, contains_vehicle, vehicle_of_body, VehicleError, BodyError::UsedByVehicle |
vehicle.rs: invalid_vehicles_create_nothing, vehicle_ids_are_sequential_and_never_reused, chassis_cannot_be_removed_while_a_vehicle_uses_it, a_removed_vehicle_no_longer_drives, dropping_a_world_with_vehicles_is_clean |
| Wheel collision testers and contact readout | VehicleCollisionTester (ray, sphere, cylinder), VehicleRef::wheels, WheelState, WheelContact, VehicleMut::set_collision_tester |
vehicle.rs: wheel_contacts_match_the_ground_geometry (every tester on a box floor and a heightfield, against the analytic plane), start_overlap_reports_a_hard_point_without_depth, start_overlap_depth_comes_from_collide_shape, collision_tester_can_be_replaced, wheels_pass_through_soft_bodies_to_the_ground |
| Wheel poses in world space, for drawing | VehicleRef::wheel_world_transform |
wheel_transforms.rs: a_resting_car_wheel_sits_on_its_suspension, steering_turns_the_front_axles, rolling_turns_the_wheel_about_its_axle, tank_and_bike_wheels_follow_their_suspension, wheel_world_transform_refuses_indices_past_the_last_wheel, wheel_transforms_move_with_a_rebase, wheel_transforms_keep_a_far_fractional_chassis_position (a rotated rebase to 1e6 m with a fraction under f64 positions, an offset centre of mass) |
| Driver input, engine and gear readout | DriverInput, VehicleMut::set_driver_input, VehicleRef::engine_rpm, current_gear |
vehicle.rs: driver_input_drives_steers_and_brakes, braking_at_the_smallest_step_stays_finite |
| Caller-applied gravity, sleep | VehicleMut::set_gravity, VehicleRef::gravity, world_up |
vehicle.rs: gravity_override_replaces_world_gravity, gravity_is_bounded_by_max_acceleration, sleeping_chassis_gets_no_override_force, never_sleeping_chassis_stays_awake; collision_steps.rs: a_vehicle_gravity_override_acts_like_world_gravity_in_every_collision_step |
| A car drives a route over heightfield terrain | built on the rows above (route and controller in tests/common/vehicle.rs) |
vehicle.rs: drive_a_route_over_terrain (four waypoints, never tipped past 60°, two wheels down on 95 % of the ticks), drive_a_route_twice_gives_identical_bits |
| Leak gate for vehicles (Windows) | create, drive, read, replace the tester, remove | vehicle_leaks.rs: vehicles_do_not_leak |
| Vehicle kinds and typed ids | VehicleId<K>, AnyVehicleId, VehicleKind, VehicleType, WheeledVehicle, TrackedVehicle, Motorcycle, remove_vehicle(impl Into<AnyVehicleId>) |
tracked_vehicle.rs: tank_chassis_is_guarded; motorcycle.rs: motorcycle_chassis_is_guarded; vehicle.rs: vehicle_ids_are_sequential_and_never_reused; root_glob.rs: the_vehicle_kinds_are_named_at_the_root; the compile_fail examples of VehicleMut (a tank's input on a car, a car's input on a tank) |
| Tracked vehicles: settings with Jolt's defaults, validation | PhysicsWorld::create_tracked_vehicle, TrackedVehicleSettings (default_engine, default_transmission), VehicleTrackSettings, TrackedWheelSettings |
src/vehicle/settings/tracked_tests.rs: tracked_wheel_defaults_are_jolts, track_defaults_are_jolts, tracked_controller_defaults_are_jolts, built_tracked_wheels_reach_jolt, tracked_settings_are_validated, tracked_wheels_share_the_wheel_rules, track_wheel_radii_stay_within_max_ratio_of_the_driven_wheel, tracked_drive_envelope_bounds_the_drivetrain; tracked_vehicle.rs: invalid_tracked_vehicles_create_nothing; drivetrain_bounds.rs: overflowing_tracked_drivetrains_are_refused, the_strongest_light_tracks_stay_finite, the_strongest_tracks_with_the_widest_radius_ratio_stay_finite, the_strongest_tracks_on_the_widest_curve_stay_finite, unequal_track_inertias_are_refused, the_widest_track_inertia_ratio_stays_finite_on_high_friction_ground, tracks_heavy_against_their_chassis_are_refused, tracks_heavy_against_a_narrow_hull_are_refused, the_heaviest_accepted_tracks_stay_finite_on_high_friction_ground, jolt_tank_tracks_are_inside_the_track_mass_ratio |
| Tracked vehicles: driving and readout | TrackedDriverInput, VehicleMut::<TrackedVehicle>::set_driver_input, VehicleRef::<TrackedVehicle>::tracks, track, track_wheels, driver_input, TrackState, TrackSide |
tracked_vehicle.rs: tank_pivots_in_place (drift below 5 cm, yaw rate above 2.3 rad/s), tank_climbs_a_12_degree_slope (more than 3 m of rise in 5 s), track_wheels_turn_with_their_track, brake_stops_the_tank, tracked_input_reads_back, tracked_inputs_are_validated, tank_testers, tank_wheels_pass_through_soft_bodies, rebase_rotates_a_tanks_gravity_override_and_tester_up |
| Motorcycles: settings with Jolt's defaults, validation | PhysicsWorld::create_motorcycle, MotorcycleSettings, VehicleError::LeanSpringIntegrationNotSaved |
src/vehicle/settings/motorcycle_tests.rs: motorcycle_defaults_are_jolts, built_lean_settings_reach_jolt, motorcycle_settings_are_validated, max_lean_angle_is_80_degrees, lean_spring_rule_boundary; motorcycle.rs: motorcycle_settings_are_refused |
Motorcycle preset: Jolt's MotorcycleTest sample |
MotorcycleSettings::bike |
presets.rs: bike_preset_equals_the_motorcycle_sample, a_bike_preset_rides_upright, bike_preset_values_are_validated |
| Motorcycles: riding, lean and readout | VehicleMut::<Motorcycle>::set_driver_input, VehicleRef::<Motorcycle>::lean, MotorcycleLean, is_lean_controller_enabled, is_lean_steering_limit_enabled, driver_input |
motorcycle.rs: motorcycle_stays_upright_at_speed, motorcycle_leans_into_a_turn, without_the_lean_controller_it_falls_over, no_lean_torque_unless_both_wheels_touch, zero_gravity_steering_limit_applies_only_while_moving, lean_spring_at_its_bound_steps_finitely, rebase_rotates_the_target_lean, motorcycle_testers, motorcycle_wheels_pass_through_soft_bodies |
| Tracked vehicles and motorcycles: same results with 1 and 4 workers, rollback, leaks | 12 tanks, 12 motorcycles and a car, 25 step listeners in 2 or 3 jobs; saves before contact, in a turn and in a gear shift | vehicle_kinds_determinism.rs: vehicle_kinds_match_with_1_and_4_workers, vehicle_kinds_match_across_processes, vehicle_kinds_detect_a_changed_input, the_scene_moves_every_kind; vehicle_kinds_state.rs: vehicle_kinds_replay_after_a_detour, vehicle_kinds_replay_across_processes, tracked_input_tracks_and_target_lean_are_restored, motorcycle_replays_at_the_default_integration_coefficient, a_refused_tank_or_motorcycle_keeps_a_state_restorable, motorcycle_world_up_in_zero_gravity_is_not_restored; vehicle_kinds_leaks.rs: tracked_vehicles_and_motorcycles_do_not_leak (Windows) |
| Live engine and transmission, every vehicle kind; gear counts and clutch release time fixed at creation | VehicleRef::engine_settings, transmission_settings, VehicleMut::set_engine, set_transmission, VehicleError::FixedAtCreation, extension JPH_WheeledVehicleController_SetEngine, _SetTransmission, JPH_TrackedVehicleController_SetEngine, _SetTransmission |
vehicle_tuning.rs: drivetrain_reads_back_as_set, more_engine_torque_accelerates_harder, a_longer_gear_ratio_changes_the_drive, gear_counts_and_clutch_release_time_are_fixed_at_creation, clutch_release_time_refusal_during_a_countdown, restore_keeps_live_drivetrain_settings, lower_rpm_limits_clamp_a_restored_rpm, invalid_live_drivetrains_change_nothing, drivetrain_setters_use_the_current_collision_tester; tracked_vehicle.rs: live_tank_engine_changes_after_fast_driving_step_finitely; src/vehicle/settings/live.rs: live_changes_keep_the_running_track_speed_bound; src/error/tests.rs: fixed_at_creation_names_the_setting; oxijolt-sys vehicle_tuning_smoke.rs: live_engine_reaches_wheeled_and_tracked_controllers, live_transmission_keeps_gear_and_refuses_fixed_changes |
| Live differentials of wheeled vehicles and motorcycles | VehicleRef::differentials, differential_limited_slip_ratio, VehicleMut::set_differentials, WheeledControllerKind, extension JPH_WheeledVehicleController_SetDifferentials, _SetDifferentialLimitedSlipRatio |
vehicle_tuning.rs: moving_drive_to_the_rear_differential_changes_which_wheels_spin, limited_slip_ratio_changes_torque_split, a_motorcycle_differential_change_changes_its_drive, invalid_live_differentials_change_nothing; oxijolt-sys vehicle_tuning_smoke.rs: live_differentials_change_which_wheels_are_driven |
| Live tracks of tracked vehicles; track wheels fixed at creation | VehicleRef::<TrackedVehicle>::track_settings, VehicleMut::<TrackedVehicle>::set_tracks, extension JPH_TrackedVehicleController_SetTrack |
tracked_tuning.rs: track_settings_read_back_as_set, heavier_brakes_stop_the_tank_sooner, a_higher_track_differential_ratio_changes_track_speed, more_track_damping_slows_the_tracks, track_wheels_are_fixed_at_creation, track_inertia_bounded_by_the_chassis, live_tracks_after_fast_driving_step_finitely; oxijolt-sys vehicle_tuning_smoke.rs: live_track_settings_reach_the_track_and_refuse_other_wheels |
Tire max impulse callback of wheeled vehicles and motorcycles: friction coefficients per wheel, panics resumed by step, dropped with the vehicle |
VehicleMut::set_tire_max_impulse_callback, clear_tire_max_impulse_callback, VehicleRef::has_tire_max_impulse_callback, TireMaxImpulseInput, TireFriction, extension JPH_WheeledVehicleController_ResetTireMaxImpulseCallback |
tire_callback.rs: zero_friction_gives_no_drive, less_longitudinal_friction_spins_the_driven_wheels, default_friction_matches_no_callback, reduced_lateral_grip_widens_the_turn, outputs_are_clamped_to_the_friction_bounds, the_largest_friction_stays_finite, the_largest_friction_of_a_callback_matches_the_largest_friction_curves, the_callback_sees_contacting_wheels, clear_restores_jolts_default, a_panicking_callback_panics_out_of_step_and_the_world_stays_usable, after_a_panic_the_step_uses_jolts_default, callbacks_are_dropped_with_replace_clear_remove_and_world_drop, distinct_callbacks_per_vehicle, callback_survives_restore_and_rebase; src/panic_slot.rs: a_later_payload_whose_drop_panics_is_dropped_without_unwinding, take_empties_the_slot; oxijolt-sys vehicle_tuning_smoke.rs: tire_callback_reset_stops_calling_the_old_callback; the doctest of set_tire_max_impulse_callback |
| Vehicle tuning: same results with 1 and 4 workers, rollback, leaks | 8 cars and 8 motorcycles with a slip-dependent tire callback and 8 tanks, every live setter of each kind at tick 30; the vehicle kinds' rollback scene changes every kind's drivetrain right after the save | vehicle_tuning_determinism.rs: tuning_matches_with_1_and_4_workers, tuning_matches_across_processes, tuning_detects_a_changed_input, the_tuning_scene_moves_and_calls_back; vehicle_kinds_state.rs: vehicle_kinds_replay_after_a_detour, vehicle_kinds_replay_across_processes; vehicle_leaks.rs: vehicles_do_not_leak; vehicle_kinds_leaks.rs: tracked_vehicles_and_motorcycles_do_not_leak (Windows) |
| Constraint settings with Jolt's defaults, validation (ragdoll joints and world constraints) | SwingTwistConstraintSettings, HingeConstraintSettings, SixDofConstraintSettings, MotorSettings, SpringSettings, SwingType, ConstraintSpace |
src/constraint/tests.rs: constraint_base_is_jolts_default, swing_twist_defaults_are_jolts, hinge_defaults_are_jolts, six_dof_defaults_are_jolts, fixed_and_free_map_to_jolts_sentinels, frames_are_validated, swing_twist_limits_are_validated, hinge_limits_are_validated, six_dof_limits_are_validated, motors_and_springs_are_validated; crates/oxijolt-sys/tests/constraint_smoke.rs: new_settings_init_to_jolts_defaults |
| World constraints of twelve kinds | PhysicsWorld::create_constraint with FixedConstraintSettings, PointConstraintSettings, DistanceConstraintSettings, HingeConstraintSettings, SliderConstraintSettings, ConeConstraintSettings, SwingTwistConstraintSettings, SixDofConstraintSettings, GearConstraintSettings, RackAndPinionConstraintSettings, PulleyConstraintSettings, PathConstraintSettings |
constraints.rs: fixed_constraint_holds_a_box_under_a_static_bar, point_constraint_keeps_the_pendulum_pivot, distance_constraint_keeps_a_rod_length, hinge_limit_holds_a_falling_door, slider_limit_holds, cone_holds_a_hanging_body_within_its_angle, swing_twist_twist_limit_holds, six_dof_translation_limit_holds, six_dof_pyramid_holds_asymmetric_limits, gear_turns_the_second_hinge_at_the_ratio, rack_moves_at_the_pinion_rate, pulley_lifts_one_body_as_the_other_falls, pulley_with_ratio_two_moves_half_as_far, path_keeps_a_body_on_its_curve; crates/oxijolt-sys/tests/constraint_smoke.rs |
| Constraint motors, springs, targets and readout | ConstraintRef, ConstraintMut, MotorState |
constraints.rs: hinge_velocity_motor_reaches_its_target_speed, hinge_position_motor_reaches_its_target_angle, soft_distance_spring_oscillates_and_damps, slider_position_motor_reaches_its_target, slider_soft_limit_spring_oscillates_and_damps, swing_twist_motor_reaches_target_orientation_with_a_rotated_parent, swing_twist_reads_back_its_motor_settings, six_dof_translation_spring_oscillates_and_damps, six_dof_rotation_position_motor_reaches_its_target, path_motor_drives_to_a_target_fraction, path_reads_back_its_motor_looping_and_rotation_impulses, looping_path_wraps, hinge_setters_check_their_values, changed_limits_motors_and_friction_act_on_sleeping_bodies |
| Constraint impulse readout and breaking | ConstraintRef::total_lambda* of every kind, ConstraintMut::set_enabled |
constraint_forces.rs: a_weld_reads_the_weight_it_holds, a_weld_off_the_centre_reads_the_torque_it_holds, a_weld_reads_a_couple_in_its_rotation_part, the_readout_is_the_impulse_of_the_whole_step, point_cone_swing_twist_and_six_dof_read_the_hanging_weight, distance_and_pulley_read_the_rope_tension, hinge_and_slider_read_their_new_parts, motors_and_limits_read_the_load_they_hold, swing_twist_limits_and_motors_read_the_couple_they_hold, two_limited_swing_axes_read_one_limit_in_the_swing_y_part, world_vectors_follow_the_world_and_axis_parts_the_frame, a_soft_limit_spring_makes_six_dof_translation_per_axis, six_dof_reads_per_axis_parts_and_motors, couplings_read_the_load_they_carry, path_reads_its_parts, a_disabled_constraint_keeps_its_last_readout, a_sleeping_constraint_keeps_its_last_readout, a_weld_reads_the_weight_of_its_last_collision_step; constraint_forces_state.rs: readouts_restore_with_the_state_for_every_kind, a_disabled_bond_saved_stays_disabled; state.rs: restore_after_a_structural_change_is_refused_and_changes_nothing (removing a constraint); constraint_forces_determinism.rs: constraint_impulses_are_identical_with_1_and_4_workers; the breakable weld in constraints.md (doctest) |
| Constraint ids, guards and removal | ConstraintId, AnyConstraintId, remove_constraint, constraint_ids, contains_constraint, constraints_of_body, ConstraintError, BodyError::UsedByConstraint |
constraints.rs: constraint_ids_are_typed_ordered_and_never_reused, wrong_world_and_removed_ids_are_rejected, bodies_of_constraints_cannot_be_removed, constraints_refuse_ragdoll_parts_inner_bodies_and_one_body, gears_racks_and_pulleys_need_two_dynamic_bodies, invalid_constraint_creates_nothing, removing_a_constraint_wakes_its_bodies, a_new_constraint_wakes_its_sleeping_bodies, every_setter_wakes_the_constraint_bodies, dropping_a_world_with_constraints_is_clean |
| Gears and racks that reference hinges and sliders | GearConstraintSettings::hinges, RackAndPinionConstraintSettings::constraints, ConstraintError::UsedByConstraint |
constraints.rs: gear_references_correct_drift, rack_references_correct_drift, a_referenced_hinge_cannot_be_removed, unrelated_or_reversed_references_are_rejected, gear_keeps_its_velocity_relation_at_the_largest_ratio |
| Hermite paths | HermitePath, HermitePathPoint, PathRotationConstraint |
constraints.rs: invalid_paths_are_rejected, path_validation_accepts_its_boundary, looping_path_wraps |
| Leak gate for constraints (Windows) | create, step, read, remove | constraint_leaks.rs: constraints_do_not_leak |
| Skeleton, ragdoll settings, stabilized masses | Skeleton, SkeletonJoint, RagdollSettings::new, new_stabilized, RagdollPart, RagdollJoint |
src/ragdoll/settings.rs: skeletons_are_validated, ragdoll_settings_are_validated, heightfield_parts_are_rejected, stabilized_settings_build; ragdoll.rs: plain_settings_keep_the_masses_and_stabilized_ones_the_total, one_settings_value_serves_two_worlds |
| Ragdoll creation, ids, removal | PhysicsWorld::create_ragdoll, remove_ragdoll, RagdollId, ragdoll_ids, ragdoll_count, contains_ragdoll, ragdoll_of_body, RagdollError, BodyError::OwnedByRagdoll |
ragdoll.rs: ragdoll_parts_and_ids_are_guarded (including a world too small for the parts), removing_a_ragdoll_drops_what_rests_on_it, dropping_a_world_with_ragdolls_is_clean; src/ragdoll/mod.rs: a_created_chain_reports_its_parts_and_joints |
| No collisions between a ragdoll's own parts | RagdollSettings |
ragdoll.rs: two_ragdolls_collide_with_each_other_but_not_with_themselves, the_same_parts_as_plain_bodies_collide (the control), humanoid_settles_on_a_heightfield_in_a_second_world (every part pair on every tick while the ragdoll is awake) |
| Pose readout and writing, kinematic drive | RagdollRef::pose, root_transform, RagdollMut::set_pose, drive_to_pose_using_kinematics, set_motion_type, SkeletonPose |
ragdoll.rs: poses_read_back_in_world_transforms, kinematic_parts_reach_the_driven_pose; src/ragdoll/settings.rs: poses_are_validated |
| Motor drive and joint readings | RagdollMut::drive_to_pose_using_motors, stop_motors, RagdollRef::joint, joint_motors_on, JointReading |
ragdoll.rs: motors_drive_each_joint_kind_to_its_target (a hinge elbow and knee, a six-DOF hip and a swing-twist neck, driven from the bind pose), motors_drive_joints_of_a_turned_ragdoll_in_the_parent_frame, rebase_moves_a_settled_ragdoll_rigidly (joint readings before and after a rebase), humanoid_settles_on_a_heightfield_in_a_second_world (the limit check reads every joint) |
| Settle detection | SettleDetector, RagdollRef::is_calm |
src/ragdoll/settle.rs: calm_updates_in_a_row_settle, default_is_the_rest_rule, invalid_limits_are_rejected; ragdoll.rs: humanoid_settles_on_a_heightfield_in_a_second_world |
A 12-capsule humanoid settles on a heightfield in a second world (fixture in tests/common/ragdoll.rs) |
built on the rows above | ragdoll.rs: humanoid_settles_on_a_heightfield_in_a_second_world (caller-applied gravity, the main world stepping on another thread, settled within 600 ticks (measured: tick 141), joints within 0.01 rad of their limits at rest), humanoid_joints_overshoot_their_limits_only_boundedly_during_the_drop (below 0.40 rad on every tick of that drop), an_environment_ray_passes_through_a_resting_part |
| Leak gate for ragdolls (Windows) | settings, create, drive, read, remove | ragdoll_leaks.rs: ragdolls_do_not_leak |
Skeleton mapper between a ragdoll and an animation skeleton (fixture in tests/common/animation.rs) |
SkeletonMapper, MappedSkeleton, TranslationLocks, RagdollError::UnmappedJoint, HierarchyMismatch, DegenerateChain |
skeleton_mapper.rs: neutral_poses_map_onto_each_other, direct_joints_follow_the_ragdoll, a_chain_start_turns_toward_the_next_ragdoll_joint, the_longest_chain_wins_and_ties_go_to_the_first_joint, unmapped_joints_follow_their_parents, reverse_mapping_uses_direct_joints_only, all_translation_locks_keep_neutral_offsets, selected_translation_locks, neutral_root_offsets_are_compared_in_one_frame, the_mapper_owns_its_state, mapped_poses_drive_a_ragdoll (pose, motors from a turned start, kinematic drive), mapping_is_a_pure_function (three threads); src/ragdoll/mapper/tests.rs: a_mapper_matches_joints_by_name, lock_masks_flag_each_listed_joint, a_long_twisting_chain_maps (128 links turning 45° each), a_mapper_is_send_and_sync |
| Leak gate for skeleton mappers (Windows) | build with each kind of lock, map both ways, refusals | skeleton_mapper_leaks.rs: skeleton_mappers_do_not_leak |
| Soft bodies: shared settings, creation, vertex readout and writes | SoftBodySharedSettings, SoftBodySharedSettingsBuilder, SoftBodySettings, PhysicsWorld::create_soft_body, soft_body, soft_body_mut, SoftBodyRef, SoftBodyMut |
soft_body.rs: a_cloth_pinned_at_two_corners_drapes_over_a_sphere, vertices_read_back_in_world_space, pinning_and_unpinning_vertices, invalid_vertex_writes_change_nothing, a_kinematic_vertex_moves_to_its_target_and_keeps_its_velocity, a_pressurised_ball_keeps_its_size_on_the_floor, a_tetrahedral_cube_keeps_its_volume_on_the_floor, explicit_constraints_add_to_the_generated_ones, explicit_edges_without_faces_hold_a_pendulum, queries_find_a_cloth, one_shared_settings_serves_worlds_on_two_threads and the other tests in the file; src/soft_body/tests.rs: default_settings_match_jolt, default_vertex_attributes_match_jolt, invalid_vertices_are_rejected, invalid_faces_are_rejected; crates/oxijolt-sys/tests/soft_body_smoke.rs |
| Body APIs a soft body refuses | BodyError::NotRigidBody, BodyMut::add_force on a soft body |
soft_body.rs: body_level_velocity_torque_and_point_force_refuse_soft_bodies, constraints_and_vehicles_refuse_soft_bodies, add_force_on_a_rotated_soft_body_pushes_along_the_world_force |
| Leak gate for soft bodies (Windows) | shared settings, create, step, read, write, remove | soft_body_leaks.rs: soft_bodies_do_not_leak |
| Contact, activation and soft body contact events | EventSettings, PhysicsWorld::set_event_settings, take_events, WorldEvents, ContactEvent, ActivationEvent, SoftBodyContacts, SoftBodyValidation |
listeners.rs: a_default_world_reports_nothing, contacts_are_added_persisted_and_removed_with_their_sub_shapes, persisted_contacts_are_opt_in, removing_a_body_reports_its_contact_removed_with_the_stale_id, falling_asleep_removes_contacts_and_deactivates, activation_follows_creation_wake_sleep_and_removal_but_not_restore, several_steps_queue_in_step_order, worlds_stepped_on_two_threads_see_only_their_own_events, a_cloth_on_a_table_reports_its_vertex_contacts, rotated_cloth_contacts_are_converted_to_world_space, a_replay_after_a_detour_reports_the_same_events and the other tests in the file; src/listener/tests.rs: a_panic_in_any_callback_during_a_step_is_resumed_by_step, a_panic_outside_a_step_is_resumed_by_take_events; crates/oxijolt-sys/tests/listener_smoke.rs |
| Contact listener | ContactListener, PhysicsWorld::set_contact_listener, ContactSettings, SoftBodyContactSettings, ContactSettingsRejection, StepReport::rejected_contact_settings |
contact_listener.rs: a_listener_makes_ice_slippery, a_conveyor_moves_a_resting_cube, sensor_contacts_let_a_cube_fall_through_and_are_still_reported, rejected_soft_body_contacts_let_a_cloth_fall_through, a_panicking_listener_is_resumed_by_step, continuous_collision_with_a_listener_steps_cleanly; src/listener/tests.rs: settings_are_checked_again_against_the_contact_that_takes_them, a_rejection_is_recorded_and_the_listener_is_still_called, settings_moved_to_a_contact_they_do_not_fit_are_rejected |
| Contact validation | ContactListener::contact_validate, ContactCandidate, ValidateResult |
contact_validation.rs: rejecting_a_pair_lets_a_body_fall_through_the_floor, a_one_way_platform_lets_a_body_through_from_below_and_holds_it_from_above, own_projectiles_pass_through_their_shooter, validation_sees_body1_with_the_higher_motion_type, continuous_collision_is_validated_too, soft_bodies_use_their_own_validator, independent_worlds_with_different_validators_step_concurrently; src/listener/tests.rs: a_validate_panic_accepts_and_is_resumed_by_step, without_a_user_listener_validation_accepts_every_pair; crates/oxijolt-sys/tests/contact_listener2_smoke.rs (the extension's listener: no faces, every validate result, settings field by field, removals, a null validate proc) |
| Contact-cache invalidation as part of the state | BodyMut::invalidate_contact_cache, WorldState, BodyMut::set_shape |
contact_cache.rs: a_cached_accepted_pair_is_not_validated_until_invalidated, a_cached_rejected_pair_stays_rejected_until_invalidated, an_invalidation_saved_before_the_step_is_replayed, an_invalidation_before_a_restore_does_not_reach_the_replay, a_detour_invalidation_is_dropped_by_restoring_a_clean_state, repeated_invalidations_are_one_request, invalidating_a_sleeping_body_wakes_it_and_revalidates_its_pairs, an_idle_step_keeps_the_request_pending, a_partial_state_saves_and_restores_the_requests, a_removed_body_leaves_no_request, a_state_saved_right_after_set_shape_replays_with_the_new_shape |
| Character contact listener | CharacterContactListener, PhysicsWorld::set_character_contact_listener, BodyVelocity, CharacterContactSettings, CharacterContactKey |
character_listener.rs: a_conveyor_moves_a_character_standing_on_a_static_body, adjust_receives_the_bodys_user_data, a_rejected_body_contact_lets_the_character_walk_through, a_rejected_character_contact_lets_two_characters_pass, a_character_without_impulses_does_not_push_a_box_sideways, a_moving_wall_that_cannot_push_does_not_push_the_character, a_moving_character_that_cannot_push_does_not_push_the_character, added_persisted_removed_follow_a_walk_across_two_boxes, removals_arrive_sorted_after_the_contact_map_grew, a_panic_in_contact_removed_keeps_the_delivered_prefix, removing_a_contacted_body_or_character_reports_its_removal, refresh_reports_added_for_a_character_created_on_the_ground, one_extended_update_is_one_tracking_scope, callbacks_run_on_the_calling_thread, replacing_or_removing_the_listener_takes_effect_at_the_next_update, independent_worlds_with_different_character_listeners_update_concurrently; src/character/tests.rs: removals_sort_by_body_character_and_sub_shape, a_panic_in_any_listener_callback_is_resumed_by_the_update, a_change_followed_by_a_panic_keeps_the_jolt_values, a_listener_is_not_called_after_a_filter_panicked, simultaneous_filter_and_listener_failures_keep_one_payload |
| Collision groups | GroupFilterTableBuilder, GroupFilterTable, CollisionGroup, BodySettings::collision_group, SoftBodySettings::collision_group, CollisionGroupError |
collision_groups.rs: bodies_of_one_sub_group_pass_through_each_other, a_disabled_pair_passes_and_an_enabled_pair_collides, different_group_ids_always_collide, same_group_id_with_different_tables_never_collides, grouped_bodies_collide_with_ungrouped_ones, can_collide_matches_the_simulation, a_chain_without_self_collision, a_vehicle_ignores_its_driver_body, a_solver_excluded_body_is_still_found_by_queries_and_blocks_characters, a_sensor_does_not_see_a_body_its_group_excludes, a_cloth_ignores_a_body_of_its_sub_group, ragdoll_parts_refuse_a_collision_group, caller_groups_never_alias_a_ragdoll, two_ragdolls_from_one_settings_collide_with_each_other, a_grouped_ragdoll_replays_after_a_restore, tables_outlive_their_handles, builder_and_group_boundaries, enabling_a_disabled_pair_makes_it_collide_again; group_table_first_call.rs: a_table_built_before_any_world_works; src/ragdoll/mod.rs: ragdoll_group_ids_stop_below_the_invalid_group |
| Contact control: determinism and rollback | the three rows above | contact_control_determinism.rs: contact_control_is_identical_with_1_and_4_workers, contact_control_is_identical_with_caller_job_systems, contact_control_replays_after_a_detour_in_one_process, contact_control_replays_after_a_detour_across_processes, the_scenes_exercise_their_rules |
| Leak gate for contact control (Windows) | validating listeners, group tables and grouped bodies, the per-update character listener, invalidations, a world per round | contact_control_leaks.rs: contact_control_does_not_leak (events.md) |
| Leak gate for listeners, materials and worlds (Windows) | listener replacement, material shapes, a world per round | listener_leaks.rs: listeners_materials_and_worlds_do_not_leak (events.md describes the gate) |
| Leak gate for the new shapes (Windows) | every new constructor, the refusals, a failed Jolt creation, bodies of a hull and a mesh | mesh_shapes_leaks.rs: new_shapes_do_not_leak |
| Magnitude policy | oxijolt::limits |
limits.rs and the unit tests in src/limits/tests.rs; the table below names the test of each input |
Jolt assertion handler (asserts feature) |
installed once per process before JPH_Init |
assertions.rs: a_failed_jolt_assertion_aborts_with_its_message; src/jolt_assert.rs: only_the_pinned_update_error_continues |
| Raw bindings | oxijolt-sys |
crates/oxijolt-sys/tests/smoke_test.rs: box_falls_onto_static_box, callback_job_system_steps_a_world, single_threaded_job_system_steps_a_world, single_threaded_job_system_runs_several_collision_steps (extension JPH_JobSystemSingleThreaded_Create); character_smoke.rs: character_lands_and_its_restored_state_continues_bit_for_bit, copying_no_bytes_accepts_a_null_buffer; vehicle_smoke.rs: vehicle_constraint_steps_and_tears_down, vehicle_step_listener_applies_the_gravity_override_once_per_collision_step (extension JPH_VehicleStepListener_*), motorcycle_target_lean_is_checked_read_and_written; vehicle_tuning_smoke.rs: the live drivetrain setters and the tire callback reset of the extension (see the vehicle rows above); ragdoll_smoke.rs: ragdoll_parts_and_constraints_reach_jolt; constraint_smoke.rs, state_smoke.rs, soft_body_smoke.rs, listener_smoke.rs, material_smoke.rs; skeleton_mapper_smoke.rs: the aligned JPH_SkeletonMapper_*2 functions through misaligned arrays, Jolt's mapping stages (identity for unmatched ragdoll joints in reverse, outputs over their inputs in both directions) and every refusal; layout checks at compile time in crates/oxijolt-sys/src/layout.rs |
| Committed bindings and the supported targets | oxijolt-sys, cargo xtask bindings |
xtask/src/main.rs: every_registered_target_is_accepted, narrow_and_unregistered_targets_are_refused, bindings_files_are_distinct, every_selectable_bindings_file_is_committed and the other tests in the file; CI's Committed bindings job compares them with LLVM 18 output |
| Examples in the guides and the README | guide.md and the other guides in docs/, README.md |
doctests of the #[cfg(all(doctest, repository_docs))] items at the end of crates/oxijolt/src/lib.rs (cargo test -p oxijolt --doc; the repository only); the hello_world example, which CI runs |
| Plane shapes | Shape::new_plane, new_plane_with_material |
plane_shape.rs: only_static_bodies_take_a_plane, planes_are_refused_where_shapes_must_move_or_query, a_static_body_may_change_to_a_plane, bodies_rest_on_a_plane, a_box_slides_down_a_tilted_plane, a_box_outside_the_half_extent_falls_past, a_compound_with_a_plane_child_catches_a_body, cloth_lands_on_a_plane, a_character_stands_and_walks_on_a_plane, rays_hit_a_plane_from_above_and_start_inside_it_from_below, contacts_report_the_planes_material; debug_lines.rs: a_plane_is_drawn_as_two_triangles; helpers_leaks.rs |
| Point queries | PhysicsWorld::collide_point, CollidePointHit, Shape::collide_point |
point_queries.rs: boxes_spheres_and_planes_have_exact_boundaries, rounded_and_hull_shapes_hold_within_a_tenth_of_a_millimetre, meshes_count_the_triangles_above_the_point, heightfields_contain_nothing, soft_bodies_are_tested_by_their_faces, compounds_report_every_child_that_holds_the_point, shape_points_are_in_the_shapes_own_frame, a_plane_child_is_bounded_by_its_half_extent, world_queries_apply_every_filter_part_and_sort_by_body, world_queries_read_from_many_threads, far_points_find_small_bodies; src/filter.rs: a_panic_in_a_filter_callback_resumes_after_the_query; helpers_determinism.rs |
| Collision estimates | EventSettings::collision_estimates, CollisionEstimate, ContactEvent::Added::estimate |
collision_estimates.rs: estimates_match_one_step_of_an_isolated_pair, a_bouncing_box_is_estimated_to_leave_at_half_speed, under_gravity_the_estimated_bounce_is_faster_by_restitution_g_dt, a_contact_found_by_the_continuous_stage_is_estimated, estimates_are_off_by_default_and_follow_the_contacts_switch, estimating_does_not_change_the_simulation, sensor_contacts_have_no_estimate, estimates_use_the_settings_jolt_resolves_the_contact_with, a_rejected_transplant_is_estimated_with_jolts_settings, locked_axes_are_not_part_of_the_estimate; src/listener/tests.rs: a_panic_while_copying_an_estimate_frees_joltcs_impulses; src/listener/order.rs: estimates_separate_added_contacts; event_determinism.rs; helpers_determinism.rs; collision_estimate_leaks.rs |
CI runs the whole test suite on Windows (MSVC) and Linux (GCC), each in five configurations:
default, cross-platform-deterministic, double-precision, debug-renderer and asserts. The
tests behind the debug-renderer feature run only in that configuration. The leak gates read the
process's private bytes through a Windows API and are built only on Windows.
Not in the safe API yet (joltc exposes them, so oxijolt-sys has them):
- for rigid bodies, setters after creation for the sensor flag, user data, gravity factor, friction,
restitution, damping, object layer and motion quality (each needs a rollback rule, since Jolt
does not save them);
mCollideKinematicVsNonDynamic; and allowed-DOF sets without a translation axis; - the manual transmission, a motorcycle's lean integration decay and wheel base readout;
- Jolt's rigid-body
Character; - the character contact listener's solve callbacks (
OnContactSolve,OnCharacterContactSolve), a collision group for a character's inner body, and collision group changes after creation (Jolt does not save groups).
Skinned soft bodies (Jolt's skin constraints) are in neither layer.
Every public setter and constructor that takes a magnitude, the rule it applies, and the tests that check it at its boundary.
| Input | Rule | Boundary test |
|---|---|---|
WorldSettings::gravity, PhysicsWorld::set_gravity |
MAX_ACCELERATION |
world_gravity_is_bounded_by_max_acceleration |
WorldSettings::max_contact_constraints |
1..=WorldSettings::MAX_CONTACT_CONSTRAINTS |
contact_constraint_capacity_is_bounded |
WorldSettings::max_bodies, worker_threads |
Jolt's and oxijolt's counts | invalid_settings_are_rejected, worker_thread_bounds_are_validated |
WorldSettings::job_system (JobSystem::max_concurrency) |
1..=WorldSettings::MAX_CONCURRENCY, read once in PhysicsWorld::new |
max_concurrency_is_bounded |
WorldSettings::velocity_steps, position_steps |
MIN_VELOCITY_STEPS..=MAX_SOLVER_STEPS, 0..=MAX_SOLVER_STEPS (limits) |
step_counts_outside_jolts_range_are_refused, step_counts_read_back_from_jolt |
WorldSettings::penetration_slop, speculative_contact_distance |
0..=MAX_SHAPE_EXTENT (limits) |
contact_distances_are_validated, contact_distances_at_their_bounds_keep_a_small_scene_finite, far_bodies_within_the_speculative_distance_stay_finite |
WorldSettings::max_body_pairs, temp_allocator_size |
at least 1; Jolt asserts nothing on their size, and joltc's temp allocator falls back to malloc |
invalid_settings_are_rejected |
PhysicsWorld::step delta time |
MIN_DELTA_TIME..=MAX_DELTA_TIME |
step_rejects_delta_time_above_the_bound, step_rejects_delta_time_below_the_bound |
PhysicsWorld::step_with_collision_steps collision count |
1..=MAX_COLLISION_STEPS (limits) |
collision_step_counts_outside_the_bound_are_refused, a_saturated_update_at_max_collision_steps_fits_jolts_job_pool |
PhysicsWorld::step_with_collision_steps collision step length |
delta_time / collision_steps >= MIN_DELTA_TIME, and the total as for step |
collision_steps_shorter_than_min_delta_time_are_refused, bad_total_delta_times_are_refused_for_every_count |
PhysicsWorld::rebase translation |
2 * MAX_POSITION per axis; results finite |
rebase_translation_is_bounded_by_twice_the_frame |
BodySettings::position |
MAX_POSITION |
body_settings_are_bounded |
BodySettings::linear_velocity, angular_velocity |
MAX_LINEAR_VELOCITY, MAX_ANGULAR_VELOCITY |
body_settings_are_bounded, creation_velocities_agree_with_jolts_length_in_many_directions |
BodySettings::restitution |
0..=1 |
body_settings_are_bounded |
BodySettings::gravity_factor |
MAX_GRAVITY_FACTOR |
body_settings_are_bounded |
BodySettings::mass, PhysicsWorld::create_body computed mass |
MIN_MASS..=MAX_MASS for dynamic bodies |
body_settings_are_bounded, computed_dynamic_mass_is_bounded_and_kinematic_mass_is_not |
BodySettings::mass_and_inertia |
mass within MIN_MASS..=MAX_MASS; inertia finite and symmetric; principal moments at least MIN_PROVIDED_INERTIA and at most 6 · mass · MAX_SHAPE_EXTENT² (Frobenius norm for a tensor that is not diagonal); a tensor that is not diagonal positive definite and well conditioned; refused by RagdollSettings::new_stabilized (see limits.md) |
body_settings.rs: invalid_provided_inertia_is_refused, provided_inertia_at_its_bounds_steps_finitely; ragdoll.rs: provided_inertia_on_a_ragdoll_part |
PhysicsWorld::create_body computed inertia of a dynamic or kinematic body (also a character's inner body and a part of RagdollSettings::new, new_stabilized) |
an exactly diagonal tensor with invertible moments or near zero; otherwise smallest principal moment bounded from below at least MIN_INERTIA_RATIO (4.8e-4) of its Frobenius norm (see limits.md) |
rigid_body_inertia_floor_holds_at_its_boundary, rigid_body_inertia_at_its_bound_decomposes, inner_body_inertia_at_its_bound_decomposes, stabilized_ragdoll_inertia_at_its_bound_decomposes |
BodySettings::friction |
0..=MAX_FRICTION |
body_settings_are_bounded, friction_at_the_bound_keeps_contacts_finite |
BodySettings::linear_damping, angular_damping |
finite, at least 0: Jolt scales by max(0, 1 - c·dt) (MotionProperties.inl:144-145) |
invalid_damping_is_rejected |
BodySettings::rotation, BodyMut::set_rotation |
finite unit quaternion | invalid_body_settings_are_rejected |
BodyMut::set_position, set_position_and_rotation |
MAX_POSITION |
body_setters_are_bounded_and_rejection_changes_nothing |
BodyMut::set_linear_velocity, set_angular_velocity |
MAX_LINEAR_VELOCITY, MAX_ANGULAR_VELOCITY |
body_setters_are_bounded_and_rejection_changes_nothing |
BodyMut::add_force |
accumulated ` | F |
BodyMut::add_torque, add_force_at_point |
accumulated torque within MAX_ANGULAR_ACCELERATION, counting the rounding of Jolt's f32 cross product (see limits.md); point within MAX_POSITION; f32 torque products |
torques_are_bounded_by_the_angular_acceleration_they_give, point_forces_along_a_long_lever_count_jolt_rounding; src/body/tests.rs: point_torque_rejects_overflowing_products_even_when_they_cancel, point_torque_rounding_covers_every_f32_evaluation_order |
BodyMut::add_impulse |
finite; on a dynamic body ` | J |
BodyMut::add_angular_impulse |
finite; on a dynamic body ` | L |
BodyMut::add_impulse_at_point |
both impulse rules with L = (p - com) × J, the angular one for the largest L Jolt's f32 cross product can give (see limits.md); point within MAX_POSITION; f32 cross product products |
body_controls.rs: point_impulses_are_bounded_by_the_angular_impulse_of_their_lever, angular_impulses_are_bounded_by_the_angular_velocity_change, point_impulses_along_a_long_lever_count_jolt_rounding; src/body/tests.rs: point_torque_rounding_covers_every_f32_evaluation_order |
BodyMut::move_kinematic |
position within MAX_POSITION, unit rotation, step delta time; Jolt's implied velocities within MAX_LINEAR_VELOCITY and MAX_ANGULAR_VELOCITY |
body_controls.rs: kinematic_moves_are_bounded_by_the_velocities_they_imply |
BuoyancySettings::surface |
position within MAX_POSITION; normal a finite unit vector |
buoyancy.rs: invalid_inputs_change_nothing |
BuoyancySettings::buoyancy, linear_drag, angular_drag |
finite, not negative; then the product chains and squared lengths of limits.md | invalid_inputs_change_nothing, bodies_at_the_bounds_stay_finite, small_light_and_far_buoyancy_centres_stay_finite; src/limits/buoyancy/tests.rs: each_chain_has_its_boundary, nan_in_any_input_is_refused, zero_factors_do_not_hide_large_ones, accepted_inputs_stay_finite_in_any_association, rotated_anisotropic_inertia_at_the_bound_stays_finite, drag_coefficients_at_their_bound_stay_finite, large_calm_bodies_are_accepted |
BuoyancySettings::fluid_velocity |
MAX_LINEAR_VELOCITY |
invalid_inputs_change_nothing, bodies_at_the_bounds_stay_finite |
BodyMut::apply_buoyancy_impulse gravity, delta time |
MAX_ACCELERATION; the bounds of PhysicsWorld::step |
invalid_inputs_change_nothing, bodies_at_the_bounds_stay_finite |
BodyMut::apply_buoyancy_impulse buoyant velocity change |
b · Vs / V · abs(gf) · length(g) · dt <= MAX_VELOCITY_CHANGE, for both signs of the gravity factor (see limits.md) |
invalid_inputs_change_nothing, velocities_are_clamped_right_after_the_call; src/limits/buoyancy/tests.rs: both_signs_of_the_gravity_factor_are_bounded_alike |
PhysicsWorld::activate_bodies_in_box |
corners within MAX_POSITION, min <= max per axis |
body_controls.rs: activation_box_is_validated, box_activation_compares_in_caller_precision |
BodySettings::allowed_dofs |
a translation axis; all six on a static body that cannot move | src/body/tests.rs: dofs_without_translation_are_refused; body_settings.rs: a_static_body_that_cannot_move_refuses_restricted_dofs |
BodySettings::sensor |
discrete motion quality; no static-only shape | sensors.rs: sensor_rules_refuse_static_only_shapes_and_linear_cast |
BodySettings::allow_dynamic_or_kinematic |
zero initial velocities; the shape and inertia rules of a kinematic body | body_structure.rs: a_movable_static_body_needs_zero_initial_velocities, allow_dynamic_or_kinematic_pays_the_moving_rules_at_creation |
BodyMut::set_motion_type to dynamic |
mass within MIN_MASS..=MAX_MASS; no static-only shape |
body_structure.rs: light_kinematic_body_cannot_become_dynamic, kinematic_mesh_body_cannot_become_dynamic |
BodyMut::set_shape |
mass within MIN_MASS..=MAX_MASS; the shape and inertia rules of create_body for the body's motion type, movement capability and sensor flag; no pending forces on a dynamic body |
shape_changes.rs: set_shape_applies_the_creation_rules_per_motion_type, set_shape_refuses_pending_forces |
BodyMut::reset_forces |
none | reset_forces_ignores_static_and_kinematic_bodies |
CharacterSettings::mass |
0..=MAX_MASS |
character_settings_and_setters_are_bounded |
CharacterSettings::shape_offset |
MAX_SHAPE_EXTENT per axis |
character_settings_and_setters_are_bounded |
CharacterSettings::humanoid height and radius |
radius finite and positive; height finite and more than twice the radius; the capsule within MAX_SHAPE_EXTENT |
humanoid_refuses_bad_dimensions |
CharacterSettings::predictive_contact_distance, character_padding, collision_tolerance |
0..=MAX_SHAPE_EXTENT (tolerance positive) |
character_settings_and_setters_are_bounded |
CharacterSettings::max_strength and the other settings |
existing ranges; strength needs no bound (see limits.md) | invalid_settings_and_poses_are_rejected_without_side_effects |
PhysicsWorld::create_character position, CharacterMut::set_position |
MAX_POSITION |
character_settings_and_setters_are_bounded |
CharacterMut::set_linear_velocity |
MAX_LINEAR_VELOCITY; Jolt does not clamp a character |
character_settings_and_setters_are_bounded |
CharacterMut::set_penetration_recovery_speed |
0..=1, finite |
character_settings_and_setters_are_bounded |
CharacterMut::set_up, set_rotation |
unit vector, unit quaternion | invalid_settings_and_poses_are_rejected_without_side_effects |
PhysicsWorld::update_character gravity |
MAX_ACCELERATION |
character_update_gravity_and_steps_are_bounded |
PhysicsWorld::update_character weight impulse |
character mass times gravity times delta time at most MAX_WEIGHT_IMPULSE |
character_weight_impulse_at_a_lever_arm_is_bounded, weight_impulse_check_accepts_its_bound_and_rejects_beyond |
ExtendedUpdateSettings steps and forward distances |
MAX_SHAPE_EXTENT |
character_update_gravity_and_steps_are_bounded |
CharacterMut::restore_state |
only states from save_state exist |
a_restored_state_saves_the_same_bytes |
PhysicsWorld::restore_state, restore_state_of |
only states from save_state, save_state_of or save_state_into of the same world at the same epoch exist; restore_state_of restores a bodies-only stream it saved itself under the same mutable borrow |
tests/state.rs, tests/state_buffers.rs |
VehicleMut::set_gravity |
MAX_ACCELERATION |
gravity_is_bounded_by_max_acceleration |
WheelSettings::new position, suspension_force_point |
MAX_SHAPE_EXTENT per axis |
wheel_magnitudes_are_bounded_by_the_policy |
WheelSettings suspension min, max and preload lengths, radius, width |
0..=MAX_SHAPE_EXTENT (radius positive, max length at least min length) |
wheel_magnitudes_are_bounded_by_the_policy |
WheeledVehicleSettings::car, MotorcycleSettings::bike wheel position and radius |
no rule of their own: the wheel rules above and the motorcycle's wheel base, checked by create_wheeled_vehicle and create_motorcycle |
car_preset_values_are_validated, bike_preset_values_are_validated |
WheelSettings::suspension_spring |
Jolt's stiffness and damping at most MAX_SPRING_COEFFICIENT for a chassis of MAX_MASS |
suspension_springs_are_bounded_by_the_coefficient, vehicle_springs_and_anti_roll_bar_at_their_bounds_step_finitely |
VehicleAntiRollBar::stiffness |
0..=VehicleAntiRollBar::MAX_STIFFNESS |
anti_roll_bars_are_validated, vehicle_springs_and_anti_roll_bar_at_their_bounds_step_finitely |
WheelSettings inertia, angular damping, brake torques; engine, transmission and differential settings |
finite, in their ranges, and every step coefficient they form finite at both time-step extremes; not bounded one by one | wheel_values_are_validated, step_coefficients_of_wheels_must_be_finite, step_coefficients_of_the_drivetrain_must_be_finite, engine_values_are_validated, transmission_values_are_validated, differential_values_are_validated |
WheelSettings friction curves |
finite points with increasing slip; the friction values are not bounded | wheel_values_are_validated |
VehicleEngineSettings::normalized_torque, every vehicle kind |
x within 0..=1 and at least MIN_TORQUE_CURVE_SPACING apart up to rounding x to f32, y within 0..=MAX_NORMALIZED_TORQUE (see limits.md) |
engine_torque_curves_stay_in_their_domain, wheeled_engines_take_only_curves_in_the_domain, motorcycle_engines_take_only_curves_in_the_domain, overflowing_tracked_drivetrains_are_refused |
WheeledVehicleSettings up, forward, max pitch roll angle; collision testers |
unit vectors and angle ranges; tester radius below every wheel's reach | vehicle_values_are_validated, collision_testers_are_validated |
VehicleMut::set_driver_input, set_max_pitch_roll_angle, set_collision_tester |
existing ranges | driver_input_drives_steers_and_brakes, invalid_vehicles_create_nothing |
TrackedDriverInput track ratios |
magnitude within 1/MAX_RATIO..=1 (see limits.md); forward and brake as DriverInput |
tracked_inputs_are_validated |
VehicleTrackSettings wheels, driven wheel, inertia, angular damping, max brake torque, differential ratio |
a wheel, a driven wheel among them; inertia within MIN_TRACK_INERTIA..=MAX_TRACK_INERTIA, the larger of the two tracks' at most MAX_TRACK_INERTIA_RATIO times the smaller (see limits.md); every wheel radius within a factor MAX_RATIO of the driven wheel's; damping and brake not negative; ratio positive |
tracked_settings_are_validated, track_inertias_stay_within_max_track_inertia_ratio, unequal_track_inertias_are_refused, the_widest_track_inertia_ratio_stays_finite_on_high_friction_ground, track_wheel_radii_stay_within_max_ratio_of_the_driven_wheel, invalid_tracked_vehicles_create_nothing |
VehicleTrackSettings::inertia against the chassis, at create_tracked_vehicle |
every wheel's track mass ratio, the track's inertia over the wheel's radius squared times the chassis' largest inverse effective mass at the wheel's farthest contact over the wheel's forward/up plane, at most MAX_TRACK_MASS_RATIO (see limits.md) |
principal_levers_measure_the_lever_with_the_inverse_inertia, track_mass_ratio_takes_the_chassis_inertia_at_the_farthest_contact, chassis_mass_turns_body_space_into_the_principal_frame, tracks_heavy_against_their_chassis_are_refused, tracks_heavy_against_a_narrow_hull_are_refused, the_heaviest_accepted_tracks_stay_finite_on_high_friction_ground, jolt_tank_tracks_are_inside_the_track_mass_ratio |
TrackedVehicleSettings engine, transmission and tracks together |
the track drive envelope at most 1e30 (see limits.md) | tracked_drive_envelope_bounds_the_drivetrain, overflowing_tracked_drivetrains_are_refused, the_strongest_light_tracks_stay_finite, the_strongest_tracks_with_the_widest_radius_ratio_stay_finite, the_strongest_tracks_on_the_widest_curve_stay_finite |
VehicleMut::set_engine, set_transmission |
the creation rules of the vehicle's kind, against its wheels, the rest of its drivetrain and its current tester; same forward and reverse gear counts and clutch release time (VehicleError::FixedAtCreation); for a tank, the track drive envelope from the track speeds earlier configurations allowed (see limits.md) |
invalid_live_drivetrains_change_nothing, overflowing_live_engines_and_transmissions_are_refused, gear_counts_and_clutch_release_time_are_fixed_at_creation, clutch_release_time_refusal_during_a_countdown, track_speeds_from_rest_match_the_envelope_and_never_shrink, a_reached_speed_can_refuse_a_change_valid_from_rest, live_tank_engine_changes_after_fast_driving_step_finitely |
VehicleMut::set_differentials |
the creation rules of the differentials and the limited slip ratio, against the vehicle's wheels, engine, transmission and current tester | invalid_live_differentials_change_nothing |
VehicleMut::set_tracks |
the creation rules of the tracks, the track mass ratio against the chassis as created, and the track drive envelope as for set_engine; same wheels and driven wheel (VehicleError::FixedAtCreation) |
track_inertia_bounded_by_the_chassis, track_wheels_are_fixed_at_creation, live_tracks_after_fast_driving_step_finitely, rescaled_weighted_bounds_take_either_operand, successive_unequal_inertia_changes_cover_every_earlier_state |
TireFriction returned by a tire max impulse callback |
each coefficient clamped to 0..=MAX_FRICTION, NaN to 0, then times the suspension impulse as in Jolt's default; the largest equals friction curves of MAX_FRICTION on ground of MAX_FRICTION, the same grip bound (see limits.md) |
coefficients_are_clamped_to_the_friction_bounds, outputs_are_clamped_to_the_friction_bounds, the_largest_friction_stays_finite, the_largest_friction_of_a_callback_matches_the_largest_friction_curves |
TrackedWheelSettings::longitudinal_friction, lateral_friction |
0..=MAX_FRICTION |
tracked_settings_are_validated |
TrackedWheelSettings position, force point, directions, lengths, spring, radius, width |
the WheelSettings rules above |
tracked_wheels_share_the_wheel_rules |
MotorcycleSettings::max_lean_angle |
0..=MotorcycleSettings::MAX_LEAN_ANGLE (80°, see limits.md) |
motorcycle_settings_are_validated, max_lean_angle_is_80_degrees |
MotorcycleSettings::lean_spring_constant, lean_spring_damping |
finite, not negative, and (k·π + c·MAX_ANGULAR_VELOCITY_CHANGE) times the chassis' largest principal inverse inertia within MAX_ANGULAR_ACCELERATION (see limits.md) |
lean_spring_rule_boundary, motorcycle_settings_are_refused, lean_spring_at_its_bound_steps_finitely |
MotorcycleSettings::lean_smoothing_factor |
0..=1 |
motorcycle_settings_are_validated |
MotorcycleSettings::lean_spring_integration_coefficient |
exactly 0 (VehicleError::LeanSpringIntegrationNotSaved) |
motorcycle_settings_are_validated, motorcycle_settings_are_refused |
MotorcycleSettings wheels |
exactly two, apart along the vehicle's forward in plain and fused f32 and in f64 |
motorcycle_settings_are_validated |
SpringSettings::StiffnessAndDamping |
MAX_SPRING_COEFFICIENT |
stiffness_springs_are_bounded_by_the_coefficient |
SpringSettings::FrequencyAndDamping in RagdollSettings::new, new_stabilized |
B·ω² and 2·B·ζ·ω at most MAX_SPRING_COEFFICIENT |
motor_springs_are_bounded_by_the_parts_effective_mass, motor_spring_of_1e20_hz_is_rejected |
MotorSettings::force_limits, torque_limits; angle limits |
finite, min <= max; Jolt clamps the motor impulse to dt · limit |
motors_and_springs_are_validated, swing_twist_limits_are_validated, hinge_limits_are_validated, six_dof_limits_are_validated |
| constraint frame points | MAX_POSITION |
constraint_frame_points_are_bounded, constraint_targets_are_bounded |
the points where PhysicsWorld::create_constraint holds a dynamic body (frame points, automatic points, every point of a path); no setter moves them, and a rebase moves them with their bodies |
lever-arm ratio at most MAX_LEVER_ARM_RATIO |
lever_arms_are_bounded_by_the_bodies_size, far_and_light_constraint_points_are_refused, constraints_at_the_lever_arm_bound_step_finitely |
SpringSettings::FrequencyAndDamping in PhysicsWorld::create_constraint, ConstraintMut::<DistanceConstraint>::set_limits_spring, ConstraintMut::<HingeConstraint>::set_motor_settings, set_limits_spring |
B·ω² and 2·B·ζ·ω at most MAX_SPRING_COEFFICIENT, B from the constraint's two bodies |
world_constraint_springs_are_bounded_by_the_bodies_effective_mass |
DistanceRange::Range, ConstraintMut::<DistanceConstraint>::set_distance |
0 <= min <= max <= MAX_SHAPE_EXTENT |
constraint_targets_are_bounded |
ConstraintMut::<HingeConstraint>::set_target_angle |
[-π, π]; Jolt clamps it to the limits |
constraint_targets_are_bounded |
ConstraintMut::<HingeConstraint>::set_target_angular_velocity |
MAX_ANGULAR_VELOCITY |
constraint_targets_are_bounded |
ConstraintMut::<HingeConstraint>::set_limits |
Jolt's hinge ranges, min == max only with a soft spring |
hinge_setters_check_their_values |
ConstraintMut::<HingeConstraint>::set_max_friction_torque |
finite, at least 0; Jolt clamps the friction impulse to dt · limit |
constraint_friction_at_f32_max_steps_finitely |
SliderConstraintSettings::limits, ConstraintMut::<SliderConstraint>::set_limits, remove_limits |
min in [-MAX_SHAPE_EXTENT, 0], max in [0, MAX_SHAPE_EXTENT], min == max only with a soft spring |
slider_cone_swing_twist_and_six_dof_targets_are_bounded |
ConstraintMut::<SliderConstraint>::set_target_position |
MAX_SHAPE_EXTENT; Jolt clamps it to the limits |
slider_cone_swing_twist_and_six_dof_targets_are_bounded |
ConstraintMut::<SliderConstraint>::set_target_velocity |
MAX_LINEAR_VELOCITY |
slider_cone_swing_twist_and_six_dof_targets_are_bounded |
SliderConstraintSettings::max_friction_force, ConstraintMut::<SliderConstraint>::set_max_friction_force, ConstraintMut::<SwingTwistConstraint>::set_max_friction_torque |
finite, at least 0 | slider_and_swing_twist_friction_at_f32_max_steps_finitely |
SpringSettings::FrequencyAndDamping in the slider, swing-twist and six-DOF motor and limit spring setters of ConstraintMut |
as for create_constraint |
slider_swing_twist_and_six_dof_motor_springs_are_bounded |
ConeConstraintSettings::new half angle, ConstraintMut::<ConeConstraint>::set_half_cone_angle |
[0, π], as Jolt asserts |
slider_cone_swing_twist_and_six_dof_targets_are_bounded |
ConstraintMut::<SwingTwistConstraint>::set_target_angular_velocity_cs, ConstraintMut::<SixDofConstraint>::set_target_angular_velocity_cs |
MAX_ANGULAR_VELOCITY |
slider_cone_swing_twist_and_six_dof_targets_are_bounded |
ConstraintMut::<SwingTwistConstraint>::set_target_orientation_cs, ConstraintMut::<SixDofConstraint>::set_target_orientation_cs |
finite unit quaternion; Jolt clamps it to the limits | slider_cone_swing_twist_and_six_dof_targets_are_bounded |
ConstraintMut::<SixDofConstraint>::set_target_velocity_cs |
MAX_LINEAR_VELOCITY |
slider_cone_swing_twist_and_six_dof_targets_are_bounded |
ConstraintMut::<SixDofConstraint>::set_target_position_cs |
MAX_SHAPE_EXTENT per axis |
slider_cone_swing_twist_and_six_dof_targets_are_bounded |
GearConstraintSettings::new, teeth ratio |
1..=MAX_GEAR_RATIO (see GearConstraintSettings and MAX_GEAR_RATIO for both bounds) |
coupling_ratios_are_bounded, ratios_at_the_bound_step_finitely, behaviour at the bound in tests/constraints.rs |
RackAndPinionConstraintSettings::new, teeth ratio |
magnitude within 1 / MAX_RATIO..=MAX_RATIO; teeth length positive within MAX_SHAPE_EXTENT |
coupling_ratios_are_bounded, ratios_at_the_bound_step_finitely |
PulleyConstraintSettings::ratio |
positive, within 1 / MAX_RATIO..=MAX_RATIO |
pulley_ratio_and_lengths_are_bounded, pulleys_at_the_ratio_bound_step_finitely |
PulleyLength::Range, ConstraintMut::<PulleyConstraint>::set_length |
0 <= min <= max <= (1 + ratio) · MAX_SHAPE_EXTENT |
pulley_ratio_and_lengths_are_bounded |
PulleyConstraintSettings::new body and fixed points |
MAX_POSITION; a rebase moves fixed points as re-expressed state, checked finite only |
pulley_ratio_and_lengths_are_bounded |
HermitePath::new points |
2 to HermitePath::MAX_POINTS; positions and tangents within MAX_SHAPE_EXTENT per axis; unit normal |
invalid_paths_are_rejected, path_inputs_are_bounded |
HermitePath::new segments |
chord at least 1 mm; derivative along the chord at least twice its bound along the normal and above an f32 margin, so Jolt's normal stays unit |
invalid_paths_are_rejected, path_validation_accepts_its_boundary |
PathConstraintSettings::path_position, path_rotation, path_fraction |
MAX_SHAPE_EXTENT per axis; unit quaternion; [0, max_fraction] |
path_inputs_are_bounded |
PathConstraintSettings::max_friction_force, ConstraintMut::<PathConstraint>::set_max_friction_force |
finite, at least 0 | path_motor_springs_and_friction_are_bounded |
PathConstraintSettings::position_motor, ConstraintMut::<PathConstraint>::set_position_motor_settings springs |
as for create_constraint |
path_motor_springs_and_friction_are_bounded |
ConstraintMut::<PathConstraint>::set_target_velocity, set_target_path_fraction |
MAX_LINEAR_VELOCITY; [0, max_fraction] |
path_inputs_are_bounded |
ConstraintRef::<PathConstraint>::closest_fraction |
point within MAX_SHAPE_EXTENT per axis, finite hint |
path_inputs_are_bounded |
SwingTwistConstraintSettings::max_friction_torque, HingeConstraintSettings::max_friction_torque, SixDofConstraintSettings::max_friction |
finite, at least 0; Jolt clamps the friction impulse to dt · limit and applies no more than stops the relative motion |
swing_twist_limits_are_validated, hinge_limits_are_validated, six_dof_limits_are_validated |
SixDofAxis::Limited on a translation axis |
finite, min < max, within MAX_SHAPE_EXTENT |
six_dof_limits_are_validated, six_dof_translation_limits_at_the_bound_step_finitely |
RagdollSettings::new, new_stabilized part masses |
MIN_MASS..=MAX_MASS, also for kinematic parts (RagdollMut::set_motion_type can make them dynamic) |
part_masses_and_velocities_are_bounded |
RagdollMut::set_pose, drive_to_pose_using_motors |
root offset and positions within MAX_POSITION |
poses_are_validated |
RagdollMut::drive_to_pose_using_kinematics |
pose as above; every part's velocity, as Jolt computes it, within MAX_LINEAR_VELOCITY and MAX_ANGULAR_VELOCITY, checked for all parts before any changes |
poses_are_validated, kinematic_drive_is_bounded_by_the_velocities_it_implies |
RagdollMut::set_linear_and_angular_velocity |
MAX_LINEAR_VELOCITY, MAX_ANGULAR_VELOCITY |
part_masses_and_velocities_are_bounded |
SkeletonMapper::new |
neutral poses as for RagdollMut::set_pose (re-expressed translations stay within 2 MAX_POSITION (1 + 2^-22)); locked joints exist and are neither joint 0 nor the joint the ragdoll root maps to |
mappings_are_validated, selected_translation_locks; src/ragdoll/mapper/tests.rs: neutral_poses_are_validated (neutral poses at opposite edges of the frame, with the first map), reexpressed_translations_round_past_twice_the_frame_by_ulps, locks_are_validated |
SkeletonMapper::map |
ragdoll pose as for set_pose; local translation components within MAX_POSITION, finite unit rotations (normalised before Jolt sees them); each chain's ragdoll direction exactly zero or at least MIN_MAPPED_CHAIN_LENGTH, its animation direction at least that after the rounding allowance; both directions at most 1e18 m and their product at most 4e18 m², a norm bound on the chain's products at most 1e30 (none reachable from the safe API's inputs); the mapped pose within MAX_POSITION |
poses_are_validated_per_call, rotations_at_the_tolerance_edge_are_normalised, degenerate_chains_are_refused, outputs_are_validated; src/ragdoll/mapper/tests.rs: a_long_twisting_chain_maps, the_deepest_spiral_chains_map; crates/oxijolt-sys/tests/skeleton_mapper_smoke.rs: map2_refuses_degenerate_chains_and_writes_nothing, map2_accepts_a_long_twisting_chain, map2_bounds_long_actual_directions_against_a_zero_desired_one |
SkeletonMapper::map_reverse |
animation pose as for set_pose; the mapped pose within MAX_POSITION |
poses_are_validated_per_call, outputs_are_validated |
Shape::new_box*, new_sphere, new_cylinder*, new_capsule |
dimensions within MAX_SHAPE_EXTENT |
primitive_extents_are_bounded |
Shape::new_compound, new_offset_center_of_mass |
positions and offset within MAX_SHAPE_EXTENT; local bounds within it |
decorated_and_compound_extents_are_bounded |
Shape::new_compound sub-shape ids |
Jolt's 32-bit width; no one-child compound starting at bit 32 (it would push its 0-bit index at bit 32) | src/shape/tests.rs: a_one_child_compound_at_bit_32_is_refused, sub_shape_id_arithmetic_follows_jolt, height_field_ids_reach_exactly_32_bits, shared_shapes_are_walked_once, walked_widths_follow_jolt |
MutableCompound::from_children, add_shape, modify_shape positions and rotations |
the rules of Shape::new_compound: positions within MAX_SHAPE_EXTENT, finite unit rotations |
src/shape/mutable/tests.rs: refused_edits_change_nothing |
MutableCompound::remove_shape, modify_shape index |
below sub_shape_count |
src/shape/mutable/tests.rs: every_edit_checks_its_index |
MutableCompound child count |
at most u32::MAX - 3 (Jolt's (count + 3) >> 2 block count) |
src/shape/mutable/tests.rs: child_count_is_bounded_by_jolts_block_arithmetic |
MutableCompound sub-shape ids |
the rules of Shape::new_compound, checked before every edit |
src/shape/mutable/tests.rs: sub_shape_ids_are_checked_before_every_edit, refused_edits_walk_a_shared_child_once, an_empty_holder_has_jolts_width |
Shape::new_compound, MutableCompound::from_children, add_shape, modify_shape, to_shape expanded tree |
at most MAX_EXPANDED_SUB_SHAPES shapes, a shared child counted at every use (ShapeError::TooManySubShapes) |
src/shape/tests.rs: compounds_above_the_expansion_bound_are_refused; src/shape/mutable/tests.rs: edits_above_the_expansion_bound_are_refused |
MutableCompound::to_shape |
at least one child; local bounds within MAX_SHAPE_EXTENT after the centre of mass moves |
src/shape/mutable/tests.rs: removing_to_zero_and_adding_again_works, extent_is_checked_after_the_centre_of_mass_moves |
Shape::new_height_field |
local bounds within MAX_SHAPE_EXTENT |
height_field_extent_is_bounded |
Shape::new_plane, new_plane_with_material |
normal a finite unit vector; constant (absolute) and positive half extent within MAX_SHAPE_EXTENT; Jolt's local bounds within it (see limits.md) |
plane_inputs_are_validated, a_tilted_plane_is_checked_against_jolts_bounds, src/shape/plane.rs: bounds_reach_half_extent_around_the_plane_and_behind_it |
HeightFieldSettings, CompoundChild::rotation |
existing ranges | invalid_height_fields_are_rejected, empty_or_invalid_compounds_are_rejected |
Shape::new_box_with_material, new_sphere_with_material, new_capsule_with_material, new_cylinder_with_material |
the plain sibling's rules | convex_constructors_apply_the_plain_rules |
HeightFieldSettings::materials |
the rules of new_height_field; material count 1..=256, (n - 1)^2 indices, each below the count |
height_field_material_lists_are_validated |
Shape::new_convex_hull points |
at least 4; at most i32::MAX; each within MAX_SHAPE_EXTENT; Jolt's initial triangle, a needle bound and a slab bound (see limits.md); bounds within MAX_SHAPE_EXTENT |
too_few_points_are_refused, points_must_be_finite_and_within_the_extent, points_without_a_triangle_are_degenerate, needle_rule_compares_the_rounding_lever_with_the_tolerance, slab_rule_counts_coplanar_distances, thin_slabs_near_the_origin_build_down_to_a_fraction_of_a_millimetre, far_clouds_need_more_thickness, needles_too_thin_for_the_hull_builder_are_degenerate |
Shape::new_convex_hull_with_convex_radius convex radius |
finite, at least 0 | convex_radius_must_be_finite_and_not_negative |
Shape::restore_binary_state bytes |
the header's magic, format version, flags and build id equal this build's, the payload length equals the bytes after the header, the XXH64 checksum matches; joltc's record checks; the restored shape within MAX_SHAPE_EXTENT, Jolt's sub-shape ids and MAX_EXPANDED_SUB_SHAPES (see limits.md) |
bytes_without_the_header_are_not_binary_state, bytes_of_another_build_are_refused, every_truncation_is_refused_as_truncated, bytes_after_the_payload_are_refused, payload_bit_flips_are_refused_as_corrupt, a_record_joltc_refuses_behind_a_valid_checksum_is_rejected, a_restored_shape_beyond_the_extent_is_malformed |
Shape::new_mesh* vertices and indices |
at least one of each, at most i32::MAX; every vertex within MAX_SHAPE_EXTENT, referenced or not; every index below the vertex count |
empty_geometry_is_refused, counts_must_fit_jolt_int, vertices_must_be_finite_and_within_the_extent, indices_beyond_the_vertices_are_refused |
Shape::new_mesh* triangles |
twice the area at least 1.000001e-6 m² (Jolt's limit, 1e-6) plus twice a per-triangle bound of what per-axis quantization and rounding (in the mesh's and in the convex shape's space) can take from it, else dropped and reported (see limits.md); none left is MeshError::NoTriangles |
small_and_thin_triangles_are_dropped, slivers_that_collapse_under_quantization_are_dropped, thin_patches_survive_far_geometry, meshes_without_usable_triangles_are_refused, dropped_triangles_are_reported_with_their_area, slivers_too_thin_for_large_convex_shapes_are_dropped, the_rule_does_not_depend_on_the_corner_order, the_smallest_kept_triangles_collide_with_convex_shapes_up_to_the_extent |
MeshSettings::max_convex_extent |
0..=2 * MAX_SHAPE_EXTENT, default 300 m, the largest round extent at which every real prop tested in CI loses under 0.1 % of its area (see limits.md) |
settings_out_of_range_are_refused, the_default_convex_extent_keeps_millimetre_bevels, slivers_too_thin_for_large_convex_shapes_are_dropped, the_thinnest_kept_slivers_collide_with_convex_shapes_up_to_the_extent |
MeshSettings::active_edge_cos_threshold_angle, max_triangles_per_leaf, materials |
[-1, 1]; 1..=8; 1 to 32 materials, one index per triangle, each below the count |
settings_out_of_range_are_refused, material_lists_are_checked |
Shape::new_scaled scale |
finite; Jolt's IsValidScale; every stored mesh or heightfield triangle collidable after scaling, for the convex extent the mesh was built with (ShapeError::ThinTriangles); scaled bounds and centre of mass within MAX_SHAPE_EXTENT (see limits.md) |
scale_components_are_checked, jolt_scale_rules_per_shape_kind, scaled_bounds_and_centre_of_mass_stay_within_the_extent, scaled_meshes_stay_kinematic_and_collidable, translations_above_a_mesh_do_not_change_its_check, flattening_follows_the_meshs_own_coordinates, scaled_meshes_are_checked_for_the_extent_they_were_built_for |
Shape::new_tapered_capsule |
finite, positive; half_height + max radius within MAX_SHAPE_EXTENT; not Jolt's sphere case; radius difference at most 2 · half_height · (1 - 2^-21) (see limits.md) |
tapered_capsule_boundaries |
Shape::new_tapered_cylinder_with_convex_radius |
half height finite, positive; radii finite, at least 0, different, the larger at least 2^-63 m; all within MAX_SHAPE_EXTENT; bounds within it; convex radius finite, at least 0 |
tapered_cylinder_boundaries, radius_floor_is_two_to_the_minus_63 |
| inertia of a moving body with an exactly diagonal tensor | every moment positive with a finite inverse, or near zero | diagonal_inertia_must_be_positive_unless_near_zero |
PhysicsWorld::cast_ray origin |
MAX_POSITION |
query_inputs_are_bounded_by_the_frame |
RayCast direction |
finite, not zero | invalid_rays_are_rejected, a_ray_with_a_huge_finite_direction_is_cast |
ShapeCast, CollideShape position |
MAX_POSITION |
query_inputs_are_bounded_by_the_frame |
ShapeCast direction |
2 * MAX_POSITION per axis |
query_inputs_are_bounded_by_the_frame |
PhysicsWorld::collide_point point |
MAX_POSITION; filter valid for the world |
invalid_points_and_filters_are_rejected |
Shape::collide_point point |
each component within MAX_SHAPE_EXTENT |
invalid_points_and_filters_are_rejected |
ShapeCast::target_distance |
ShapeCast::MAX_TARGET_DISTANCE |
target_distance_at_the_bound_gives_a_finite_depth |
CollideShape::max_separation_distance |
0..=MAX_SHAPE_EXTENT |
query_inputs_are_bounded_by_the_frame |
SoftBodySharedSettingsBuilder::build vertices |
at least one; position within MAX_SHAPE_EXTENT per axis; velocity within MAX_LINEAR_VELOCITY; inverse mass 0 or the inverse of a mass within MIN_MASS..=MAX_MASS |
soft_body_shared_settings_are_bounded, invalid_vertices_are_rejected |
SoftBodySharedSettingsBuilder::build total mass |
masses of the movable vertices (1 / w in f32, as Jolt) add up to at most MAX_MASS |
soft_body_total_mass_is_bounded, total_movable_mass_is_bounded |
SoftBodySharedSettingsBuilder::build faces |
indices name vertices, three different ones; every edge at least MIN_SOFT_BODY_EDGE_LENGTH in f32; area above 0; with distance bends the vertices opposite a shared edge as far apart |
soft_body_shared_settings_are_bounded, invalid_faces_are_rejected, edge_lengths_are_measured_in_f32_like_jolt, distance_bends_need_separate_opposite_vertices |
SoftBodyVertexAttributes compliances |
0..=MAX_COMPLIANCE |
soft_body_shared_settings_are_bounded, attributes_are_validated |
SoftBodyVertexAttributes::long_range_attachment multiplier |
1..=MAX_RATIO (see limits.md) |
soft_body_shared_settings_are_bounded |
SoftBodySharedSettingsBuilder::edge, dihedral_bend, volume |
indices name different vertices; compliance 0..=MAX_COMPLIANCE; edge and shared bend edge at least MIN_SOFT_BODY_EDGE_LENGTH; tetrahedron six-volume finite and not 0 in f32 |
soft_body_explicit_constraints_are_bounded, invalid_explicit_constraints_are_rejected |
SoftBodySettings position, rotation, object layer, friction, restitution, gravity factor |
as for BodySettings |
soft_body_settings_are_bounded |
SoftBodySettings::num_iterations |
1..=SoftBodySettings::MAX_ITERATIONS; Jolt divides the step by it |
soft_body_settings_are_bounded |
SoftBodySettings::linear_damping, max_linear_velocity, vertex_radius |
finite, at least 0; (0, MAX_LINEAR_VELOCITY]; 0..=MAX_SHAPE_EXTENT |
soft_body_settings_are_bounded |
SoftBodySettings::pressure |
0..=MAX_SOFT_BODY_PRESSURE; above 0 only when the faces enclose a volume large enough for it (see limits.md) |
soft_body_settings_are_bounded, pressure_at_the_bound_steps_finitely, pressure_needs_a_volume_for_its_faces, a_sliver_at_the_pressure_bound_steps_finitely |
PhysicsWorld::create_soft_body vertices (positions about the origin, masses) |
without a kinematic vertex: smallest principal moment of the inertia at least MIN_INERTIA_RATIO (4.8e-4) of its Frobenius norm after Jolt's f32 error, or an exactly diagonal tensor that is near zero or has every moment above 1e-30 (see limits.md) |
soft_body_inertia_is_checked_at_creation, soft_body_inertia_at_its_bound_decomposes |
SoftBodyMut::set_vertex_velocity |
MAX_LINEAR_VELOCITY |
soft_body_vertex_writes_are_bounded_and_rejection_changes_nothing |
SoftBodyMut::set_vertex_inverse_mass |
0 or the inverse of a mass within MIN_MASS..=MAX_MASS; total movable mass at most MAX_MASS; the force accumulated this step within the soft body force bound for the new inverse masses; the inertia rule of create_soft_body at the current vertex positions |
soft_body_vertex_writes_are_bounded_and_rejection_changes_nothing, unpinning_cannot_release_an_accumulated_force, unpinning_checks_the_inertia_at_the_current_positions |
SoftBodyMut::move_kinematic_vertex |
target within MAX_POSITION; a time step step accepts; the implied velocity within MAX_LINEAR_VELOCITY |
soft_body_vertex_writes_are_bounded_and_rejection_changes_nothing |
BodyMut::add_force on a soft body |
accumulated ` | F |
ContactSettings::set_combined_friction (in a ContactListener) |
0..=MAX_FRICTION |
contact_settings_setters_accept_their_range_and_refuse_beyond |
ContactSettings::set_combined_restitution |
0..=1 |
contact_settings_setters_accept_their_range_and_refuse_beyond |
ContactSettings::set_inverse_mass_scale1, set_inverse_mass_scale2, set_inverse_inertia_scale1, set_inverse_inertia_scale2; the SoftBodyContactSettings scales |
0 or MIN_CONTACT_SCALE..=1 (see limits.md) |
contact_settings_setters_accept_their_range_and_refuse_beyond, soft_body_contact_settings_setters_keep_scales_in_range, contact_scales_at_their_floor_step_finitely |
ContactSettings::set_is_sensor |
stays true for a contact with a sensor body, as Jolt asserts |
a_contact_with_a_sensor_body_stays_a_sensor_contact |
ContactSettings returned by a ContactListener |
every rule above, checked again against the contact the listener was called for; a failing value is not applied and is reported in WorldEvents::rejected_contact_settings |
settings_are_checked_again_against_the_contact_that_takes_them, settings_moved_to_a_contact_they_do_not_fit_are_rejected |
ContactSettings::set_relative_linear_surface_velocity, set_relative_angular_surface_velocity |
MAX_LINEAR_VELOCITY, MAX_ANGULAR_VELOCITY, and len(v) + len(ω) · R <= MAX_LINEAR_VELOCITY (see limits.md) |
surface_velocities_are_bounded_alone_and_together, a_conveyor_moves_a_resting_cube |
BodyVelocity::set_linear_velocity, set_angular_velocity (in a CharacterContactListener) |
finite, MAX_LINEAR_VELOCITY, MAX_ANGULAR_VELOCITY (see limits.md) |
adjusted_velocities_outside_the_limits_are_refused, a_far_lever_adjusted_velocity_stays_finite |
GroupFilterTableBuilder::new, disable_collision, enable_collision, GroupFilterTable::is_collision_enabled |
1..=GroupFilterTable::MAX_SUB_GROUPS sub groups; sub groups below the count, and different for an edit (see limits.md) |
builder_and_group_boundaries, enabling_a_disabled_pair_makes_it_collide_again |
CollisionGroup::new |
group id at most CollisionGroup::MAX_GROUP_ID, sub group below the table's count |
builder_and_group_boundaries |
DebugLineSettings (feature debug-renderer) |
centre within MAX_POSITION, radius at most twice it |
center_and_radius_are_bounded_by_the_frame |
The asserts leg of CI runs every test with Jolt's assertions. These paths are exercised there by scenes with inputs at their bounds; no derivation backs them:
- contact and constraint impulses the solver generates: spheres of
MIN_MASSandMAX_MASScolliding head-on at the velocity bounds, restitution 1, friction atMAX_FRICTIONincluding a speculative contact with zero normal impulse, motors at the spring bound, six-DOF translation limits at the extent bound between parts of both mass extremes, suspension springs and anti-roll bars at their bounds (bodies_at_every_bound_step_finitely,friction_at_the_bound_keeps_contacts_finite,six_dof_translation_limits_at_the_bound_step_finitely,vehicle_springs_and_anti_roll_bar_at_their_bounds_step_finitely,motor_springs_at_the_coefficient_bound_drive_finitely); - impulses at a lever arm on the lightest bodies: the weight impulse of a character of
MAX_MASSatMAX_WEIGHT_IMPULSEon the edge of a 6 cm cube ofMIN_MASS, the cube that turns fastest (character_weight_impulse_at_a_lever_arm_is_bounded), and a box ofMIN_MASSpressed on its floor and pushed at the velocity bound by such a character (character_at_its_bounds_pushes_the_lightest_body); - query arithmetic: rays, shape casts and collisions from frame corners
(
query_inputs_are_bounded_by_the_frame) and a sphere at the extent bound cast across the frame and collided with separations up to the extent bound (queries_with_shapes_at_the_extent_bound_stay_finite); - world constraints: gears at both ends of
1..=MAX_GEAR_RATIO, racks and pinions and pulleys at bothMAX_RATIObounds between bodies of both mass extremes, one of them turning or sliding at the velocity bound (ratios_at_the_bound_step_finitely,pulleys_at_the_ratio_bound_step_finitely); motor and limit springs accepted at the effective-mass bound and rejected one representable frequency above it (world_constraint_springs_are_bounded_by_the_bodies_effective_mass,slider_swing_twist_and_six_dof_motor_springs_are_bounded,path_motor_springs_and_friction_are_bounded); targets at their bounds (constraint_targets_are_bounded,slider_cone_swing_twist_and_six_dof_targets_are_bounded,path_inputs_are_bounded); hinge, slider, swing-twist and path friction off32::MAXon bodies moving at the velocity bounds (constraint_friction_at_f32_max_steps_finitely,slider_and_swing_twist_friction_at_f32_max_steps_finitely,path_motor_springs_and_friction_are_bounded); and a path segment at the margin of the segment check stepped end to end with every rotation constraint (path_validation_accepts_its_boundary); points that hold a dynamic body atMAX_LEVER_ARM_RATIO: a ball joint and a hinge on a static body, and a weld, a six-DOF joint with every axis fixed and a swing-twist joint with zero ranges on a static body or between two bodies, each holding a 1 g, 6 cm cube or a 1 kg, 1 m cube at the velocity bounds under gravity (constraints_at_the_lever_arm_bound_step_finitely); - a motorcycle lean spring at the bound of its rule on a 1 g chassis of 7 cm, turning at the
smallest, a 60 Hz and the largest step (
lean_spring_at_its_bound_steps_finitely); - buoyancy on 500 seeded bodies of seven shape kinds across the surface, at speeds up to the bounds,
with gravity factors up to 1000 of either sign, drags up to 1e30 and both step bounds: every call
is accepted or refused, and the velocities stay finite and within the bounds after the call and
after a step (
seeded_calls_are_accepted_or_refused_and_stay_finite).
-
Collision estimates are Jolt's estimate for an isolated pair. More than two bodies in contact, the contact's mass and inertia scales, surface velocities, locked axes, the speed limits and the solver's removal of the step's gravity from a bounce are not part of them; a speculative contact is estimated as touching, and a contact the continuous stage finds is estimated from velocities after the solve. The tests pin these limits (
locked_axes_are_not_part_of_the_estimate,estimates_use_the_settings_jolt_resolves_the_contact_with,under_gravity_the_estimated_bounce_is_faster_by_restitution_g_dt,a_contact_found_by_the_continuous_stage_is_estimated) rather than bound them. -
The spin a buoyancy impulse gives through the lever of its centre of buoyancy is clamped to
MAX_ANGULAR_VELOCITYright after the call, not refused (see limits.md). -
State the simulation produces itself is not an input and is not checked again: a body Jolt carries out of the frame, the positions a rebase computes (only checked to be finite), or a character state restored with
CharacterMut::restore_state, which can only come fromCharacterRef::save_state. -
Bodies with a principal inverse inertia above
√3 · 1e6, such as a light needle-thin shape or a centre of mass far from the shape.PhysicsWorld::create_bodybounds no inverse inertia from above: an exactly diagonal tensor (an unrotated shape, or a centre of mass moved along a principal axis) only needs finite inverse moments, and the rigid body inertia floor (see limits.md) bounds how badly conditioned any other tensor is, not how small it is. The boundary tests of that floor step slender bodies for two ticks without contacts or impulses, so impulses at a lever arm on such a body, including a character's weight impulse, are neither derived nor tested. The same holds for the suspension effective mass Jolt forms from a wheel's force point and the chassis's inverse inertia (VehicleConstraint.cpp:448-451). -
Constraints whose solver diverges for reasons the lever-arm ratio does not measure. In the
assertsbuild Jolt then asserts that a squared velocity is finite (MotionProperties.inl:28or:38); in the release build the joint tears apart by metres and the state can become NaN: Jolt's velocity clamp scales a velocity whose squared length overflows to zero, turns an already infinite component into NaN (inf * 0) and lets NaN through. No impact is needed. Measured with one constraint to a static body under gravity unless stated otherwise:- a hinge whose pin lies off the least-inertia axis of a slender body, outside its cross section
(an offset
dof about twice the half thicknesstor more). A 2 kg rod of 1 m by 2 cm hinged 5 cm off its axis (a lever-arm ratio of 43.5) asserted while swinging about the hinge at 10 rad/s; a 10 kg barrier arm of 3 m by 5 cm on a 5 cm bracket (ratio 12) asserted at 30 rad/s. In the release build both became NaN within a few hundred steps of such a swing (the step depends on the scene); a 1 m rod 1 cm thick with the pin 5 cm beside it along the hinge line asserted while falling from horizontal under gravity alone. On 1 m rods swung at up to 47 rad/s,t= 5 mm failed fromd= 1 cm,t= 1 cm from 2 cm,t= 2 cm only at 10 cm, andt= 5 cm not up to 10 cm; - chains of hinges with non-parallel axes, also between cubes: of 100 seeded chains of 3 to 10 cubes, 3 asserted at kicks of 15 m/s and 1.4 rad/s per body at lever-arm ratios of 10 to 100, and 18 at ratios of 100 to 500; in the release build 2 of 100 chains became NaN at 15 m/s and 31 of 100 at 50 m/s;
- a light body held by two to four constraints to static anchors, also without a hinge (point and fixed, cone, distance and six-DOF, ...): of 300 seeded scenes at 0.3 to 1 of the lever-arm bound, 24 asserted at the velocity bounds with every constraint kind and 8 without hinges, 3 at a tenth of the bounds and none at 3 %.
The same probes found no assert and no NaN for a 2 m by 1 m by 5 cm door hinged at its edge, capsule limbs on hinges, a rod hinged on its axis, or the 12-capsule test ragdoll, at kicks up to 499 m/s and 47 rad/s, but their joints still opened by more than 0.5 m: of 24 kicks at 150 m/s, 8 for the door, 10 for the rod and 9 for a forearm or shin capsule, and at 499 m/s 16, 24 and 19, up to 3.3 m for the door, 4.1 m for the rod and 7.3 m for the capsules; one of 24 exploded ragdolls opened by 1 m at 150 m/s. A six-DOF joint with the hinge's free axis did not assert on the slender bodies but let the joint drift apart by 0.2 to 3.3 m while swinging and up to 6.9 m under kicks of 50 m/s, so it is no workaround. No bound in
oxijolt::limitsexcludes these cases. - a hinge whose pin lies off the least-inertia axis of a slender body, outside its cross section
(an offset
-
A slider adds the distance travelled along its axis to the lever of body 1 (
SliderConstraint.cpp), whichMAX_LEVER_ARM_RATIOchecks only at creation; with a dynamic body 1 and a long travel the lever grows beyond the bound. -
Inputs that are only checked to be finite or ordered, as the table above says: ray directions, damping, motor force and torque limits, ragdoll joint friction (world constraint friction is probed as above), wheel friction curves and the wheel and drivetrain values that only have to give finite step coefficients.
-
A soft body with pressure whose volume shrinks after creation (crushed, or its vertices moved or recentred by Jolt so that an open mesh encloses less): Jolt divides the pressure by the volume of every sub-step (
SoftBodyMotionProperties.cpp:300-307), and the creation check holds only for the start geometry. -
Soft body constraint stability:
MAX_COMPLIANCEkeeps Jolt's compliance terms finite, not the solver convergent. Measured: a 2 m cube of 1 g vertices, three of them kinematic, with six tetrahedral volume constraints of compliances 0 to 1e20, per-vertex attributes with edge and shear compliances up to 1e20 and LRA multipliers up to 1e4, distance bends, 100 iterations, no damping, restitution 1, a vertex speed limit of 1 m/s, one vertex given 155 m/s and an accepted force of 3.7e6 N, had NaN vertices after its first step in the release build and asserted that a squared velocity is finite (MotionProperties.inl:28) in the asserts build. With a tenth of the force, 5 iterations, no volume constraints or uniform attributes it stayed finite. No bound inoxijolt::limitsexcludes it. Two more seeded scenes of the same kind failed in the asserts build: a cube of 1 g vertices with six volume constraints, 37 iterations, a step of 0.1 s, friction 1000, a vertex radius of 2000 m, a force of 3.7e6 N and a vertex unpinned to 1 kg asserted atMotionProperties.inl:28; a cube with pressure 1e5 under gravity of about 1000 m/s², pushed by repeated forces of 3.8e9 N and resting against rigid bodies (without them it did not fail), grew bounds beyondcLargeFloat(QuadTree.cpp:68). -
A tracked vehicle's tracks pulled by the ground (
TrackedVehicleController.cpp:394-406) at 1 s steps, or with a dynamic body under the wheels. At 1 s steps the chassis reached Jolt's angular velocity clamp from a track mass ratio of 0.32 at game friction, and went NaN from 0.062 with tire and ground friction 1000, both below whatMAX_TRACK_MASS_RATIOaccepts; a body under the wheels adds its own inverse mass, which the ratio does not count (limits.md). -
A motorcycle's lean compensation divides by the sum of both wheels' suspension impulses times the chassis' inverse mass (
MotorcycleController.cpp:246); a small positive impulse can make that product underflow to 0. It was not seen in the tests. -
RagdollSettings::new_stabilizedreports Jolt'sStabilizefailing to decompose an inertia tensor as an error, but Jolt asserts on that path first (Ragdoll.cpp:158). -
The assertion
errors == EPhysicsUpdateError::Noneat the end of every step that drops contacts (PhysicsSystem.cpp:679) is intentional:PhysicsWorld::stepreturns the same errors in itsStepReport, and oxijolt's assertion handler lets the process continue for this assertion only.