# Input System Source: `src/dusk/input/`, platform layers in `src/dusk/input/` ## Architecture The input system has two layers: 1. **Action layer** (`inputaction_t`) -- named gameplay inputs, e.g. UP, DOWN, ACCEPT, CANCEL. This is what game code reads. 2. **Button layer** (`inputbutton_t`) -- physical hardware inputs, e.g. keyboard key, gamepad button, analog axis, mouse axis. Multiple buttons can bind to the same action (the highest value wins). The platform layer implements two hooks: - `inputUpdatePlatform()` -- read hardware state once per frame - `inputButtonGetValuePlatform()` -- return the analog value [0.0, 1.0] for a given button ## Defined actions Actions are defined in `src/dusk/input/input.csv` and code-generated into the `inputaction_t` enum. Current values: | Constant | Meaning | |----------|---------| | `INPUT_ACTION_NULL` | Invalid / sentinel (0) | | `INPUT_ACTION_UP` | Up direction | | `INPUT_ACTION_DOWN` | Down direction | | `INPUT_ACTION_LEFT` | Left direction | | `INPUT_ACTION_RIGHT` | Right direction | | `INPUT_ACTION_ACCEPT` | Confirm / primary action | | `INPUT_ACTION_CANCEL` | Back / secondary action | | `INPUT_ACTION_RAGEQUIT` | Quit the application | | `INPUT_ACTION_CONSOLE` | Toggle debug console | | `INPUT_ACTION_POINTERX` | Mouse / pointer X axis | | `INPUT_ACTION_POINTERY` | Mouse / pointer Y axis | | `INPUT_ACTION_COUNT` | Total count (not a valid action) | ## Global state ```c extern input_t INPUT; // INPUT.actions[INPUT_ACTION_COUNT] -- all action states // INPUT.platform -- platform-specific data ``` ## Reading actions (game code) ```c // Analog value this frame (0.0 - 1.0) float_t inputGetCurrentValue(inputaction_t action); // Analog value last frame float_t inputGetLastValue(inputaction_t action); // Boolean helpers (built on current/last values) bool_t inputIsDown(inputaction_t action); bool_t inputWasDown(inputaction_t action); bool_t inputPressed(inputaction_t action); // was up, now down bool_t inputReleased(inputaction_t action); // was down, now up // Single axis from a neg + pos pair of actions (returns [-1, 1]): float_t inputAxis(inputaction_t neg, inputaction_t pos); // 2D axis from four actions (negX/posX/negY/posY): void inputAxis2D( inputaction_t negX, inputaction_t posX, inputaction_t negY, inputaction_t posY, vec2 result ); // Same four-action axis, normalized to a unit vector via atan2: void inputAngle2D( inputaction_t negX, inputaction_t posX, inputaction_t negY, inputaction_t posY, vec2 result ); // Deadzone filter (applied to raw axis values) float_t inputDeadzone(float_t value, float_t deadzone); ``` ## Binding buttons to actions ```c void inputBind(inputaction_t action, inputbutton_t button); ``` Each platform's init function calls `inputBind` to wire its hardware buttons to the standard action IDs. Game code should never need to call `inputBind` -- it is set up once during platform init. ## Button types ```c INPUT_BUTTON_TYPE_KEYBOARD // SDL scancode (SDL2 targets only) INPUT_BUTTON_TYPE_POINTER // Mouse axes: X, Y, Z, WHEEL_X, WHEEL_Y INPUT_BUTTON_TYPE_TOUCH // Touch (defined, not fully implemented) INPUT_BUTTON_TYPE_GAMEPAD // Digital gamepad buttons INPUT_BUTTON_TYPE_GAMEPAD_AXIS // Analog axes (-1.0 to 1.0 internally) ``` ## Events Each action has `onPressed` and `onReleased` event callbacks. Subscribe via the event system (see `.claude/events.md`): ```c eventSubscribe(&INPUT.actions[ACTION_ACCEPT].onPressed, myCallback, NULL); ``` ## Platform implementations ### SDL2 (`src/dusksdl2/input/`) Handles Linux, Knulli, and PSP (PSP adds its own button mapping layer on top of SDL2). - Keyboard: SDL scancode array from `SDL_GetKeyboardState()` - Pointer: normalized mouse position (0.0-1.0), scroll axes - Gamepad: first available `SDL_GameController`; axis values normalized to [-1.0, 1.0] with deadzone (default 0.2f via `inputGetDeadzoneSDL2`) ### Dolphin -- GameCube / Wii (`src/duskdolphin/input/`) Uses `libogc` PAD API. No keyboard or pointer input -- trying to use those button types is a compile-time `#error`. - Gamepad: `PAD_ScanPads()` + `PAD_ButtonsHeld()` for pad 0 - Axes: left stick X/Y, C-stick X/Y, L/R triggers (6 total) - Deadzone: hardcoded 0.2f - Default bindings set at init: D-pad/L-stick = directional actions, A = ACCEPT, B = CANCEL, X = CONSOLE, Start = RAGEQUIT ### PSP (`src/duskpsp/input/`) Layered on top of SDL2. `inputInitPSP()` remaps SDL2 controller button constants to PSP button names, then calls `inputBind` to wire them: | PSP button | SDL2 constant | |------------|---------------| | Cross | `SDL_CONTROLLER_BUTTON_A` | | Circle | `SDL_CONTROLLER_BUTTON_B` | | Triangle | `SDL_CONTROLLER_BUTTON_Y` | | Square | `SDL_CONTROLLER_BUTTON_X` | | L / R | `SDL_CONTROLLER_BUTTON_LEFTSHOULDER` / `RIGHTSHOULDER` | | L-Stick | `SDL_CONTROLLER_AXIS_LEFTX/Y` | ### Vita (`src/duskvita/input/`) Layered on top of SDL2 (via vitaSDL2). Behaviour is similar to PSP -- no keyboard, no pointer, gamepad only. ## JS module (`Input`) The input system is exposed to JS as the global `Input` object with static methods. Action constants are pre-defined as numeric properties on the `Input` object (e.g. `Input.ACCEPT`, `Input.UP`): ```js // Check if the accept button is held this frame: if(Input.isDown(Input.ACCEPT)) { ... } // Was the cancel button just pressed? if(Input.pressed(Input.CANCEL)) { ... } // Analog value for the right trigger: var val = Input.getValue(Input.RIGHT); // Single axis (-1 to 1) from a neg/pos pair: var h = Input.axis(Input.LEFT, Input.RIGHT); ``` All `Input.*` action constants match the `INPUT_ACTION_*` enum values from the C layer (UP, DOWN, LEFT, RIGHT, ACCEPT, CANCEL, RAGEQUIT, CONSOLE, POINTERX, POINTERY). ## Platform capability notes | Feature | Linux/Knulli | PSP | Vita | GameCube/Wii | |---------|-------------|-----|------|--------------| | Keyboard | Yes (SDL2) | No | No | No | | Pointer/Mouse | Yes (SDL2) | No | No | No | | Gamepad | Yes (SDL2) | Yes (SDL2) | Yes (SDL2) | Yes (PAD) | | Analog axes | Yes | L-Stick only | L-Stick, R-Stick | L-Stick, C-Stick, Triggers | | Touch | Defined, not implemented | -- | -- | -- |