# Input: actions, buttons, gamepads, touch screens, the mouse and raw keys

`ctx.input` is a `Controls`: this player's input, polled once a frame. A game plays on a keyboard
and mouse, a gamepad and a phone's touch screen, with no device code, when it reads:
- **actions** (`move`, `action`, `up/down/left/right`): what every game gets, and all a board
  minigame may use;
- **its own buttons**, declared in `defineGame({ buttons })`: a key, a pad button and a touch
  button each;
- **the mouse** (games with `mouse` / `pointerLock` in `game.json`'s `input`). A pad's right stick
  and triggers drive it too, and so do the touch screen's drag and its FIRE / AIM buttons.

Raw keys (`input.keys`) are there for what nothing else covers. A raw key has no pad button and no
touch button, so prefer a declared button.

## Contents
1. Choosing controls
2. Actions
3. Your own buttons (`defineGame({ buttons })`)
4. Gamepads and touch screens
5. The mouse (`"mouse"`)
6. Pointer lock and mouse look (`"pointerLock"`)
7. Raw keys (`"keyboard"`)
8. Recipe: a first-person camera
9. Recipe: aim and shoot at the pointer (top-down)
10. Networking input
11. Title-card labels

---

## 1. Choosing controls

| Game | `input` | Controls |
|---|---|---|
| Board minigame | `[]` (required) | move + `action`, at most 3 controls |
| Top-down arena, twin-stick shooter, RTS-ish | `["mouse"]` | WASD to move, the pointer to aim, click to shoot |
| First-person shooter, flight, anything with mouse look | `["mouse", "pointerLock", "keyboard"]` | WASD + mouse look, click to fire, R to reload, Shift to sprint |
| A builder, a card game | `["mouse"]` | point and click |

Ask for what the game really uses: players see the manifest ("Mouse"). Keep controls few and
conventional: WASD, Space to jump, Shift to sprint, R to reload, E to use, the mouse wheel or 1–4
to switch. Declare each extra key as a button (§3), so it gets a pad button and a touch button. A
phone has two thumbs, so a stick and three or four buttons is about as much as it can play.

## 2. Actions

```ts
type Action = 'action' | 'up' | 'down' | 'left' | 'right';
input.move();               // Stick { x, z }, each -1..1: x right, z towards the camera (analog on a stick)
input.dir();                // grid games: the most recently pressed direction still held, or null
input.down('action');       // held (SPACE, X, ENTER, a left click or tap, a pad's A, the big touch button)
input.pressed('action');    // went down this frame
input.presses('action');    // presses since the game began: stream this counter, not a flag
input.pressedAt('action');  // the last press on link.now()'s clock, accurate to the event
input.pressTimes('action'); // every press since the last frame: readonly { at, local }[] (reaction games)
```
A left click (or a tap) anywhere that isn't a button also counts as `action`, except in mouse-look
games (`pointerLock`), where a click shoots. On a pad or the touch stick `move()` is analog, so a
half push is a slow walk. `down('up')` and the other directions count once the stick is past
halfway.

## 3. Your own buttons (`defineGame({ buttons })`)

Each key a game uses beyond the actions is a named button. It reads like an action, and gets a
pad button and a touch button with no more code.

```ts
export default defineGame({
  …,
  controls: [[KEYS.move, 'Run'], [KEYS.action, 'Jump'], ['R', 'Reload'], ['SHIFT', 'Dash']],
  buttons: {
    reload: { keys: ['KeyR'] },                                   // pad: the next free button (X); touch: "RELOAD"
    dash: { keys: ['ShiftLeft', 'ShiftRight'], pad: 'rb', label: 'DASH' },
    map: { keys: ['KeyM'], touch: false },                        // no touch button (a phone doesn't need it)
    action: { label: 'JUMP' },                                    // relabel (or add keys or pad buttons to) the built-in one
  },
  create: (ctx) => new MyGame(ctx),
});
// in update:
if (input.pressed('reload')) reload();
if (input.down('dash')) dash();
send({ ..., r: input.presses('reload') });    // counters, like actions
```
- A `ButtonSpec` has these fields:
  - `keys`: key codes.
  - `pad`: one pad button or several: `'a' | 'b' | 'x' | 'y' | 'lb' | 'rb' | 'lt' | 'rt' | 'select' | 'start' | 'l3' | 'r3' | 'up' | …`.
    The default is the next free one of X, Y, B, RB, LB, R3, L3 and VIEW.
  - `label`: the touch button's text. The default is the title card's words for its key, else the
    button's name.
  - `icon`: an image URL for the touch button, e.g. `ctx.engine.icon(model)`.
  - `touch: false`: no touch button.
- `down`, `pressed`, `presses`, `pressedAt` and `pressTimes` all take button names. An unknown name
  throws, so a typo shows up on the first frame.
- The names `click`, `rightClick`, `middleClick` and `wheel` are taken: they're the mouse's, on touch
  screens.

## 4. Gamepads and touch screens

There's nothing to do: the platform maps both onto the same actions, buttons and mouse.

| | Keyboard and mouse | Gamepad | Touch screen |
|---|---|---|---|
| `move()`, directions | WASD / arrows | left stick (analog), d-pad | a floating stick under the left thumb |
| `action` | SPACE, X, ENTER, a click | A | the big button, or a tap on the game |
| your buttons | their `keys` | their `pad` button | a button each, around the big one |
| `look()` | the mouse | right stick (eased, dt-scaled, in mouse pixels) | drag on the right half (mouse-look games) |
| `mouseDown(0)` | left button | RT | FIRE (mouse-look games), or a finger on the game |
| `mouseDown(2)` | right button | LT | AIM, when the title card mentions RIGHT CLICK |
| `wheel()` | the wheel | LB / RB (unless a button has them) | SWAP, when the title card mentions WHEEL |
| title card: ready, results | SPACE / ENTER / the button | A or MENU | tap the button |
| a `Menu`: move, press, close | arrows / WASD, ENTER / SPACE, ESC | d-pad or left stick, A, B (LB / RB a row) | tap |
| open a keyed `Menu` | its `key` | the pad button `buttons` gave that key, else VIEW (`Menu({ pad })`) | its own button, if you give it one |

```ts
input.device;                    // 'keyboard' | 'touch' | 'pad': the last thing they used (changes mid-game)
input.aiming;                    // look is live: the pointer is locked, or they're on a pad or a touch screen
input.label(KEYS.action);        // "SPACE", "A" or "TAP"; label('reload') → "R", "X" or "RELOAD"
input.rumble(0.6, 120);          // shake the pad (a phone buzzes where it can); harmless elsewhere
input.pad;                       // the pad itself: down('lt'), pressed('y'), stick('right'), trigger('rt') (0..1), connected
```
- Gate look, fire and "Click to aim" hints on `input.aiming`, not `input.locked`: pads and touch
  screens look without a lock.
- Use `input.label` in your own HUD hints ("Press ${input.label('reload')} to reload") so they read
  right on every device. The title card already does.

**The touch controls** show on touch screens while the game is live and no `Menu` is open. They're
worked out from game.json's `input` and the title card:
- A stick. In `mouse` games it only works in the bottom-left corner, so a tap anywhere else stays a
  click.
- The big button: FIRE in mouse-look games, else `action`. Its label comes from its title-card row
  ("Jump" → JUMP).
- Then `action`, then your buttons.

Change the layout with `defineGame({ touch })`:
```ts
touch: { buttons: ['action', 'dash', { name: 'reload', label: '⟳' }], stick: true, look: false },
touch: false,                    // none: draw your own
```
At run time, `input.touch` has:
- `enabled`: false hides the controls, e.g. for a phase with its own touch UI.
- `shown`: whether they're on screen now.
- `setButtons([...])`: a new set per phase, e.g. BUILD in the build phase and FIRE in the fight.
- `root`: the layer's element.

The controls are plain DOM with stable class names, so your CSS can move or restyle any of them:
`.vp-touch`, `.vp-touch-stick`, `.vp-touch-btns`, `.vp-touch-btn[data-button="dash"]` (`.big`, and
`.on` while held). Each piece that takes up screen space has `data-vp-touch`.

Keep your HUD clear of thumbs:
- `--vp-touch-h` on :root is how far up the buttons reach (0 while hidden).
  `bottom: calc(var(--vp-touch-h, 0px) + 12px)` keeps a HUD bar above them.
- `html.vp-touching` is set while they play by touch, for CSS that moves or hides desktop-only HUD.
  The screen's size is `html.vp-phone` (a phone-sized screen either way up, whatever the input:
  `vp docs api` §13).

**Your own touch UI**: `input.bind(element, name)` makes any element a button (an action, one of
yours, or `'click'` / `'rightClick'`) for both held and pressed, and returns the unbind. A game that
draws everything itself (`touch: false`) can also listen to pointer events with
`e.pointerType === 'touch'`. A finger on the game counts as the left button and moves `pointer()`.

To test on a phone-sized touch screen: `vp shot --phone` (pictures on a phone held sideways, and
how much of the screen your HUD covers: `vp docs testing`), or by hand Chrome's device toolbar
(Ctrl+Shift+M) with a phone in landscape.

## 5. The mouse (`"mouse"`)

```ts
input.pointer();            // { x, y } in normalized device coordinates: -1..1, x right, y up, (0,0) the centre
input.mouseDown(0);         // a button is held: 0 left (default), 1 middle, 2 right
input.mousePressed(2);      // a button went down this frame (right click: the browser menu is suppressed)
input.wheel();              // wheel turn this frame in px: + = scrolled down / towards you, 0 if it didn't turn
input.look();               // { dx, dy } mouse movement this frame in px (dx right, dy down)
input.ray(camera, out?);    // a three.js Ray from the camera through the pointer (the centre when locked)
```
`pointer()` is `Raycaster.setFromCamera`'s input. `ray()` does it for you and writes into
`out`, so aiming every frame allocates nothing:
```ts
private readonly caster = new Raycaster();
private readonly ground = new Plane(new Vector3(0, 1, 0), 0);
private readonly aim = new Vector3();
…
this.ctx.input.ray(this.view.camera, this.caster.ray);
if (this.caster.ray.intersectPlane(this.ground, this.aim)) { /* aim.x, aim.z is where the pointer is on the floor */ }
const hit = this.caster.intersectObjects(this.targets, false)[0];   // or what it's over
```
These all exist in every game (they return 0 / false / the centre when nothing happens), so
shared code can call them; only games with `mouse` in `input` should depend on them.

## 6. Pointer lock and mouse look (`"pointerLock"`)

Mouse look needs the pointer locked: the cursor hides, and `look()` keeps reporting movement
past the edge of the screen. Browsers only lock on a user gesture (a click), and the sandbox only
allows it when the manifest lists `pointerLock`.

Let the platform do it: `defineGame({ pointerLock: true, … })` locks on "Click to play", locks
again on a click while playing (after Escape gave the pointer back), and unlocks at the finish.
To do it yourself, call `input.lockPointer()` on a frame where `input.mousePressed()` is true,
or from `flow.onPlay(cb)` (it runs inside the "Click to play" gesture).

For a stretch where the player points at things (a shop, a map, a pointer-driven mode), set
`input.pointerLock = false`. The pointer is freed and the platform stops re-locking on clicks. Set
`input.pointerLock = true` again and the next click locks it. Pads and touch screens look without a
lock (`input.aiming`).

Mouse look runs at the player's own sensitivity: the site's settings (the gear) have a slider for
it, and `look()` already scales a locked pointer's movement by it (a free cursor's never). A game
needn't offer its own; one that does multiplies on top.

```ts
input.locked;               // true while locked
input.lockPointer();        // on a click only; harmless if refused (and on touch screens)
input.unlockPointer();
input.pointerLock = false;  // the platform's automatic lock: off for now
```
- **Escape** always unlocks (the browser does it) and opens the page's menu. While unlocked the
  game keeps running: show a small "Click to aim" hint (`ui.hint()`) when `flow.live && !input.aiming`.
- `look()` also works unlocked (it's the mouse's movement), but then the cursor hits the screen
  edge: only use it for look when `input.aiming`.
- Sensitivity: about `0.0022` radians per pixel feels standard; clamp pitch to ±1.45 rad.

## 7. Raw keys (`"keyboard"`)

`input.keys` is the raw keyboard (`Keys`, by `KeyboardEvent.code`):
```ts
input.keys.down('ShiftLeft', 'ShiftRight');   // held
input.keys.down('Shift');                     // either side: 'Shift', 'Control', 'Alt', 'Meta'
input.keys.shift; input.keys.ctrl; input.keys.alt; input.keys.meta;   // held (a Shift held before the game had focus counts once they click)
input.keys.pressed('KeyR');                   // went down this frame
input.keys.latest(['Digit1', 'Digit2', 'Digit3']);   // the most recently pressed of these still held, or null
```
Codes are physical positions (`KeyW` is W on QWERTY and Z on AZERTY), which is what you want for
WASD. `Keys` is exported from `@voxelparty/sdk` for types. A raw key has no pad or touch button,
so declare a button (§3) for anything a player needs. Don't bind Escape (the page owns it) or
browser shortcuts (Ctrl+W…). Tab is yours: the runtime stops the browser from moving focus with it
(onto the page's buttons), so it's fine for a held scoreboard. Alt is yours too: letting go of it
doesn't open the browser's menu.

## 8. Recipe: a first-person camera

Use the kit (`vp docs fps`; `vp init <id> --fps` starts from a working shooter). `FpsCamera` puts
the camera at a body's eyes with the feel done: eased stairs, a landing dip, head bob, recoil,
shake, zoom, and a held gun that can't poke into walls. `readIntent` reads WASD, `action` (jump),
mouse look, clicks, 1–9 and the wheel into one intent, the same shape a CPU returns, and reads a
pad and a touch screen the same way. `fire` / `alt` are held; `firePressed` / `altPressed` went down
this frame (semi-automatic guns, a scope toggle).

`game.json`: `"input": ["mouse", "pointerLock", "keyboard"]`. `index.ts`: `pointerLock: true`.

```ts
import { FpsCamera, arenaStage, readIntent } from '@voxelparty/sdk';
import { fpsStep, turn } from '@voxelparty/sdk/core';

this.view = arenaStage(ctx.engine, { fov: 95, tilt: 0, near: 0.04, pollen: false });
this.view.scene.add(this.view.camera);
this.fp = new FpsCamera(this.view.camera);
// every frame, while you play:
const it = readIntent(ctx.input, { scale: this.fp.lookScale });   // mouse right turns right
turn(this.body, it.dyaw, it.dpitch);
const moved = fpsStep(this.grid, this.body, it, dt);            // collides with your VoxelGrid
this.fp.update(dt, this.body, moved, it);
```
- Don't call `view.rig.update` in first person (the rig would move the camera). Hide your own avatar.
- Spectators and CPU-only runs have no body of their own: give the camera something to look at
  (orbit the map, follow the leader), or `vp check`'s screenshots show the sky.
- On autopilot (`input.autopilot`, in `vp shot` and `vp check`) your own CPU plays your seat:
  return null from your intent and let the core use your bot (`vp docs testing` §1).
- Keep the sun's shadow box around the player: `view.sunAt(position)` (a `Vector3`) when you move far.

## 9. Recipe: aim and shoot at the pointer (top-down)

`"input": ["mouse"]`. The arena camera stays as usual (`view.rig.fit`), WASD moves, and the
avatar faces the pointer:
```ts
this.ctx.input.ray(this.view.camera, this.caster.ray);
if (this.caster.ray.intersectPlane(this.ground, this.aim)) me.yaw = Math.atan2(this.aim.x - me.x, this.aim.z - me.z);
if (input.mouseDown()) this.tryFire(me);   // held = automatic fire, with a cooldown
```
A pad has no pointer: when `input.device === 'pad'`, aim with `input.pad.stick('right')` and fire on
`mouseDown()`, which RT drives. On a touch screen a tap aims and fires where it lands.
Bots aim with the same numbers (a yaw towards a target plus a little error), so the rules code
takes an aim angle, not the mouse.

## 10. Networking input

- Stream what you do, not what you press: your position, `yaw`/`pitch` (angles: add them to
  `PlayerSync`'s `angles` so they interpolate the short way), and **counters** for one-off
  actions (`shots: 12`), never `fired: true` flags.
- Shots that matter (damage, kills) are decided by one authority, the host. Send the shot (origin,
  direction, the time on `link.now()`) with `link.sendEvent`; the host checks it against where it
  had everyone and broadcasts the hit with `HostSync.event`. Show your own muzzle flash and
  tracer at once; show damage when the host confirms.
- At 20 Hz a player moves ~0.3 units between updates: be generous with hit radii (or have the
  host test against where the target was ~100 ms ago), so hits that looked right count.

## 11. Title-card labels

The title card takes `controls: [[keys, what]]` rows. `KEYS` has the standard labels:
- `KEYS.move` ('WASD / ARROWS'), `KEYS.action` ('SPACE'), `KEYS.upDown`, `KEYS.leftRight`;
- `KEYS.mouse` ('MOUSE'), `KEYS.click` ('CLICK'), `KEYS.rightClick`, `KEYS.wheel`.

Anything else is a plain string: `['R', 'Reload']`, `['SHIFT', 'Sprint']`.

The title card speaks the player's device. On a pad, `KEYS.move` reads LEFT STICK, and
`['R', 'Reload']` reads X when a button's key is R. On a phone they read STICK and RELOAD. The rows'
words also label the touch buttons, so write them as short verbs ("Jump", "Dash").
