arcane-engine 0.26.1

Arcane game engine — agent-native 2D engine with embedded TypeScript runtime
# Input: Keyboard, Gamepad & Touch

## Keyboard

```typescript
import { isKeyDown, isKeyPressed } from "@arcane/runtime/rendering";

// isKeyDown: true while held
if (isKeyDown("ArrowRight")) player.x += speed * dt;

// isKeyPressed: true only on the frame the key goes down
if (isKeyPressed("Space")) jump();
```

Key names use the `KeyName` type: `"ArrowLeft"`, `"ArrowRight"`, `"ArrowUp"`, `"ArrowDown"`, `"Space"`, `"Enter"`, `"Escape"`, `"Shift"`, `"Control"`, `"Alt"`, `"a"` through `"z"`, `"Digit0"` through `"Digit9"`. Space is `"Space"`, not `" "`.

## Gamepad

Xbox layout as canonical button/axis names:

```typescript
import {
  isGamepadConnected, isGamepadButtonDown, isGamepadButtonPressed,
  getGamepadAxis, getGamepadCount,
} from "@arcane/runtime/rendering";

if (isGamepadConnected(0)) {
  // Analog sticks: -1 to 1 (apply deadzone!)
  const rawX = getGamepadAxis("LeftStickX");
  const rawY = getGamepadAxis("LeftStickY");
  const DEADZONE = 0.15;
  const dx = Math.abs(rawX) > DEADZONE ? rawX : 0;
  const dy = Math.abs(rawY) > DEADZONE ? rawY : 0;
  player.x += dx * speed * dt;
  player.y += dy * speed * dt;

  // Face buttons
  if (isGamepadButtonPressed("A")) jump();
  if (isGamepadButtonDown("RightTrigger")) fire();
}
```

See `types/input.d.ts` for the full `GamepadButton` and `GamepadAxis` type definitions.

## Touch

```typescript
import { isTouchActive, getTouchPosition, getTouchWorldPosition, getTouchCount } from "@arcane/runtime/rendering";

if (isTouchActive()) {
  const screenPos = getTouchPosition(0);      // screen pixels
  const worldPos = getTouchWorldPosition(0);   // world coordinates (camera-aware)
  if (worldPos) moveToward(player, worldPos.x, worldPos.y, speed * dt);
}

// Multi-touch
if (getTouchCount() >= 2) {
  const pos1 = getTouchPosition(0);
  const pos2 = getTouchPosition(1);
  // pinch-to-zoom, two-finger gestures, etc.
}
```

## Input Actions

Higher-level abstraction over raw input. Map named actions to physical inputs:

```typescript
import {
  createInputMap, isActionDown, isActionPressed, getActionValue, setActionBindings,
} from "@arcane/runtime/input";

const input = createInputMap({
  jump: ["Space", "GamepadA"],
  attack: ["x", "GamepadX", "MouseLeft"],
  moveRight: [
    { type: "key", key: "d" },
    { type: "key", key: "ArrowRight" },
    { type: "gamepadAxis", axis: "LeftStickX", direction: 1 },
    "GamepadDPadRight",
  ],
  moveLeft: [
    { type: "key", key: "a" },
    { type: "key", key: "ArrowLeft" },
    { type: "gamepadAxis", axis: "LeftStickX", direction: -1 },
    "GamepadDPadLeft",
  ],
});

// In onFrame:
if (isActionPressed("jump", input)) player.vy = -300;
if (isActionDown("attack", input)) swingSword();

// Analog value: 0/1 for digital, -1 to 1 for analog
const moveX = getActionValue("moveRight", input) - getActionValue("moveLeft", input);
player.x += moveX * speed * dt;

// Remapping at runtime
setActionBindings(input, "jump", ["w", "GamepadB"]);
```

String shorthands: `"Space"`, `"a"`-`"z"`, `"ArrowLeft"`, `"GamepadA"`, `"GamepadDPadUp"`, `"MouseLeft"`. Or use full `InputSource` objects for analog axes. See `types/input.d.ts` for all bindings.

## Input Buffering & Combos

```typescript
import { createInputBuffer, updateInputBuffer, checkCombo, consumeCombo } from "@arcane/runtime/input";

const buffer = createInputBuffer(1.0);  // 1s window
const fireball = { sequence: ["down", "right", "attack"], window: 0.5 };

// In onFrame:
updateInputBuffer(buffer, input, totalTime);
if (checkCombo(buffer, fireball, totalTime)) {
  consumeCombo(buffer, fireball);
  castFireball();
}
```

## Multi-Input Fallback Pattern

Check keyboard first, then gamepad, then touch:

```typescript
let dx = 0;
if (isKeyDown("ArrowRight")) dx = 1;
else if (isKeyDown("ArrowLeft")) dx = -1;
else {
  const raw = getGamepadAxis("LeftStickX");
  dx = Math.abs(raw) > 0.15 ? raw : 0;
}
```

Or use the Input Actions system above for cleaner multi-input handling.