Input¶
One input system covers keyboard, mouse, pen, tablet, controller and touch. Every input is a
control of a device type (Key.SPACE, Mouse.LEFT, Controller.A, Tablet.RING), so a
binding is a typed value rather than a raw string.
Input actions¶
An action is a named binding that decouples game logic from specific keys. It is typed, and its bindings compose into that type:
from simvx.core import Axis1D, Axis2D, Controller, Input, Key, Node
class Player(Node):
input_context = {
"jump": [Key.SPACE, Controller.A], # bool
"throttle": [Axis1D(low=Key.LEFT_CONTROL, high=Key.SPACE),
Controller.RIGHT_TRIGGER], # float
"move": [Axis2D.WASD, Controller.LEFT_STICK], # Vec2
}
def on_update(self, dt):
if Input.just_pressed("jump"):
self.jump()
self.position += Input.value("move") * self.speed * dt
Bindings |
|
|
|---|---|---|
Buttons only |
|
|
An |
|
|
An |
|
|
Axis2D.WASD and Axis2D.ARROWS are ready-made. When several devices drive one action, the
largest magnitude wins, and a key, being full deflection, takes a tie. A plain dict on
input_context is shorthand for an InputContext named after the node’s class;
InputMap.add_action("shoot", [Mouse.LEFT]) is the imperative spelling, into the tree’s own context.
Action queries¶
Method |
Returns |
Description |
|---|---|---|
|
|
The action’s typed value |
|
|
True while held; for a continuous action, past its deadzone |
|
|
True on the frame the action activates |
|
|
True on the frame the action deactivates |
|
|
The magnitude of |
A name no context declares raises a KeyError naming the nearest action. An action that exists but
is hidden or shadowed reads as at rest.
Inside a physics tick¶
A frame runs zero or more physics ticks. Each tick reads held state as it was when the tick started, and the frame’s edges arrive on its first tick only (or the next tick, if the frame ran none), so a press fires exactly once whatever the display and tick rates are.
def on_fixed_update(self, dt):
if Input.just_pressed("jump") and self.is_on_floor():
self.velocity.y = -450.0
self.velocity.x = Input.value("move").x * 250.0
Input.mouse_delta and Input.scroll_delta follow the same rule. Input.tick is the snapshot (a
TickInput) while a tick runs and None outside one; it can be recorded and replayed with
tree.physics_tick(dt, tick_input=recorded).
Combining separate actions¶
Input.get_vector(neg_x, pos_x, neg_y, pos_y) and Input.get_axis(neg, pos) combine separate
actions. For movement, declare one Axis2D or Axis1D action instead.
InputMap¶
InputMap is the flattened result of the active contexts, plus an override layer for the player’s
rebinds. Every tree has its own. Its layers, bottom to top:
the engine’s entries (named controls and the
ui_*actions);the project’s, from
[input]insimvx.toml(applied byProjectSettings.apply_input(), shared by every tree and read live);the tree’s own context,
"tree", whichadd_actionwrites and which lasts as long as the tree;node contexts, in activation order;
the player’s rebinds.
The bare InputMap is the tree whose code is running, else SceneTree.current(); with no tree it
raises RuntimeError, so an action cannot be registered from main() before run(). Declare it on
a node’s input_context or in the project settings. tree.input_map names one tree’s map.
Input contexts¶
A game’s controls change with what it is doing: in a car move steers, and a pause menu stops the
player’s keys reaching the player. An InputContext expresses that as something the tree owns:
from simvx.core import Axis2D, InputContext, Key, Node2D
class Vehicle(Node2D):
input_context = InputContext(
{"move": [Axis2D.WASD], "honk": [Key.H]},
name="vehicle",
)
A context is active exactly while its node is in the tree. Assign
node.input_contextat runtime, orNoneto remove it.Contexts stack in activation order and shadow by action name: the vehicle’s
movesteers while the walking controller’sjumpstill jumps.hides=InputContext.ALLtakes every name and handler below it, which a pause menu wants. The engine’s entries,ui_acceptamong them, come back as the engine declared them. A tuple of names hides exactly those.The root’s context covers the tree while that scene is mounted;
change_sceneswaps it.A change releases and never presses: a key held across a pause reads unpressed on resume.
class PauseMenu(Control):
input_context = InputContext({"resume": [Key.ESCAPE]}, hides=InputContext.ALL)
One map: controls, device aliases and actions¶
Every name is an entry of one map, resolving to raw controls or to other entries’ names. The
engine’s entries (Controller.A, Pointer.PRIMARY) are in every tree; a game adds device
aliases and actions that name them:
input_context = {
"throttle": [Controller.axis(2)], # a flight stick's third axis
"start": [Controller.button(5)],
"engine_power": ["throttle"], # a string is another entry's name
"pause": ["start", Key.ESCAPE],
"move": [Axis2D(up="forward", down=Key.S, left=Key.A, right=Key.D)],
"forward": [Key.W], # names may be used before they are declared
}
A loop, an unknown name (with the near miss) or a type that does not fit is refused when the entry is made. A string is always a name, so a key combo is
Key.SHIFT + Key.TAB.Every name on a raw control fires: if
Controller.AandController.Bboth resolve to pad button 1, pressing it fires both.An entry names only entries in its own context or one beneath it.
InputMap.resolve(Controller.A)is the raw control an engine entry stands for now;InputMap.aliases(raw)lists every entry naming a raw control.
Rebinding¶
A player’s rebind writes the override layer, never an InputContext (which is frozen), so it
survives a scene change, a pause or a vehicle being entered.
from simvx.core import BindingRole
InputMap.rebind("jump", Key.J) # a button action
InputMap.rebind("move", Key.T, slot=0, role=BindingRole.UP) # one direction
InputMap.rebind("pause", "start") # an action onto a device alias
InputMap.rebind(Controller.A, Controller.button(1)) # an engine entry: everything using A follows
InputMap.swap(Controller.A, Controller.B) # a pad with the two the wrong way round
slotpicks the source, in declaration order (in[Axis2D.WASD, Controller.LEFT_STICK]the keys are slot 0);rolepicks its part:WHOLE,LOW/HIGH, orUP/DOWN/LEFT/RIGHT.roles_of(source)lists the parts a rebind screen draws a row for.binding=Noneleaves a part unbound,player=makes the rebind one player’s, andclear_rebind()/clear_rebinds()reset.
InputMap.export() returns JSON-able data and load() puts it back. A binding pinned to one
device is filed under that device’s guid, so it follows the controller after a USB reorder; a device
with no guid keeps its rebinds for the session only.
Listening for the next input¶
An InputListener is a node that hears the player’s next press before the UI and every handler,
and claims it, so the press that picks a new jump key does not also jump. Its context hides every
action while it listens.
from simvx.core import DeviceType, InputListener, InputMap, Key
def listen(self):
listener = self.add_child(InputListener(devices=[DeviceType.KEYBOARD, DeviceType.MOUSE]))
listener.heard.connect(self.captured)
def captured(self, heard): # a ControlInput
if heard.control is not Key.ESCAPE: # cancelling is the game's choice
InputMap.rebind("jump", heard)
heard carries control, device_id, pressed, value, held and the event; heard.binding
is the binding that hears it again. A modifier is heard alone only when released with nothing pressed
under it, so Ctrl+S is heard as the chord. The knobs are devices, threshold (axis travel, 0.5),
releases and once (True).
get_sources() and get_bindings() answer a hidden action too.
examples/features/input/standard_controls.py is a complete rebind screen.
Bindings, modifiers and chords¶
InputBinding takes one control with options: a deadzone, one half of an axis, one pad.
from simvx.core import Controller, InputBinding, InputMap, Key
InputMap.add_action("move_left", [
Key.A,
Key.LEFT,
InputBinding(Controller.LEFT_X.negative, deadzone=0.2), # the stick pushed left
])
An axis’s halves are .positive and .negative (a stick’s are .up, .right and so on), and
Controller.A.on(pad) hears one pad only, for local co-op.
A modifier is a held control, written with +, the last control being the one pressed:
InputMap.add_action("save", [Key.CTRL + Key.S, Key.META + Key.S]) # Ctrl+S, and Cmd+S on a Mac
InputMap.add_action("quick_save", [Key.RIGHT_CONTROL + Key.Q]) # the right Control key only
InputMap.add_action("extrude_x", [Key.G + Key.X]) # hold G, press X
InputMap.add_action("super_jump", [Controller.LEFT_BUMPER + Controller.A])
Key.CTRL, Key.SHIFT, Key.ALT and Key.META are either side. A binding holding a modifier is
exact (Ctrl+S does not fire on Ctrl+Shift+S); one holding none ignores modifiers (Space still jumps
while Shift runs). exact= overrides either.
InputBinding(Key.F, held=(Key.CTRL,), exact=False) # Ctrl+F, and Ctrl+Alt+F too
InputBinding(Key.D, exact=True) # D with no modifier down
An @on_input target is read by the same rule: @on_input(Key.CTRL + Key.Z) does not fire on
Ctrl+Shift+Z, and @on_input(Key.S, exact=True) is S alone.
Bindings in simvx.toml¶
simvx.toml and the rebind file spell sources the same way. In a file a string is always a control
(a key name, a combo, or "type:control"); another entry is {name = "..."}:
[input]
jump = ["space", "controller:a"]
dedent = ["shift+tab"]
redo = [{key = "y", held = ["ctrl"]}]
super_jump = [{control = "controller:a", held = ["controller:left_bumper"]}]
move = ["controller:left_stick", {up = "w", down = "s", left = "a", right = "d"}]
aim = [{control = "controller:right_stick", deadzone = 0.3}]
pause = [{name = "start"}, "escape"]
Units: scale and per-second rate¶
Sources in different units share one action when each converts into the action’s unit: *
scales, and .per_second(rate) turns a held value into motion over the frame or tick:
input_context = {
"zoom": [Touch.PINCH, # the unit: the frame's change of scale
Mouse.WHEEL * 0.1, # a notch is a tenth
Axis1D(low=Controller.LEFT_TRIGGER,
high=Controller.RIGHT_TRIGGER).per_second(1.0)], # held fully, 1.0 a second
}
The deadzone is measured before scaling. In a file: {control = "mouse:wheel", scale = 0.1}.
Actions belong to a player¶
Routing decides which tree an input reaches; ownership decides which player it belongs to.
two = Input.add_player()
two.pair(pad) # a ControllerDevice from Input.controllers leaves the unpaired pool
node.input_player = two # Input queries in this subtree answer for player two
ActionEvent.player carries the player. Player 0 owns every unpaired device, so a single-player
game never sees any of this. Device state is never per player: Input.is_key_pressed is a fact
about the keyboard.
Direct key and mouse queries¶
For input that is not an action (debug tools, editor code):
Input.is_key_pressed(Key.ESCAPE)
Input.is_key_just_pressed(Key.F3)
Input.is_mouse_button_just_pressed(Mouse.LEFT)
pos = Input.mouse_position # Vec2
delta = Input.mouse_delta # Vec2, this frame's movement
scroll_x, scroll_y = Input.scroll_delta
Input.set_mouse_capture_mode(MouseCaptureMode.CAPTURED) # hidden and locked: a first-person camera
MouseCaptureMode is VISIBLE, HIDDEN (moves freely), CAPTURED or CONFINED (visible, kept
inside the window).
Controllers¶
Most games never touch a device: bind an action to Controller.A and it answers for whichever pad
the player picked up. Input.controllers is for which pad; it maps a device id to a
ControllerDevice:
from simvx.core import Controller, Input
for device_id, pad in Input.controllers.items():
print(device_id, pad.name, pad.family, pad.source)
pad = next(iter(Input.controllers.values()))
if pad.just_pressed(Controller.A):
self.jump()
stick = pad.stick(Controller.LEFT_STICK) # deadzoned Vec2
trigger = pad.axis(Controller.RIGHT_TRIGGER) # 0.0 released, 1.0 fully pulled
pad.rumble(strong=0.8, duration=0.3) # False if this pad cannot
Stick axes run from -1.0 to 1.0, +y down the screen; triggers from 0.0 to 1.0, bound by their
positive half (Controller.RIGHT_TRIGGER.positive).
Unrecognised controllers¶
A controller the database does not know (pad.recognised False, such as a flight stick) has only
Controller.button(i) and Controller.axis(i) until the player maps names onto it:
InputMap.rebind(Controller.A, Controller.button(3).on(stick))
The mapping is saved with the other rebinds. Until then, asking that device for Controller.A
raises, and an action bound to the name reads at rest on it.
Connection¶
Input.controller_connected and Input.controller_disconnected fire with the ControllerDevice;
an unplugged pad’s held buttons release with a real edge and its connected is False. The Qt window
backend reports no controllers.
The on-screen controller¶
TouchController draws a stick and buttons and publishes them as a pad, so a game that binds a
controller is playable with two thumbs:
from simvx.core.ui import TouchController
def on_ready(self):
self.add_child(TouchController())
While on screen it is in Input.controllers with source ControllerSource.VIRTUAL, which a lobby
can filter out. It is a CanvasLayer, so it is pinned to the screen.
Knob |
Values |
|---|---|
|
|
|
any of A, B, X, Y, the shoulders and Back; |
|
leave the screen while the player uses a real controller |
|
the pointers it answers, |
None takes a coordinate: the geometry follows the window. It answers fingers only and never takes
focus; add PointerType.MOUSE to pointer_types to test with a mouse. VirtualJoystick and
VirtualButton are its pieces.
What the player is using¶
Input.input_method is KEYBOARD_MOUSE, CONTROLLER or TOUCH: the kind of device the player
last acted with, which is what a prompt should be drawn for.
if Input.input_method is InputMethod.CONTROLLER:
self.hint.text = "Press A to start"
Mouse motion switches it only past Input.mouse_method_threshold, so a nudged desk does not count.
Input.input_method_changed fires with the new method. Input.capabilities is what the machine can
do (touch, pen, coarse_pointer, rumble, soft_keyboard and more).
Prompts¶
Input.prompt(action) is the label for an action on whatever the player holds:
self.hint.text = f"Press {Input.prompt('jump')} to jump"
That reads “Press Space” at a keyboard, “Press A” on an Xbox pad and “Press Cross” on a PlayStation
one: Controller.A is the bottom face button everywhere and only the label changes. device_id=
prompts for one pad, and Input.label(Key.CTRL + Key.S) is “Ctrl+S”.
Typing on a phone¶
Focusing a control whose wants_text_input is True, such as TextEdit, opens the soft keyboard;
Input.begin_text_input(rect) opens it for what focus cannot know about. node.viewport_insets
is how much of each edge the keyboard, a notch or a home indicator covers: keep anything the player
must touch out of it, and read it every frame.
Controls and roles¶
Key, Mouse, Pen, Touch, Tablet and Controller hold each device type’s named controls.
A control is interned, so event.button == Mouse.LEFT works and a control is a dict key; a control
nothing names arrives in its raw form, such as Tablet.button(11).
A role groups controls across device types and is asked with in, since a group is not one of
its members (Mouse.LEFT != Pointer.PRIMARY):
Role |
Members |
|---|---|
|
|
|
|
if event.phase is PointerPhase.DOWN and event.button in Pointer.PRIMARY:
self.press()
The full list of controls is in simvx.core.input.
Touch gestures¶
Gestures are controls. They bind, rebind and are heard like a key, and each is recognised only while something names it, so a game that names none pays nothing:
Control |
Kind |
Value |
|---|---|---|
|
button |
a finger resting unmoved for |
|
button |
two fingers touching and lifting within |
|
axis |
the frame’s change of scale, 0.05 for five percent wider |
|
axis |
radians, clockwise |
|
stick |
the two fingers’ shared translation, over |
input_context = {
"zoom": [Touch.PINCH, Mouse.WHEEL],
"pan": [Touch.PAN],
"inspect": [Touch.LONG_PRESS],
}
@on_input(Touch.ROTATE)
def _twist(self, event): # an AxisEvent; event.delta is this frame's turn
self.rotation += event.delta
A long press never fires on a finger a control captured (a joystick), and a scroll a control took
moves no binding on Mouse.WHEEL.
A finger never right-clicks unless the game asks. Input.touch_secondary = Touch.LONG_PRESS (or
Touch.TWO_FINGER_TAP) puts that gesture in Pointer.SECONDARY, and recognising it cancels the
press that began it.
GestureRecognizer is a node with one signal per gesture (tap, long_press, swipe, pinch,
rotate, pan). Its timings follow the scene clock, so a paused scene never completes a long press; a recogniser
in a pause menu uses GestureRecognizer(clock="wall"). See examples/features/input/touch_gestures.py.
Input events¶
One event class per device family; a widget receives the same object a handler does:
Event |
From |
|---|---|
|
the keyboard |
|
the mouse, pens and fingers |
|
a pad’s or a tablet’s buttons and axes |
|
an action |
|
a pickable 3D shape under a press |
Every event carries time, source_id and the modifier flags; a device event carries one control
(event.key, event.button, event.axis) and the device_id it came from:
match event:
case KeyEvent(key=Key.ESCAPE, pressed=True): ...
case ButtonEvent(button=Controller.START): ...
Handlers¶
@on_input filters on these:
Axis |
Form |
|---|---|
event family |
a positional event class, or a tuple of them: |
target |
positional, typed: an action name ( |
edge |
|
repeat |
|
held controls |
a button target holds others with |
claim |
|
A target holding a modifier is exact, so Ctrl+S reaches the Ctrl+S handler and not the Ctrl+Shift+S one; a target holding none ignores modifiers. An unknown keyword raises naming the nearest valid one.
A held key’s auto-repeats are opt-in, and only a key target can opt in. A repeat never opens
an action, so @on_input("move_right") fires once per press however long the key is held, and in
the ordinary broadcast it reaches only a key handler declared with repeat=True, such as a cursor
that walks a grid while an arrow is held. Anything else that asks for repeats is refused when the
class is defined. In the capture phase an event family hears every repeat, as it always has.
A key’s repeats belong to the node that claimed its press, in every phase. A press claimed in
the capture phase sends its repeats and its release to that node’s own capture handlers and to no
other, claimed for it whether or not it asked to hear them, so a held Ctrl+Z never leaks into
another captor or the focused text field. A press a control claimed sends its repeats to that
control, and one it declines never falls through to focus traversal or the game. A press an
ordinary handler claimed sends its repeats only to that node’s repeat=True handlers. A press
nobody claimed leaves its repeats to every repeat=True handler, and each physical press starts
the question afresh.
A handler returns True to claim the event, False to decline it, or None to defer to
consume=; anything else raises. A claim stops every later handler and changes no device state.
Bare @on_input on the root runs last, and only if nobody claimed.
The targeted phase has its own two decorators. @on_gui_input hears what
Node.on_gui_input hears: keys and text at the focus owner, a pointer at its captor or the
controls under it, the wheel along that chain. @on_picked hears what Node.on_picked hears: the
pick a pointer press casts on a pickable 3D shape. Both take the filters above, routed the same
way, less what cannot reach them:
class Map(Control):
zoom = Property(1.0)
@on_gui_input(ScrollEvent) # the wheel
def zoomed(self, event):
self.zoom *= 1.1 ** event.scroll[1]
return True
@on_gui_input(Pointer.PRIMARY, edge="release") # any pointer's main button coming up
def drop(self, event):
...
@on_gui_input(Key.CTRL + Key.C) # while focused
def copy(self, event):
...
class Crate(Node3D):
@on_picked(Pointer.PRIMARY)
def select(self, event):
self.selected = True
@on_picked(Mouse.RIGHT)
def open_menu(self, event):
...
Bare @on_gui_input hears all four classes the phase delivers (PointerEvent, ScrollEvent,
KeyEvent, TextEvent), and bare @on_picked every pick. Neither takes an action name, because
actions resolve after the targeted phase from what no control claimed, nor capture= or
samples=, which are the broadcast’s. @on_gui_input refuses a pad’s buttons and axes, which
never reach a control; @on_picked takes a pointer button alone, and no phase=. Within one node the decorated handlers run in declaration
order, base classes first, until one claims, and the on_gui_input or on_picked override runs
after them only if none did. The pointer bubble (press, release, wheel) skips a node whose class
declares neither; keys and text reach the focus owner, and motion and a captor’s events reach
their target, whatever its class declares.
A key target on @on_gui_input hears one press per physical press, as on @on_input, and
repeat=True adds the auto-repeats, which is what a key that walks a list wants. An event family,
a bare @on_gui_input and the on_gui_input override hear every event the phase delivers,
repeats included, which is the feed a text field reads. A key’s repeats and its release go to the
control that claimed its press: a repeat that control declines is still its own and never falls
through to focus traversal or the game, and an edge="release" handler hears its key only after a
press handler on the same node claimed (consume=True, or edge="both" returning True on the
press).
A pick can take a release. @on_picked(Pointer.PRIMARY, edge="release") hears the pick cast
where the button comes up, over this node, and edge="both" hears both, told apart by
event.pressed. A release casts a ray only while some node in the tree declares such a handler, so
a scene that never asks pays nothing for it, and only those handlers hear it: a bare @on_picked
and the on_picked override hear presses alone. A release belongs to whoever claimed its press:
when a node claimed the press, a release pick’s claim stops only that node’s later handlers. When
nobody claimed the press, a release pick that claims stops every later handler, as any claim does,
so no broadcast or action handler hears the release. The action the press opened still closes, so
Input.just_released and the physics tick see it end.
Declare decorated handlers above the override. A class body fills top to bottom, so below
def on_gui_input(...) the name on_gui_input is that method, and an @on_gui_input(...) written
there calls it instead of the decorator; the class fails with a missing-argument error that names
neither. Below on_ready, on_enter_tree or on_exit_tree, which take no argument, nothing
fails: the override runs at class definition and the decorated handler silently becomes None.
Put the decorated handlers first, or qualify the decorator (@decorators.on_gui_input).
tools/decorator_shadowing.py finds the mistake for every lifecycle and input decorator.
Pointers¶
Pointer 0 is the mouse; every other pointer has an id allocated each time it touches down, and
pointer_type says what it is. While Input.emulate_mouse is on (the default) the
primary finger also presses Mouse.LEFT, so a game that handles a click handles a tap. A
PointerEvent carries its phase, position, delta, button, pressure (1.0 without a pen),
tilt and more.
@on_input(PointerEvent)
def _draw(self, event):
if event.phase is PointerPhase.MOVE and event.pointer_type is PointerType.PEN:
self.stroke(event.position, width=event.pressure * 8.0)
A motion event carries the frame’s accumulated delta, one per frame. A drawing canvas that needs
every sample declares @on_input(PointerEvent, samples=True) and receives each with its own
position, pressure and timestamp.
Capture, picking and debugging¶
The capture phase runs first. capture=True puts a handler there: it hears the event
root-first, ahead of the focused control, the actions and every other handler, and a claim there
takes the whole event, its release included. Global shortcuts live there, so a focused text field
cannot take them. ShortcutManager matches bindings by the same rule, and a chord is two of them,
(Key.CTRL + Key.K, Key.CTRL + Key.C):
class EditorRoot(Node):
def on_ready(self):
self.shortcuts = ShortcutManager()
self.shortcuts.register("save", Key.CTRL + Key.S, self.save)
@on_input(KeyEvent, capture=True)
def _shortcut(self, event):
return self.shortcuts.handle(event) or None
Picking. A press the UI does not take casts a ray, and the nearest CollisionShape3D with
pickable=True delivers a PickEvent (node, world point, normal, distance) to its owner. A release
casts one too while a node in the tree declares @on_picked(..., edge="release"), and reaches only
such handlers. tree.input_cast(position, button, pressed=False) casts a release pick by hand.
Debugging. A tree’s input_delivered signal fires after each delivery with
(event, claimed_by). InputMap.explain("jump") says why an action reads what it reads: its
bindings, the declaring context, any context hiding it and the rebinds that apply.
Hosting a tree inside another¶
A tree can run inside another: an arcade cabinet in a game, a game in an editor’s viewport. A
SceneHost is the control in the host tree that runs it and forwards input to it: a pointer while
it is over the host’s rect or held by it, and keys, text and controllers while it holds focus.
from simvx.core import Anchor, Control, FocusMode, Key, SceneHost, on_input
class Cabinet(Control):
def on_ready(self):
self.host = self.add_child(
SceneHost(ArcadeGame(), focus_mode=FocusMode.HOVER, anchor=Anchor.FULL_RECT))
@on_input(Key.ESCAPE, capture=True) # heard before the game
def _leave(self, event):
self.host.forwarding = False # hands everything over
return True
A press stays with the host that forwarded it, so a drag leaving the rect still reaches the guest.
When input stops reaching the guest it is handed over: held actions close with a real edge, pointers cancel, hover leaves and the text session ends; nothing presses on arrival. Turning
forwardingoff, hiding or removing the host hands over everything; losing focus hands over keys only.tree.input_handover(pointers=..., keys=...)does it by hand.host.to_guest(point)andhost.to_host(point)convert between the spaces.A guest starts from the engine and project entries alone;
SceneTree(input_base=host_tree)builds it on the host’s entries instead.
API reference¶
See simvx.core.input for the complete input API.