simvx.core.physics.root

PhysicsRoot: branch-isolating physics node + node->world resolution.

A PhysicsRoot node carves its sub-branch of the scene tree into a separate, isolated :class:~simvx.core.physics.world.PhysicsWorld. Bodies resolve their world by walking up to the nearest PhysicsRoot ancestor; if there is none, they fall back to a default world (one per scene tree). Nested roots: the innermost wins.

The factory used to build a world for a root (and for the default) is pluggable so the backend can be selected without changing the resolution logic. Which backend a root builds is decided by :mod:simvx.core.physics.backends (precedence: explicit PhysicsRoot(backend=) > project physics_backend setting > auto-discovered native > Builtin); a direct factory= argument is the test escape hatch that bypasses precedence.

World ownership is SceneTree-owned. The default world (for bodies with no PhysicsRoot ancestor) lives on the tree as tree.physics_world (lazy, tree-scoped, and it survives change_scene like the event bus), and each PhysicsRoot registers its own isolated world with the tree on enter and unregisters + drops it on exit, so no worlds leak on teardown. Registered worlds are advanced by SceneTree.physics_tick.

The one exception is the treeless fallback: a single module-level default world used only when :func:resolve_world is given a node with no SceneTree (standalone unit tests that call step() manually).

Module Contents

Classes

PhysicsRoot

A node that owns an isolated :class:PhysicsWorld for its sub-branch.

PhysicsRoot2D

A node that owns an isolated :class:Physics2DWorld for its sub-branch.

Functions

resolve_world

Resolve the :class:PhysicsWorld a node’s bodies belong to.

resolve_world_2d

Resolve the :class:Physics2DWorld a node’s 2D bodies belong to.

Data

API

simvx.core.physics.root.WorldFactory

None

simvx.core.physics.root.WorldFactory2D

None

simvx.core.physics.root.resolve_world(node: simvx.core.node.Node) simvx.core.physics.world.PhysicsWorld[source]

Resolve the :class:PhysicsWorld a node’s bodies belong to.

Walks up from node (inclusive) to the nearest :class:PhysicsRoot ancestor and returns that root’s world. If no PhysicsRoot is found, returns the tree’s default world (node.tree.physics_world), or, for a treeless node, the module-level fallback world.

Args: node: The node whose world to resolve.

Returns: The resolved :class:PhysicsWorld (innermost root wins; tree default otherwise; treeless fallback when the node is not in a tree).

simvx.core.physics.root.resolve_world_2d(node: simvx.core.node.Node) simvx.core.physics.world2d.Physics2DWorld[source]

Resolve the :class:Physics2DWorld a node’s 2D bodies belong to.

The 2D sibling of :func:resolve_world. Walks up from node (inclusive) to the nearest :class:PhysicsRoot2D ancestor and returns that root’s 2D world. If none is found, returns the tree’s default 2D world (node.tree.physics_world_2d), or, for a treeless node, the module-level 2D fallback world.

Args: node: The node whose 2D world to resolve.

Returns: The resolved :class:Physics2DWorld (innermost PhysicsRoot2D wins; tree 2D default otherwise; treeless 2D fallback when not in a tree).

class simvx.core.physics.root.PhysicsRoot(name: str = '', *, gravity: simvx.core.math.Vec3 = _DEFAULT_GRAVITY, backend: str | None = None, factory: simvx.core.physics.root.WorldFactory | None = None, **kwargs: object)[source]

Bases: simvx.core.node.Node

A node that owns an isolated :class:PhysicsWorld for its sub-branch.

Bodies under a PhysicsRoot (with no nearer PhysicsRoot) simulate in this node’s world, fully isolated from the rest of the tree: independent gravity and stepping. Resolve a node’s world with :func:resolve_world.

The world is created lazily on first access via the configured factory (defaults to BuiltinPhysics), so constructing the node is cheap and the backend choice stays in one place.

This root registers its world with the SceneTree on enter so the fixed-step loop (SceneTree.physics_tick) advances it, and unregisters + drops it on exit so nothing leaks across scene swaps or teardown.

Initialization

Initialise the physics root.

Args: name: Node name (defaults to the class name). gravity: Gravity for this root’s isolated world. backend: Explicit backend-name override, the most specific level of the selection precedence: explicit > project physics_backend setting > auto-discovered native > Builtin. None (default) defers to the setting / auto / Builtin levels. An unknown name degrades to the next level with a warning (a missing optional native backend does not crash the game). factory: Direct backend-factory escape hatch. When given it bypasses the backend precedence entirely and is used verbatim (used by tests to inject a stub world). Defaults to None -> resolve via backend precedence. **kwargs: Forwarded to :class:~simvx.core.node.Node.

property world: simvx.core.physics.world.PhysicsWorld[source]

This root’s isolated world, created lazily on first access.

Built from the explicit factory if one was given, else from the backend resolved by precedence (explicit backend > project setting > auto-discovered native > Builtin).

on_enter_tree() None[source]

Register this root’s isolated world with the tree so it gets stepped.

on_exit_tree() None[source]

Unregister + drop this root’s world (no leaked worlds on teardown).

Uses self._world (not the creating .world property) so exit never builds a world a never-used root never had. Re-entering the tree rebuilds a fresh world lazily and re-registers it; the registry guard makes the re-register idempotent.

strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

script_error_raised

‘Signal(…)’

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
reset_error() None
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) None
reparent(new_parent: simvx.core.node.Node)
node_at(path, default=_NO_DEFAULT)
find(target, *, direct: bool = False)
find_all(target, *, direct: bool = False)
expect(target, *, direct: bool = False)
ancestor(target)
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]
property path: str
property is_scene_root: bool
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
on_ready() None
on_update(dt: float) None
on_fixed_update(dt: float) None
on_draw(renderer) None
on_picked(event: simvx.core.events.InputEvent) None
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
queue_redraw() None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None
property app
property tree: simvx.core.scene_tree.SceneTree
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()
class simvx.core.physics.root.PhysicsRoot2D(name: str = '', *, gravity: simvx.core.math.Vec2 = _DEFAULT_GRAVITY_2D, backend: str | None = None, factory: simvx.core.physics.root.WorldFactory2D | None = None, **kwargs: object)[source]

Bases: simvx.core.physics.root.PhysicsRoot

A node that owns an isolated :class:Physics2DWorld for its sub-branch.

The 2D sibling of :class:PhysicsRoot: a distinct class rather than a dims flag, so it matches the node taxonomy split and the resolution walk stays a plain isinstance test. 2D bodies under a PhysicsRoot2D (with no nearer PhysicsRoot2D) simulate in this node’s 2D world, isolated from the tree’s default 2D world. Resolve a node’s 2D world with :func:resolve_world_2d.

It deliberately does NOT reuse the 3D base’s _world slot / world property (those build a 3D BuiltinPhysics): a PhysicsRoot2D owns its own 2D world via :attr:world_2d, built lazily by the 2D factory (defaults to BuiltinPhysics2D), and registers THAT world with the tree.

Initialization

Initialise the 2D physics root.

Args: name: Node name (defaults to the class name). gravity: Gravity (Vec2) for this root’s isolated 2D world. backend: Explicit backend-name override, the most specific level of the selection precedence (see :class:PhysicsRoot). None defers to the project setting / auto / Builtin levels. factory: Direct 2D backend-factory escape hatch; when given it bypasses the backend precedence and is used verbatim (test injection). Defaults to None -> resolve via backend precedence. **kwargs: Forwarded to :class:~simvx.core.node.Node.

property world_2d: simvx.core.physics.world2d.Physics2DWorld[source]

This root’s isolated 2D world, created lazily on first access.

Built from the explicit factory if one was given, else from the backend resolved by precedence (explicit backend > project setting > auto-discovered native > Builtin).

on_enter_tree() None[source]

Register this root’s isolated 2D world with the tree so it gets stepped.

on_exit_tree() None[source]

Unregister + drop this root’s 2D world (no leaked worlds on teardown).

Uses self._world_2d_obj (not the creating .world_2d property) so exit never builds a world a never-used root never had. Re-entering rebuilds a fresh world lazily and re-registers it (the registry guard is idempotent).

property world: simvx.core.physics.world.PhysicsWorld
strict_errors: ClassVar[bool]

True

dev_checks: ClassVar[bool]

None

script_error_raised

‘Signal(…)’

dynamic: bool

False

visible

‘Property(…)’

update_mode

‘Property(…)’

__properties__: ClassVar[dict[str, simvx.core.descriptors.Property]]

None

classmethod __init_subclass__(**kwargs)
property name: str
property visible_in_tree: bool
reset_error() None
add_child(node: simvx.core.node.T) simvx.core.node.T
remove_child(node: simvx.core.node.Node) None
reparent(new_parent: simvx.core.node.Node)
node_at(path, default=_NO_DEFAULT)
find(target, *, direct: bool = False)
find_all(target, *, direct: bool = False)
expect(target, *, direct: bool = False)
ancestor(target)
walk(*, include_self: bool = True) collections.abc.Iterator[simvx.core.node.Node]
property path: str
property is_scene_root: bool
add_to_group(group: str)
remove_from_group(group: str)
is_in_group(group: str) bool
on_ready() None
on_update(dt: float) None
on_fixed_update(dt: float) None
on_draw(renderer) None
on_picked(event: simvx.core.events.InputEvent) None
on_unhandled_input(event: simvx.core.events.TreeInputEvent) None
start_coroutine(gen: simvx.core.descriptors.Coroutine) simvx.core.descriptors.CoroutineHandle
stop_coroutine(gen_or_handle)
queue_redraw() None
property render_dirty: bool
clear_children()
destroy()
property destroying: bool
call_deferred(method: collections.abc.Callable[..., Any], *args: Any) None
property app
property tree: simvx.core.scene_tree.SceneTree
property physics
property physics_2d
__getitem__(key: str)
classmethod get_properties() dict[str, simvx.core.descriptors.Property]
__repr__()
simvx.core.physics.root.__all__

[‘PhysicsRoot’, ‘PhysicsRoot2D’, ‘resolve_world’, ‘resolve_world_2d’, ‘WorldFactory’, ‘WorldFactory2…