Builtin Library
README.md
map
A record-shaped map: Zin.actions / Zin.axes entries organized per
device class (kbm | gamepad | touch) with single-parent extends,
materialized into an EFFECTIVE map and applied as one binding set at a
time. Backs Zin.map.
An .inputMap asset with .inputBinding children is the map, and
Zin.scheme is the live set: several maps are live at once and compose,
each declaring the group its controls belong to and the groups it
suppresses while live. Zin.map.bake(name) writes the map active here
out as an .inputMap asset, with synthesis made explicit and editable.
man topics/input covers the control model.
Resolution order per action/axis and device class:
- the map's own class bindings
- the
extendsparent's (recursively) - synthesis from the
kbmshape (touch class only)
A vector kbm binding synthesizes a touchStick; a boolean kbm binding
synthesizes a touchButton; a vector-shaped mouseDelta synthesizes a
touchDrag; scroll synthesizes a touchPinch; an axis-kind kbm
binding and a single-axis mouseDelta are left unsynthesized (ambiguous
which touch shape to guess). A synthesized touchButton carries
priority = 100 — an authored B.touchButton's default priority (50)
always outranks synthesis on the touch-controls overlay, so a hand-tuned
scheme's own layout hints win over whatever filled a gap automatically.
A profile record (actions with bindings, axes with binding) is accepted
anywhere a map record is — its bindings are treated as the kbm class.
Activation and the <axis>@<class> convention
activate flattens every class's bindings into each action/axis's single
bindings list and applies it through Zin.profile, so any device fires the
action. For axes — which evaluate exactly one binding — the primary slot is
the first of kbm / touch / gamepad present; any OTHER class's binding
registers as a class-suffixed sibling axis (look + look@touch). A
consumer that wants both device values reads both names; a kbm-only world's
primary axis (Zin.axes.value("move")) behaves identically whether or not a
touch class exists.
Zin.profile.ensureActive("default") delegates to Zin.map.ensureActive()
when the resolved scheme is the builtin default, so it carries every device
class rather than kbm alone. Zin.map.ensureActive() / Zin.map.activate
win regardless of call order (activate re-registers the flattened profile
under the same name and forces a reactivate).
Exports
M.materialize(record: any) -> effective map— resolveextends+ synthesize, without activating.M.activate(record: any) -> effective map— materialize + apply as the live binding set (registers/activates aZin.profile).M.effective() -> effective map?— the active map's materialized form.M.activeName() -> string?— the active map's name.M.bindingsFor(eff, name, class) -> bindings?— one action/axis's bindings for one device class.M.ensureActive() -> effective map— keep the current map, else activate@builtin::inputMaps.default.M.bake(name: string) -> AssetRef— write the active effective map as a new.inputMapinstance (asset.create("inputMap", name, { record = ... })).M.addTouchButton(actionName, buttonOpts, emitKey?) -> binding— add a touchButton binding to an action's touch class on the live effective map (creating the action if absent) and re-activate. Used byZin.touchControls.button.M.removeTouchButton(actionName, binding) -> boolean— remove a binding added viaaddTouchButtonand re-activate; drops the action entry once every class is empty.M._reset()— test-only: clear active-map state.
Usage
local Zin = require("@builtin::modules.zinput")
Zin.map.ensureActive()
local eff = Zin.map.effective()
print(eff.actions.jump.classes.touch ~= nil) -- synthesized or explicit
-- Write the active scheme out as a control-asset map:
Zin.map.bake("my_scheme")