simvx.core.physics.pymunk_backend¶
Role¶
The FIRST real native :class:~simvx.core.physics.world2d.Physics2DWorld
implementation, proving the P1 backend seam end-to-end (until now only stub
backends exercised it). It is an OPTIONAL accelerator: installing pymunk (the
pymunk extra) makes :class:~simvx.core.physics.root.PhysicsRoot2D auto-select
it, exactly mirroring the engine’s miniaudio “installed -> used” model. When
pymunk is absent, auto-discovery silently falls back to
- class:
~simvx.core.physics.builtin.world2d.BuiltinPhysics2D(no crash, no warning spam).
The contract is parity, not bit-identity: “switching backends never changes how a
game plays”. A game using CharacterBody2D must behave the same (within
tolerance) on pymunk as on Builtin, so the SAME
- class:
~simvx.core.physics.world2d.CharacterMoveResult2Dsemantics hold even though Chipmunk2D’s solver differs from the pure-Python tier.
What maps cleanly to Chipmunk2D¶
bodies / motion types:
BodyModeSTATIC/KINEMATIC/DYNAMIC ->pymunk.BodySTATIC/KINEMATIC/DYNAMIC; mass + per-shape moment.shapes: Circle/Box(Poly)/Capsule(rounded poly)/Segment/ConvexPolygon(Poly)/ ConcavePolygon(static segment soup) ->
pymunk.Circle/pymunk.Poly/pymunk.Segment.step:
space.step(dt)at the seam’s fixed dt.forces:
apply_force_at_world_point/apply_impulse_at_world_point/ per-step torque accumulation.joints: fixed/pin/hinge/spring/groove ->
PinJoint/PivotJoint/DampedSpring/GrooveJoint(a weld is a PivotJoint + a stiffDampedRotarySpringto lock the relative angle).queries: raycast ->
segment_query; shapecast/overlap ->shape_query.events: a per-pair
on_collisionhandler edge-diffs body / sensor pairs into the seam’s two event streams.
What needs care (documented honesty caveats)¶
CombineMode: Chipmunk multiplies friction (
mu_a * mu_b) and takes the max of elasticity. There is no per-contact combine-mode switch, so the seam combines the coefficients itself (via :func:~simvx.core.physics.material._combine) at body-creation time using a single representative-material pair where possible; a per-PAIR combine mode that differs across every neighbour cannot be honoured natively and degrades to Chipmunk’s built-in rule (see_combine_into_shape).character controller: pymunk has NO native character controller, so it is implemented exactly like :class:
BuiltinPhysics2D– a kinematic collider swept withshape_query+ deflect-and-slide + floor/slope classification – so theCharacterMoveResult2Dcontract is identical.KINEMATIC bulk WRITE:
pymunk.batchSET is experimental, so per-body writes are used on the (cold) set paths; the bulk READ usespymunk.batchzero-copy when present.
PymunkPhysics2D: the optional native (Chipmunk2D / pymunk) 2D backend (Stage P4).
Module Contents¶
Classes¶
Native Chipmunk2D (pymunk) 2D backend implementing the full seam. |
Functions¶
Self-register the pymunk backend with the selection seam. |
Data¶
API¶
- class simvx.core.physics.pymunk_backend.PymunkPhysics2D(*, gravity: simvx.core.math.Vec2 | None = None)¶
Bases:
simvx.core.physics.world2d.Physics2DWorldNative Chipmunk2D (pymunk) 2D backend implementing the full seam.
See :class:
~simvx.core.physics.world2d.Physics2DWorldfor the contract. Every@abstractmethodis implemented over a singlepymunk.Space; the character controller is a kinematic collider with deflect-and-slide (pymunk has no native one), so theCharacterMoveResult2Dcontract matches Builtin.Initialization
Initialise the world.
Args: gravity: World gravity acceleration vector (
Vec2), metres/s^2. Y-up:Vec2(0, -9.81)is “down”.- property body_count: int¶
- clear() None¶
Remove every body, character, and joint, emptying the world.
See :meth:
~simvx.core.physics.world2d.Physics2DWorld.clear. Routes through the existingremove_joint/destroy_character/destroy_bodyso each is correctly removed from the livepymunk.Space(joints first, then characters, then bodies). The per-step edge-diff buffers are reset too. Gravity, shapes, and handle counters are intentionally NOT reset.
- create_circle(radius: float) simvx.core.physics.world2d.ShapeHandle¶
- create_box(half_extents: simvx.core.math.Vec2) simvx.core.physics.world2d.ShapeHandle¶
- create_capsule(radius: float, height: float) simvx.core.physics.world2d.ShapeHandle¶
- create_segment(a: simvx.core.math.Vec2, b: simvx.core.math.Vec2, radius: float = 0.0) simvx.core.physics.world2d.ShapeHandle¶
- create_convex_polygon(points: numpy.ndarray) simvx.core.physics.world2d.ShapeHandle¶
- create_concave_polygon(segments: numpy.ndarray) simvx.core.physics.world2d.ShapeHandle¶
- create_body(shape: simvx.core.physics.world2d.ShapeHandle, body_type: simvx.core.physics.world.BodyMode, transform: object, *, 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.world2d.BodyHandle¶
- destroy_body(handle: simvx.core.physics.world2d.BodyHandle) None¶
- set_body_transform(handle: simvx.core.physics.world2d.BodyHandle, transform: object) None¶
- set_body_velocity(handle: simvx.core.physics.world2d.BodyHandle, linear: simvx.core.math.Vec2, angular: float = 0.0) None¶
- set_body_mode(handle: simvx.core.physics.world2d.BodyHandle, mode: simvx.core.physics.world.BodyMode) None¶
- body_velocity(handle: simvx.core.physics.world2d.BodyHandle) tuple[simvx.core.math.Vec2, float]¶
- body_transform(handle: simvx.core.physics.world2d.BodyHandle) tuple[simvx.core.math.Vec2, float]¶
- sleeping(handle: simvx.core.physics.world2d.BodyHandle) bool¶
- apply_impulse(handle: simvx.core.physics.world2d.BodyHandle, impulse: simvx.core.math.Vec2, *, at: simvx.core.math.Vec2 | None = None, angular: float = 0.0) None¶
- apply_force(handle: simvx.core.physics.world2d.BodyHandle, force: simvx.core.math.Vec2, *, at: simvx.core.math.Vec2 | None = None) None¶
- apply_torque(handle: simvx.core.physics.world2d.BodyHandle, torque: float) None¶
- create_fixed_joint(a: simvx.core.physics.world2d.BodyHandle, b: simvx.core.physics.world2d.BodyHandle) simvx.core.physics.world2d.JointHandle¶
- create_pin_joint(a: simvx.core.physics.world2d.BodyHandle, b: simvx.core.physics.world2d.BodyHandle, anchor: simvx.core.math.Vec2) simvx.core.physics.world2d.JointHandle¶
- create_hinge_joint(a: simvx.core.physics.world2d.BodyHandle, b: simvx.core.physics.world2d.BodyHandle, anchor: simvx.core.math.Vec2) simvx.core.physics.world2d.JointHandle¶
- create_spring_joint(a: simvx.core.physics.world2d.BodyHandle, b: simvx.core.physics.world2d.BodyHandle, rest_length: float, stiffness: float, damping: float) simvx.core.physics.world2d.JointHandle¶
- create_groove_joint(a: simvx.core.physics.world2d.BodyHandle, b: simvx.core.physics.world2d.BodyHandle, groove_a: simvx.core.math.Vec2, groove_b: simvx.core.math.Vec2, anchor_b: simvx.core.math.Vec2) simvx.core.physics.world2d.JointHandle¶
- remove_joint(handle: simvx.core.physics.world2d.JointHandle) None¶
- set_one_way(handle: simvx.core.physics.world2d.BodyHandle, enabled: bool, normal: simvx.core.math.Vec2 = _DEFAULT_UP_2D) None¶
- step(dt: float) None¶
- drain_contact_events() list[simvx.core.physics.world2d.ContactEvent2D]¶
- drain_overlap_events() list[simvx.core.physics.world2d.OverlapEvent2D]¶
- register_bodies(handles: list[simvx.core.physics.world2d.BodyHandle]) None¶
- read_transforms(out: numpy.ndarray) None¶
- read_velocities(out: numpy.ndarray) None¶
- raycast(origin: simvx.core.math.Vec2, direction: simvx.core.math.Vec2, max_dist: float, *, mask: int = 4294967295) simvx.core.physics.world2d.RaycastHit2D | None¶
- raycast_all(origin: simvx.core.math.Vec2, direction: simvx.core.math.Vec2, max_dist: float, *, mask: int = 4294967295) list[simvx.core.physics.world2d.RaycastHit2D]¶
- shapecast(shape: simvx.core.physics.world2d.ShapeHandle, origin: simvx.core.math.Vec2, direction: simvx.core.math.Vec2, max_dist: float, *, mask: int = 4294967295) simvx.core.physics.world2d.Contact2D | None¶
- overlap(shape: simvx.core.physics.world2d.ShapeHandle, transform: object, *, mask: int = 4294967295) list[simvx.core.physics.world2d.BodyHandle]¶
- move_and_collide(handle: simvx.core.physics.world2d.BodyHandle, motion: simvx.core.math.Vec2) simvx.core.physics.world2d.Contact2D | None¶
- create_character(shape: simvx.core.physics.world2d.ShapeHandle, transform: object, *, up: simvx.core.math.Vec2 = _DEFAULT_UP_2D, 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.world2d.CharacterHandle¶
- destroy_character(handle: simvx.core.physics.world2d.CharacterHandle) None¶
- set_character_transform(handle: simvx.core.physics.world2d.CharacterHandle, transform: object) None¶
- character_transform(handle: simvx.core.physics.world2d.CharacterHandle) tuple[simvx.core.math.Vec2, float]¶
- character_move_and_slide(handle: simvx.core.physics.world2d.CharacterHandle, velocity: simvx.core.math.Vec2, dt: float, *, up: simvx.core.math.Vec2, max_slides: int = 4) simvx.core.physics.world2d.CharacterMoveResult2D¶
Collide-and-slide identical in contract to :meth:
BuiltinPhysics2D.character_move_and_slide.Sweeps the kinematic character along
velocity * dt; on each blocking contact it advances to the contact, classifies floor / wall / ceiling from the normal vsup(+slope_limit), deflects remaining motion AND velocity out of the surface, and repeats up tomax_slides. A dedicated downward ground probe establisheson_floor/floor_normalfor a character resting on a surface (the horizontal sweep ignores the non-opposing floor). Step-up overstep_heightis a single up/forward/down probe (basic tier; multi-step stairs are a follow-on).
- capabilities() frozenset[simvx.core.physics.capability.Capability]¶
- __slots__¶
()
- simvx.core.physics.pymunk_backend.register() None¶
Self-register the pymunk backend with the selection seam.
Called on import (below) and idempotent (
register_backendreplaces a same- named entry), so importing this module installs the"pymunk"backend as an auto-discoverable native (mirroring miniaudio’s “installed -> used” model).
- simvx.core.physics.pymunk_backend.__all__¶
[‘PymunkPhysics2D’, ‘register’]