simvx.core.scene_step

The single canonical per-frame logic-phase sequence, shared by every embedder.

Consumers that drive a :class:~simvx.core.scene_tree.SceneTree one frame at a time (the windowed/headless/streaming :class:FrameLoop and the editor’s PlayMode today; the AI rendered session and future embedders next) re-spell the same load-bearing order: step physics, dispatch queued UI events, run logic. The order is pure SceneTree logic (physics_tick / interpolate_physics / tick are core methods with zero Vulkan), so it lives in core where every embedder, including the ones that cannot import graphics, can share one definition. (The web runtime and SceneRunner own deliberately different policies and are not routed through here yet: see their notes.)

func:

step_scene_logic runs exactly that sequence and stops BEFORE the GPU half (Draw2D reset + tree.render + submit/present) and BEFORE the windowed Input frame boundary (Input._end_frame / Input._new_frame): those are the caller’s job, because they diverge per embedder (swapchain present vs offscreen render-to-target vs nothing). This module imports nothing from graphics.

Per-phase variation is expressed through :class:ScenePhases: a frozen set of optional hooks. phases=None runs the engine-default behaviour (one fixed physics step + interpolate to the step boundary, then one logic tick). A caller that owns a different physics-stepping policy (the windowed accumulator clock) or a different per-node walk (the editor’s error-tolerant / profiled walkers) supplies node_walk; a caller with a queued UI-event drain or a between-physics-and-logic window-sync supplies ui_events; a profiler supplies on_phase.

Module Contents

Classes

ScenePhases

Optional per-phase hooks for :func:step_scene_logic.

Functions

step_scene_logic

Run one frame of the canonical logic-phase sequence on tree.

Data

API

simvx.core.scene_step.__all__

[‘ScenePhases’, ‘step_scene_logic’]

class simvx.core.scene_step.ScenePhases[source]

Optional per-phase hooks for :func:step_scene_logic.

Every field defaults to None, which selects the engine-default behaviour for that phase. Supply a hook only to override one phase; the canonical order (physics, then UI events, then logic) is fixed and not overridable.

Attributes: node_walk: (root, dt, phase) where phase is "physics" or "process". When supplied it replaces the default physics step and/or the default logic tick: the caller owns how the tree is walked for that phase (its own physics-stepping policy, an error-tolerant or profiled per-node walk, etc.). root is the scene root, or None when the tree has no root. ui_events: called once, after physics and before logic, to drain queued UI / input events into the tree. This is the same slot the windowed loop uses for gamepad + window-size sync and the editor uses to flush its viewport UI-event queue. on_phase: (name, edge) with edge in {"begin", "end"}, fired around each of "physics", "ui", "process" so a profiler can bracket per-phase timings.

node_walk: collections.abc.Callable[[simvx.core.node.Node | None, float, str], None] | None

None

ui_events: collections.abc.Callable[[], None] | None

None

on_phase: collections.abc.Callable[[str, str], None] | None

None

simvx.core.scene_step.step_scene_logic(tree: simvx.core.scene_tree.SceneTree, dt: float, *, time_scale: float = 1.0, phases: simvx.core.scene_step.ScenePhases | None = None) None[source]

Run one frame of the canonical logic-phase sequence on tree.

Order (load-bearing):

  1. physicstree.physics_tick(dt * time_scale) then tree.interpolate_physics(1.0), OR phases.node_walk(root, dt, "physics").

  2. uiphases.ui_events() if supplied (no engine default).

  3. processtree.tick(dt * time_scale), OR phases.node_walk(root, dt, "process").

Stops before Draw2D / submit / present and before the Input frame boundary: those stay with the caller. Imports nothing from graphics.

With phases=None this is byte-identical to a hand-rolled tree.physics_tick(dt * time_scale); tree.interpolate_physics(1.0); tree.tick(dt * time_scale).

Args: tree: the scene tree to advance. dt: frame delta in seconds (unscaled). time_scale: multiplier applied to dt for the default physics and logic steps (slow-mo / fast-forward). When node_walk is supplied the caller owns scaling, so dt is passed through unscaled. phases: optional per-phase overrides; None selects engine defaults.