diff --git a/.claude/display-shader.md b/.claude/display-shader.md index 5a0e4672..31046b55 100644 --- a/.claude/display-shader.md +++ b/.claude/display-shader.md @@ -43,8 +43,21 @@ typedef union shadermaterial_u { } shadermaterial_t; ``` -`shaderunlitmaterial_t` typically holds a `texture_t *` and a -tint colour. Check `shaderunlitmaterial.h` for the exact fields. +`shaderunlitmaterial_t` fields: + +```c +typedef struct { + color_t color; // tint colour (multiplied with the texture sample) + texture_t *texture; // NULL uses TEXTURE_WHITE (solid colour draw) +} shaderunlitmaterial_t; +``` + +The shader exposes uniforms `u_Proj`, `u_View`, `u_Model` (mat4), +`u_Texture` (sampler), and `u_Color` (vec4). They are uploaded via +`shaderUnlitSetMaterial(shader, material)`. + +A global singleton `SHADER_UNLIT` is the live shader object; +`SHADER_UNLIT_DEFINITION` is its platform definition descriptor. To use a shader material on a renderable entity: diff --git a/.claude/events.md b/.claude/events.md index 79ad1007..763024ca 100644 --- a/.claude/events.md +++ b/.claude/events.md @@ -97,6 +97,13 @@ eventSubscribe(&state.onComplete, myHandler, myUserPtr); ## Where events are used -- `inputactiondata_t`: `onPressed`, `onReleased` per action -- Any engine subsystem that exposes hooks (network connect/disconnect, - asset batch completion, etc.) +| Subsystem | Event | Fires when | +|-----------|-------|-----------| +| `inputactiondata_t` | `onPressed`, `onReleased` | Action button state changes | +| `uitextbox_t` | `onPageComplete` | Typewriter scroll reveals the full page | +| `uitextbox_t` | `onLastPage` | Last page is fully scrolled | +| `uifullbox_t` | `onTransitionEnd` | Colour transition animation completes | +| `uiloading_t` | `onShow` / `onHide` | Loading indicator fade completes | +| Asset system | `assetbatch_t` callback | All entries in a batch reach LOADED/ERROR | + +See `.claude/ui.md` for the UI event details. diff --git a/.claude/input.md b/.claude/input.md index b22abdf1..3075fa99 100644 --- a/.claude/input.md +++ b/.claude/input.md @@ -17,6 +17,26 @@ The platform layer implements two hooks: - `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 @@ -40,14 +60,22 @@ 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 -// 2D axis helpers +// 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 horiz, - inputaction_t vert, - vec2 out + 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 ); -float_t inputAngle2D(inputaction_t horiz, inputaction_t vert); -void inputAxis(inputaction_t action, float_t *out); // Deadzone filter (applied to raw axis values) float_t inputDeadzone(float_t value, float_t deadzone); @@ -119,12 +147,41 @@ constants to PSP button names, then calls `inputBind` to wire them: | 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 | GameCube/Wii | -|---------|-------------|-----|--------------| -| Keyboard | Yes (SDL2) | No | No | -| Pointer/Mouse | Yes (SDL2) | No | No | -| Gamepad | Yes (SDL2) | Yes (SDL2) | Yes (PAD) | -| Analog axes | Yes | L-Stick only | L-Stick, C-Stick, Triggers | -| Touch | Defined, not implemented | -- | -- | +| 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 | -- | -- | -- | diff --git a/.claude/scene.md b/.claude/scene.md index d8fc0589..6f7afd82 100644 --- a/.claude/scene.md +++ b/.claude/scene.md @@ -86,10 +86,38 @@ The shader used for each renderable: ## Transitioning between scenes -To move to a new scene from JS, call the scene module's transition -function (exact API in the `scene` JS module). The C side defers the -actual switch to the start of the next `sceneUpdate` call so the -current tick completes cleanly before any dispose runs. +Scene transitions are handled entirely in JS via the `Scene` global. +The `Scene` object is a singleton with: + +```js +// Switch to a new scene. Calls dispose() on the current scene, then +// init() on the new one. Both happen synchronously this tick. +Scene.set(newSceneObject); + +// The current scene object (may be null): +Scene.current +``` + +Typical scene-switch pattern: + +```js +// Inside a scene's update or event handler: +const nextScene = require("scenes/gameplay.js"); +Scene.set(nextScene); +``` + +`Scene.set` is synchronous -- it calls `dispose` on the old scene and +`init` on the new scene before returning. If `init` needs async work +(loading assets), use an async function and `await` inside `init`: + +```js +nextScene.init = async function() { + await batch.load(); // wait for assets before proceeding +}; +``` + +The C side does not defer the transition; the switch happens inside +the current `sceneUpdate` call. ## Relationship to the engine loop