# `@combos-fun/plugin-renderer-3d-sprite-animation` — Agent notes

Animates spritesheet UVs on a double-sided Three.js plane for effects, animated billboards, and flat characters.

Prerequisite: follow the parent `@combos-fun/plugin-renderer-3d` skill for system order, named resources, pose seeding, hierarchy, and stale-async handling.

## Public API

```ts
import {
  SpriteAnimation3D,
  SpriteAnimation3DSystem,
  type SpriteAnimation3DParams,
} from '@combos-fun/plugin-renderer-3d-sprite-animation';
```

`componentName = 'SpriteAnimation3D'`; `systemName = 'SpriteAnimation3DSystem'`.

## Parameters

| Field | Type | Default |
|-------|------|---------|
| `resource` | `string` | `''`; empty on ADD is a silent no-op |
| `autoPlay` | `boolean` | `true` |
| `speed` | `number` | `100` milliseconds per frame |
| `positionX/Y/Z` | `number` | `0` |
| `rotationX/Y/Z` | `number` | `0` radians |
| `scaleX/Y` | `number` | `1` |

There is no `scaleZ` and no public `play()`, `stop()`, or `gotoAndPlay()`.

## Resource and lifecycle

A `SPRITE_ANIMATION` resource needs both `src.image` and `src.json`. The system reads `data.image` and a top-level `data.json.frames`; `meta.size` is optional and falls back to image dimensions. It creates its own Three texture and does not overwrite the Pixi resource instance.

Loading is async and validates the request before attachment. Resource replacement/removal invalidates old work and disposes an already-created stale texture. Removing an attached animation disposes geometry, material, and texture.

`autoPlay` initializes the private `playing` flag only when the sheet loads; changing `autoPlay` later has no effect. `speed` is read every frame, so runtime changes affect the next frame interval without reloading. Changing `resource` rebuilds the entry.

The first frame determines plane aspect ratio. Array frames retain array order; object-map frame keys are sorted lexically.

## Package-specific pitfalls

- Missing JSON, missing frames, or malformed `{ frame: { x, y, w, h } }` entries fail loading.
- A one-frame sheet appears static.
- Use an array when lexical filename sorting is not the desired sequence.
- `speed` is a frame duration, not an FPS multiplier.

## Minimal example

```ts
const fx = new GameObject('explosion');
fx.addComponent(new SpriteAnimation3D({
  resource: 'explosion',
  autoPlay: true,
  speed: 80,
  positionZ: -1,
}));
```

## Verification

`pnpm --filter @combos-fun/plugin-renderer-3d-sprite-animation run build`
