Pointer input
Status: built 2026-10-09 (docs/design/audit-2026-10.md, Wave 4: "Pointer input: picking, mouse aim, UI clicks"). Read with ADR-0002 (games are data), ADR-0004 (replayable simulation), ADR-0007 (Compatibility is the baseline), ARCHITECTURE §5.2 (input), docs/design/m1-player.md §6 and §8 (input frames, tours) and docs/design/touch-controls.md.
Until now the mouse did nothing in a game: input was actions (keys, a pad, touch buttons) that the InputMap maps into input frames. That ruled out point-and-click, mouse aim, strategy, cards and tower defense, and it left the game's own menus to the keyboard. This makes the pointer (a mouse, or a finger tapping the game's frame) a first-class input that replays.
1. What reaches the simulation
The pointer reaches the simulation only through the input frame (GwInputFrame), and only in
world terms the view has already worked out:
| Field | JSON | What |
|---|---|---|
pointing, point |
"point": [x, z] |
where the pointer is on the ground, world metres on a grid of 1/64 m (POINT_STEP) |
clicked |
"click": true |
the click button went down over the world this tick, or a tap |
target |
"target": "villager.2" |
on a click, what was under it as the view picked it: an entity id, prop: and a prop's stable id, or nothing for the ground |
No pixel, ray or camera float enters. The view casts the camera's ray through the pointer, finds
the ground (GwPick.ground: it marches the world's heightfield, then bisects) and the thing in
front of it (GwPick.thing: entities as upright segments of their move radius and a body's
height, standing props of a tile's breadth and their Biome height; the one the ray reaches
first), and puts the answer in the frame. The grid is a power of two, so a point is an exact
float and comes back exactly from a replay's JSON (the same reason state keeps 1/1024).
So a recording (GwReplay), a trace and a Playtest carry the pointer as data, and a replay with
pointer input gives the same state hash with no renderer at all. A frame with no pointer is
exactly the frame it always was, so every game that doesn't use the pointer keeps every hash.
Point, not direction. The frame carries where the pointer is, not an aim direction from the
player. The simulation works out the direction from the body's own (quantized) position
(GwKitSystem.pointer_aim), so aim is exact relative to where the body really is, and the same
point serves a walk, a use and a rule.
2. What the InputMap says
The InputMap gains pointer (schemas/v1/input.schema.json, $defs/Pointer):
"pointer": { "aim": true, "click": "use", "button": "left" }
- aim (default false): the body the player drives faces the point and aims there. kit.move turns it to the point (strafing, when it walks the other way), kit.ranged's fire shoots there, kit.melee swings there. A pushed aim stick wins while it is pushed.
- click (default none): what a click on the world does.
walkwalks the world's route to the point or the thing clicked;usedoes that, then uses the thing as the use key would (point-and-click);selectmoves nothing, so only rules answer. Every click butnonesendspointer.clicked {target, x, z}(kit.core) for rules. - button (default left): the mouse button that clicks. A mouse button still presses any
action bound to it (
actions[].mouse), so the click button should be bound to none.
Kits read it through GwKitSystem.pointer() (GwPointerSpec). Leaving it out means the mouse
plays nothing in the world; menus and Screens answer the mouse either way (§5).
3. Click to walk, click to use
A click is an order the simulation keeps, not a stream of presses an autopilot makes:
- kit.move (input-driven bodies, click walk or use): a click sets
comp.move.clickTo(the thing's position, followed as it moves, or the point) and the body walks the world's route there (route_next, the route the goto tour verb and ai bodies take) while no key or stick moves it. It arrives withinarriveMof a point or 1.1 m of a thing, or stops where the route goes no further, and saysmove.arrived {entity, reached}(latcharrived). A key or a stick ends it. - kit.harvest (click use,
GwClickUse): a click on something the use key works (a usable, a pile, a standing prop with a take) asks for it to be used. Once it is in reach the body turns to it (face_at, at most 30 ticks) and, once it is in front,use()works it: the same rule the key and the highlight obey (harvest_target), so a click never does what the key won't. A click on anything else only walks there.
Nothing is written to state until a click comes, so a game that never clicks hashes as before.
Because the order is state, a save made mid-walk carries it, and a replay needs only the click's
frame, not the walk's. A click on something already in reach of its use is used in the tick it
comes and starts no walk: kit.harvest steps before kit.move, so its stop_click marks that
tick's click answered before kit.move would take it.
Bots that click. The tour verbs click_to place:<name> | near <arch> (kit.move: the ground,
a metre short of a person, so a click doesn't use them) and click_use prop:<kind> | <arch>
(kit.harvest: one click on the thing, then the walk, the turn and the use) click once and wait.
The bot seek_click is the seek bot with only a mouse: its plan's walks become click_to, a walk
up to something with its use one click_use, and the Screens' presses (a talk's lines and
choices, make, use_item) stay, since a click on the Screen sends them. It never fumbles, and a
goal only a key reaches (fight, a use with nothing clicked) fails, "takes a key". So a
Playtest with "bot": "seek_click" says a point-and-click game plays without a keyboard:
Thornwick's opening (talk to Maren and Odo, gather berries, reach the clearing) is met in 1376
ticks through L2. The monkey clicks too, in a game whose clicks do something: half its walks are a
click instead, on a live thing within 12 m of the player or the ground there, from a stream of its
own (bots/monkey/<n>/click), so a game whose clicks do nothing gets the frames it always got.
Thornwick's fuzz clicks about 25 times a minute: it walks up to Odo and talks, with every
invariant kept.
4. Where the pointer comes from
- A person's run (
GwPointerSensor, a node first in the game's viewport). Godot has already taken the pointer's position through the window's scale, the touch layout's frame, a hosted play view's size and its design size by the time an event reaches inside the viewport, so the sensor reads the viewport's own pixels everywhere: the desktop player, the web player, and the Studio's play view (a SubViewport,GwFrameServer.design). It hears only what the game's Screens didn't take (kit.ui hears unhandled input first).GwPointerInputlatches clicks between ticks, as keys are latched, and fills each device frame: the click's point and target, else (aim) the live pointer's point. A pad's stick or button hands the aim back to the pad until the mouse moves again. Over something a click would walk to or use, the frame shows the pointing hand, and while a click's walk is under way kit.move's view rings its end on the ground (click_mark), as point-and-click games show where you are going. - Touch. A tap on the game's frame is already a left click there (docs/design/touch- controls.md); it now is for a game whose pointer clicks too, not only one that binds the left button. A tap clicks but never aims: a twin-stick on a phone aims with its right stick.
- Tours (
GwTourPointer), by name, never by pixel:point at WHATandpoint off,aim DEG(round the player, as facings go: 0 is +X, 90 is -Z),click [WHAT]. WHAT is an entity id,prop:<kind>(the nearest standing one, then that one) orplace:<name>. A click walks awalkorusegame there:await arrived 20. - Bots don't point yet.
5. Menus and Screens
Menus and the game's Screens answer the mouse as they answer keys, hit-tested against what kit.ui
laid out, with or without a pointer in the InputMap:
- The menu every game has (
GwPlayerMenu, drawn by kit.ui on ui.pause and ui.options): the row under the pointer is chosen as it moves, a click takes it (as OK), and the right button goes back. kit.ui says which row is where (GwGameMenu.row_at); the player works the play view's pixels into the frame's (GwFrameServer.to_frame). Nothing the mouse does on the open menu reaches the game. - kit.ui's Screens (
GwUiPointer): a click on a key hint presses its action (the HUD's "[E] take driftwood"), through the stage, as a key's press would reach the next frame, so a replay repeats it. On the title and on the end card any click presses the hint under it, else the screen's first ("[ENT] begin", "play again"). While a talk runs, a click on one of its choices takes it (the presschoose:<link id>kit.core's dialogue reads), and anywhere else goes on (advance). On the slate, the row or button under the pointer is chosen as it moves, a click takes it, a click on the key strip does what that key does, a click off the device closes it, the right button goes back and the wheel moves the choice. A click kit.ui took is handled, so it is never also a click on the world.
What a Screen laid out is hit-tested where its legibility check reads it (GwUiScreen.hit walks
the widgets drawn last first, each answering in its own pixels: a hint's cap and words, a list's
row bars, a button's bar; the slate's key strip is hit where it was drawn), so the place a person
clicks is the place the text is drawn.
Grid puzzles (2026-10-21). kit.grid's view answers pick_flat with the middle of the cell under
the pointer (x its column, z its row), so a click or a tap on a cell walks the avatar there
(pointer: {click: walk}, on in the puzzle_grid template): the fewest steps over cells free on
its layer, pushing nothing on the way, a turn every 6 ticks (GwGridWalk, GwGridSystem.walk,
saved with the grid). A click on the cell next to it plays that one step, so clicking a drum
pushes it, and two in a row stay as the key would leave them. A key takes over; a refused step
ends the walk. Tours click a cell by name: click cell:5,2. Lamplighter's first level is solved
by clicks alone in 9 turns.
6. Left for later
- A platformer's world: the pointer reaches a world through its 3D camera, a top-down 2D world's view or a grid puzzle's board only.
- Drag (box select, drag and drop), the wheel, hover for rules (a tooltip a rule writes).
- Hover over a talk's choices: a click takes one, but only the keys move the lit one.