simvx.core.physics.builtin.world

What it is adequate for

Spheres and capsules on flat ground, low-density scenes, prototypes, and anything whose gameplay does not turn on the exact resting pose of a stack. For oriented boxes, hulls, meshes under load and dense stacking, install simvx-physics-jolt: installing it makes Jolt the default 3D backend, and PhysicsRoot(backend= "builtin") pins this solver back.

Mechanism: semi-implicit (symplectic) Euler integration, a sweep-and-prune broad phase feeding an analytic narrow phase, and a sequential normal-impulse solver with Baumgarte positional correction and a small penetration slop. The GEOMETRY is written for clarity over raw throughput; what is optimised is how OFTEN it runs. The broad phase proposes only plausible pairs and a pair whose bodies have not moved replays its last answer, so a settled scene costs almost nothing however many bodies are in it.

Shape set: sphere, box, capsule, cylinder, convex hull and static triangle mesh.

EXACT

  • Every sphere and capsule pairing: capsule contacts reduce to a sphere test at the closest point(s) of the segments, reusing the sphere depth/normal maths.

  • Sphere / capsule / box against a static triangle mesh: closest-point-on-triangle (Ericson RTCD 5.1.5) per candidate triangle, with the box reporting its OWN support along the contact direction, so a box rests on a mesh floor at the height it rests on a box floor. Mesh raycast is Moller-Trumbore.

  • Cylinder-sphere and every ray query against a cylinder: analytic finite-cylinder maths.

  • Convex hull boolean overlap: GJK.

  • All queries (raycast, shapecast, overlap, sweep_body) as to WHETHER they hit, subject to the substepping note below.

APPROXIMATE, and named at the call site

None of these silently fakes a result; each carries a comment where it is computed.

  • Cylinder-box: AABB of the cylinder.

  • Cylinder-cylinder and capsule-cylinder: the cylinder is treated as a capsule, so the rims round off. Cylinders are the honest weak shape here.

  • Convex hull penetration depth and normal: EPA-lite, a bounded-iteration expanding polytope with a search-direction fallback.

  • Convex hull raycast: the slab test against the AABB of the point cloud.

  • Contact response: one linear impulse at the centre of mass. Rotation is integrated for round-tripping, but there is no inertia tensor, so an off-centre impulse never spins a body. The 2D solver DOES carry a real moment of inertia; see world2d.py.

  • Swept queries substep rather than solving a true time of impact, so a reported distance is a lower bound and a fast enough body can tunnel.

ROTATION IGNORED

_box_box, _hull_world_points and a mesh body’s own orientation in _shape_vs_mesh: each treats its geometry in world axes offset by the body position. An oriented box against another box, a rotated hull, and a rotated mesh level are therefore all wrong here.

REFUSED

Convex hull against a triangle mesh. There is no routine for it, and what the pair returned before it was refused was NO CONTACT, so a hull fell through a mesh floor in silence. It raises at create_body, set_body_shape, set_body_filter and at the two query entry points, naming simvx-physics-jolt. Box-vs-mesh is NOT refused: it is exact (see above).

BuiltinPhysics world: the dependency-free 3D reference solver.

Concrete :class:~simvx.core.physics.world.PhysicsWorld implementation, pure Python (numpy only). It is always present, it needs nothing installed, it runs everywhere the engine runs including the browser, and it is the behavioural parity target the seam’s cross-backend tests are written against.

Module Contents

Classes

BuiltinPhysics

Pure-Python default backend (basic tier).

Data

API

class simvx.core.physics.builtin.world.BuiltinPhysics(*, gravity: simvx.core.math.Vec3)[source]

Bases: simvx.core.physics.world.PhysicsWorld

Pure-Python default backend (basic tier).

See :class:~simvx.core.physics.world.PhysicsWorld for the full contract.

Initialization

Initialise the world.

Args: gravity: World gravity acceleration vector (Vec3), metres/s^2.

capabilities() frozenset[simvx.core.physics.capability.Capability][source]

Advertise the measured contact impulse and continuous collision.

This backend owns its sequential-impulse solver, so it reads the converged normal impulse straight off the last velocity iteration and puts it in the contact-event payload: CONTACT_IMPULSE is a real measurement here, not an estimate. CONTINUOUS is honoured by _integrate, which sweeps a flagged body’s centre displacement against STATIC geometry and clamps it to the time of impact. It advertises no cross-platform determinism (its float maths is reproducible on one machine only), no vehicles and no soft bodies. SLEEP is honoured by _update_sleeping, which parks a settled DYNAMIC body and skips it in integrate and solve until something disturbs it. SENSOR_DETECTS_STATIC is honoured because _collide_sensors sweeps a sensor against every body its mask admits rather than against a moving set, so a trigger over level geometry reports it. Explicit (not inherited) so the claim is a deliberate, tested promise, not an accident of the default.

property body_count: int[source]

Number of bodies currently in the world (len of the body table).

clear() None[source]

Remove every body and joint, emptying the world.

See :meth:~simvx.core.physics.world.PhysicsWorld.clear. Resets the body / joint tables and the per-step edge-diff + warm-start caches so a re-populated world starts from a clean broadphase state. Gravity, shapes, and the handle counters are intentionally NOT reset (shapes are reusable resources; monotonic handles keep freed handles from aliasing a live one).

create_sphere(radius: float) simvx.core.physics.world.ShapeHandle[source]
create_box(half_extents: simvx.core.math.Vec3) simvx.core.physics.world.ShapeHandle[source]
create_capsule(radius: float, height: float) simvx.core.physics.world.ShapeHandle[source]
create_cylinder(radius: float, height: float) simvx.core.physics.world.ShapeHandle[source]
create_convex_hull(points: numpy.ndarray) simvx.core.physics.world.ShapeHandle[source]
create_mesh(vertices: numpy.ndarray, indices: numpy.ndarray) simvx.core.physics.world.ShapeHandle[source]
destroy_shape(shape: simvx.core.physics.world.ShapeHandle) None[source]

Release this world’s record of a shape handle.

See :meth:~simvx.core.physics.world.PhysicsWorld.destroy_shape. There is nothing native to free here: the record is simply dropped from the shape table. Bodies built from it hold their own _Shape record directly and are unaffected. Unknown handles are a silent no-op.

create_body(shape: simvx.core.physics.world.ShapeHandle, body_type: simvx.core.physics.world.BodyMode, transform: object, *, mass: float = 1.0, scale: simvx.core.math.Vec3 | None = None, can_sleep: bool = True, linear_damping: float = DEFAULT_LINEAR_DAMPING, angular_damping: float = DEFAULT_ANGULAR_DAMPING, gravity_scale: float = DEFAULT_GRAVITY_SCALE, collision_layer: int = 1, collision_mask: int = 4294967295, is_sensor: bool = False, material: simvx.core.physics.material.PhysicsMaterial | None = None, continuous: bool = False) simvx.core.physics.world.BodyHandle[source]
destroy_body(handle: simvx.core.physics.world.BodyHandle) None[source]
set_body_transform(handle: simvx.core.physics.world.BodyHandle, transform: object, *, scale: simvx.core.math.Vec3 | None = None, wake: bool = True) None[source]
set_body_velocity(handle: simvx.core.physics.world.BodyHandle, linear: simvx.core.math.Vec3, angular: simvx.core.math.Vec3 | None = None) None[source]
set_body_mode(handle: simvx.core.physics.world.BodyHandle, mode: simvx.core.physics.world.BodyMode, *, wake: bool = True) None[source]
set_body_mass(handle: simvx.core.physics.world.BodyHandle, mass: float, *, wake: bool = True) None[source]
set_body_filter(handle: simvx.core.physics.world.BodyHandle, collision_layer: int, collision_mask: int, *, wake: bool = True) None[source]
set_body_material(handle: simvx.core.physics.world.BodyHandle, material: simvx.core.physics.material.PhysicsMaterial | None) None[source]
set_body_damping(handle: simvx.core.physics.world.BodyHandle, linear: float, angular: float) None[source]
set_body_gravity_scale(handle: simvx.core.physics.world.BodyHandle, scale: float) None[source]
set_body_continuous(handle: simvx.core.physics.world.BodyHandle, enabled: bool) None[source]
set_body_shape(handle: simvx.core.physics.world.BodyHandle, shape: simvx.core.physics.world.ShapeHandle, *, wake: bool = True) None[source]
body_velocity(handle: simvx.core.physics.world.BodyHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Vec3][source]
body_mass(handle: simvx.core.physics.world.BodyHandle) float[source]
wake(handle: simvx.core.physics.world.BodyHandle) None[source]
sleep(handle: simvx.core.physics.world.BodyHandle) None[source]
set_body_can_sleep(handle: simvx.core.physics.world.BodyHandle, enabled: bool) None[source]
sleeping(handle: simvx.core.physics.world.BodyHandle) bool[source]
apply_impulse(handle: simvx.core.physics.world.BodyHandle, impulse: simvx.core.math.Vec3, *, at: simvx.core.math.Vec3 | None = None, angular: simvx.core.math.Vec3 | None = None) None[source]
apply_force(handle: simvx.core.physics.world.BodyHandle, force: simvx.core.math.Vec3, *, at: simvx.core.math.Vec3 | None = None) None[source]
apply_torque(handle: simvx.core.physics.world.BodyHandle, torque: simvx.core.math.Vec3) None[source]
create_fixed_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle) simvx.core.physics.world.JointHandle[source]
create_pin_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle, anchor: simvx.core.math.Vec3) simvx.core.physics.world.JointHandle[source]
create_hinge_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle, anchor: simvx.core.math.Vec3, axis: simvx.core.math.Vec3) simvx.core.physics.world.JointHandle[source]
create_spring_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle, rest_length: float, stiffness: float, damping: float) simvx.core.physics.world.JointHandle[source]
remove_joint(handle: simvx.core.physics.world.JointHandle) None[source]
step(dt: float) None[source]
drain_contact_events() list[simvx.core.physics.world.ContactEvent][source]
drain_overlap_events() list[simvx.core.physics.world.OverlapEvent][source]
register_bodies(handles: list[simvx.core.physics.world.BodyHandle]) None[source]
read_transforms(out: numpy.ndarray) None[source]
read_velocities(out: numpy.ndarray) None[source]
raycast(origin: simvx.core.math.Vec3, direction: simvx.core.math.Vec3, max_dist: float, *, mask: int = 4294967295) simvx.core.physics.world.RaycastHit | None[source]
raycast_all(origin: simvx.core.math.Vec3, direction: simvx.core.math.Vec3, max_dist: float, *, mask: int = 4294967295) list[simvx.core.physics.world.RaycastHit][source]
sweep_body(handle: simvx.core.physics.world.BodyHandle, motion: simvx.core.math.Vec3, *, from_transform: tuple[simvx.core.math.Vec3, simvx.core.math.Quat] | None = None, skin: float = 0.0) simvx.core.physics.world.SweepHit | None[source]

Substepped, non-mutating sweep of a body’s shape (basic tier).

Conservative advancement is out of tier scope (the narrow phase is analytic AABB-ish overlap, not a true continuous TOI shape-cast), so this substeps along motion with a step count sized from the mover’s smallest feature, capped at 64. The first substep that penetrates a blocking body brackets the contact between the last fraction proved clear and that one, and the bracket is then BISECTED against that blocker alone (_SWEEP_REFINE_STEPS halvings), so the reported distance is a bound good to |motion| / (substeps * 2**8) rather than to one whole substep. It is still a lower bound and may be exactly 0.0 for a sweep that begins in contact. Fast movers vs very thin colliders can still tunnel BETWEEN substeps, because the bisect refines a bracket the scan found rather than finding brackets the scan missed.

Structurally non-mutating: the mover’s shape and pose are wrapped in a TRANSIENT _Body probe, so nothing in the body table is touched and from_transform costs nothing. skin is accepted and IGNORED: the substep quantum is already larger than any sane skin (see

Attr:

~simvx.core.physics.world.SweepHit.distance).

A body blocks only when it is not the mover, is not a sensor, passes the canonical AND layer/mask rule, and presents a contact that OPPOSES the sweep. Sensors take no part in collision resolution, so one never blocks a sweep and (because the ground and step-up probes route through here) never counts as ground or as a step surface. A non-opposing touch, e.g. the floor a character already rests on while it walks, is a touch and not a blocker: reporting it would halt every slide at distance 0 and the mover could never move. Fraction 0 is never sampled (the substep loop starts at s = 1), so this backend cannot report the cast axis as a normal.

shapecast(shape: simvx.core.physics.world.ShapeHandle, origin: simvx.core.math.Vec3, direction: simvx.core.math.Vec3, max_dist: float, *, mask: int = 4294967295) simvx.core.physics.world.SweepHit | None[source]

Substepped shape sweep, earliest-TOI contact (basic tier).

Mirrors :meth:sweep_body’s substepped scan but with a transient probe shape (not a registered body) and the single query-mask convention (mask & body.layer). Earliest TOI wins: substeps are the outer loop, bodies the inner, so the first penetrating substep is the earliest hit.

overlap(shape: simvx.core.physics.world.ShapeHandle, transform: object, *, mask: int = 4294967295) list[simvx.core.physics.world.BodyHandle][source]

Static shape-vs-body overlap test, all matches (basic tier).

Places a transient probe shape at transform and returns the handles of every body it overlaps whose layer & mask is set, sorted by handle for determinism. Single query-mask convention.

body_transform(handle: simvx.core.physics.world.BodyHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Quat][source]
property gravity: simvx.core.math.Vec3
property solver_iterations: int
property position_iterations: int
property sleep_time_threshold: float
property sleep_velocity_threshold: float
property contact_slop: float
move_and_collide(handle: simvx.core.physics.world.BodyHandle, motion: simvx.core.math.Vec3) simvx.core.physics.world.SweepHit | None
__slots__

()

simvx.core.physics.builtin.world.__all__

[‘BuiltinPhysics’]