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_ZHTW.md CHANGED
@@ -1,266 +1,97 @@
1
1
  # aispritejs
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/aispritejs.svg)](https://www.npmjs.com/package/aispritejs)
4
- [![CI](https://github.com/islumina/aispritejs/actions/workflows/ci.yml/badge.svg)](https://github.com/islumina/aispritejs/actions/workflows/ci.yml)
5
- [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
6
- [![AI Generated](https://img.shields.io/badge/AI_Generated-Claude_Code_Opus_4.8-blueviolet.svg)](https://www.anthropic.com/claude-code)
7
- [![English](https://img.shields.io/badge/lang-English-blue.svg)](README.md)
3
+ Input-driven、renderer-agnostic 的 2D sprite animation runtime。JSON graph 會把 Number/Boolean/Trigger inputs 對應到 visual states 與 frames;adapter 再把選到的 frame 綁到 renderer。
8
4
 
9
- > 以輸入驅動、與渲染器無關的 2D sprite 動畫 runtime —— 一個輕巧、類 Rive 的*視覺*狀態機,由 `Number` / `Boolean` / `Trigger` 輸入驅動。
5
+ > **狀態:0.5.8 - 穩定 family-aligned API。** Core、PixiJS adapter、atlas parser、JSON Schema subpath 都已發布。
10
6
 
11
- `aispritejs` 依據一小組執行期**輸入**(例如 `speed`、`isGrounded`、`jump`),透過 JSON 定義的轉移圖,決定**畫面上要顯示哪一格動畫影格**。你的程式設定輸入,`aispritejs` 挑選視覺狀態並推進當前影格。核心是純 TypeScript,**零相依**且**不 import 任何渲染器** —— 透過薄薄一層轉接器即可接上 PixiJS v8(或任何東西)。
7
+ ## 安裝
12
8
 
13
- 屬於 **ai\*js** 家族:零跨套件相依、核心與框架無關、AI 可讀的文件。
14
-
15
- > **狀態:0.5.7 — 已對齊 ai\*js 家族版本線。** Renderer-agnostic core 加上 `/pixi`、`/atlas`、`/schema` subpath;atlas/graph 驗證已強化。歷史見 [CHANGELOG.md](CHANGELOG.md)。
16
-
17
- ## 為什麼用 aispritejs
18
-
19
- - **輸入驅動,而非名稱驅動。** 你設定參數(`speed=4`、`isGrounded=false`、`fireTrigger("jump")`),而不是動畫名稱。視覺轉移存在於資料中,與遊戲程式解耦。
20
- - **核心與渲染器無關。** 狀態機從 delta-time 加上輸入算出當前影格;它從不 import PixiJS 也不碰 DOM。轉接器負責把結果對映成 texture。
21
- - **視覺 ≠ 邏輯。** 這嚴格來說是一個*視覺*動畫器,**不是**遊戲邏輯 FSM,也**不**相依 `aifsmjs`。用任何邏輯層(純程式、FSM、ECS)來驅動它 —— 它們以慣例組合,從不以相依耦合。
22
- - **輕巧又快。** O(1) 的輸入查找、對離開當前狀態的轉移做 O(N) 檢查、無每幀配置。
23
-
24
- ## 何時你「不需要」aispritejs
25
-
26
- `aispritejs` 的價值在於影格選擇是**由執行期輸入驅動**、且橫跨**數個視覺狀態**時才會顯現。在這個門檻之下,直接用 PixiJS 就好 —— 用狀態機並無任何好處:
27
-
28
- - **單一 sprite/一張靜態圖片** → 純 PixiJS [`Sprite`](https://pixijs.download/release/docs/scene.Sprite.html)。沒有動畫、不需要圖集。
29
- - **單一循環片段、無任何分支**(轉動的金幣、閃爍的火把)→ PixiJS [`AnimatedSprite`](https://pixijs.download/release/docs/scene.AnimatedSprite.html)(`AnimatedSprite.fromFrames(...)`、`.play()`)。永遠以同一種方式播放的單一片段不需要輸入。
30
- - **沒有 texture atlas**(你並未把影格打包成 spritesheet)→ 直接載入圖片;`aispritejs` 從 PixiJS-v8 atlas(`animations` / `frames`)讀取它的影格。
31
- - **沒有多狀態切換** —— 若你的程式早已確切知道要播哪個片段、只需呼叫 `.play()` / `.gotoAndStop()`,你並不需要轉移圖。
32
-
33
- 當你有**由輸入驅動、以 texture atlas 為後盾的多狀態視覺切換**時,才該動用 `aispritejs` —— 例如 `idle ⇄ walk → jump`,或由 trigger 觸發的一次性受擊 FX —— 此時畫面上顯示哪一格是 `speed` / `isGrounded` / `attack` 的函數,而非寫死的 `play()` 呼叫。
34
-
35
- ## 心智模型
36
-
37
- ```
38
- inputs ─▶ [轉移圖] ─▶ 當前狀態 ─▶ (Δt) ─▶ 當前影格 ─▶ 轉接器 ─▶ texture
9
+ ```bash
10
+ pnpm add aispritejs
11
+ pnpm add pixi.js # only when using aispritejs/pixi
39
12
  ```
40
13
 
41
- - **Inputs(輸入)** —— `Number`(連續,如 `speed`)、`Boolean`(開關,如 `isGrounded`)、`Trigger`(一次性;被某個轉移消耗後自動重置,如 `jump` / `attack`)。
42
- - **States(狀態)** —— 一個動畫鍵(指向 atlas 的 `animations`)加上 loop / on-end 行為與選用的速度倍率。
43
- - **Transitions(轉移)** —— 當輸入條件成立時(`Equals` / `NotEquals` / `GreaterThan` / `LessThan` / `Trigger`),從某狀態(或 **Any State**)轉到另一狀態。優先度最高且成立者勝出。
44
- - **`update(dt)`** —— 推進播放計時器;評估轉移(切換狀態、觸發 `onStateChange`、消耗 trigger);由動畫的逐影格時長加 loop 算出當前影格;非循環片段播完時觸發 `onComplete`。
45
-
46
- ## 快速開始 —— 核心(零相依)
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
- // 在你的 render loop 裡:
61
- anim.update(deltaMs);
62
- const frameKey = anim.activeFrameKey; // 交給你的渲染器
63
- ```
64
-
65
- ## 快速開始 —— PixiJS v8 轉接器
66
-
67
- `aispritejs/pixi` 子路徑把核心綁到一個 `PIXI.Sprite`。`pixi.js` 是**選用的** `peerDependency`,且僅以 **type-only** 方式 import —— 編譯後的轉接器不含任何 `pixi.js` runtime require,核心也永不碰它。
68
-
69
- ```ts
70
- import { createPixiSpriteAnimator } from "aispritejs/pixi"; // pixi.js 是選用 peer
71
-
72
- // `textures` 是 PIXI.Spritesheet(或 frame-key → Texture 的 map),需涵蓋 graph
73
- // 參照的每一格 —— 缺鍵會丟出 MissingTextureError。
74
- const view = createPixiSpriteAnimator(sprite, graph, spritesheet);
75
-
76
- // 每幀:
77
- view.update(deltaMs); // 把綁定 sprite 的 texture 換成當前影格,
78
- // 並套用該格的 atlas anchor(texture.defaultAnchor)
79
-
80
- view.setInput("speed", 4);
81
- view.fireTrigger("jump");
82
16
  ```
83
17
 
84
- 它只在當前影格改變時換 texture,並尊重逐影格 `duration`(透過核心)與非置中/腳底樞軸(透過 `texture.defaultAnchor`;傳 `{ applyAnchor: false }` 可自行管理 anchor)。`view.sprite` 是綁定的 sprite;`dispose()` 拆除核心但不銷毀 sprite。
85
-
86
- ### 完整範例 —— 一段 6 格爆炸(play-once FX)
87
-
88
- 最常見的入門場景:一次性的受擊/撞擊 FX(水花、槍口閃光),來自一張 **6 格爆炸 sprite sheet**,透過 `/pixi` 轉接器驅動。一個 `Trigger` 觸發一段**非循環**片段,播放一次後經由 `onEnd` 自動回到靜止影格。完整可執行版本在 [`examples/02-explosion-pixi/index.ts`](examples/02-explosion-pixi/index.ts)(`pnpm example:explosion`)。
18
+ ## 快速開始 - Core
89
19
 
90
20
  ```ts
91
- import { Assets, Sprite } from "pixi.js";
92
- import { createPixiSpriteAnimator } from "aispritejs/pixi";
93
-
94
- // 一張 6 格爆炸 sprite sheet(PixiJS-v8 atlas):`animations` 區塊把片段
95
- // 命名 → 它的影格鍵;`frames` 帶有逐影格時長。
96
- const graph = {
97
- animations: {
98
- explosion: ["explosion_0", "explosion_1", "explosion_2", "explosion_3", "explosion_4", "explosion_5"],
99
- idle: ["explosion_0"], // 一段 1 格的靜止片段,在每次爆發之間維持
21
+ const anim = createSpriteAnimator({
22
+ inputs: {
23
+ speed: { type: "number", default: 0 },
24
+ jump: { type: "trigger" },
100
25
  },
101
- frames: {
102
- explosion_0: { duration: 40 }, explosion_1: { duration: 40 }, explosion_2: { duration: 40 },
103
- explosion_3: { duration: 40 }, explosion_4: { duration: 40 }, explosion_5: { duration: 40 },
104
- },
105
- inputs: { detonate: { type: "trigger" } },
26
+ initial: "idle",
106
27
  states: {
107
- idle: { animation: "idle", loop: true },
108
- boom: { animation: "explosion", loop: false, onEnd: "idle" }, // 播放一次 → 回到 idle
28
+ idle: { animation: "idle" },
29
+ run: { animation: "run", speed: 1 },
30
+ jump: { animation: "jump", loop: false, onEnd: "idle" },
109
31
  },
110
32
  transitions: [
111
- { from: "*", to: "boom", when: [{ input: "detonate", op: "Trigger" }], priority: 10 },
33
+ { from: "idle", to: "run", when: [{ input: "speed", op: "GreaterThan", value: 0 }] },
34
+ { from: "*", to: "jump", when: [{ input: "jump", op: "Trigger" }] },
112
35
  ],
113
- initial: "idle",
114
- } as const;
115
-
116
- // 載入打包好的 sheet;它的 `.textures` 涵蓋 graph 用到的每一個影格鍵。
117
- const sheet = await Assets.load("explosion.json"); // 一個 PIXI.Spritesheet
118
- const sprite = new Sprite();
119
- const fx = createPixiSpriteAnimator(sprite, graph, sheet);
120
-
121
- // 在撞擊時觸發一次性 trigger:
122
- fx.fireTrigger("detonate");
123
-
124
- // 從你的 PixiJS render loop(例如 `app.ticker`)驅動這段爆發,依經過的
125
- // 毫秒數推進:`app.ticker.add((ticker) => fx.update(ticker.deltaMS))`。
126
- ```
127
-
128
- `update(dt)` 把 `explosion_0…explosion_5` 播放一次(尊重每一格的 `duration`);在播完片段的那一個 tick,它會觸發 `onComplete`,並因為 `boom` 宣告了 `onEnd`,在**同一個** tick 內自動轉移回 `idle` —— 所以不會停在最後一格,呼叫之後 `activeState` 立即讀到 `idle`。再次 `fireTrigger("detonate")` 即可重播。同一份 graph 在沒有渲染器時也能跑 —— 見 [`examples/02-explosion-pixi/index.ts`](examples/02-explosion-pixi/index.ts),它以純 `Texture` / `Sprite` 實例、在無頭環境下操練真正的轉接器。
129
-
130
- ## 資料格式(atlas)
131
-
132
- `aispritejs` 讀取 **PixiJS v8 原生**的 spritesheet atlas(`meta` / `frames` / `animations`),並額外加上一個 `aispritejs` 的**輸入驅動**控制區塊:
133
-
134
- ```jsonc
135
- {
136
- "meta": { "image": "sheet.png", "size": { "w": 1024, "h": 1024 }, "scale": "1" },
137
- "frames": { /* PixiJS 原生:frame{x,y,w,h}、anchor、duration、trimmed... */ },
138
- "animations": { "idle": ["idle_0", "idle_1"], "walk": ["walk_0", "..."], "jump": ["jump_0", "..."] },
139
-
140
- "inputs": {
141
- "speed": { "type": "number", "default": 0 },
142
- "isGrounded": { "type": "boolean", "default": true },
143
- "jump": { "type": "trigger" }
144
- },
145
- "states": {
146
- "idle": { "animation": "idle", "loop": true },
147
- "walk": { "animation": "walk", "loop": true },
148
- "jump": { "animation": "jump", "loop": false }
36
+ animations: {
37
+ idle: ["idle_0"],
38
+ run: ["run_0", "run_1"],
39
+ jump: ["jump_0", "jump_1"],
149
40
  },
150
- "transitions": [
151
- { "from": "*", "to": "jump", "when": [{ "input": "jump", "op": "Trigger" }], "priority": 10 },
152
- { "from": "idle", "to": "walk", "when": [{ "input": "speed", "op": "GreaterThan", "value": 0 }] },
153
- { "from": "walk", "to": "idle", "when": [{ "input": "speed", "op": "Equals", "value": 0 }] }
154
- ]
155
- }
156
- ```
157
-
158
- 這個**輸入驅動**模型刻意與事件驅動 FSM 區隔。`aispritejs` 只吃通用的 `frames` / `animations`;`inputs` / `states` / `transitions` 屬於它自己。若某個 atlas 帶有來自其他工具的事件驅動 `states` 區塊,`aispritejs` 會忽略它。
41
+ });
159
42
 
160
- ## 載入 atlas —— `aispritejs/atlas`
161
-
162
- `aispritejs/atlas` 子路徑把已解析的 PixiJS v8 atlas 轉成 graph(或現成的 animator)。它是純函式且零相依。
163
-
164
- ```ts
165
- import { parseAtlas, loadAtlas } from "aispritejs/atlas";
166
-
167
- // 增強型 atlas(上述形狀,inputs/states/transitions 內嵌):
168
- const anim = loadAtlas(atlasJson);
169
-
170
- // 真實 atlas 的 `states` 是 foreign(事件驅動)或不存在 —— 另外傳入輸入驅動
171
- // 控制區塊;foreign 區塊會被忽略:
172
- 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);
173
47
  ```
174
48
 
175
- - `parseAtlas(atlas, control?)` → `SpriteGraph`;`loadAtlas(atlas, control?)` → `SpriteAnimator`(一步 parse + create,fail-fast)。
176
- - foreign 事件驅動 `states`(`{ initial, definitions }` 的 FSM 形狀)會被**偵測並忽略** —— 改傳 `aispritejs` 控制區塊。結構問題丟出 `InvalidAtlasError`;語意問題由核心丟出 `InvalidGraphError`。
177
- - 標準結構以 JSON Schema 發佈於 [`schemas/aispritejs-graph.schema.json`](schemas/aispritejs-graph.schema.json)(亦匯出為 `aispritejs/schema`),供編輯器與 CI 驗證;parser 以程式碼鏡像它,因此無 runtime schema-validator 相依。
178
-
179
- ## 核心 API
180
-
181
- 公開介面是單一工廠函式,加上型別與具名錯誤。**不匯出任何 class 建構子** —— `createSpriteAnimator` 回傳一個 `SpriteAnimator`。
49
+ ## PixiJS Adapter
182
50
 
183
51
  ```ts
184
- const anim = createSpriteAnimator(graph); // graph 不合法時丟出 InvalidGraphError
185
-
186
- anim.setInput(name, value); // Number | Boolean;丟出 Unknown/InputTypeError
187
- anim.fireTrigger(name); // 將某個 Trigger 標記為 pending
188
- anim.update(deltaMs); // 推進;評估轉移;推進影格
189
- anim.reset(); // 回到初始狀態與預設輸入(保留 buffer)
190
- anim.dispose(); // 冪等;之後呼叫 mutator 會丟錯
191
-
192
- const off = anim.onStateChange((to, from) => {}, { signal?, once? }); // → 取消訂閱
193
- const off2 = anim.onComplete((state) => {}, { signal?, once? });
52
+ import { createPixiSpriteAnimator } from "aispritejs/pixi";
194
53
 
195
- anim.activeState; // 當前狀態名稱
196
- anim.activeFrameKey; // 指向 atlas `frames` 的影格鍵 —— 交給渲染器
197
- anim.activeFrameIndex; // 在當前動畫中的索引
198
- anim.disposed; // boolean
54
+ const view = createPixiSpriteAnimator(sprite, graph, spritesheet);
55
+ view.update(deltaMs);
56
+ view.dispose(); // dispose core animator;不 destroy Pixi sprite
199
57
  ```
200
58
 
201
- 每個訂閱都回傳一個取消訂閱函式,並接受 `{ signal }`(用 `AbortSignal` 移除監聽)與 `{ once }`。
202
-
203
- ## 語意(精確規則)
204
-
205
- 這些規則是決定性的,並在 1.0 推出後對 1.x 凍結:
206
-
207
- - **`update(dt)` 順序** —— 以 `dt × speed` 推進計時器(非有限或非正值的 `dt` 夾到 `0`);評估轉移;重算當前影格;為播完的非循環片段觸發 `onComplete`,接著執行任何 `onEnd` 自動轉移。因此同一幀內,明確的輸入轉移優先於片段結束行為。
208
- - **轉移解析** —— 在離開當前狀態的轉移(加上 **Any-State** `from: "*"`)中,候選依 `priority`(遞減)再依宣告順序(遞增)排序;取**第一個有效**者。所有 `when` 條件須全部成立(邏輯 AND)。
209
- - **自我轉移規則** —— `to` 等於當前狀態的轉移,*只有在它消耗一個 Trigger 時才有效*。純 Number/Boolean 的自我迴圈會被跳過,因此不會每幀把片段重置回第 0 格。帶 trigger 的自我轉移會**重啟**片段(例如連續攻擊),但**不**觸發 `onStateChange`(狀態名稱沒變)。
210
- - **Triggers** —— `fireTrigger(name)` 把某 trigger 標記為 pending;它跨幀保持 pending,直到某個檢查它的轉移被採用而**消耗**它。一次 fire → 至多一次轉移。
211
- - **影格時序** —— 當前影格是累積時長首次超過已經過時間的那一格。循環片段在總時長處回繞;非循環片段停在最後一格並只觸發一次 `onComplete`。逐影格 `duration` 取自 atlas `frames`;沒有的影格採用 `defaultFrameDuration`(預設 `100` ms)。`speed` 是時間倍率(`2` = 兩倍速)。
212
- - **決定性** —— 相同的輸入加 `dt` 序列必產生相同的影格序列。無轉移的路徑不做任何配置。
213
-
214
- ## 錯誤
215
-
216
- 具名錯誤,從不裸 throw:
217
-
218
- - `InvalidGraphError` —— `createSpriteAnimator` 在 graph 驗證失敗時丟出(缺少動畫、未知的轉移目標、運算子/型別不符、非正的時長/速度、`onEnd` 配 `loop:true`…)。Fail-fast:不合法的 graph 絕不產出半成品動畫器。
219
- - `UnknownInputError` —— 對 `inputs` 未宣告的輸入呼叫 `setInput` / `fireTrigger`(帶有 `.input`)。
220
- - `InputTypeError` —— 值型別錯誤、對 Trigger 呼叫 `setInput`、或對非 Trigger 呼叫 `fireTrigger`(帶有 `.input`)。
221
- - `SpriteAnimatorDisposedError` —— `dispose()` 之後呼叫任何 mutator(`setInput` / `fireTrigger` / `update` / `reset`)。
59
+ `pixi.js` 是 optional peer dependency,adapter 只用 type-only import。root package 不 import Pixi、DOM 或 canvas API。
222
60
 
223
- ## 解耦(P0)
61
+ ## Atlas and Schema
224
62
 
225
- - **零跨套件 import** —— `aispritejs` 不 import `aifsmjs`、`aieventjs` 或任何手足套件。它有自己最小的具名 emitter。
226
- - **核心與渲染器無關** —— 根進入點永不 import `pixi.js`。只有 `aispritejs/pixi` 會,而 `pixi.js` 是**選用的** `peerDependency`。
227
- - **僅是視覺動畫器** —— 用設定輸入的方式搭配遊戲邏輯層;它不假設你的邏輯如何組織。
63
+ - `parseAtlas(atlas, control?)` 將 PixiJS-v8 atlas 加 control block 轉成 `SpriteGraph`。
64
+ - `loadAtlas(atlas, control?)` parse 後直接建立 `SpriteAnimator`。
65
+ - `aispritejs/schema` 匯出 `schemas/aispritejs-graph.schema.json`,可用於 editor/CI validation。
66
+ - Parser 做 structural validation(`InvalidAtlasError`);compiler 做 semantic validation(`InvalidGraphError`)。
228
67
 
229
- ## 比較
230
-
231
- | | aispritejs | Rive | aifsmjs | 原生 `AnimatedSprite` |
232
- |---|---|---|---|---|
233
- | 控制模型 | 輸入驅動(Number/Boolean/Trigger) | 輸入驅動 | 事件驅動(邏輯) | 手動 |
234
- | 範圍 | 視覺動畫 | 視覺動畫 | 遊戲邏輯 | 僅播放 |
235
- | Runtime | 輕巧 TS,無 wasm | wasm runtime | 輕巧 TS | — |
236
- | 渲染器 | 無關 + 轉接器 | 自帶 | n/a | PixiJS |
237
-
238
- `aispritejs` 與 `aifsmjs` 互補 —— 邏輯 FSM 設定輸入、視覺動畫器挑影格 —— 且永不耦合。
239
-
240
- ## 給 AI agent 的閱讀指南
241
-
242
- - **一次抓完整 context** —— [`llms-full.txt`](llms-full.txt) 串接了本 README、changelog、contributing 指南與範例索引。
243
- - **原始碼版面** —— 核心位於 [`src/sprite/`](src/sprite/):`types.ts`(所有公開型別集中一檔)、`machine.ts`(`createSpriteAnimator` 引擎)、`compile.ts`(graph 驗證與正規化)、`inputs.ts`(輸入儲存)、`emitter.ts`(自有具名 signal)、`errors.ts`。根 [`src/index.ts`](src/index.ts) 再匯出公開介面,且**不** import 任何渲染器。
244
- - **穩定度分級** —— 見 [STABILITY.md](STABILITY.md)。
245
-
246
- ## 測試
247
-
248
- `vitest` 行為測試涵蓋輸入、轉移、trigger 消耗、影格時序、`onComplete` / `onEnd`、訂閱(`signal` / `once`)、`dispose` / `reset` 與 graph 驗證。`fast-check` 屬性測試驗證**轉移決定性**(相同輸入加 `dt` 序列 ⇒ 相同影格軌跡)與 **trigger 消耗**(一次 fire ⇒ 一次進入)。覆蓋率以家族下限執行(≥95% statements / ≥90% branches / 100% functions 與 lines)。
68
+ ## 核心 API
249
69
 
250
- ```bash
251
- pnpm test # 跑一次
252
- pnpm coverage # 帶門檻
253
- pnpm example:platformer
254
- ```
70
+ - `createSpriteAnimator(graph)` 回傳 `SpriteAnimator`。
71
+ - `setInput(name, value)` 接受 Number/Boolean inputs。
72
+ - `fireTrigger(name)` 觸發 Trigger input,transition 後會消耗。
73
+ - `update(deltaMs)` 推進時間、transition、frame index 與 `onEnd`。
74
+ - `reset()` 回到 initial state。
75
+ - `dispose()` 可重複呼叫;dispose 後 mutators 會丟 `SpriteAnimatorDisposedError`。
76
+ - `onStateChange(handler, options?)` 與 `onComplete(handler, options?)` 支援 `once` 與 `signal`。
255
77
 
256
- ## 狀態
78
+ ## 注意事項
257
79
 
258
- **已對齊 ai\*js 家族版本線。** 套件版本與家族共享版本(`0.5.x`)同步——精確版次見上方 banner 與 [CHANGELOG.md](CHANGELOG.md)。所有四個 roadmap 模組均已上線:與渲染器無關的核心(`.`)、PixiJS v8 轉接器(`aispritejs/pixi`)、atlas parser(`aispritejs/atlas`)、JSON Schema(`aispritejs/schema`)。先前強化波次已完成:在編譯期拒絕非有限的 `speed` / `duration` / `defaultFrameDuration`、在 `update()` 執行期把非有限或非正值的 `dt` 夾到 `0`,以及惡意 atlas 格式的具名錯誤覆蓋。零執行期相依;根 import 圖不含 `pixi.js`;`pixi.js` 是選用、type-only 的 peer,僅 `/pixi` 子路徑使用。
80
+ - 非 loop state 若有 `onEnd`,會在 clip 完成的同一個 `update()` tick 轉場。
81
+ - Pixi adapter 接受 `AnimatedSprite`,因為它 extends `Sprite`;bind 時會 stop playback,避免跟 adapter 搶 texture。
82
+ - 所有 reachable frame keys 都必須存在於 texture map/spritesheet;缺少時丟 `MissingTextureError`。
83
+ - `duration`、`defaultFrameDuration`、state `speed` 都必須是 finite 且大於 0。
84
+ - 目前 schema backlog:替 `animations` 加 `minProperties`,並考慮用 finite numeric maximums 對齊 runtime guards。
259
85
 
260
- ## Roadmap
86
+ ## AI Context
261
87
 
262
- 見 [ROADMAP.md](ROADMAP.md)。
88
+ - 短索引:[`llms.txt`](llms.txt)
89
+ - 完整生成內容:[`llms-full.txt`](llms-full.txt)
90
+ - 穩定度契約:[`STABILITY.md`](STABILITY.md)
91
+ - 目前 review backlog:[`REVIEW.md`](REVIEW.md)
92
+ - 範例索引:[`examples/README.md`](examples/README.md)
93
+ - 版本紀錄:[`CHANGELOG.md`](CHANGELOG.md)
263
94
 
264
95
  ## License
265
96
 
266
- MIT © yshengliao —— 見 [LICENSE](LICENSE)。
97
+ MIT
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkY663UAIF_cjs = require('../chunk-Y663UAIF.cjs');
3
+ var chunkQF5N7LOI_cjs = require('../chunk-QF5N7LOI.cjs');
4
4
 
5
5
  // src/atlas/parse.ts
6
6
  var InvalidAtlasError = class extends Error {
@@ -10,10 +10,10 @@ var InvalidAtlasError = class extends Error {
10
10
  }
11
11
  };
12
12
  function isForeignStates(states) {
13
- return chunkY663UAIF_cjs.isObject(states) && typeof states.initial === "string" && chunkY663UAIF_cjs.isObject(states.definitions);
13
+ return chunkQF5N7LOI_cjs.isObject(states) && typeof states.initial === "string" && chunkQF5N7LOI_cjs.isObject(states.definitions);
14
14
  }
15
15
  function assertAnimations(value) {
16
- if (!chunkY663UAIF_cjs.isObject(value)) {
16
+ if (!chunkQF5N7LOI_cjs.isObject(value)) {
17
17
  throw new InvalidAtlasError("`animations` must be an object of frame-key lists");
18
18
  }
19
19
  for (const [name, list] of Object.entries(value)) {
@@ -24,17 +24,17 @@ function assertAnimations(value) {
24
24
  return value;
25
25
  }
26
26
  function parseAtlas(atlas, control) {
27
- if (!chunkY663UAIF_cjs.isObject(atlas)) {
27
+ if (!chunkQF5N7LOI_cjs.isObject(atlas)) {
28
28
  throw new InvalidAtlasError("atlas must be an object");
29
29
  }
30
30
  const animations = assertAnimations(atlas.animations);
31
31
  const frames = atlas.frames;
32
- if (frames !== void 0 && !chunkY663UAIF_cjs.isObject(frames)) {
32
+ if (frames !== void 0 && !chunkQF5N7LOI_cjs.isObject(frames)) {
33
33
  throw new InvalidAtlasError("`frames`, if present, must be an object keyed by frame key");
34
34
  }
35
- if (chunkY663UAIF_cjs.isObject(frames)) {
35
+ if (chunkQF5N7LOI_cjs.isObject(frames)) {
36
36
  for (const [key, entry] of Object.entries(frames)) {
37
- if (!chunkY663UAIF_cjs.isObject(entry)) {
37
+ if (!chunkQF5N7LOI_cjs.isObject(entry)) {
38
38
  const actualType = entry === null ? "null" : Array.isArray(entry) ? "array" : typeof entry;
39
39
  throw new InvalidAtlasError(`frame entry "${key}" must be an object, got ${actualType}`);
40
40
  }
@@ -49,19 +49,19 @@ function parseAtlas(atlas, control) {
49
49
  "atlas `states` is event-driven (has `initial`/`definitions`); pass an aispritejs control block as the second argument"
50
50
  );
51
51
  }
52
- if (!chunkY663UAIF_cjs.isObject(atlas.inputs) || !chunkY663UAIF_cjs.isObject(atlas.states) || !Array.isArray(atlas.transitions)) {
52
+ if (!chunkQF5N7LOI_cjs.isObject(atlas.inputs) || !chunkQF5N7LOI_cjs.isObject(atlas.states) || !Array.isArray(atlas.transitions)) {
53
53
  throw new InvalidAtlasError(
54
54
  "atlas has no aispritejs control block (inputs/states/transitions); pass one as the second argument"
55
55
  );
56
56
  }
57
57
  for (const [key, val] of Object.entries(atlas.inputs)) {
58
- if (!chunkY663UAIF_cjs.isObject(val)) {
58
+ if (!chunkQF5N7LOI_cjs.isObject(val)) {
59
59
  const actualType = val === null ? "null" : Array.isArray(val) ? "array" : typeof val;
60
60
  throw new InvalidAtlasError(`input entry "${key}" must be an object, got ${actualType}`);
61
61
  }
62
62
  }
63
63
  for (const [key, val] of Object.entries(atlas.states)) {
64
- if (!chunkY663UAIF_cjs.isObject(val)) {
64
+ if (!chunkQF5N7LOI_cjs.isObject(val)) {
65
65
  const actualType = val === null ? "null" : Array.isArray(val) ? "array" : typeof val;
66
66
  throw new InvalidAtlasError(`state entry "${key}" must be an object, got ${actualType}`);
67
67
  }
@@ -69,7 +69,7 @@ function parseAtlas(atlas, control) {
69
69
  const rawTransitions = atlas.transitions;
70
70
  for (let i = 0; i < rawTransitions.length; i++) {
71
71
  const entry = rawTransitions[i];
72
- if (!chunkY663UAIF_cjs.isObject(entry)) {
72
+ if (!chunkQF5N7LOI_cjs.isObject(entry)) {
73
73
  const actualType = entry === null ? "null" : Array.isArray(entry) ? "array" : typeof entry;
74
74
  throw new InvalidAtlasError(`transitions[${i}] must be an object, got ${actualType}`);
75
75
  }
@@ -77,7 +77,7 @@ function parseAtlas(atlas, control) {
77
77
  const rawWhen = entry.when;
78
78
  for (let j = 0; j < rawWhen.length; j++) {
79
79
  const cond = rawWhen[j];
80
- if (!chunkY663UAIF_cjs.isObject(cond)) {
80
+ if (!chunkQF5N7LOI_cjs.isObject(cond)) {
81
81
  const actualType = cond === null ? "null" : Array.isArray(cond) ? "array" : typeof cond;
82
82
  throw new InvalidAtlasError(
83
83
  `transitions[${i}].when[${j}] must be an object, got ${actualType}`
@@ -105,7 +105,7 @@ function parseAtlas(atlas, control) {
105
105
  };
106
106
  }
107
107
  function loadAtlas(atlas, control) {
108
- return chunkY663UAIF_cjs.createSpriteAnimator(parseAtlas(atlas, control));
108
+ return chunkQF5N7LOI_cjs.createSpriteAnimator(parseAtlas(atlas, control));
109
109
  }
110
110
 
111
111
  exports.InvalidAtlasError = InvalidAtlasError;
@@ -1,4 +1,4 @@
1
- import { isObject, createSpriteAnimator } from '../chunk-F4MDNT4Y.js';
1
+ import { isObject, createSpriteAnimator } from '../chunk-6E4ILDBY.js';
2
2
 
3
3
  // src/atlas/parse.ts
4
4
  var InvalidAtlasError = class extends Error {
@@ -30,10 +30,15 @@ var InputTypeError = class extends Error {
30
30
 
31
31
  // src/sprite/compile.ts
32
32
  var DEFAULT_FRAME_DURATION = 100;
33
+ var MAX_DURATION = 864e5;
34
+ var MAX_SPEED = 1e3;
33
35
  function isObject(v) {
34
36
  return typeof v === "object" && v !== null && !Array.isArray(v);
35
37
  }
36
38
  function compileGraph(graph) {
39
+ if (Object.keys(graph.animations).length === 0) {
40
+ throw new InvalidGraphError("animations must declare at least one animation");
41
+ }
37
42
  const stateEntries = Object.entries(graph.states);
38
43
  if (stateEntries.length === 0) {
39
44
  throw new InvalidGraphError("states must declare at least one state");
@@ -64,6 +69,11 @@ function compileGraph(graph) {
64
69
  `defaultFrameDuration must be a finite number > 0, got ${defaultDuration}`
65
70
  );
66
71
  }
72
+ if (defaultDuration > MAX_DURATION) {
73
+ throw new InvalidGraphError(
74
+ `defaultFrameDuration must be \u2264 ${MAX_DURATION} ms, got ${defaultDuration}`
75
+ );
76
+ }
67
77
  if (graph.frames) {
68
78
  for (const [key, timing] of Object.entries(graph.frames)) {
69
79
  if (!isObject(timing)) {
@@ -74,6 +84,11 @@ function compileGraph(graph) {
74
84
  `frame "${key}" duration must be a finite number > 0, got ${timing.duration}`
75
85
  );
76
86
  }
87
+ if (timing.duration !== void 0 && timing.duration > MAX_DURATION) {
88
+ throw new InvalidGraphError(
89
+ `frame "${key}" duration must be \u2264 ${MAX_DURATION} ms, got ${timing.duration}`
90
+ );
91
+ }
77
92
  }
78
93
  }
79
94
  const initial = graph.initial ?? stateEntries[0][0];
@@ -95,6 +110,9 @@ function compileGraph(graph) {
95
110
  `state "${name}" speed must be a finite number > 0, got ${speed}`
96
111
  );
97
112
  }
113
+ if (speed > MAX_SPEED) {
114
+ throw new InvalidGraphError(`state "${name}" speed must be \u2264 ${MAX_SPEED}, got ${speed}`);
115
+ }
98
116
  const loop = st.loop === true;
99
117
  if (loop && st.onEnd !== void 0) {
100
118
  throw new InvalidGraphError(
@@ -484,5 +502,5 @@ function createSpriteAnimator(graph) {
484
502
  }
485
503
 
486
504
  export { InputTypeError, InvalidGraphError, SpriteAnimatorDisposedError, UnknownInputError, createSpriteAnimator, isObject };
487
- //# sourceMappingURL=chunk-F4MDNT4Y.js.map
488
- //# sourceMappingURL=chunk-F4MDNT4Y.js.map
505
+ //# sourceMappingURL=chunk-6E4ILDBY.js.map
506
+ //# sourceMappingURL=chunk-6E4ILDBY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sprite/errors.ts","../src/sprite/compile.ts","../src/sprite/emitter.ts","../src/sprite/inputs.ts","../src/sprite/machine.ts"],"names":["v"],"mappings":"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","file":"chunk-6E4ILDBY.js","sourcesContent":["// aispritejs — named error classes. Named errors instead of bare throws let\n// callers branch on failure mode (e.g. distinguish a typo'd input from a\n// post-dispose call) and keep stack traces meaningful.\n\n/**\n * Thrown by `setInput` / `fireTrigger` / `update` / `reset` after the animator\n * has been disposed.\n *\n * @public\n */\nexport class SpriteAnimatorDisposedError extends Error {\n constructor() {\n super(\"aispritejs: animator has been disposed; this operation is not allowed\");\n this.name = \"SpriteAnimatorDisposedError\";\n }\n}\n\n/**\n * Thrown by `createSpriteAnimator` when the graph fails validation — a state\n * references a missing animation, a transition points at an unknown state, a\n * condition uses an operator the input kind does not support, a duration is\n * non-positive, and so on. Fail-fast: an invalid graph never produces a\n * half-built animator.\n *\n * @public\n */\nexport class InvalidGraphError extends Error {\n constructor(message: string) {\n super(`aispritejs: invalid graph — ${message}`);\n this.name = \"InvalidGraphError\";\n }\n}\n\n/**\n * Thrown by `setInput` / `fireTrigger` when the named input is not declared in\n * the graph's `inputs` block.\n *\n * @public\n */\nexport class UnknownInputError extends Error {\n readonly input: string;\n constructor(input: string) {\n super(`aispritejs: unknown input \"${input}\"; declare it in the graph's inputs block`);\n this.name = \"UnknownInputError\";\n this.input = input;\n }\n}\n\n/**\n * Thrown when an input is used against its kind — `setInput` with the wrong\n * value type, `setInput` on a Trigger, or `fireTrigger` on a non-Trigger.\n *\n * @public\n */\nexport class InputTypeError extends Error {\n readonly input: string;\n constructor(input: string, message: string) {\n super(`aispritejs: input \"${input}\" — ${message}`);\n this.name = \"InputTypeError\";\n this.input = input;\n }\n}\n","// aispritejs — graph compiler. Validates every cross-reference the type system\n// cannot (animation existence, transition targets, operator/kind compatibility,\n// positive durations) and normalises the graph into a precomputed runtime form:\n// per-state cumulative frame timings and per-state transition candidate lists\n// sorted by (priority desc, declared-order asc). All allocation happens here,\n// once, so `update()` stays allocation-free.\n\nimport { InvalidGraphError } from \"./errors.js\";\nimport type { InputStore } from \"./inputs.js\";\nimport type { SpriteGraph } from \"./types.js\";\n\n/** A compiled condition: a closure that reads the live store and returns a boolean. */\nexport type ConditionFn = (store: InputStore) => boolean;\n\nexport interface CompiledTransition {\n readonly from: string;\n readonly to: string;\n readonly priority: number;\n /** Declared index — the deterministic tie-break when priorities are equal. */\n readonly order: number;\n /** AND-ed at runtime; empty means \"matches unconditionally\". */\n readonly conditions: readonly ConditionFn[];\n /** Trigger input names this transition consumes when taken. */\n readonly triggers: readonly string[];\n}\n\nexport interface CompiledState {\n readonly name: string;\n readonly animation: string;\n readonly loop: boolean;\n readonly speed: number;\n readonly onEnd: string | undefined;\n readonly frameKeys: readonly string[];\n /** `cumulative[i]` = summed duration of frames `0..i`; last entry === `total`. */\n readonly cumulative: readonly number[];\n readonly total: number;\n}\n\nexport interface CompiledGraph {\n readonly initial: string;\n readonly states: ReadonlyMap<string, CompiledState>;\n readonly candidatesByState: ReadonlyMap<string, readonly CompiledTransition[]>;\n}\n\nconst DEFAULT_FRAME_DURATION = 100;\n/** Maximum allowed frame / default-frame duration in ms (24 hours). */\nconst MAX_DURATION = 86_400_000;\n/** Maximum allowed state speed multiplier (1000× normal). */\nconst MAX_SPEED = 1_000;\n\n/** @internal */\nexport function isObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nexport function compileGraph(graph: SpriteGraph): CompiledGraph {\n if (Object.keys(graph.animations).length === 0) {\n throw new InvalidGraphError(\"animations must declare at least one animation\");\n }\n\n const stateEntries = Object.entries(graph.states);\n if (stateEntries.length === 0) {\n throw new InvalidGraphError(\"states must declare at least one state\");\n }\n\n // Inputs: types are enforced by TS, but a graph loaded from untyped JSON can\n // still carry a bad `type` or a `default` whose typeof mismatches the kind.\n // Validate so a bad input fails here, not silently in the store.\n for (const [name, def] of Object.entries(graph.inputs)) {\n if (def.type !== \"number\" && def.type !== \"boolean\" && def.type !== \"trigger\") {\n throw new InvalidGraphError(\n `input \"${name}\" has unknown type \"${(def as { type: string }).type}\"`,\n );\n }\n // Validate `default` typeof matches the declared kind (schema constraint in\n // code). A wrong-typed default would be adopted unvalidated and silently\n // misevaluate conditions (e.g. \"5\" === 5 is false for Equals).\n const rawDef = def as { type: string; default?: unknown };\n if (rawDef.default !== undefined) {\n if (def.type === \"number\" && typeof rawDef.default !== \"number\") {\n throw new InvalidGraphError(\n `input \"${name}\" default must be a number (declared type \"number\"), got ${typeof rawDef.default}`,\n );\n }\n if (def.type === \"boolean\" && typeof rawDef.default !== \"boolean\") {\n throw new InvalidGraphError(\n `input \"${name}\" default must be a boolean (declared type \"boolean\"), got ${typeof rawDef.default}`,\n );\n }\n }\n }\n\n const defaultDuration = graph.defaultFrameDuration ?? DEFAULT_FRAME_DURATION;\n if (!Number.isFinite(defaultDuration) || defaultDuration <= 0) {\n throw new InvalidGraphError(\n `defaultFrameDuration must be a finite number > 0, got ${defaultDuration}`,\n );\n }\n if (defaultDuration > MAX_DURATION) {\n throw new InvalidGraphError(\n `defaultFrameDuration must be ≤ ${MAX_DURATION} ms, got ${defaultDuration}`,\n );\n }\n\n if (graph.frames) {\n for (const [key, timing] of Object.entries(graph.frames)) {\n if (!isObject(timing as unknown)) {\n throw new InvalidGraphError(`frame \"${key}\" timing must be an object`);\n }\n if (\n timing.duration !== undefined &&\n (!Number.isFinite(timing.duration) || timing.duration <= 0)\n ) {\n throw new InvalidGraphError(\n `frame \"${key}\" duration must be a finite number > 0, got ${timing.duration}`,\n );\n }\n if (timing.duration !== undefined && timing.duration > MAX_DURATION) {\n throw new InvalidGraphError(\n `frame \"${key}\" duration must be ≤ ${MAX_DURATION} ms, got ${timing.duration}`,\n );\n }\n }\n }\n\n const initial = graph.initial ?? stateEntries[0]![0];\n if (!Object.hasOwn(graph.states, initial)) {\n throw new InvalidGraphError(`initial state \"${initial}\" is not declared`);\n }\n\n // --- compile states -----------------------------------------------------\n const states = new Map<string, CompiledState>();\n for (const [name, st] of stateEntries) {\n const frameKeys = graph.animations[st.animation];\n if (frameKeys === undefined || !Object.hasOwn(graph.animations, st.animation)) {\n throw new InvalidGraphError(`state \"${name}\" references unknown animation \"${st.animation}\"`);\n }\n if (frameKeys.length === 0) {\n throw new InvalidGraphError(`animation \"${st.animation}\" (state \"${name}\") has no frames`);\n }\n const speed = st.speed ?? 1;\n if (!Number.isFinite(speed) || speed <= 0) {\n throw new InvalidGraphError(\n `state \"${name}\" speed must be a finite number > 0, got ${speed}`,\n );\n }\n if (speed > MAX_SPEED) {\n throw new InvalidGraphError(`state \"${name}\" speed must be ≤ ${MAX_SPEED}, got ${speed}`);\n }\n const loop = st.loop === true;\n if (loop && st.onEnd !== undefined) {\n throw new InvalidGraphError(\n `state \"${name}\" loops, so onEnd \"${st.onEnd}\" would never fire; set loop:false or drop onEnd`,\n );\n }\n if (st.onEnd !== undefined && !Object.hasOwn(graph.states, st.onEnd)) {\n throw new InvalidGraphError(`state \"${name}\" onEnd target \"${st.onEnd}\" is not declared`);\n }\n\n const cumulative: number[] = [];\n let running = 0;\n for (const key of frameKeys) {\n running += graph.frames?.[key]?.duration ?? defaultDuration;\n cumulative.push(running);\n }\n\n states.set(name, {\n name,\n animation: st.animation,\n loop,\n speed,\n onEnd: st.onEnd,\n // `frameKeys` is aliased directly from `graph.animations[st.animation]`\n // without copying. The declared `readonly` typing makes mutation a\n // compile-time error for typed callers, but callers holding the raw JSON\n // object can mutate it at runtime. Callers must NOT mutate the atlas\n // `animations` arrays while a machine built from that graph is alive;\n // doing so desyncs `frameKeys` from the precomputed `cumulative` timings\n // and can cause `frameKeys[activeFrameIndex]` to yield `undefined`.\n frameKeys,\n cumulative,\n total: running,\n });\n }\n\n // --- compile transitions ------------------------------------------------\n const compiled: CompiledTransition[] = [];\n graph.transitions.forEach((t, order) => {\n if (!isObject(t as unknown)) {\n throw new InvalidGraphError(`transition #${order} must be an object`);\n }\n if (t.when !== undefined && !Array.isArray(t.when)) {\n throw new InvalidGraphError(`transition #${order} \"when\" must be an array`);\n }\n if (t.from !== \"*\" && !Object.hasOwn(graph.states, t.from)) {\n throw new InvalidGraphError(`transition #${order} from \"${t.from}\" is not a declared state`);\n }\n if (!Object.hasOwn(graph.states, t.to)) {\n throw new InvalidGraphError(`transition #${order} to \"${t.to}\" is not a declared state`);\n }\n\n const conditions: ConditionFn[] = [];\n const triggers: string[] = [];\n for (const c of t.when ?? []) {\n const def = graph.inputs[c.input];\n if (def === undefined || !Object.hasOwn(graph.inputs, c.input)) {\n throw new InvalidGraphError(\n `transition #${order} condition references unknown input \"${c.input}\"`,\n );\n }\n compileCondition(order, c.input, c.op, c.value, def.type, conditions, triggers);\n }\n\n compiled.push({\n from: t.from,\n to: t.to,\n priority: t.priority ?? 0,\n order,\n conditions,\n triggers,\n });\n });\n\n // --- group candidates per state (own + Any-State), sorted ---------------\n const candidatesByState = new Map<string, readonly CompiledTransition[]>();\n for (const [name] of stateEntries) {\n const list = compiled.filter((t) => t.from === name || t.from === \"*\");\n // Stable by construction (declared order), but make the tie-break explicit.\n list.sort((a, b) => b.priority - a.priority || a.order - b.order);\n candidatesByState.set(name, list);\n }\n\n return { initial, states, candidatesByState };\n}\n\nfunction compileCondition(\n order: number,\n input: string,\n op: string,\n value: number | boolean | undefined,\n kind: \"number\" | \"boolean\" | \"trigger\",\n out: ConditionFn[],\n triggers: string[],\n): void {\n switch (op) {\n case \"Trigger\": {\n if (kind !== \"trigger\") {\n throw new InvalidGraphError(\n `transition #${order}: op Trigger requires a trigger input, but \"${input}\" is ${kind}`,\n );\n }\n if (value !== undefined) {\n throw new InvalidGraphError(\n `transition #${order}: Trigger condition on \"${input}\" must not carry a value`,\n );\n }\n out.push((s) => s.isPending(input));\n triggers.push(input);\n return;\n }\n case \"GreaterThan\":\n case \"LessThan\": {\n if (kind !== \"number\") {\n throw new InvalidGraphError(\n `transition #${order}: op ${op} requires a number input, but \"${input}\" is ${kind}`,\n );\n }\n if (typeof value !== \"number\") {\n throw new InvalidGraphError(\n `transition #${order}: op ${op} on \"${input}\" needs a numeric value`,\n );\n }\n const v = value;\n out.push(\n op === \"GreaterThan\" ? (s) => s.readNumber(input) > v : (s) => s.readNumber(input) < v,\n );\n return;\n }\n case \"Equals\":\n case \"NotEquals\": {\n if (kind === \"trigger\") {\n throw new InvalidGraphError(\n `transition #${order}: op ${op} cannot apply to trigger input \"${input}\"`,\n );\n }\n if (kind === \"number\") {\n if (typeof value !== \"number\") {\n throw new InvalidGraphError(\n `transition #${order}: op ${op} on number input \"${input}\" needs a numeric value`,\n );\n }\n const v = value;\n out.push(\n op === \"Equals\" ? (s) => s.readNumber(input) === v : (s) => s.readNumber(input) !== v,\n );\n return;\n }\n // kind === \"boolean\"\n if (typeof value !== \"boolean\") {\n throw new InvalidGraphError(\n `transition #${order}: op ${op} on boolean input \"${input}\" needs a boolean value`,\n );\n }\n const v = value;\n out.push(\n op === \"Equals\" ? (s) => s.readBoolean(input) === v : (s) => s.readBoolean(input) !== v,\n );\n return;\n }\n default:\n throw new InvalidGraphError(`transition #${order}: unknown operator \"${op}\"`);\n }\n}\n","// aispritejs — a tiny single-payload typed signal. This is the package's OWN\n// emitter; it deliberately does NOT import `aieventjs` or any sibling (P0\n// zero-cross-package rule). Two instances back `onStateChange` and\n// `onComplete`.\n\nimport type { ListenerOptions, Unsubscribe } from \"./types.js\";\n\n/**\n * A typed fan-out of one payload `P`. `emit` is allocation-free in the common\n * case; it snapshots listeners only when there is at least one, so a handler\n * that unsubscribes (or a `once` handler) cannot corrupt the in-flight\n * iteration.\n */\nexport interface Signal<P> {\n on(handler: (payload: P) => void, options?: ListenerOptions): Unsubscribe;\n emit(payload: P): void;\n /** Drop every listener (used by `dispose`). */\n clear(): void;\n}\n\nexport function createSignal<P>(): Signal<P> {\n const listeners = new Set<(payload: P) => void>();\n // Parallel registry of cleanup functions so clear() can detach abort hooks\n // as well as clearing the listener set (fixes SPR-R-01).\n const cleanups = new Map<(payload: P) => void, Unsubscribe>();\n\n function on(handler: (payload: P) => void, options?: ListenerOptions): Unsubscribe {\n // An already-aborted signal means the listener is dead on arrival.\n if (options?.signal?.aborted) return () => {};\n\n let detachAbort: (() => void) | undefined;\n // One teardown shared by the unsubscribe return, the once-wrapper, and the\n // abort handler, so every path also detaches the abort listener — a `once`\n // handler that passed a `{ signal }` must not leave the abort listener\n // attached after it fires.\n const cleanup: Unsubscribe = () => {\n listeners.delete(wrapped);\n cleanups.delete(wrapped);\n if (detachAbort) {\n detachAbort();\n detachAbort = undefined;\n }\n };\n\n let wrapped: (payload: P) => void = handler;\n if (options?.once) {\n wrapped = (payload) => {\n cleanup();\n handler(payload);\n };\n }\n listeners.add(wrapped);\n cleanups.set(wrapped, cleanup);\n\n const sig = options?.signal;\n if (sig) {\n const onAbort = () => cleanup();\n sig.addEventListener(\"abort\", onAbort, { once: true });\n detachAbort = () => sig.removeEventListener(\"abort\", onAbort);\n }\n\n return cleanup;\n }\n\n function emit(payload: P): void {\n if (listeners.size === 0) return;\n // Snapshot so a handler that unsubscribes (including the once-wrapper)\n // during dispatch does not perturb this pass.\n for (const fn of [...listeners]) fn(payload);\n }\n\n function clear(): void {\n // Run each cleanup so abort hooks are detached from caller AbortSignals\n // (SPR-R-01). Snapshot the values first because cleanup() mutates the map.\n for (const cleanup of [...cleanups.values()]) cleanup();\n // Each cleanup already removed its own entries; these clears make the\n // empty post-condition explicit. (A throwing cleanup would propagate and\n // skip them — listener callbacks and detach hooks are plain functions in\n // every documented path, so that trade-off is accepted over a try/finally.)\n listeners.clear();\n cleanups.clear();\n }\n\n return { on, emit, clear };\n}\n","// aispritejs — the input store. Holds Number / Boolean values and Trigger\n// pending flags with O(1) lookup. All declared inputs are seeded at\n// construction so reads are total (the compiler guarantees every condition\n// references a declared input of the matching kind, so the non-null reads\n// below are sound).\n\nimport { InputTypeError, UnknownInputError } from \"./errors.js\";\nimport type { InputDef } from \"./types.js\";\n\nexport interface InputStore {\n /** Set a Number/Boolean input. Throws on unknown name or kind mismatch. */\n setInput(name: string, value: number | boolean): void;\n /** Mark a Trigger pending. Throws on unknown name or non-Trigger. */\n fireTrigger(name: string): void;\n /** Read a Number input (caller guarantees `name` is a declared Number). */\n readNumber(name: string): number;\n /** Read a Boolean input (caller guarantees `name` is a declared Boolean). */\n readBoolean(name: string): boolean;\n /** Whether a Trigger is currently pending. */\n isPending(name: string): boolean;\n /** Consume (reset) a pending Trigger after a transition takes it. */\n consume(name: string): void;\n /** Restore every input to its declared default and clear all triggers. */\n reset(): void;\n}\n\ntype Kind = \"number\" | \"boolean\" | \"trigger\";\n\nexport function createInputStore(inputs: Readonly<Record<string, InputDef>>): InputStore {\n const kinds = new Map<string, Kind>();\n const numberDefaults = new Map<string, number>();\n const booleanDefaults = new Map<string, boolean>();\n const numbers = new Map<string, number>();\n const booleans = new Map<string, boolean>();\n const triggers = new Map<string, boolean>();\n\n for (const [name, def] of Object.entries(inputs)) {\n kinds.set(name, def.type);\n if (def.type === \"number\") {\n const d = def.default ?? 0;\n numberDefaults.set(name, d);\n numbers.set(name, d);\n } else if (def.type === \"boolean\") {\n const d = def.default ?? false;\n booleanDefaults.set(name, d);\n booleans.set(name, d);\n } else {\n triggers.set(name, false);\n }\n }\n\n function setInput(name: string, value: number | boolean): void {\n const kind = kinds.get(name);\n if (kind === undefined) throw new UnknownInputError(name);\n if (kind === \"trigger\") {\n throw new InputTypeError(name, \"is a Trigger; use fireTrigger()\");\n }\n if (kind === \"number\") {\n if (typeof value !== \"number\") {\n throw new InputTypeError(name, `expects a number, received ${typeof value}`);\n }\n if (Number.isNaN(value)) {\n throw new InputTypeError(name, \"cannot be set to NaN\");\n }\n numbers.set(name, value);\n return;\n }\n // kind === \"boolean\"\n if (typeof value !== \"boolean\") {\n throw new InputTypeError(name, `expects a boolean, received ${typeof value}`);\n }\n booleans.set(name, value);\n }\n\n function fireTrigger(name: string): void {\n const kind = kinds.get(name);\n if (kind === undefined) throw new UnknownInputError(name);\n if (kind !== \"trigger\") {\n throw new InputTypeError(name, \"is not a Trigger; use setInput()\");\n }\n triggers.set(name, true);\n }\n\n return {\n setInput,\n fireTrigger,\n // `!` is sound: every declared input is seeded at construction and the\n // compiler validates that conditions only read declared inputs of the\n // matching kind, so these reads always hit a present entry.\n readNumber: (name) => numbers.get(name)!,\n readBoolean: (name) => booleans.get(name)!,\n isPending: (name) => triggers.get(name) === true,\n consume: (name) => {\n triggers.set(name, false);\n },\n reset: () => {\n for (const [name, d] of numberDefaults) numbers.set(name, d);\n for (const [name, d] of booleanDefaults) booleans.set(name, d);\n for (const name of triggers.keys()) triggers.set(name, false);\n },\n };\n}\n","// aispritejs — the input-driven visual state machine (the \"RiveSpriteStateMachine\"\n// engine), exposed only through the `createSpriteAnimator` factory. Implemented\n// as closures, not a class, so methods never depend on `this` and the\n// constructor surface stays hidden (family convention: public API = createX).\n//\n// Determinism: the only state advanced by `update` is `elapsed` (by `dt * speed`)\n// plus the input store; transitions are resolved over precomputed, deterministically\n// ordered candidate lists. Identical input + dt sequences therefore yield identical\n// frame sequences. The no-transition path allocates nothing.\n\nimport { type CompiledState, compileGraph } from \"./compile.js\";\nimport { createSignal } from \"./emitter.js\";\nimport { SpriteAnimatorDisposedError } from \"./errors.js\";\nimport { createInputStore } from \"./inputs.js\";\nimport type {\n CompleteHandler,\n ListenerOptions,\n SpriteAnimator,\n SpriteGraph,\n StateChangeHandler,\n Unsubscribe,\n} from \"./types.js\";\n\ninterface StateChangePayload {\n readonly to: string;\n readonly from: string;\n}\n\nconst NO_TRIGGERS: readonly string[] = [];\n\n/**\n * Build a renderer-agnostic visual animator from an input-driven graph.\n *\n * @param graph - inputs, states, transitions, animations (+ optional per-frame\n * timings). Validated eagerly; an invalid graph throws {@link InvalidGraphError}\n * and no animator is returned.\n * @returns a {@link SpriteAnimator}.\n *\n * @public\n */\nexport function createSpriteAnimator(graph: SpriteGraph): SpriteAnimator {\n const compiled = compileGraph(graph);\n const store = createInputStore(graph.inputs);\n const stateChange = createSignal<StateChangePayload>();\n const complete = createSignal<string>();\n\n let current: CompiledState = mustState(compiled.initial);\n let elapsed = 0;\n let completed = false;\n let activeFrameIndex = 0;\n // Every compiled state has >= 1 frame (validated), so frame 0 always exists.\n let activeFrameKey: string = current.frameKeys[0]!;\n let disposed = false;\n\n // `name` is always an initial / transition `to` / `onEnd` target, all\n // validated to exist by compileGraph, so the lookup never misses.\n function mustState(name: string): CompiledState {\n return compiled.states.get(name)!;\n }\n\n /** Enter `to`, optionally consuming the triggers the taking transition used. */\n function enter(to: string, consume: readonly string[]): void {\n const from = current.name;\n for (let i = 0; i < consume.length; i++) {\n store.consume(consume[i]!);\n }\n current = mustState(to);\n elapsed = 0;\n completed = false;\n activeFrameIndex = 0;\n activeFrameKey = current.frameKeys[0]!;\n // A self-re-entry (to === from) restarts the clip but is not a state change.\n if (to !== from) stateChange.emit({ to, from });\n }\n\n /**\n * First *effective* transition leaving `current` (own + Any-State), in\n * (priority desc, declared order) — or `undefined`. A transition whose target\n * equals the current state is effective only if it consumes a Trigger (a\n * Number/Boolean self-loop would otherwise restart the clip every frame).\n */\n function resolve(): { to: string; triggers: readonly string[] } | undefined {\n // Every state has a candidate list (built for all states by compileGraph).\n const list = compiled.candidatesByState.get(current.name)!;\n for (let i = 0; i < list.length; i++) {\n const t = list[i]!;\n let ok = true;\n const conds = t.conditions;\n for (let j = 0; j < conds.length; j++) {\n if (!conds[j]!(store)) {\n ok = false;\n break;\n }\n }\n if (!ok) continue;\n if (t.to === current.name && t.triggers.length === 0) continue;\n return { to: t.to, triggers: t.triggers };\n }\n return undefined;\n }\n\n function update(deltaMs: number): void {\n if (disposed) throw new SpriteAnimatorDisposedError();\n\n // Advance the playback timer (non-finite or non-positive dt clamped to 0 for determinism).\n // NOTE: `elapsed` accumulates without bound in looping states (intentional design choice).\n // Drift from floating-point accumulation is sub-ULP-relevant only after continuous play\n // on the order of years; a periodic reset (e.g. on state entry) would be premature.\n elapsed += (Number.isFinite(deltaMs) && deltaMs > 0 ? deltaMs : 0) * current.speed;\n\n // Evaluate input-driven transitions first; an explicit transition wins over\n // implicit end-of-clip behaviour.\n const chosen = resolve();\n if (chosen) {\n enter(chosen.to, chosen.triggers);\n return;\n }\n\n // Compute the active frame from accumulated time vs cumulative durations.\n const cs = current;\n const n = cs.frameKeys.length;\n let ended = false;\n if (!cs.loop && elapsed >= cs.total) {\n activeFrameIndex = n - 1;\n ended = true;\n } else {\n const t = cs.loop ? elapsed % cs.total : elapsed;\n let i = 0;\n // First index whose cumulative end exceeds t (i < n-1 keeps it in range,\n // and t < total guarantees a match no later than the last frame).\n while (i < n - 1 && t >= cs.cumulative[i]!) i++;\n activeFrameIndex = i;\n }\n activeFrameKey = cs.frameKeys[activeFrameIndex]!;\n\n // Fire onComplete once for a finished non-looping clip, then auto-transition.\n if (ended && !completed) {\n completed = true;\n complete.emit(cs.name);\n if (cs.onEnd !== undefined) enter(cs.onEnd, NO_TRIGGERS);\n }\n }\n\n function reset(): void {\n if (disposed) throw new SpriteAnimatorDisposedError();\n const from = current.name;\n store.reset();\n current = mustState(compiled.initial);\n elapsed = 0;\n completed = false;\n activeFrameIndex = 0;\n activeFrameKey = current.frameKeys[0]!;\n if (from !== compiled.initial) stateChange.emit({ to: compiled.initial, from });\n }\n\n function dispose(): void {\n if (disposed) return;\n disposed = true;\n stateChange.clear();\n complete.clear();\n }\n\n return {\n setInput(name, value) {\n if (disposed) throw new SpriteAnimatorDisposedError();\n store.setInput(name, value);\n },\n fireTrigger(name) {\n if (disposed) throw new SpriteAnimatorDisposedError();\n store.fireTrigger(name);\n },\n update,\n reset,\n dispose,\n onStateChange(handler: StateChangeHandler, options?: ListenerOptions): Unsubscribe {\n if (disposed) return () => {};\n return stateChange.on((p) => handler(p.to, p.from), options);\n },\n onComplete(handler: CompleteHandler, options?: ListenerOptions): Unsubscribe {\n if (disposed) return () => {};\n return complete.on(handler, options);\n },\n get activeState() {\n return current.name;\n },\n get activeFrameKey() {\n return activeFrameKey;\n },\n get activeFrameIndex() {\n return activeFrameIndex;\n },\n get disposed() {\n return disposed;\n },\n };\n}\n"]}