simvx.core.physics.world

Role

This module defines PhysicsWorld, the abstract interface every physics backend (BuiltinPhysics now, JoltPhysics later) implements. It is a transport abstraction, not a semantics one: all backends run the same kind of rigid-body simulation, so the contract is about moving body state across the Python<->native boundary efficiently, not about defining solver behaviour.

The load-bearing part of the contract is the bulk-array transfer: per-frame body state is exchanged as a single numpy buffer (one transfer per world per frame), in a fixed body->row order, by filling a caller-preallocated array in place. Per-body crossings are reserved for setup/teardown and are never used on the hot path. See :meth:PhysicsWorld.register_bodies,

meth:

PhysicsWorld.read_transforms, and :meth:PhysicsWorld.read_velocities.

Stage 1 status

This is additive, non-breaking scaffolding. It defines the interface only (full signatures, docstrings, @abstractmethod); there is no implementation here, and nothing in this module is wired into SceneTree or the old simvx.core.physics system. The seam is testable standalone by constructing a concrete backend and calling :meth:step manually.

PhysicsWorld: the backend transport seam (Stage 1 additive scaffolding).

Module Contents

Classes

BodyMode

Motion mode of a body, mirroring every serious solver’s seam.

RaycastHit

Result of a successful raycast against the world.

Contact

Result of a kinematic / character shape-sweep stopping against a body.

ContactPhase

Edge phase of a body-pair contact, diffed by the broadphase each step.

ContactEvent

A node-agnostic body-pair collision event emitted by the seam.

OverlapEvent

A node-agnostic sensor-overlap event emitted by the seam.

CharacterMoveResult

Returned struct of a character collide-and-slide move.

PhysicsWorld

Abstract backend seam: one isolated simulation world.

Data

API

simvx.core.physics.world.BodyHandle

None

simvx.core.physics.world.ShapeHandle

None

simvx.core.physics.world.CharacterHandle

None

simvx.core.physics.world.JointHandle

None

class simvx.core.physics.world.BodyMode[source]

Bases: enum.Enum

Motion mode of a body, mirroring every serious solver’s seam.

  • STATIC: immovable collider. Never integrated; infinite mass.

  • DYNAMIC: force-simulated; responds to gravity, impulses, contacts.

  • KINEMATIC: code-moved; pushes dynamic bodies, immune to forces.

STATIC

‘static’

DYNAMIC

‘dynamic’

KINEMATIC

‘kinematic’

__new__(value)
__repr__()
__str__()
__dir__()
__format__(format_spec)
__hash__()
__reduce_ex__(proto)
__deepcopy__(memo)
__copy__()
name()
value()
class simvx.core.physics.world.RaycastHit[source]

Result of a successful raycast against the world.

Attributes: body: Handle of the body the ray hit. point: World-space contact point (Vec3). normal: World-space surface normal at the hit (Vec3, unit length). distance: Distance from the ray origin to point along the ray.

body: simvx.core.physics.world.BodyHandle

None

point: simvx.core.math.Vec3

None

normal: simvx.core.math.Vec3

None

distance: float

None

class simvx.core.physics.world.Contact[source]

Result of a kinematic / character shape-sweep stopping against a body.

Mirrors :class:RaycastHit exactly (a single “other” body, like a query result): the swept body is implicit (the caller). Maps cleanly onto Jolt (body <- hit BodyID, normal <- contact normal, distance <- fraction * |motion|).

Attributes: body: Handle of the OTHER body that was hit. point: World-space contact point (Vec3). normal: World-space surface normal (Vec3, unit), pointing AWAY from the other body toward the moving body, i.e. the direction that separates the mover. distance: Distance the mover actually travelled along motion before contact (TOI distance, 0..|motion|).

body: simvx.core.physics.world.BodyHandle

None

point: simvx.core.math.Vec3

None

normal: simvx.core.math.Vec3

None

distance: float

None

class simvx.core.physics.world.ContactPhase[source]

Bases: enum.Enum

Edge phase of a body-pair contact, diffed by the broadphase each step.

Only the two transitions are reported (no per-frame “stay”): a pair fires ENTER the step it begins overlapping and EXIT the step it stops.

ENTER

‘enter’

EXIT

‘exit’

__new__(value)
__repr__()
__str__()
__dir__()
__format__(format_spec)
__hash__()
__reduce_ex__(proto)
__deepcopy__(memo)
__copy__()
name()
value()
class simvx.core.physics.world.ContactEvent[source]

A node-agnostic body-pair collision event emitted by the seam.

Keyed by body HANDLES only: the seam never names a node. The tree maps a / b back to nodes and fires the node-level collided / separated Signals. Both static-dynamic and dynamic-dynamic pairs are reported, and both bodies are notified (the tree reorients per side).

Orientation convention is fixed here as a -> b (mirroring the internal narrow-phase _Contact.normal); the tree negates normal / rel_velocity for the b side so each body sees the separating direction pointing toward itself.

Attributes: a: Handle of the first body of the pair (canonical order). b: Handle of the second body of the pair. phase: :class:ContactPhase (ENTER / EXIT). point: World contact point (Vec3). Meaningful on ENTER; degenerate (Vec3(0)) on EXIT (no live manifold). normal: Unit contact normal oriented a -> b (Vec3). Degenerate (Vec3(0)) on EXIT. impulse: Normal impulse magnitude applied to the pair this step. 0 on EXIT and 0 on an ENTER where the solver applied no impulse (e.g. separating velocity). rel_velocity: Pre-solve velocity of b w.r.t. a at the contact (Vec3). Degenerate (Vec3(0)) on EXIT.

a: simvx.core.physics.world.BodyHandle

None

b: simvx.core.physics.world.BodyHandle

None

phase: simvx.core.physics.world.ContactPhase

None

point: simvx.core.math.Vec3

None

normal: simvx.core.math.Vec3

None

impulse: float

None

rel_velocity: simvx.core.math.Vec3

None

class simvx.core.physics.world.OverlapEvent[source]

A node-agnostic sensor-overlap event emitted by the seam.

A SECOND, independent edge-diffed stream, parallel to :class:ContactEvent but never mixed with it: a sensor pair produces NO collision response and NO manifold, so there is no point / normal / impulse / rel_velocity to carry.

Keyed by body HANDLES only (node-agnostic, like :class:ContactEvent), but DIRECTED sensor -> other rather than a canonical unordered pair: the detection is one-directional (the observing sensor decides via its mask), so a sensor-vs-sensor overlap can fire on one side without the other. The tree maps both handles to nodes and routes body_entered vs area_entered by the OTHER node’s type.

Attributes: sensor: Handle of the detecting sensor body (the observer). other: Handle of the detected body (a normal body OR another sensor). phase: :class:ContactPhase (ENTER / EXIT); reused, no second phase enum.

sensor: simvx.core.physics.world.BodyHandle

None

other: simvx.core.physics.world.BodyHandle

None

phase: simvx.core.physics.world.ContactPhase

None

class simvx.core.physics.world.CharacterMoveResult[source]

Returned struct of a character collide-and-slide move.

Returning a struct (rather than mutating caller state) keeps the seam node-agnostic and gives Jolt a clean place to surface CharacterVirtual::GetGroundState() / GetGroundNormal(). Floor/wall/ceiling are computed seam-side from contact normals vs up (and the character’s slope_limit) so every backend exposes identical semantics.

Attributes: velocity: Post-slide velocity (deflected along contact normals); the caller writes this back as its new velocity. on_floor: True if a contact this move was classified as floor. on_wall: True if a contact this move was classified as wall. on_ceiling: True if a contact this move was classified as ceiling. floor_normal: Normal of the floor contact this move (unit), or +up if there was no floor contact.

velocity: simvx.core.math.Vec3

None

on_floor: bool

None

on_wall: bool

None

on_ceiling: bool

None

floor_normal: simvx.core.math.Vec3

None

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

Bases: abc.ABC

Abstract backend seam: one isolated simulation world.

A PhysicsWorld owns a set of bodies, advances them as a unit at a fixed timestep via :meth:step, and exchanges per-frame state in bulk. Concrete backends (BuiltinPhysics, later JoltPhysics) implement every method.

Bulk-array contract (the keystone)

  1. Call :meth:register_bodies once (or whenever membership changes) to fix the body->row order used by the bulk readers.

  2. Each frame, after :meth:step, call :meth:read_transforms and/or

    meth:

    read_velocities, passing a caller-preallocated, C-contiguous float32 numpy array of the documented shape. The backend fills it in place; it must not allocate or return a new array on the hot path.

The array shapes/dtypes/contiguity are part of the contract and MUST be asserted by subclasses (see _check_transforms_out / _check_velocities_out).

Initialization

Initialise the world.

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

property gravity: simvx.core.math.Vec3[source]

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

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

Return the set of Tier-3 :class:Capability features this backend honours.

The strict Tier-1 rigid-body surface is the parity contract every backend implements identically; this method is the ONE place backends advertise the few features that genuinely cannot be faked (cross-platform determinism, vehicles, soft bodies). A Tier-3 node checks Capability.X in world.capabilities() and degrades / refuses if absent.

The default (this base implementation) is the empty set: a backend advertises nothing it cannot honour. Backends override to list only the capabilities they actually support. Not abstract: the empty default is the honest answer for any plain rigid-body backend.

abstractmethod create_sphere(radius: float) simvx.core.physics.world.ShapeHandle[source]

Create a sphere collision shape and return an opaque handle.

Args: radius: Sphere radius, world units (> 0).

Returns: An opaque shape handle for use with :meth:create_body.

abstractmethod create_box(half_extents: simvx.core.math.Vec3) simvx.core.physics.world.ShapeHandle[source]

Create an axis-aligned box collision shape (centred at the origin).

Args: half_extents: Half-sizes along x/y/z (Vec3, all > 0).

Returns: An opaque shape handle for use with :meth:create_body.

abstractmethod create_capsule(radius: float, height: float) simvx.core.physics.world.ShapeHandle[source]

Create a Y-axis capsule collision shape and return an opaque handle.

Args: radius: Capsule radius, world units (> 0). height: Total extent along Y including the two hemispherical caps (> 0). The central segment half-length is max(0, height / 2 - radius); when height <= 2 * radius the segment collapses to a point and the capsule behaves as a sphere of radius.

Returns: An opaque shape handle for use with :meth:create_body.

abstractmethod create_cylinder(radius: float, height: float) simvx.core.physics.world.ShapeHandle[source]

Create a Y-axis cylinder collision shape and return an opaque handle.

Args: radius: Cylinder radius, world units (> 0). height: Total extent along Y with flat caps at +-height / 2 (> 0).

Returns: An opaque shape handle for use with :meth:create_body.

abstractmethod create_convex_hull(points: numpy.ndarray) simvx.core.physics.world.ShapeHandle[source]

Create a convex-hull collision shape from a point cloud.

Args: points: (N, 3) float32 array of >= 4 finite points. The backend computes its own internal hull representation from the cloud. Orientation is supported via the body transform like other shapes, EXCEPT the basic BuiltinPhysics backend, which IGNORES hull rotation (the cloud is treated in world axes offset by the body position); the Jolt backend rotates properly.

Returns: An opaque shape handle for use with :meth:create_body. The basic backend’s penetration depth/normal for a hull is an EPA-lite approximation (GJK overlap is exact); see builtin/world.py.

abstractmethod create_mesh(vertices: numpy.ndarray, indices: numpy.ndarray) simvx.core.physics.world.ShapeHandle[source]

Create a STATIC triangle-mesh collision shape (level geometry).

Args: vertices: (N, 3) float32 vertex positions. indices: (3 * T,) int64 flat triangle-list indices (three per triangle), each in [0, N).

Returns: An opaque shape handle for use with :meth:create_body. A mesh shape is a STATIC-ONLY collider: placing it on a non-STATIC body is an error (rejected at :meth:create_body and :meth:set_body_mode). It carries no inertia / mass and cannot be used as a moving query shape (:meth:shapecast / :meth:overlap reject a mesh probe).

abstractmethod create_body(shape: simvx.core.physics.world.ShapeHandle, body_type: simvx.core.physics.world.BodyMode, transform: Any, *, mass: float = 1.0, collision_layer: int = 1, collision_mask: int = 4294967295, is_sensor: bool = False, friction: float = 0.5, restitution: float = 0.0, friction_combine: simvx.core.physics.material.CombineMode = CombineMode.AVERAGE, restitution_combine: simvx.core.physics.material.CombineMode = CombineMode.AVERAGE, continuous: bool = False) simvx.core.physics.world.BodyHandle[source]

Create a body in the world and return its handle.

Args: shape: An opaque shape handle from :meth:create_sphere, :meth:create_box, :meth:create_capsule, :meth:create_cylinder, :meth:create_convex_hull, or :meth:create_mesh. A mesh shape on a non-STATIC body is an error (mesh colliders are STATIC-only), rejected here and in :meth:set_body_mode. body_type: One of :class:BodyMode. transform: Initial world transform (a Transform3D or anything a backend accepts as an initial pose; position + orientation). mass: Body mass in kg, used only for DYNAMIC bodies. Ignored for STATIC / KINEMATIC (treated as infinite). collision_layer: 32-bit layer membership of this body (which layers it lives on). Stored verbatim; defaults to layer 1. collision_mask: 32-bit mask of layers this body scans for collisions. Defaults to all (0xFFFFFFFF) so the bare API collides every pair (non-breaking). A pair (a, b) collides iff (a.mask & b.layer) or (b.mask & a.layer) (bidirectional OR). is_sensor: When True, this body is a SENSOR (trigger). It is created / destroyed / teleported exactly like a normal body and participates in the broadphase, but is EXCLUDED from collision resolution (it skips the solver, applies no impulse, and never appears in the contact-event stream) and instead generates a SEPARATE overlap-event stream (see :meth:drain_overlap_events) using the ONE-DIRECTIONAL filter sensor.mask & other.layer (the observer decides; the other body’s mask is irrelevant), never the AND body-body rule. A sensor is an ordinary body with a flag, not a separate kind of handle. friction: Coulomb friction coefficient mu (>= 0). The contact solver clamps the tangential impulse by mu times the normal impulse. Defaults to 0.5 (matching :class:PhysicsMaterial). Node-agnostic primitive: the node unpacks its PhysicsMaterial resource into this and the next three params (the seam never sees the resource or the node). restitution: Bounciness in [0, 1] (0 = inelastic, the default, so the bare seam API does not bounce). Combined per-contact and gated by a small velocity rest-threshold (see the builtin solver). friction_combine: How this body’s friction combines with the other body’s at a contact (:class:CombineMode); defaults to AVERAGE. restitution_combine: How this body’s restitution combines with the other body’s, INDEPENDENT of friction_combine (defaults to AVERAGE). continuous: When True, this body uses continuous collision detection: each step its centre displacement is swept against STATIC geometry and clamped to the time-of-impact so a fast small body cannot tunnel through thin static colliders. Defaults False (discrete). Basic-tier honesty: a CENTRE ray / shapecast sweep vs STATIC bodies only, no rotational sweep and no dynamic-vs-dynamic CCD; the Jolt backend honours the flag faithfully via EMotionQuality::LinearCast.

Returns: An opaque body handle, stable until :meth:destroy_body.

abstractmethod destroy_body(handle: simvx.core.physics.world.BodyHandle) None[source]

Remove a body from the world.

After destruction the handle is invalid. Callers that use the bulk readers must re-call :meth:register_bodies to re-establish row order.

Args: handle: A handle previously returned by :meth:create_body.

abstractmethod set_body_transform(handle: simvx.core.physics.world.BodyHandle, transform: Any) None[source]

Teleport a body to a new world transform.

Args: handle: Body handle. transform: New world transform (position + orientation).

abstractmethod set_body_velocity(handle: simvx.core.physics.world.BodyHandle, linear: simvx.core.math.Vec3, angular: simvx.core.math.Vec3 | None = None) None[source]

Set a body’s linear and angular velocity directly.

Args: handle: Body handle. linear: Linear velocity (Vec3), world units/s. angular: Angular velocity (Vec3), radians/s about each axis. None (default) means zero angular velocity.

abstractmethod set_body_mode(handle: simvx.core.physics.world.BodyHandle, mode: simvx.core.physics.world.BodyMode) None[source]

Change a live body’s motion mode in place (no destroy/recreate).

Flips the body between STATIC / KINEMATIC / DYNAMIC, updating its effective (inverse) mass: STATIC and KINEMATIC are infinite-mass (inv_mass 0), DYNAMIC uses the body’s stored mass. Maps onto Jolt’s Body::SetMotionType; the builtin backend flips body_type + inverse_mass.

abstractmethod body_velocity(handle: simvx.core.physics.world.BodyHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Vec3][source]

Read a body’s current (linear, angular) velocity, per-body.

Cold per-body read parallel to :meth:body_transform. The bulk

Meth:

read_velocities stays the hot scatter path; this is the accessor used by PhysicsBody3D.velocity / .spin for a single synchronous read-back (e.g. self.velocity += dv).

Args: handle: Body handle.

Returns: (linear, angular) velocity (Vec3, Vec3); angular in radians/s. Returns zero velocities for an infinite-mass body that was never moved.

abstractmethod sleeping(handle: simvx.core.physics.world.BodyHandle) bool[source]

True if the body is asleep (skipped by integrate + solve until woken).

STATIC / KINEMATIC bodies are never ‘asleep’ (they were never awake): returns False for them. A sleeping body stays a full collider and still reads back its (frozen) transform / velocity through the bulk readers.

abstractmethod 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 an instantaneous velocity change to a body NOW.

Unlike :meth:apply_force this takes effect immediately (it mutates velocity, not an accumulator) and is NOT cleared by :meth:step. Inert on non-DYNAMIC bodies (inverse mass 0).

Args: handle: Body handle. impulse: Linear impulse (Vec3), N*s. Adds impulse * inv_mass to the linear velocity. at: Optional world-space application point. When given, the offset r = at - position contributes an angular impulse cross(r, impulse) (basic tier scales it by inv_mass as a stand-in for the inverse inertia tensor, which the basic backend does not model). None applies the impulse purely through the centre of mass (no torque). angular: Optional explicit angular impulse (Vec3), for spin_up. Adds angular * inv_mass (basic-tier inverse inertia stand-in) to the angular velocity, independent of at.

abstractmethod apply_force(handle: simvx.core.physics.world.BodyHandle, force: simvx.core.math.Vec3, *, at: simvx.core.math.Vec3 | None = None) None[source]

Accumulate a continuous force, applied during the NEXT :meth:step.

The force is integrated as acceleration (force * inv_mass) before position integration, then auto-cleared at the end of the step. To sustain a force the caller must re-add it every fixed step (per design S7); a single call affects exactly one step. Inert on non-DYNAMIC.

Args: handle: Body handle. force: Linear force (Vec3), N. at: Optional world-space application point. When given, the offset r = at - position adds a torque cross(r, force) to the torque accumulator. None applies the force through the COM.

abstractmethod apply_torque(handle: simvx.core.physics.world.BodyHandle, torque: simvx.core.math.Vec3) None[source]

Accumulate a continuous torque, applied during the NEXT :meth:step.

Auto-cleared after the step like :meth:apply_force: re-add each fixed step to sustain it. Inert on non-DYNAMIC. Basic tier applies it as torque * inv_mass (inverse inertia stand-in).

Args: handle: Body handle. torque: Torque (Vec3), N*m.

abstractmethod create_fixed_joint(a: simvx.core.physics.world.BodyHandle, b: simvx.core.physics.world.BodyHandle) simvx.core.physics.world.JointHandle[source]

Weld two bodies: lock their full relative transform.

Captures the CURRENT relative pose of b in a’s frame at create time (relative position AND relative orientation) and holds it: the two bodies thereafter move as one rigid assembly.

Basic-tier honesty: the builtin backend has NO inertia tensor, so the angular lock uses inverse_mass as the inverse-inertia scalar (per T1a); a long thin body or an off-centre weld will rotate too easily versus a real solver. Convergence is a few sequential-impulse iterations, so a long weld chain sags slightly. Precise articulated mechanisms are a Jolt concern.

Args: a: First body handle (the reference frame). b: Second body handle (welded into a’s frame).

Returns: An opaque :data:JointHandle, valid until :meth:remove_joint (or until either body is destroyed, which silently drops the joint).

abstractmethod 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]

Pin two bodies at a single world-space point (ball / point-to-point).

Constrains the two bodies so the world point anchor stays coincident on both (they cannot separate there) while leaving all three rotational DOF free. The anchor is stored as a per-body offset (r_a = anchor - pos_a, r_b = anchor - pos_b) captured at create.

Basic-tier honesty: the stored anchor offset does NOT rotate with the body (re-derived each step as pos + r in WORLD axes, same limitation as the hull-rotation-ignored narrowphase), so a pin on a spinning body drifts. Angular cross-coupling uses inverse_mass as the inverse-inertia scalar. A few iterations of convergence.

Args: a: First body handle. b: Second body handle. anchor: World-space pivot point shared by both bodies (Vec3).

Returns: An opaque :data:JointHandle.

abstractmethod 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]

Hinge two bodies: pin at anchor + one free rotational DOF about axis.

A point constraint at anchor (like :meth:create_pin_joint) PLUS an angular constraint that locks the two off-axis rotational DOF, leaving free rotation only about the world-space axis (normalised and captured at create). Motors and angular limits are explicitly OUT of scope this stage (a deliberate follow-on).

Basic-tier honesty: same anchor-does-not-rotate and inverse_mass inverse-inertia-scalar caveats as :meth:create_pin_joint; a few iterations of convergence.

Args: a: First body handle. b: Second body handle. anchor: World-space hinge pivot point (Vec3). axis: World-space hinge axis (Vec3, normalised at create).

Returns: An opaque :data:JointHandle.

abstractmethod 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]

Soft distance-spring between the two body centres (compliant, not rigid).

A soft constraint that pulls the two body centres of mass toward rest_length apart with spring constant stiffness (N/m) and damping damping (N*s/m). Unlike the rigid joints it is intentionally compliant: it applies a soft velocity impulse with a k*x bias and a c*v damping term, and is NEVER position-corrected.

Basic-tier honesty: the builtin backend uses the two COMs, NOT per-body anchors (Pin / Hinge use anchors, Spring uses centres for simplicity). The explicit soft-impulse form can oscillate or overshoot when stiffness is large relative to the fixed dt; a stiff spring needs a smaller dt or the Jolt backend. Nothing is silently clamped.

Args: a: First body handle. b: Second body handle. rest_length: Target centre-to-centre separation (world units, >= 0). stiffness: Spring constant k (N/m). damping: Damping coefficient c (N*s/m).

Returns: An opaque :data:JointHandle.

abstractmethod remove_joint(handle: simvx.core.physics.world.JointHandle) None[source]

Remove a constraint; the handle is invalid afterwards.

A no-op if handle is unknown (already removed, or silently dropped because one of its bodies was destroyed): this is the SAME silent-drop contract :meth:destroy_body already uses for touching / overlap pairs, not an error-swallowing shim. A joint whose body was freed is the expected case, so removing it twice (once by the body-purge, once by the joint node’s own teardown) must be safe in either teardown order.

Args: handle: A handle previously returned by a create_*_joint call.

abstract property body_count: int[source]

Number of bodies currently in the world.

Read-only. Used by SceneTree.physics_tick to skip stepping empty worlds for zero overhead, mirroring PhysicsServer.body_count.

abstractmethod clear() None[source]

Remove every body, character, and joint, emptying the world.

The seam equivalent of the old global PhysicsServer.reset(): a level teardown / restart-the-scene primitive that returns the world to an empty state (body_count == 0) WITHOUT discarding the world object, its configured :attr:gravity, or its backend. Per-step edge-diff buffers (contacts / overlaps) and any warm-start cache are reset so the next step starts from a clean broadphase. Cached shape handles stay valid (shapes are reusable resources), and handle counters keep advancing so a freed handle is never re-issued to a new body. After :meth:clear, callers using the bulk readers must re-:meth:register_bodies.

abstractmethod step(dt: float) None[source]

Advance the whole world once by a fixed timestep.

This integrates every body, resolves collisions, and updates internal state for the entire world as a unit. It is designed to be driven by a fixed-step accumulator (Stage 2 wires it to SceneTree.physics_tick); in Stage 1 it is called manually by tests.

Args: dt: Fixed timestep in seconds. Callers must pass a constant value.

abstractmethod drain_contact_events() list[simvx.core.physics.world.ContactEvent][source]

Return and CLEAR this step’s buffered enter/exit contact events.

Edge-only and broadphase-driven: the backend diffs the touching-pair set each :meth:step and buffers a :class:ContactEvent for every pair that began (ENTER) or stopped (EXIT) overlapping. Returns [] when nothing changed. Node-agnostic: events are keyed by body handles only; the tree maps handles to nodes and fires the Signals. Both static-dynamic AND dynamic-dynamic pairs are reported, filtered by the same layer/mask rule the simulation uses.

abstractmethod drain_overlap_events() list[simvx.core.physics.world.OverlapEvent][source]

Return and CLEAR this step’s buffered sensor-overlap enter/exit events.

Edge-only, broadphase-driven, DIRECTED (keyed sensor -> other), filtered by the one-directional sensor rule (sensor.mask & other.layer), node-agnostic. Distinct from

Meth:

drain_contact_events: a sensor pair produces NO collision response and NO manifold, so the event carries only the two handles plus the :class:ContactPhase. Returns [] when nothing changed.

abstractmethod register_bodies(handles: list[simvx.core.physics.world.BodyHandle]) None[source]

Fix the body->row order used by the bulk readers.

Establishes the mapping from each body handle to a row index. After this call, :meth:read_transforms / :meth:read_velocities fill row i with the state of handles[i]. Call again whenever membership or desired ordering changes.

Args: handles: Ordered list of body handles. len(handles) is the row count N expected by the bulk readers.

abstractmethod read_transforms(out: numpy.ndarray) None[source]

Fill out with current body transforms, in place.

Bulk hot-path read. The backend writes into the caller-owned buffer and allocates nothing.

Args: out: Pre-allocated array of shape (N, 7), dtype float32, C-contiguous, where N matches the most recent :meth:register_bodies. Each row is [px, py, pz, qx, qy, qz, qw]: position xyz followed by an orientation quaternion in xyzw order (scalar-last). Row i corresponds to handles[i].

abstractmethod read_velocities(out: numpy.ndarray) None[source]

Fill out with current body velocities, in place.

Bulk hot-path read. The backend writes into the caller-owned buffer and allocates nothing.

Args: out: Pre-allocated array of shape (N, 6), dtype float32, C-contiguous, where N matches the most recent :meth:register_bodies. Each row is [lx, ly, lz, ax, ay, az]: linear velocity xyz followed by angular velocity xyz (radians/s). Row i corresponds to handles[i].

abstractmethod raycast(origin: simvx.core.math.Vec3, direction: simvx.core.math.Vec3, max_dist: float, *, mask: int = 4294967295) simvx.core.physics.world.RaycastHit | None[source]

Cast a ray and return the nearest hit, or None.

Args: origin: Ray origin in world space (Vec3). direction: Ray direction (Vec3); need not be normalised. max_dist: Maximum distance along direction to test. mask: Query layer mask. Only bodies whose collision_layer & mask is non-zero are considered. Defaults to all layers. This is the single query-mask convention (one query mask vs each body’s layer), distinct from the bidirectional body-pair rule used by the simulation.

Returns: A :class:RaycastHit for the closest body intersected within max_dist whose layer matches mask, or None if the ray hits nothing.

abstractmethod 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]

Cast a ray and return EVERY hit within max_dist, sorted by distance.

Like :meth:raycast but collects all intersected bodies (whose collision_layer & mask is set) instead of only the nearest, returned ascending by :attr:RaycastHit.distance. Empty list on no hit. Backs PhysicsQuery.raycast_all and the exclude= filter path of raycast (which must skip excluded nearer hits).

Args: origin: Ray origin in world space (Vec3). direction: Ray direction (Vec3); need not be normalised. max_dist: Maximum distance along direction to test. mask: Query layer mask (single query-mask vs body-layer convention).

Returns: All :class:RaycastHit\ s within max_dist, sorted ascending by distance (empty if the ray hits nothing).

abstractmethod 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.Contact | None[source]

Sweep a shape along a ray and return the earliest-TOI contact, or None.

Sweeps shape from origin along direction (need not be normalised) up to max_dist against world bodies, returning the earliest time-of-impact contact among bodies whose collision_layer & mask is set, else None. Reuses

Class:

Contact: the swept shape is the implicit caller, body is the hit body, normal is the separating normal pointing back toward the cast origin, and distance is the TOI distance along direction.

Basic-tier honesty: this is a substepped sweep, not a true continuous cast, so fast casts vs very thin colliders can tunnel and box orientation is ignored (AABB), matching :meth:move_and_collide.

Args: shape: An opaque shape handle from :meth:create_sphere, :meth:create_box, :meth:create_capsule, or :meth:create_cylinder. origin: Cast origin in world space (Vec3). direction: Cast direction (Vec3); need not be normalised. max_dist: Maximum sweep distance along direction. mask: Query layer mask (single query-mask vs body-layer convention).

Returns: The earliest-TOI :class:Contact, or None if nothing was hit.

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

Return all bodies a static shape overlaps at transform.

Places shape at transform (same flexible forms as

Meth:

create_body) and returns the handles of every body it overlaps whose collision_layer & mask is set, sorted by handle for determinism. Basic-tier honesty: AABB-ish narrowphase, box orientation ignored, like :meth:move_and_collide.

Args: shape: An opaque shape handle from :meth:create_sphere, :meth:create_box, :meth:create_capsule, or :meth:create_cylinder. transform: World pose to place the shape at (same flexible forms as :meth:create_body). mask: Query layer mask (single query-mask vs body-layer convention).

Returns: Sorted list of overlapping body handles (empty if none).

abstractmethod move_and_collide(handle: simvx.core.physics.world.BodyHandle, motion: simvx.core.math.Vec3) simvx.core.physics.world.Contact | None[source]

Move a kinematic body by motion, stopping at the first contact.

Shape-casts the body’s shape from its current pose along world-space motion (already velocity * dt: the seam takes a displacement, not a velocity, so dt lives in the node) against every OTHER body and finds the earliest time-of-impact. Sets the body’s stored transform to the position it actually reached (origin + motion * toi minus a tiny safe margin). Does NOT slide and does NOT integrate gravity: one sweep, stop at first contact.

Args: handle: A body created with :attr:BodyMode.KINEMATIC. motion: World-space displacement (Vec3).

Returns: A :class:Contact (other body, world point, separating normal, distance travelled) if the sweep stopped early, else None after moving the full motion.

abstractmethod body_transform(handle: simvx.core.physics.world.BodyHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Quat][source]

Read a body’s current pose as (position, orientation).

Parallel to :meth:character_transform; returns plain Vec3 / Quat so callers (e.g. KinematicBody3D) get a clean synchronous read-back after a user-driven :meth:move_and_collide without the bulk path.

Args: handle: Body handle.

Returns: (position, orientation).

abstractmethod create_character(shape: simvx.core.physics.world.ShapeHandle, transform: Any, *, up: simvx.core.math.Vec3 = _DEFAULT_UP, slope_limit: float = math.radians(45.0), step_height: float = 0.0, skin_width: float = 0.001, collision_layer: int = 1, collision_mask: int = 4294967295) simvx.core.physics.world.CharacterHandle[source]

Create a character controller bound to this world.

The character collides against world bodies but is NOT added to the dynamic-body set: it is never force-integrated and never appears in

Meth:

register_bodies / :meth:read_transforms. This mirrors a Jolt CharacterVirtual (distinct from a rigid Body).

Args: shape: An opaque shape handle from :meth:create_sphere, :meth:create_box, :meth:create_capsule, or :meth:create_cylinder. transform: Initial world pose (position + orientation), same flexible forms accepted by :meth:create_body. up: World up vector (Vec3), engine Y-up convention. slope_limit: Maximum walkable slope, in RADIANS (engine convention). step_height: Maximum step-up height in world units (0 disables). skin_width: Collision safe margin in world units. collision_layer: 32-bit layer membership of this character. Defaults to layer 1. collision_mask: 32-bit mask of layers the character collides with. Defaults to all (0xFFFFFFFF). Character sweeps use the same bidirectional OR rule as bodies (char.mask & body.layer or body.mask & char.layer).

Returns: An opaque :data:CharacterHandle, distinct from any body handle.

abstractmethod destroy_character(handle: simvx.core.physics.world.CharacterHandle) None[source]

Remove a character controller from the world.

Args: handle: A handle previously returned by :meth:create_character.

abstractmethod set_character_transform(handle: simvx.core.physics.world.CharacterHandle, transform: Any) None[source]

Teleport a character to a new pose (no collision).

For sync-down on re-parent / explicit position writes.

Args: handle: Character handle. transform: New world pose (position + orientation).

abstractmethod character_transform(handle: simvx.core.physics.world.CharacterHandle) tuple[simvx.core.math.Vec3, simvx.core.math.Quat][source]

Read a character’s current pose as (position, orientation).

For synchronous node sync after a move.

Args: handle: Character handle.

Returns: (position, orientation).

abstractmethod character_move_and_slide(handle: simvx.core.physics.world.CharacterHandle, velocity: simvx.core.math.Vec3, dt: float, *, up: simvx.core.math.Vec3, max_slides: int = 4) simvx.core.physics.world.CharacterMoveResult[source]

Collide-and-slide a character by velocity * dt against the world.

Deflects velocity along contact normals over up to max_slides iterations, classifies floor/wall/ceiling from contact normals vs up (and the character’s slope_limit), and updates the character’s stored pose. up is passed per-call (the node owns the up vector); the fixed collider config (slope_limit / step_height / skin_width) is set at create time, like Jolt’s CharacterVirtualSettings.

Floor/wall/ceiling state is returned per-move in

Class:

CharacterMoveResult (not a separate query method), so it is valid only right after this call, matching Jolt’s GetGroundState().

Args: handle: Character handle. velocity: Desired world-space velocity (Vec3), units/s. dt: Timestep in seconds. up: World up vector (Vec3). max_slides: Maximum collide-and-slide iterations.

Returns: A :class:CharacterMoveResult.

__slots__

()

simvx.core.physics.world.__all__

[‘BodyMode’, ‘Capability’, ‘CombineMode’, ‘RaycastHit’, ‘Contact’, ‘ContactPhase’, ‘ContactEvent’, ‘…