Input & aim
All input leaves as virtual-HID device input (the Akira driver): real button/key transitions and relative mouse counts. Scripts never write view angles or input memory. When the HID mouse is not connected, aim calls are silent no-ops, and all synthetic movement stops while the game is not the foreground window.
Action names#
Used by key.tap/hold/release/block/held, input.* aliases, and util.held:
| Name | Action | Name | Action |
|---|---|---|---|
Mouse1 / primary | primary fire | Space | jump |
Mouse2 / secondary | secondary fire | Ctrl | crouch |
Mouse3-Mouse5 | side buttons | R | reload |
Shift | ability 1 | V | melee |
E | ability 2 | F | interact |
Q | ultimate | W A S D | movement (Forward/Left/Back/Right aliases) |
mouse.click/down/up/held take 'left' | 'right' | 'middle' | 'mouse4' | 'mouse5'
(the raw Mouse1-Mouse5 spellings work too) and default to 'left'.
Merge semantics (per engine tick)#
- Scripts queue intents during
on_tick:tap(randomized 2-5 tick pulse, or an explicit tick count up to 120),hold(this frame),block(force off),release(immediate). - After all scripts ticked, intents merge into one desired mask: hold + tap, minus block - block always wins, across scripts.
- The arbiter diffs the merged mask against the other input sources (built-in features, real device) and emits only the transitions.
Consequences: hold must be re-asserted every tick; a block from any script suppresses an
action for everyone that frame (this is how defensive scripts stop your fire during fade /
deflect windows); release and block apply immediately, not at end of frame.
Aim#
| Call | Behavior |
|---|---|
mouse.move(dx, dy) | pixel delta → HID counts, +/-1 count noise per call, applied immediately |
mouse.aim(x, y, z) | clamped step toward a world point |
mouse.face(x, y, z) | clamped step toward a direction |
mouse.direction() | current view direction (unit vector, zeros = unknown) |
mouse.aim / mouse.face never teleport: each call rotates at most a randomized 20-30 deg
and lands with a randomized 0.3-0.9 deg endpoint error, so a script calling every tick
converges over several ticks. One aim request per tick wins
(last call, across all scripts).
Typical targeting loop:
local me = util.me(); if not me then return end
local e = util.sticky_enemy(track, { fov = 150, range = 40, bone = 'head' })
if not e then return end
local p = util.lead_world(e, 'head', 0) -- 0 = hitscan; pass proj speed + cast for projectiles
if p then mouse.aim(p.x, p.y, p.z) end
For projectile weapons pass the projectile speed (and optional cast time) to
util.lead_world / util.lead_screen instead of aiming at the raw bone.
Reading physical input#
key.held(name) / mouse.held(button) report the real device state; util.held(name)
normalizes keybind config values ('None' is never held, 'Mouse1'-'Mouse5' and
'left'/'right'/'middle' map to buttons). Use it with menu.keybind values rather than
hardcoding keys.
Last updated 2026-10-03