# Game cursors

Games own cursor artwork and gameplay meaning. UI controls provide automatic cursor roles. Games choose native OS rendering or software rendering through the game UI on macOS and Windows. Cursor appearance never changes pointer capture, focus, or input ownership. A captured gameplay pointer stays hidden even when a game requests a different cursor.

## Register image assets

Author one PNG in the game's assets tree. Use ordinary Shinra import settings in its `.png.shinmeta.json` sidecar (settings only, not a second image):

```json
{
  "texture": {
    "uncompressed": true,
    "generate_mipmaps": false,
    "premultiplied_alpha": false,
    "srgb": false,
    "address_u": "clamp",
    "address_v": "clamp"
  }
}
```

Shinra cooks the PNG into one `.shintexture` in the managed cook/product output. Native cursors need CPU pixels, so cursor imports retain lossless straight-alpha RGBA8 instead of GPU block compression. Neither raw `.rgba` files nor embedded-image manifests belong in game source.

Register the extension-free asset URI once per World:

```zig
try hi.ui().setCursorAsset(.default, .{
    .path = "asset://./ui/cursors/arrow",
    .size = .{ 32, 32 },
    .hotspot = .{ 0.125, 0.09375 },
});
```

The host copies the path, resolves pending asset cooks, and refreshes the sprite after an editor recook. Registration needs no game-side retry loop. Existing artwork remains until its replacement is ready; a role without artwork uses the normal fallback. Invalid cooked images produce a diagnostic and preserve the previous sprite. Use `setCursorAsset(.default, null)` to remove it.

`size` is in logical UI points; the host applies display and UI scaling. `hotspot` is the normalized click location within the sprite, from 0 inclusive to 1 exclusive. Use `{ 0.5, 0.5 }` for a centred crosshair. Source images must be plain 2D RGBA8, straight alpha, one mip, at most 256×256. Higher source resolution keeps cursors sharp on high-DPI screens.

The `hikari-games/example/src/cursor_theme.zig` uses six PNGs in `assets/ui/cursors/`, displayed at 32 logical points from 128×128 images. Its arrow, hand, text caret, busy indicator, cyan sonar rings and amber impact burst register on scene entry and clear on exit. Edit those PNGs directly, or run `python3 ../hikari-games/example/tools/gen_cursors.py` to regenerate the original geometric designs. Art changes use normal asset recooking and do not require recompiling the game module.

## Procedural sprites

Games that generate pixels in memory can use `hi.ui().setCursorSprite`. It accepts borrowed straight-alpha RGBA8 and copies the pixels during the call; it does not require a `.rgba` file.

```zig
try hi.ui().setCursorSprite(.custom_0, .{
    .pixels = generated_rgba,
    .width = 64,
    .height = 64,
    .size = .{ 32, 32 },
    .hotspot = .{ 0.5, 0.5 },
});
```

Asset and procedural registrations share the same role slots. Registering procedural pixels replaces that role's asset binding. Passing null to either registration API clears the role.

## Choose the renderer

```zig
const ui = hi.ui();
ui.setCursorMode(.software); // Draw the registered sprite above the game HUD.
ui.setCursorMode(.native);   // Restore OS rendering (the default).
const requested_mode = ui.cursorMode();
_ = requested_mode;
```

The mode is a persistent per-World preference; `setCursor` remains a per-frame role override. Both modes use the same sprites, hover hints, size, hotspot, and input coordinates. Software mode draws one clipped textured quad after the game HUD, including its modals, but below editor chrome. Its movement follows game presentation; native movement remains independent of game rendering. Sprite animation can select pre-registered custom roles each frame in either mode.

Software rendering requires registered role or default artwork. Until its texture is GPU-ready, the host keeps the native sprite visible. Missing artwork falls back to native platform shapes. The input driver uses a cached transparent native image while the software quad is active; this does not alter OS hide/show counters or pointer capture. If creating that image fails, the host retains native rendering. `cursorMode()` reports the requested preference during fallback too.

The session selects appearance once after UI building. Leaving the viewport, entering editor chrome, detaching the camera, or capturing the pointer suppresses the software quad and restores the normal native policy. This is not a virtual-pointer or gamepad-navigation API; the software cursor follows the same physical mouse input as the native cursor.

The focused AI demos install a scene-specific cursor theme. Leaving a demo removes its theme and resets the mode to native; games can choose software rendering with `setCursorMode`.

## Roles and overrides

| Role | Selection |
|---|---|
| `default` | Free pointer over ordinary game content |
| `pointer` | Enabled buttons, sliders, and controls composed from buttons |
| `text` | Editable text fields; retained throughout a text-selection drag |
| `busy`, `crosshair`, `not_allowed` | Explicit game request |
| `grab`, `grabbing`, `resize_horizontal`, `resize_vertical` | Explicit game request |
| `custom_0` … `custom_15` | Game-specific situations, such as noise placement or targeting |

Automatic roles reset each UI frame. A game can override the role for the current frame:

```zig
hi.ui().setCursor(.busy);     // While the game's loading state remains active.
hi.ui().setCursor(.custom_0); // A game-specific interaction.
hi.ui().setCursor(null);      // Return to automatic UI selection this frame.
```

An explicit request wins over automatic hints. Make a world-interaction request only after checking that the pointer is over the world; AI Perception & Brains uses `worldPointer()` and excludes its panel before selecting its noise cursor. Otherwise a permanent crosshair override would also cover buttons.

Missing role artwork falls back to the game's `default` sprite, then to a platform cursor when no default sprite is registered. macOS uses an arrow for the native busy fallback; register a busy sprite for a consistent loading appearance. This first API displays static sprites. A game can author several custom frames and select them on its UI clock if it needs animation.

Remove a role's custom artwork with `try ui.setCursorSprite(.text, null)`. Registrations last for the World, including scene changes within it; they are released when the World terminates. Reloaded game modules must register their theme again. No game-module pixel pointers are retained.

## Ownership and performance

Game cursors apply only inside the primary game viewport while its pointer is free. Editor chrome, detached editor camera interaction, other windows, and other applications retain their own cursor ownership. Entering Play or choosing a sprite never forces capture. Standalone movement scenes can continue requesting capture through the existing gameplay input API.

Each World accepts up to 32 role slots and 1 MiB of copied pixel data. Source dimensions are limited to 256×256, logical sizes to 128 points per axis, and native output to 256×256 pixels. Invalid replacement leaves the old sprite intact. The input system keeps a bounded cache of 32 native cursor objects keyed by World, registration generation, and output size. Native movement reuses these objects. Software rendering lazily uploads each used sprite once through the existing texture residency queue and reuses its GPU handle. One extra transparent native cursor is cached per input system. Warm cursor movement allocates no pixel memory and neither mode consumes the game HUD sprite-atlas slot. Failed native creation uses the platform fallback and is retried after a new registration or output size.

Software cursor textures use the shared allocator so queued uploads may safely outlive sprite removal. Replacements are transactional; pending uploads retain the old pixels until applied or cancelled. World termination queues GPU releases before Play retirement transfers its command queue. Render frames carry generation-checked texture snapshots, never borrowed game pixel pointers. The UI shader has a dedicated third texture binding for the cursor.

The cursor API is versioned through the UI API table and the driver contract. Rebuild the editor host and game module together after changing either.
