Architecture¶
SimVX is a Godot-inspired game engine in pure Python: node-based scene hierarchy, Vulkan GPU-driven rendering, and NumPy-based math. This page covers the key internal pipelines.
Node Lifecycle¶
Every node progresses through these stages:
construction --> enter_tree --> ready --> [process / physics_process] --> exit_tree
Construction –
__init__()sets properties andPropertydefaults. No tree access yet.Enter tree –
add_child()inserts the node.Childdescriptors are instantiated. The node receives a reference to itsSceneTree.on_enter_tree()fires.Ready –
on_ready()runs bottom-up (children first, then parent).OnReadydescriptors resolve. Safe to access children by path:self["Camera"].Process –
on_process(dt)runs every frame with wall-clock delta.on_physics_process(dt)runs at a fixed rate (default 60 Hz) with accumulator-based catch-up.Exit tree –
destroy()triggerson_exit_tree(). Signals are disconnected, children are recursively removed, and the node is dequeued from theSceneTree.
Children added during on_ready() go through the full lifecycle before the parent’s on_ready() returns. This guarantees that self["Player/Camera"] is valid inside on_ready().
Scene Tree¶
SceneTree owns the root node and drives the frame loop:
SceneTree.tick(dt) # walk tree, call on_process(dt) on every node
SceneTree.physics_tick(dt) # walk tree, call on_physics_process(dt)
SceneTree.render(Draw2D) # 2D draw pass -- nodes' on_draw(renderer) emits commands
Driver (SceneTree.tick / physics_tick / render) and handler (Node.on_process / on_physics_process / on_draw) deliberately use different verbs so a misspelt override on a Node never silently shadows the engine’s per-frame loop.
Groups allow batch queries: tree.get_group("enemies") returns all nodes tagged with that group. The tree also manages deferred deletions – destroy() queues removal until the end of the current frame to avoid mutation during iteration.
Render Pipeline¶
SimVX uses a GPU-driven forward renderer. The per-frame work driven from
Python is bounded: scene state goes into flat NumPy arrays once, then a
single multi-draw-indirect call submits every visible instance. There are
no per-object vkCmdDraw loops in Python.
The public contract is: GPU-driven, one indirect submission for opaque
geometry, a separate sorted pass for transparent. Buffer sizes, descriptor
counts, culling strategy, and pass ordering are implementation details
that may change without notice: read the code in
packages/graphics/src/simvx/graphics/renderer/ if you need the current
specifics.
Input Flow¶
GLFW key/mouse event --> input_adapter --> Input singleton --> InputMap --> game code
GLFW callback –
key_callback_with_uireceives raw key events from GLFW.Input adapter – Translates GLFW key codes to
Key/MouseButtonenums, updates theInputsingleton’s pressed/released state, and routes events to the UI system.Input singleton – Stores per-frame state.
Input.is_key_pressed(Key.W)checks instantaneous state;Input.is_action_just_pressed("jump")checks action bindings.InputMap – Maps named actions to typed key bindings:
InputMap.add_action("jump", [Key.SPACE, JoyButton.A]). Actions are queried by name inon_process(dt).
UI widgets receive input first. If a focused widget consumes the event, it does not propagate to game nodes.
Performance Notes¶
Node counts: The tree walk is pure Python, so keep node counts under ~5,000 for 60 fps process ticks. Use groups and
find()to avoid deep recursive searches.Draw call batching: All opaque geometry is drawn in a single indirect draw call regardless of material count. Only transparent objects require sorting.
SSBO capacity: Default buffers support 16,384 instances and 256 lights. Exceeding these triggers a buffer resize (one-time stall).
Physics tick: Fixed at 60 Hz by default. Multiple physics steps per frame occur when the frame rate drops below 60 fps (capped at 100 ms accumulation to prevent spiral-of-death).
Headless mode:
App(visible=False)creates a real Vulkan surface with an invisible GLFW window. Rendering is identical to visible mode – useful for automated visual tests in CI.