Files
dusk/.claude/input.md
T
2026-06-16 13:07:21 -05:00

6.1 KiB

Input System

Source: src/dusk/input/, platform layers in src/dusk<platform>/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

extern input_t INPUT;
// INPUT.actions[INPUT_ACTION_COUNT] -- all action states
// INPUT.platform                   -- platform-specific data

Reading actions (game code)

// 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

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

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):

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):

// 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 -- -- --