simvx.core.physics._pose_reconcile¶
Node-transform -> physics-handle pose reconciliation.
A physics node (PhysicsBody2D/PhysicsBody3D, CharacterBody2D/
CharacterBody3D) owns BOTH a Node transform and a body handle in a
physics world. The scene tree syncs handle -> node every step, and render
interpolation does it every frame. Without the reverse direction, a direct
node.position = x would move only the visual node, and the next step /
move_and_slide would snap it back to the stale handle pose (the body teleports
to the world corner). That made attribute assignment – the obvious way to place
a node – silently wrong.
This mixin closes the loop: assigning the node’s OWN transform eagerly teleports
the simulated body to match. So node.position = spawn is the single, canonical
way to place a physics node (there is deliberately no separate teleport()
method: it would be a pure behavioural alias). Because the push is eager, every
later read – the step, move_and_slide, and raycast/overlap queries – sees
the assigned pose with no further bookkeeping.
Mechanics:
_invalidate_transformis the single transform choke point (localposition, theworld_positionsetter, in-placeVecmutation, androtation’son_change). The override pushes the node world pose into the handle for a direct write to THIS node (_from_parentFalse), and for an ANCESTOR’s move only where the body follows it (:meth:_body_follows_parent_transform, true for a STATIC body). Neither fires during the engine’s own handle->node write-backs (_applying_physics_pose).A simulated body deliberately does NOT follow its ancestors: dragging a character through its handle every time a parent moves would override
move_and_slide, and a parent move erased by the next simulation write-back is worse than one that never happened. A STATIC body has no such authority to protect – nothing simulates it, and its pose is authored data – so it follows. Note this is a deliberate difference from engines that push the composed transform for every body type.The engine’s handle->node write-backs (bulk scatter, render interpolation,
move_and_slide/move_and_collide) MUST route through- meth:
_write_simulated_pose, which sets the guard so they never bounce the simulated / interpolated pose back into the body (that would, depending on backend, zero velocity, wake sleepers, or corrupt interpolation).
Subclass contract: implement :meth:_push_node_pose_to_body to write
(world_position, world_rotation) into the handle, a no-op when inert (no
handle yet, e.g. before on_enter_tree or with no collider). In practice that
is implemented ONCE per dimension, on PhysicsObject2D / PhysicsObject3D:
every physics node owns a body, so every one of them writes through
set_body_transform. Subclasses that create their handle in __init__ order
must set their world / handle fields BEFORE super().__init__ so the hook, which
can fire on a position= kwarg during base init, reads valid state.