---
title: "input"
description: "The input namespace — the engine's Luau API reference for input."
section: "API Reference"
slug: "api-input"
canonical: "https://origozero.ai/docs/api-input"
updated: "2026-09-06T10:26:32.621666487+00:00"
tags: ["api", "reference"]
---

# input

The `input` namespace — 34 functions.

## input/armObservation {#input-armobservation}

```lua
input.armObservation(on: boolean?)
```

Hold the input observation's mapping half open (`true`, the default) or close it now (`false`). A read arms it for a window of frames on its own, so this is for a test or a tool that wants the mapping half building continuously, or wants to stop paying for it the moment it is done.

**Parameters**

- `on` `boolean?` — Arm (default) or disarm.

## input/emulateKeyDown {#input-emulatekeydown}

```lua
input.emulateKeyDown(code)
```

The input-emulation floor's write surface: mark `code` held by the floor. Merged into the default polling views (`isDown`, `snapshot().keys`) so `Zin.state.keyDown` cannot tell an emulated hold from a real one; tracked separately in `snapshot().keys_emulated`, and readable real-only via `Zin.state.keyDownReal`. Drained before the next frame's controller scripts run, like `simulate*` — but pushes no frame event and is never forwarded to the UI layer: emulated input exists for polling surfaces, and event-driven consumers already receive the real touch events the floor was driven from. Internal — backs the map-driven emulation floor.

**Parameters**

- `code` `string` — Key code

## input/emulateKeyUp {#input-emulatekeyup}

```lua
input.emulateKeyUp(code)
```

Release `code` from the input-emulation floor. Only lifts the held state if a real key-press hasn't since claimed the code (real press owns the key). No frame event, no UI forwarding. Internal — backs the map-driven emulation floor.

**Parameters**

- `code` `string` — Key code

## input/emulateMouseButton {#input-emulatemousebutton}

```lua
input.emulateMouseButton(button, held)
```

Set emulated mouse button `button` (0=left, 1=right, 2=middle) held/released from the input-emulation floor. Mirrors `emulateKeyDown`/`emulateKeyUp`: a real mouse press owns the button thereafter. No frame event, no UI forwarding. Internal — backs the map-driven emulation floor.

**Parameters**

- `button` `number` — Button index (0=left, 1=right, 2=middle)
- `held` `boolean` — True to hold, false to release

## input/emulateMouseDelta {#input-emulatemousedelta}

```lua
input.emulateMouseDelta(dx, dy)
```

Accumulate an emulated mouse-delta contribution from the input-emulation floor into this frame's `mouse_delta`. No frame event, no UI forwarding. Internal — backs the map-driven emulation floor.

**Parameters**

- `dx` `number` — Delta X
- `dy` `number` — Delta Y

## input/events {#input-events}

```lua
input.events() -> {table}
```

Get the current frame's input events as an array of { kind = '...', ... } records in dispatch order. Internal — Zin wraps this as `Zin.events.frame()`. Variants produced today: key.down { code, repeat }, key.up { code }, mouse.down { button, x, y }, mouse.up { button, x, y }, mouse.move { x, y, dx, dy }, mouse.wheel { dx, dy }, text { text }, touch.down { id, x, y, pressure }, touch.move { id, x, y, dx, dy, pressure }, touch.up { id, x, y }, touch.cancel { id }, gamepad.connected { slot, name }, gamepad.disconnected { slot }, gamepad.down { slot, button }, gamepad.up { slot, button }, gamepad.axis { slot, axis, value }. Returns an empty array if no events have been published yet. Cleared at end of frame (PostRender). Reading does not consume.

**Returns** `{table}` — Frame events in dispatch order

## input/frameId {#input-frameid}

```lua
input.frameId() -> integer
```

Get the engine's monotonic frame counter. Bumped once per frame at PostRender. Internal — used by `Zin.tick` to dedup cross-coroutine double-ticks within the same engine frame. Stable across multiple reads inside the same frame, strictly greater after a `task.wait(0)` yield. Returns 0 only before the very first frame has elapsed.

**Returns** `integer` — Monotonic frame counter

## input/isAnyDown {#input-isanydown}

```lua
input.isAnyDown({name, ...}) -> boolean, string?
```

Are any of the given keys currently held? Returns `held, which`: when any key in the array is held, `held=true` and `which` is the first matched code (in iteration order); otherwise `false, nil`. Internal — used by Zin.

**Parameters**

- `names` `{string}` — Array of key codes to check

**Returns** `boolean, string?` — (held, first matched code or nil)

## input/isDown {#input-isdown}

```lua
input.isDown(name) -> boolean
```

Is the given key currently held this frame? Internal — Zin wraps this as `Zin.state.keyDown(name)`. Direct lookup on the published snapshot's `keys` array.

**Parameters**

- `name` `string` — Key code (e.g. "KeyD", "Space", "ShiftLeft")

**Returns** `boolean` — True if the key is held this frame

## input/observationArmedFrames {#input-observationarmedframes}

```lua
input.observationArmedFrames() -> integer
```

How many more frames the arming window has left before a mapping layer stops building its half of the input observation. A read of `input.observe()` or of `/runtime/input` sets it back to the full window; every frame that passes takes one off it, and `0` means nothing is observing.

**Returns** `integer` — Frames left in the arming window.

## input/observationWanted {#input-observationwanted}

```lua
input.observationWanted() -> boolean
```

Whether something has read the input observation recently enough that a mapping layer should spend a frame building its half of it. A read of `input.observe()` or of `/runtime/input` arms this for a window of frames; a world nobody is observing reads false and pays nothing. Internal — the mapping layer's per-frame tick consults it.

**Returns** `boolean` — Whether a mapping report is wanted this frame.

## input/observe {#input-observe}

```lua
input.observe() -> table
```

Report what the input layer did with the last completed frame: every device event that arrived with the surface it came from and whether the UI took it (`consumed_by_ui`), the pointer-lock quartet, which focus the UI holds, what device classes the session has, and — while the mapping layer is publishing — its account of which controls those events became and why a live control stayed silent. The observation covers ONE engine frame, the frame that has just been drawn; a read taken from a script during frame N answers for frame N-1. Reading consumes nothing and clears nothing, so any number of observers see the same frame. `mappingStatus` says whether the mapping half is current, stale, arming (this read armed it — read again), or unarmed. Serialised from the same document `/runtime/input` serves.

**Returns** `table` — The engine's current input observation.

## input/publishMapping {#input-publishmapping}

```lua
input.publishMapping(report: table)
```

Publish the mapping layer's account of this frame into the engine's input observation, where `input.observe()` and the `/runtime/input` node serve it beside the device half. Internal — called once per frame by the mapping layer's tick while `input.observationWanted()` holds.

**Parameters**

- `report` `table` — The mapping layer's report for this frame.

## input/releasePointerLock {#input-releasepointerlock}

```lua
input.releasePointerLock()
```

Release pointer lock (cursor ungrab + show). Internal — backs Zin.pointer.unlock().

## input/releaseUiFocus {#input-releaseuifocus}

```lua
input.releaseUiFocus()
```

Give the UI's focus pair back to the UI pass, which writes its own opinion on its next run. Internal — backs Zin.test.releaseUiFocus().

## input/requestPointerLock {#input-requestpointerlock}

```lua
input.requestPointerLock()
```

Request pointer lock (cursor grab + hide). Internal — backs Zin.pointer.lock().

## input/simulateGamepadAxis {#input-simulategamepadaxis}

```lua
input.simulateGamepadAxis(slot, axis, value)
```

Queue a simulated pad-axis move. `axis` is a canonical name: left_stick_x, left_stick_y, right_stick_x, right_stick_y, left_trigger, right_trigger. Stick axes clamp to -1..1 with y screen-down positive; trigger axes clamp to 0..1 and latch their digital button at 0.5. Internal — backs the `inputSim` toolbox's `padStick` / `padTrigger`.

**Parameters**

- `slot` `number` — Pad slot
- `axis` `string` — Canonical axis name
- `value` `number` — Axis value

## input/simulateGamepadButton {#input-simulategamepadbutton}

```lua
input.simulateGamepadButton(slot, button, held)
```

Queue a simulated pad-button state change. `button` is a canonical name: south, east, west, north, left_shoulder, right_shoulder, left_trigger, right_trigger, left_stick, right_stick, select, start, guide, dpad_up, dpad_down, dpad_left, dpad_right. Internal — backs the `inputSim` toolbox's `padDown` / `padUp`.

**Parameters**

- `slot` `number` — Pad slot
- `button` `string` — Canonical button name
- `held` `boolean` — True for down, false for up

## input/simulateGamepadConnect {#input-simulategamepadconnect}

```lua
input.simulateGamepadConnect(name?) -> ()
```

Queue a simulated pad connection. The pad takes the lowest free slot and reports `name` (default "Simulated Gamepad") as its device name. Internal — backs the `inputSim` toolbox's `connect`. Read the slot it took from the next frame's snapshot `gamepads` array.

**Parameters**

- `name` `string?` — Device name the pad reports

## input/simulateGamepadDisconnect {#input-simulategamepaddisconnect}

```lua
input.simulateGamepadDisconnect(slot)
```

Queue a simulated pad disconnection. Anything the pad still held publishes its button-up first. Internal — backs the `inputSim` toolbox's `disconnect`.

**Parameters**

- `slot` `number` — Pad slot to disconnect

## input/simulateKeyDown {#input-simulatekeydown}

```lua
input.simulateKeyDown(key)
```

Queue a simulated key-down event. Drained before the next frame's controller scripts run. Internal — backs Zin.test.pressKey(); production code should not call this directly.

**Parameters**

- `key` `string` — Key name

## input/simulateKeyUp {#input-simulatekeyup}

```lua
input.simulateKeyUp(key)
```

Queue a simulated key-up event. Internal — backs Zin.test.releaseKey().

**Parameters**

- `key` `string` — Key name

## input/simulateMouseDown {#input-simulatemousedown}

```lua
input.simulateMouseDown(button?)
```

Queue a simulated mouse-button-down event. 0=left (default), 1=right, 2=middle. Internal — backs Zin.test.pressMouse().

**Parameters**

- `button` `number` — Button index (default: 0)

## input/simulateMouseMove {#input-simulatemousemove}

```lua
input.simulateMouseMove(x, y)
```

Set mouse position, in PHYSICAL pixels — the space `input.mousePosition`, the camera viewport rect and `worldToScreen` speak. A logical layout coordinate (`getLayoutInfo` / `ui.screenSize`) converts with `ui.pixelRatio()`. Internal — backs Zin.test.moveMouse().

**Parameters**

- `x` `number` — Screen X (physical pixels)
- `y` `number` — Screen Y (physical pixels)

## input/simulateMouseMoveBy {#input-simulatemousemoveby}

```lua
input.simulateMouseMoveBy(dx, dy)
```

Move the pointer by a relative motion, the way a mouse device reports it: the contribution accumulates into this frame's `mouse_delta` and advances the position by the same amount. Repeating the same `(dx, dy)` keeps delivering it. Internal — backs Zin.test.moveMouseBy().

**Parameters**

- `dx` `number` — Horizontal motion in screen pixels
- `dy` `number` — Vertical motion in screen pixels

## input/simulateMouseUp {#input-simulatemouseup}

```lua
input.simulateMouseUp(button?)
```

Queue a simulated mouse-button-up event. Internal — backs Zin.test.releaseMouse().

**Parameters**

- `button` `number` — Button index (default: 0)

## input/simulateSceneContext {#input-simulatescenecontext}

```lua
input.simulateSceneContext(active?: boolean)
```

Hand the active input context to the scene (`true`, the default) 
             or to the UI (`false`). While a viewport widget is on screen the 
             scene owns continuous pointer input — buttons, motion, scroll — 
             only while it is the active context, which a pointer gesture 
             decides by where it begins; this decides it without one. The next 
             gesture decides it again. Internal — backs Zin.test.focusScene().

**Parameters**

- `active` `boolean` — true hands the context to the scene, false to the UI (default: true)

## input/simulateScroll {#input-simulatescroll}

```lua
input.simulateScroll(dy)
```

Queue a simulated mouse-wheel event. Internal — backs Zin.test.scroll().

**Parameters**

- `dy` `number` — Scroll delta (positive = up)

## input/simulateTouchCancel {#input-simulatetouchcancel}

```lua
input.simulateTouchCancel(id)
```

Queue a simulated platform touch-cancel for an active finger id.

**Parameters**

- `id` `number` — Stable finger id

## input/simulateTouchDown {#input-simulatetouchdown}

```lua
input.simulateTouchDown(id, x, y, pressure?)
```

Queue a simulated touch-contact-begin event. `id` is any stable number identifying the finger until its matching simulateTouchUp/Cancel. Drained before the next frame's controller scripts run. The primary touch (slot 0) also drives the mouse path. Internal — backs Zin test touch helpers.

**Parameters**

- `id` `number` — Stable finger id
- `x` `number` — Screen X
- `y` `number` — Screen Y
- `pressure` `number` — Contact pressure 0..1 (default: 1.0)

## input/simulateTouchMove {#input-simulatetouchmove}

```lua
input.simulateTouchMove(id, x, y, pressure?)
```

Queue a simulated touch-move event for an active finger id.

**Parameters**

- `id` `number` — Stable finger id
- `x` `number` — Screen X
- `y` `number` — Screen Y
- `pressure` `number` — Contact pressure 0..1 (default: 1.0)

## input/simulateTouchUp {#input-simulatetouchup}

```lua
input.simulateTouchUp(id)
```

Queue a simulated touch-lift event for an active finger id.

**Parameters**

- `id` `number` — Stable finger id

## input/simulateUiFocus {#input-simulateuifocus}

```lua
input.simulateUiFocus(pointer: boolean, keyboard: boolean)
```

Hold the UI layer's two focus opinions at `pointer` and `keyboard` until `input.releaseUiFocus()` gives them back to the UI pass. The pair decides, per event surface, whether the UI took an event: a pointer event is judged on pointer focus and a key on keyboard focus, so a run with no UI on screen can still put a handler on either side of `consumed_by_ui`. Queued like every other simulated input, so the events queued behind it are stamped against it. Internal — backs Zin.test.uiFocus().

**Parameters**

- `pointer` `boolean` — Whether the UI holds pointer focus
- `keyboard` `boolean` — Whether the UI holds keyboard focus

## input/snapshot {#input-snapshot}

```lua
input.snapshot() -> table
```

Get the current frame's input state as a table. Internal — Zin wraps this as `Zin.state.get()`. Fields: keys, keys_pressed, keys_released — ARRAYS of pressed key code strings (e.g. {"KeyD", "Space"}). NOT a {KeyD=true} dict; `t.keys["KeyD"]` is always nil. keys_emulated — array of key codes held by the input-emulation floor, a subset of `keys` (merged there for polling); `Zin.state.keyDownReal` reads `keys` minus `keys_emulated`. mouse_position, mouse_delta — [x, y]; mouse_buttons, mouse_pressed, mouse_released — [L, R, M] bool arrays (InputResource convention); scroll_delta — number; pointer_locked, ui_focused — booleans; touches — array of { id, slot, x, y, dx, dy, pressure, phase } per active finger contact (slot 0 = primary; phase: began|moved|stationary|ended|cancelled); touch_capable — boolean, true once the session has (or declares) a touch input source; gamepads — array of { slot, name, buttons, buttons_pressed, buttons_released, axes, simulated } per connected pad, ordered by slot (button fields are ARRAYS of canonical names, `axes` is a map of canonical axis name to number); gamepad_capable — boolean, true once the session has seen a pad.

**Returns** `table` — Input state snapshot for this frame
