aispritejs 0.5.7 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -231
- package/README_ZHTW.md +59 -228
- package/dist/atlas/index.cjs +13 -13
- package/dist/atlas/index.js +1 -1
- package/dist/{chunk-F4MDNT4Y.js → chunk-6E4ILDBY.js} +20 -2
- package/dist/chunk-6E4ILDBY.js.map +1 -0
- package/dist/{chunk-Y663UAIF.cjs → chunk-QF5N7LOI.cjs} +20 -2
- package/dist/chunk-QF5N7LOI.cjs.map +1 -0
- package/dist/index.cjs +6 -6
- package/dist/index.js +1 -1
- package/dist/pixi/index.cjs +2 -2
- package/dist/pixi/index.js +1 -1
- package/llms-full.txt +111 -509
- package/llms.txt +8 -38
- package/package.json +1 -1
- package/schemas/aispritejs-graph.schema.json +5 -4
- package/dist/chunk-F4MDNT4Y.js.map +0 -1
- package/dist/chunk-Y663UAIF.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,269 +1,97 @@
|
|
|
1
1
|
# aispritejs
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/islumina/aispritejs/actions/workflows/ci.yml)
|
|
5
|
-
[](LICENSE)
|
|
6
|
-
[](https://www.anthropic.com/claude-code)
|
|
7
|
-
[](README_ZHTW.md)
|
|
3
|
+
Input-driven, renderer-agnostic 2D sprite animation runtime. A JSON graph maps Number/Boolean/Trigger inputs to visual states and frames; adapters bind the chosen frame to a renderer.
|
|
8
4
|
|
|
9
|
-
>
|
|
5
|
+
> **Status: 0.5.8 - stable family-aligned surface.** Core, PixiJS adapter, atlas parser, and JSON Schema subpath are shipped.
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
## Install
|
|
12
8
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Why aispritejs
|
|
18
|
-
|
|
19
|
-
- **Input-driven, not name-driven.** You set parameters (`speed=4`, `isGrounded=false`, `fireTrigger("jump")`), not animation names. Visual transitions live in data, decoupled from game code.
|
|
20
|
-
- **Renderer-agnostic core.** The state machine computes the active frame from delta-time + inputs; it never imports PixiJS or touches the DOM. Adapters map the result to textures.
|
|
21
|
-
- **Visual ≠ logic.** This is strictly a *visual* animator. It is **not** a game-logic FSM and does **not** depend on `aifsmjs`. Drive it from any logic layer (plain code, an FSM, an ECS) — they compose by convention, never by dependency.
|
|
22
|
-
- **Tiny + fast.** O(1) input lookups, O(N) checks over transitions leaving the current state, no per-frame allocation.
|
|
23
|
-
|
|
24
|
-
## When you DON'T need aispritejs
|
|
25
|
-
|
|
26
|
-
`aispritejs` earns its keep when frame selection is **driven by runtime inputs** across **several visual states**. Below that threshold, reach for PixiJS directly — there is nothing to gain from a state machine:
|
|
27
|
-
|
|
28
|
-
- **A single sprite / one static image** → a plain PixiJS [`Sprite`](https://pixijs.download/release/docs/scene.Sprite.html). No animation, no graph.
|
|
29
|
-
- **One looping clip with no branching** (a coin spin, a torch flicker) → a PixiJS [`AnimatedSprite`](https://pixijs.download/release/docs/scene.AnimatedSprite.html) (`AnimatedSprite.fromFrames(...)`, `.play()`). One clip that always plays the same way needs no inputs.
|
|
30
|
-
- **No texture atlas** (you aren't packing frames into a spritesheet) → load images directly; `aispritejs` reads its frames from a PixiJS-v8 atlas (`animations` / `frames`).
|
|
31
|
-
- **No multi-state switching** — if your code already knows exactly which clip to play and just calls `.play()` / `.gotoAndStop()`, you don't need a transition graph.
|
|
32
|
-
|
|
33
|
-
Reach for `aispritejs` when you have **input-driven, multi-state visual switching backed by a texture atlas** — e.g. `idle ⇄ walk → jump`, or a one-shot hit FX fired by a trigger — where which frame is on screen is a function of `speed` / `isGrounded` / `attack`, not a hard-coded `play()` call.
|
|
34
|
-
|
|
35
|
-
## Mental model
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
inputs ─▶ [transition graph] ─▶ active state ─▶ (Δt) ─▶ active frame ─▶ adapter ─▶ texture
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add aispritejs
|
|
11
|
+
pnpm add pixi.js # only when using aispritejs/pixi
|
|
39
12
|
```
|
|
40
13
|
|
|
41
|
-
- **Inputs** — `Number` (continuous, e.g. `speed`), `Boolean` (toggle, e.g. `isGrounded`), `Trigger` (one-shot; auto-resets after a transition consumes it, e.g. `jump` / `attack`).
|
|
42
|
-
- **States** — an animation key (into the atlas `animations`) + loop / on-end behaviour + optional speed multiplier.
|
|
43
|
-
- **Transitions** — from a state (or **Any State**) to another when conditions over inputs hold (`Equals` / `NotEquals` / `GreaterThan` / `LessThan` / `Trigger`). The highest-priority satisfied transition wins.
|
|
44
|
-
- **`update(dt)`** — advances the playback timer; evaluates transitions (switching state, firing `onStateChange`, consuming triggers); computes the current frame from the animation's per-frame durations + loop; fires `onComplete` when a non-looping clip ends.
|
|
45
|
-
|
|
46
|
-
## Quick start — core (zero-dep)
|
|
47
|
-
|
|
48
14
|
```ts
|
|
49
15
|
import { createSpriteAnimator } from "aispritejs";
|
|
50
|
-
|
|
51
|
-
const anim = createSpriteAnimator(graph); // graph = { inputs, states, transitions, animations }
|
|
52
|
-
|
|
53
|
-
anim.setInput("speed", 4);
|
|
54
|
-
anim.setInput("isGrounded", true);
|
|
55
|
-
anim.fireTrigger("jump");
|
|
56
|
-
|
|
57
|
-
anim.onStateChange((to, from) => {/* ... */});
|
|
58
|
-
anim.onComplete((state) => {/* ... */});
|
|
59
|
-
|
|
60
|
-
// in your render loop:
|
|
61
|
-
anim.update(deltaMs);
|
|
62
|
-
const frameKey = anim.activeFrameKey; // hand to your renderer
|
|
63
16
|
```
|
|
64
17
|
|
|
65
|
-
## Quick
|
|
66
|
-
|
|
67
|
-
The `aispritejs/pixi` subpath binds the core to a `PIXI.Sprite`. `pixi.js` is an **optional** `peerDependency`, imported **type-only** — the built adapter contains no runtime `pixi.js` require, and the core never imports it.
|
|
18
|
+
## Quick Start - Core
|
|
68
19
|
|
|
69
20
|
```ts
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const view = createPixiSpriteAnimator(sprite, graph, spritesheet);
|
|
75
|
-
|
|
76
|
-
// each frame:
|
|
77
|
-
view.update(deltaMs); // swaps the bound sprite's texture to the active frame,
|
|
78
|
-
// applying that frame's atlas anchor (texture.defaultAnchor)
|
|
79
|
-
|
|
80
|
-
view.setInput("speed", 4);
|
|
81
|
-
view.fireTrigger("jump");
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
It swaps the texture only when the active frame changes, and honours per-frame `duration` (via the core) and non-centre / foot pivots (via `texture.defaultAnchor`; pass `{ applyAnchor: false }` to manage the anchor yourself). `view.sprite` is the bound sprite; `dispose()` tears down the core without destroying the sprite.
|
|
85
|
-
|
|
86
|
-
Pass a plain `Sprite` — the adapter owns frame selection. (An `AnimatedSprite` is accepted since it extends `Sprite`, but its own playback is stopped on bind so it cannot fight the adapter for the texture.)
|
|
87
|
-
|
|
88
|
-
### Complete example — a 6-frame explosion (play-once FX)
|
|
89
|
-
|
|
90
|
-
The most common entry: a one-shot hit/impact FX (net-splash, muzzle flash) from a **6-frame explosion sprite sheet**, driven through the `/pixi` adapter. A `Trigger` fires a **non-looping** clip that plays once and auto-returns to a resting frame via `onEnd`. The full runnable version is [`examples/02-explosion-pixi/index.ts`](examples/02-explosion-pixi/index.ts) (`pnpm example:explosion`).
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
import { Assets, Sprite } from "pixi.js";
|
|
94
|
-
import { createPixiSpriteAnimator } from "aispritejs/pixi";
|
|
95
|
-
|
|
96
|
-
// A 6-frame explosion sprite sheet (PixiJS-v8 atlas): the `animations` block
|
|
97
|
-
// names the clip → its frame keys; `frames` carries per-frame durations.
|
|
98
|
-
const graph = {
|
|
99
|
-
animations: {
|
|
100
|
-
explosion: ["explosion_0", "explosion_1", "explosion_2", "explosion_3", "explosion_4", "explosion_5"],
|
|
101
|
-
idle: ["explosion_0"], // a 1-frame resting clip to hold between bursts
|
|
21
|
+
const anim = createSpriteAnimator({
|
|
22
|
+
inputs: {
|
|
23
|
+
speed: { type: "number", default: 0 },
|
|
24
|
+
jump: { type: "trigger" },
|
|
102
25
|
},
|
|
103
|
-
|
|
104
|
-
explosion_0: { duration: 40 }, explosion_1: { duration: 40 }, explosion_2: { duration: 40 },
|
|
105
|
-
explosion_3: { duration: 40 }, explosion_4: { duration: 40 }, explosion_5: { duration: 40 },
|
|
106
|
-
},
|
|
107
|
-
inputs: { detonate: { type: "trigger" } },
|
|
26
|
+
initial: "idle",
|
|
108
27
|
states: {
|
|
109
|
-
idle: { animation: "idle"
|
|
110
|
-
|
|
28
|
+
idle: { animation: "idle" },
|
|
29
|
+
run: { animation: "run", speed: 1 },
|
|
30
|
+
jump: { animation: "jump", loop: false, onEnd: "idle" },
|
|
111
31
|
},
|
|
112
32
|
transitions: [
|
|
113
|
-
{ from: "
|
|
33
|
+
{ from: "idle", to: "run", when: [{ input: "speed", op: "GreaterThan", value: 0 }] },
|
|
34
|
+
{ from: "*", to: "jump", when: [{ input: "jump", op: "Trigger" }] },
|
|
114
35
|
],
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const sheet = await Assets.load("explosion.json"); // a PIXI.Spritesheet
|
|
120
|
-
const sprite = new Sprite();
|
|
121
|
-
const fx = createPixiSpriteAnimator(sprite, graph, sheet);
|
|
122
|
-
|
|
123
|
-
// Fire the one-shot trigger on impact:
|
|
124
|
-
fx.fireTrigger("detonate");
|
|
125
|
-
|
|
126
|
-
// Drive the burst from your PixiJS render loop (e.g. `app.ticker`), advancing by
|
|
127
|
-
// elapsed ms: `app.ticker.add((ticker) => fx.update(ticker.deltaMS))`.
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
`update(dt)` plays `explosion_0…explosion_5` once (honouring each frame's `duration`); on the tick that completes the clip it fires `onComplete` and, because `boom` declares `onEnd`, auto-transitions to `idle` in that **same** tick — so the last frame isn't held, and `activeState` reads `idle` right after. Re-`fireTrigger("detonate")` to replay. The same graph runs with no renderer — see [`examples/02-explosion-pixi/index.ts`](examples/02-explosion-pixi/index.ts), which exercises the real adapter headlessly with plain `Texture` / `Sprite` instances.
|
|
131
|
-
|
|
132
|
-
## Data format (atlas)
|
|
133
|
-
|
|
134
|
-
`aispritejs` reads a **PixiJS v8-native** spritesheet atlas (`meta` / `frames` / `animations`) — the same shape the family's sprite pipeline emits — augmented with an `aispritejs` **input-driven** control block:
|
|
135
|
-
|
|
136
|
-
```jsonc
|
|
137
|
-
{
|
|
138
|
-
"meta": { "image": "sheet.png", "size": { "w": 1024, "h": 1024 }, "scale": "1" },
|
|
139
|
-
"frames": { /* PixiJS native: frame{x,y,w,h}, anchor, duration, trimmed, ... */ },
|
|
140
|
-
"animations": { "idle": ["idle_0", "idle_1"], "walk": ["walk_0", "..."], "jump": ["jump_0", "..."] },
|
|
141
|
-
|
|
142
|
-
"inputs": {
|
|
143
|
-
"speed": { "type": "number", "default": 0 },
|
|
144
|
-
"isGrounded": { "type": "boolean", "default": true },
|
|
145
|
-
"jump": { "type": "trigger" }
|
|
146
|
-
},
|
|
147
|
-
"states": {
|
|
148
|
-
"idle": { "animation": "idle", "loop": true },
|
|
149
|
-
"walk": { "animation": "walk", "loop": true },
|
|
150
|
-
"jump": { "animation": "jump", "loop": false }
|
|
36
|
+
animations: {
|
|
37
|
+
idle: ["idle_0"],
|
|
38
|
+
run: ["run_0", "run_1"],
|
|
39
|
+
jump: ["jump_0", "jump_1"],
|
|
151
40
|
},
|
|
152
|
-
|
|
153
|
-
{ "from": "*", "to": "jump", "when": [{ "input": "jump", "op": "Trigger" }], "priority": 10 },
|
|
154
|
-
{ "from": "idle", "to": "walk", "when": [{ "input": "speed", "op": "GreaterThan", "value": 0 }] },
|
|
155
|
-
{ "from": "walk", "to": "idle", "when": [{ "input": "speed", "op": "Equals", "value": 0 }] }
|
|
156
|
-
]
|
|
157
|
-
}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
This **input-driven** model is deliberately distinct from an event-driven FSM. `aispritejs` ingests only the universal `frames` / `animations`; the `inputs` / `states` / `transitions` are its own. If an atlas carries a foreign event-driven `states` block from another tool, `aispritejs` ignores it.
|
|
41
|
+
});
|
|
161
42
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
```ts
|
|
167
|
-
import { parseAtlas, loadAtlas } from "aispritejs/atlas";
|
|
168
|
-
|
|
169
|
-
// Augmented atlas (the shape above, with inputs/states/transitions inline):
|
|
170
|
-
const anim = loadAtlas(atlasJson);
|
|
171
|
-
|
|
172
|
-
// Real atlas whose own `states` block is foreign (event-driven) or absent —
|
|
173
|
-
// supply the input-driven control separately; the foreign block is ignored:
|
|
174
|
-
const graph = parseAtlas(atlasJson, { inputs, states, transitions, initial });
|
|
43
|
+
anim.setInput("speed", 1);
|
|
44
|
+
anim.fireTrigger("jump");
|
|
45
|
+
anim.update(16.7);
|
|
46
|
+
console.log(anim.activeState, anim.activeFrameKey);
|
|
175
47
|
```
|
|
176
48
|
|
|
177
|
-
|
|
178
|
-
- A foreign event-driven `states` block (the `{ initial, definitions }` FSM shape) is **detected and ignored** — pass an `aispritejs` control block instead. Structural problems throw `InvalidAtlasError`; semantic ones surface as `InvalidGraphError` from the core.
|
|
179
|
-
- The canonical structure is published as a JSON Schema at [`schemas/aispritejs-graph.schema.json`](schemas/aispritejs-graph.schema.json) (also exported as `aispritejs/schema`) for editor and CI validation; the parser mirrors it in code, so there is no runtime schema-validator dependency.
|
|
180
|
-
|
|
181
|
-
## Decoupling (P0)
|
|
182
|
-
|
|
183
|
-
- **Zero cross-package imports** — `aispritejs` does not import `aifsmjs`, `aieventjs`, or any sibling. It has its own minimal typed emitter.
|
|
184
|
-
- **Renderer-agnostic core** — the root entry never imports `pixi.js`. Only `aispritejs/pixi` does, and `pixi.js` is an **optional `peerDependency`**.
|
|
185
|
-
- **Visual animator only** — pair it with a game-logic layer by setting inputs; it makes no assumption about how your logic is structured.
|
|
186
|
-
|
|
187
|
-
## Core API
|
|
188
|
-
|
|
189
|
-
The public surface is a single factory plus types and named errors. There is **no exported class constructor** — `createSpriteAnimator` returns a `SpriteAnimator`.
|
|
49
|
+
## PixiJS Adapter
|
|
190
50
|
|
|
191
51
|
```ts
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
anim.setInput(name, value); // Number | Boolean; throws Unknown/InputTypeError
|
|
195
|
-
anim.fireTrigger(name); // marks a Trigger pending
|
|
196
|
-
anim.update(deltaMs); // advance; evaluate transitions; tick the frame
|
|
197
|
-
anim.reset(); // back to initial + default inputs (keeps buffers)
|
|
198
|
-
anim.dispose(); // idempotent; mutators throw afterwards
|
|
199
|
-
|
|
200
|
-
const off = anim.onStateChange((to, from) => {}, { signal?, once? }); // → unsubscribe
|
|
201
|
-
const off2 = anim.onComplete((state) => {}, { signal?, once? });
|
|
52
|
+
import { createPixiSpriteAnimator } from "aispritejs/pixi";
|
|
202
53
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
anim.disposed; // boolean
|
|
54
|
+
const view = createPixiSpriteAnimator(sprite, graph, spritesheet);
|
|
55
|
+
view.update(deltaMs);
|
|
56
|
+
view.dispose(); // disposes core animator; does not destroy the Pixi sprite
|
|
207
57
|
```
|
|
208
58
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
## Semantics (the precise rules)
|
|
212
|
-
|
|
213
|
-
These rules are deterministic and frozen for the 1.x line once 1.0 ships:
|
|
214
|
-
|
|
215
|
-
- **`update(dt)` order** — advance the timer by `dt × speed` (non-finite or non-positive `dt` clamps to `0`); evaluate transitions; recompute the active frame; fire `onComplete` for a finished non-looping clip and then any `onEnd` auto-transition. An explicit input transition therefore wins over end-of-clip behaviour on the same frame.
|
|
216
|
-
- **Transition resolution** — among the transitions leaving the current state (plus **Any-State** `from: "*"`), candidates are ordered by `priority` (desc) then declared order (asc); the **first effective** one is taken. All `when` conditions must hold (logical AND).
|
|
217
|
-
- **Self-transition rule** — a transition whose `to` equals the current state is *effective only if it consumes a Trigger*. A Number/Boolean self-loop is skipped, so it cannot reset the clip to frame 0 every frame. A trigger-bearing self-transition **restarts** the clip (e.g. re-attack) but does **not** fire `onStateChange` (the state name is unchanged).
|
|
218
|
-
- **Triggers** — `fireTrigger(name)` marks a trigger pending; it stays pending across frames until a transition that checks it is taken, which **consumes** it. One fire → at most one transition.
|
|
219
|
-
- **Frame timing** — the active frame is the first whose cumulative duration exceeds the elapsed time. Looping clips wrap at the total duration; non-looping clips hold the last frame and fire `onComplete` exactly once. Per-frame `duration` comes from the atlas `frames`; frames without one use `defaultFrameDuration` (default `100` ms). `speed` is a time-scale multiplier (`2` = twice as fast).
|
|
220
|
-
- **Determinism** — identical input + `dt` sequences always yield identical frame sequences. The no-transition path allocates nothing.
|
|
221
|
-
|
|
222
|
-
## Errors
|
|
59
|
+
`pixi.js` is an optional peer dependency and is imported type-only by the adapter. The root package never imports Pixi, DOM, or canvas APIs.
|
|
223
60
|
|
|
224
|
-
|
|
61
|
+
## Atlas and Schema
|
|
225
62
|
|
|
226
|
-
- `
|
|
227
|
-
- `
|
|
228
|
-
- `
|
|
229
|
-
-
|
|
63
|
+
- `parseAtlas(atlas, control?)` converts a PixiJS-v8 style atlas plus control block into a `SpriteGraph`.
|
|
64
|
+
- `loadAtlas(atlas, control?)` parses and creates a `SpriteAnimator`.
|
|
65
|
+
- `aispritejs/schema` exports `schemas/aispritejs-graph.schema.json` for editor/CI validation.
|
|
66
|
+
- Parser validation is structural (`InvalidAtlasError`); compiler validation is semantic (`InvalidGraphError`).
|
|
230
67
|
|
|
231
|
-
##
|
|
232
|
-
|
|
233
|
-
| | aispritejs | Rive | aifsmjs | raw `AnimatedSprite` |
|
|
234
|
-
|---|---|---|---|---|
|
|
235
|
-
| Control model | input-driven (Number/Boolean/Trigger) | input-driven | event-driven (logic) | manual |
|
|
236
|
-
| Scope | visual animation | visual animation | game logic | playback only |
|
|
237
|
-
| Runtime | tiny TS, no wasm | wasm runtime | tiny TS | — |
|
|
238
|
-
| Renderer | agnostic + adapters | own | n/a | PixiJS |
|
|
239
|
-
|
|
240
|
-
`aispritejs` and `aifsmjs` are complementary — logic FSM sets inputs, visual animator picks frames — and never coupled.
|
|
241
|
-
|
|
242
|
-
## AI-agent reading guide
|
|
243
|
-
|
|
244
|
-
- **Whole context in one fetch** — [`llms-full.txt`](llms-full.txt) concatenates this README, the changelog, the contributing guide, and the examples index.
|
|
245
|
-
- **Source layout** — the core lives in [`src/sprite/`](src/sprite/): `types.ts` (every public type in one file), `machine.ts` (the `createSpriteAnimator` engine), `compile.ts` (graph validation + normalisation), `inputs.ts` (the input store), `emitter.ts` (the own typed signal), `errors.ts`. The root [`src/index.ts`](src/index.ts) re-exports the public surface and imports **no** renderer.
|
|
246
|
-
- **Stability tiers** — see [STABILITY.md](STABILITY.md).
|
|
247
|
-
|
|
248
|
-
## Testing
|
|
249
|
-
|
|
250
|
-
Behavioural `vitest` suites cover inputs, transitions, trigger consumption, frame timing, `onComplete` / `onEnd`, subscriptions (`signal` / `once`), `dispose` / `reset`, and graph validation. `fast-check` property tests assert **transition determinism** (identical input + `dt` sequences ⇒ identical frame traces) and **trigger consumption** (one fire ⇒ one entry). Coverage runs at the family floor (≥95 % statements / ≥90 % branches / 100 % functions-and-lines).
|
|
68
|
+
## Core API
|
|
251
69
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
70
|
+
- `createSpriteAnimator(graph)` returns a `SpriteAnimator`.
|
|
71
|
+
- `setInput(name, value)` accepts Number/Boolean inputs.
|
|
72
|
+
- `fireTrigger(name)` consumes Trigger inputs on transition.
|
|
73
|
+
- `update(deltaMs)` advances time, transitions, frame index, and `onEnd`.
|
|
74
|
+
- `reset()` returns to the initial state.
|
|
75
|
+
- `dispose()` is idempotent; mutators throw `SpriteAnimatorDisposedError` afterward.
|
|
76
|
+
- `onStateChange(handler, options?)` and `onComplete(handler, options?)` support `once` and `signal`.
|
|
258
77
|
|
|
259
|
-
##
|
|
78
|
+
## Sharp Edges
|
|
260
79
|
|
|
261
|
-
|
|
80
|
+
- Non-looping states with `onEnd` transition during the same `update()` tick that completes the clip.
|
|
81
|
+
- `AnimatedSprite` is accepted by the Pixi adapter because it extends `Sprite`, but playback is stopped on bind so it cannot fight the adapter.
|
|
82
|
+
- Every reachable frame key must exist in the texture map/spritesheet; missing keys throw `MissingTextureError`.
|
|
83
|
+
- `duration`, `defaultFrameDuration`, and state `speed` must be finite numbers greater than zero.
|
|
84
|
+
- Current schema backlog: add `minProperties` for `animations` and optional finite numeric maximums to mirror runtime guards.
|
|
262
85
|
|
|
263
|
-
##
|
|
86
|
+
## AI Context
|
|
264
87
|
|
|
265
|
-
|
|
88
|
+
- Short index: [`llms.txt`](llms.txt)
|
|
89
|
+
- Full generated context: [`llms-full.txt`](llms-full.txt)
|
|
90
|
+
- Stability contract: [`STABILITY.md`](STABILITY.md)
|
|
91
|
+
- Current review backlog: [`REVIEW.md`](REVIEW.md)
|
|
92
|
+
- Examples index: [`examples/README.md`](examples/README.md)
|
|
93
|
+
- Release history: [`CHANGELOG.md`](CHANGELOG.md)
|
|
266
94
|
|
|
267
95
|
## License
|
|
268
96
|
|
|
269
|
-
MIT
|
|
97
|
+
MIT
|