simvx.core.physics.backends

Precedence (most specific wins)

  1. An explicit PhysicsRoot(backend=...) override on the node.

  2. The project / App-level physics_backend setting (GeneralConfig, .simvx/config.json).

  3. An auto-discovered installed native backend package.

  4. The pure-Python :class:~simvx.core.physics.builtin.BuiltinPhysics default.

Only the resolution lives here; the consumers (PhysicsRoot and the tree’s default world) call :func:resolve_world_factory / :func:resolve_world_factory_2d to get a ready-to-call :data:~simvx.core.physics.root.WorldFactory for the chosen backend. This keeps backend choice out of the resolution walk in root.py (which only finds which world a node belongs to, never which kind).

Auto-discovery (precedence 3)

A native backend self-registers on import, mirroring the engine’s miniaudio “installed -> used” model. The probe (:func:_try_import_native_backends) unions two routes: (a) the in-core pymunk (Chipmunk2D) 2D module, imported directly, and (b) any out-of-tree package advertising a simvx.physics.backends entry point (e.g. simvx-physics-jolt), discovered via :mod:importlib.metadata and register()-ed. Either route calls :func:register_backend to add itself to

data:

_REGISTRY; once registered a backend is both auto-discoverable (3) and addressable by name (2 / 1). When no native backend is installed the registry holds only "builtin" and every level collapses to Builtin.

Auto-discovery (3) never wins over an explicit Builtin choice: an explicitly requested "builtin" resolves at 1 / 2 before 3 is consulted, so an installed native backend is opt-in for the default, not a silent replacement.

A backend further controls whether auto-discovery may pick it at all via

attr:

BackendEntry.auto_default. pymunk follows the “installed -> used” model (auto_default=True) and so wins auto-discovery for 2D; a gameplay-affecting 3D solver like Jolt registers auto_default=False: it stays name-addressable (1 / 2) but installing it does not change the default 3D backend.

Auto-discovery is PER DIMENSION

The registry is one flat namespace but a backend may implement only one half of it, so :func:_auto_discovered_native takes the dimension it is choosing for and never offers a backend that cannot serve it. Without that, a machine with pymunk installed picks "pymunk" for a 3D world, finds no 3D factory, and falls back to Builtin while warning about a choice nothing asked for; and a 3D-only backend that registered second could never win 3D at all, because the one flat answer is registration order and pymunk is imported first.

Backend selection: the single place a physics backend is chosen.

Module Contents

Classes

BackendEntry

A registered physics backend: its name + 3D / 2D world factories.

Functions

register_backend

Register a physics backend so it is name-addressable and auto-discoverable.

resolve_backend_name

Resolve the backend NAME by precedence: explicit > setting > auto > builtin.

resolve_world_factory

Return the 3D :data:WorldFactory for the resolved backend.

resolve_world_factory_2d

Return the 2D :data:WorldFactory2D for the resolved backend.

Data

API

simvx.core.physics.backends.log

‘getLogger(…)’

simvx.core.physics.backends.BUILTIN

‘builtin’

class simvx.core.physics.backends.BackendEntry[source]

A registered physics backend: its name + 3D / 2D world factories.

A native backend registers one of these via :func:register_backend. A backend may support only one dimension; the missing factory is None and resolution for that dimension falls through to Builtin rather than failing, so a 3D-only native backend never breaks a 2D scene. The fall-through warns when the backend was explicitly requested (node override or project setting) and logs at debug from auto-discovery, which has no business picking a backend for a dimension it cannot serve.

Attributes: name: The token used in physics_backend config / PhysicsRoot(backend=). world_factory: Builds a 3D world for a given gravity, or None. world_factory_2d: Builds a 2D world for a given gravity, or None. A missing factory does not fail resolution for that dimension: an explicitly requested backend degrades to Builtin with a warning, and from auto-discovery it degrades silently, since a backend that cannot serve the dimension is not a choice for it. native: True for an installed native backend (auto-discoverable), False for the always-present Builtin (never auto-selected over a native one; it is the final fallback). auto_default: When True (the miniaudio “installed -> used” model, used by pymunk), the backend may win auto-discovery and become the default for every dimension it serves. When False the backend is still name-addressable (via a node override or the project setting) but never auto-selected: installing it does NOT change the default backend. A gameplay-affecting 3D solver swap (Jolt) sets this False so it is strictly explicit opt-in.

name: str

None

world_factory: simvx.core.physics.root.WorldFactory | None

None

world_factory_2d: simvx.core.physics.root.WorldFactory2D | None

None

native: bool

True

auto_default: bool

True

simvx.core.physics.backends.register_backend(entry: simvx.core.physics.backends.BackendEntry) None[source]

Register a physics backend so it is name-addressable and auto-discoverable.

The single plug point for a future native backend (Jolt, pymunk): the native package calls this on import to add itself, mirroring miniaudio’s “installed -> used” model. Re-registering the same name replaces the entry (idempotent for re-import). "builtin" is reserved as the fallback name.

Args: entry: The :class:BackendEntry to register.

Raises: ValueError: If entry.name is "builtin" (reserved) or empty.

simvx.core.physics.backends.resolve_backend_name(explicit: str | None, setting: str | None, *, dimension: int | None = None) str[source]

Resolve the backend NAME by precedence: explicit > setting > auto > builtin.

Args: explicit: A PhysicsRoot(backend=...) override, or None if unset. setting: The project/App physics_backend config value, or None / empty if unset. dimension: 2 or 3 to restrict auto-discovery to backends that serve that dimension, or None for the dimension-agnostic answer. It affects level 3 only: an explicit name or project setting is honoured whatever it can serve, so a backend named for a dimension it does not implement still reaches the warned fall-through in :func:resolve_world_factory.

Returns: The resolved backend name (a key of :data:_REGISTRY). An explicit / setting value that names an unknown backend logs a warning and falls back to the next level (auto, then Builtin) rather than raising: a missing optional native backend degrades to Builtin, it does not crash the game.

simvx.core.physics.backends.resolve_world_factory(explicit: str | None = None) simvx.core.physics.root.WorldFactory[source]

Return the 3D :data:WorldFactory for the resolved backend.

Applies the full precedence (explicit > project setting > auto > Builtin) and returns the chosen backend’s 3D factory. If the chosen backend has no 3D factory (a 2D-only native backend), falls back to Builtin’s 3D factory, warning when the backend was explicitly requested (a node override or project setting naming it) and logging at debug from auto-discovery, where a backend that cannot serve 3D is not a 3D choice at all.

Args: explicit: A PhysicsRoot(backend=...) override, or None.

Returns: A callable (gravity: Vec3) -> PhysicsWorld.

simvx.core.physics.backends.resolve_world_factory_2d(explicit: str | None = None) simvx.core.physics.root.WorldFactory2D[source]

Return the 2D :data:WorldFactory2D for the resolved backend.

2D sibling of :func:resolve_world_factory. Falls back to Builtin’s 2D factory when the chosen backend has no 2D factory (a 3D-only native backend), with the same tier-dependent logging: warned for an explicit request, debug from auto-discovery.

Args: explicit: A PhysicsRoot2D(backend=...) override, or None.

Returns: A callable (gravity: Vec2) -> Physics2DWorld.

simvx.core.physics.backends.__all__

[‘BUILTIN’, ‘BackendEntry’, ‘register_backend’, ‘resolve_backend_name’, ‘resolve_world_factory’, ‘re…