aispritejs 0.5.6 → 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
- ## 為什麼用 aispritejs
16
-
17
- - **輸入驅動,而非名稱驅動。** 你設定參數(`speed=4`、`isGrounded=false`、`fireTrigger("jump")`),而不是動畫名稱。視覺轉移存在於資料中,與遊戲程式解耦。
18
- - **核心與渲染器無關。** 狀態機從 delta-time 加上輸入算出當前影格;它從不 import PixiJS 也不碰 DOM。轉接器負責把結果對映成 texture。
19
- - **視覺 ≠ 邏輯。** 這嚴格來說是一個*視覺*動畫器,**不是**遊戲邏輯 FSM,也**不**相依 `aifsmjs`。用任何邏輯層(純程式、FSM、ECS)來驅動它 —— 它們以慣例組合,從不以相依耦合。
20
- - **輕巧又快。** O(1) 的輸入查找、對離開當前狀態的轉移做 O(N) 檢查、無每幀配置。
21
-
22
- ## 何時你「不需要」aispritejs
23
-
24
- `aispritejs` 的價值在於影格選擇是**由執行期輸入驅動**、且橫跨**數個視覺狀態**時才會顯現。在這個門檻之下,直接用 PixiJS 就好 —— 用狀態機並無任何好處:
25
-
26
- - **單一 sprite/一張靜態圖片** → 純 PixiJS [`Sprite`](https://pixijs.download/release/docs/scene.Sprite.html)。沒有動畫、不需要圖集。
27
- - **單一循環片段、無任何分支**(轉動的金幣、閃爍的火把)→ PixiJS [`AnimatedSprite`](https://pixijs.download/release/docs/scene.AnimatedSprite.html)(`AnimatedSprite.fromFrames(...)`、`.play()`)。永遠以同一種方式播放的單一片段不需要輸入。
28
- - **沒有 texture atlas**(你並未把影格打包成 spritesheet)→ 直接載入圖片;`aispritejs` 從 PixiJS-v8 atlas(`animations` / `frames`)讀取它的影格。
29
- - **沒有多狀態切換** —— 若你的程式早已確切知道要播哪個片段、只需呼叫 `.play()` / `.gotoAndStop()`,你並不需要轉移圖。
30
-
31
- 當你有**由輸入驅動、以 texture atlas 為後盾的多狀態視覺切換**時,才該動用 `aispritejs` —— 例如 `idle ⇄ walk → jump`,或由 trigger 觸發的一次性受擊 FX —— 此時畫面上顯示哪一格是 `speed` / `isGrounded` / `attack` 的函數,而非寫死的 `play()` 呼叫。
32
-
33
- ## 心智模型
34
-
35
- ```
36
- inputs ─▶ [轉移圖] ─▶ 當前狀態 ─▶ (Δt) ─▶ 當前影格 ─▶ 轉接器 ─▶ texture
9
+ ```bash
10
+ pnpm add aispritejs
11
+ pnpm add pixi.js # only when using aispritejs/pixi
37
12
  ```
38
13
 
39
- - **Inputs(輸入)** —— `Number`(連續,如 `speed`)、`Boolean`(開關,如 `isGrounded`)、`Trigger`(一次性;被某個轉移消耗後自動重置,如 `jump` / `attack`)。
40
- - **States(狀態)** —— 一個動畫鍵(指向 atlas 的 `animations`)加上 loop / on-end 行為與選用的速度倍率。
41
- - **Transitions(轉移)** —— 當輸入條件成立時(`Equals` / `NotEquals` / `GreaterThan` / `LessThan` / `Trigger`),從某狀態(或 **Any State**)轉到另一狀態。優先度最高且成立者勝出。
42
- - **`update(dt)`** —— 推進播放計時器;評估轉移(切換狀態、觸發 `onStateChange`、消耗 trigger);由動畫的逐影格時長加 loop 算出當前影格;非循環片段播完時觸發 `onComplete`。
43
-
44
- ## 快速開始 —— 核心(零相依)
45
-
46
14
  ```ts
47
15
  import { createSpriteAnimator } from "aispritejs";
48
-
49
- const anim = createSpriteAnimator(graph); // graph = { inputs, states, transitions, animations }
50
-
51
- anim.setInput("speed", 4);
52
- anim.setInput("isGrounded", true);
53
- anim.fireTrigger("jump");
54
-
55
- anim.onStateChange((to, from) => {/* ... */});
56
- anim.onComplete((state) => {/* ... */});
57
-
58
- // 在你的 render loop 裡:
59
- anim.update(deltaMs);
60
- const frameKey = anim.activeFrameKey; // 交給你的渲染器
61
- ```
62
-
63
- ## 快速開始 —— PixiJS v8 轉接器
64
-
65
- `aispritejs/pixi` 子路徑把核心綁到一個 `PIXI.Sprite`。`pixi.js` 是**選用的** `peerDependency`,且僅以 **type-only** 方式 import —— 編譯後的轉接器不含任何 `pixi.js` runtime require,核心也永不碰它。
66
-
67
- ```ts
68
- import { createPixiSpriteAnimator } from "aispritejs/pixi"; // pixi.js 是選用 peer
69
-
70
- // `textures` 是 PIXI.Spritesheet(或 frame-key → Texture 的 map),需涵蓋 graph
71
- // 參照的每一格 —— 缺鍵會丟出 MissingTextureError。
72
- const view = createPixiSpriteAnimator(sprite, graph, spritesheet);
73
-
74
- // 每幀:
75
- view.update(deltaMs); // 把綁定 sprite 的 texture 換成當前影格,
76
- // 並套用該格的 atlas anchor(texture.defaultAnchor)
77
-
78
- view.setInput("speed", 4);
79
- view.fireTrigger("jump");
80
16
  ```
81
17
 
82
- 它只在當前影格改變時換 texture,並尊重逐影格 `duration`(透過核心)與非置中/腳底樞軸(透過 `texture.defaultAnchor`;傳 `{ applyAnchor: false }` 可自行管理 anchor)。`view.sprite` 是綁定的 sprite;`dispose()` 拆除核心但不銷毀 sprite。
83
-
84
- ### 完整範例 —— 一段 6 格爆炸(play-once FX)
85
-
86
- 最常見的入門場景:一次性的受擊/撞擊 FX(水花、槍口閃光),來自一張 **6 格爆炸 sprite sheet**,透過 `/pixi` 轉接器驅動。一個 `Trigger` 觸發一段**非循環**片段,播放一次後經由 `onEnd` 自動回到靜止影格。完整可執行版本在 [`examples/02-explosion-pixi/index.ts`](examples/02-explosion-pixi/index.ts)(`pnpm example:explosion`)。
18
+ ## 快速開始 - Core
87
19
 
88
20
  ```ts
89
- import { Assets, Sprite } from "pixi.js";
90
- import { createPixiSpriteAnimator } from "aispritejs/pixi";
91
-
92
- // 一張 6 格爆炸 sprite sheet(PixiJS-v8 atlas):`animations` 區塊把片段
93
- // 命名 → 它的影格鍵;`frames` 帶有逐影格時長。
94
- const graph = {
95
- animations: {
96
- explosion: ["explosion_0", "explosion_1", "explosion_2", "explosion_3", "explosion_4", "explosion_5"],
97
- idle: ["explosion_0"], // 一段 1 格的靜止片段,在每次爆發之間維持
98
- },
99
- frames: {
100
- explosion_0: { duration: 40 }, explosion_1: { duration: 40 }, explosion_2: { duration: 40 },
101
- explosion_3: { duration: 40 }, explosion_4: { duration: 40 }, explosion_5: { duration: 40 },
21
+ const anim = createSpriteAnimator({
22
+ inputs: {
23
+ speed: { type: "number", default: 0 },
24
+ jump: { type: "trigger" },
102
25
  },
103
- inputs: { detonate: { type: "trigger" } },
26
+ initial: "idle",
104
27
  states: {
105
- idle: { animation: "idle", loop: true },
106
- 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" },
107
31
  },
108
32
  transitions: [
109
- { 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" }] },
110
35
  ],
111
- initial: "idle",
112
- } as const;
113
-
114
- // 載入打包好的 sheet;它的 `.textures` 涵蓋 graph 用到的每一個影格鍵。
115
- const sheet = await Assets.load("explosion.json"); // 一個 PIXI.Spritesheet
116
- const sprite = new Sprite();
117
- const fx = createPixiSpriteAnimator(sprite, graph, sheet);
118
-
119
- // 在撞擊時觸發一次性 trigger:
120
- fx.fireTrigger("detonate");
121
-
122
- // 從你的 PixiJS render loop(例如 `app.ticker`)驅動這段爆發,依經過的
123
- // 毫秒數推進:`app.ticker.add((ticker) => fx.update(ticker.deltaMS))`。
124
- ```
125
-
126
- `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` 實例、在無頭環境下操練真正的轉接器。
127
-
128
- ## 資料格式(atlas)
129
-
130
- `aispritejs` 讀取 **PixiJS v8 原生**的 spritesheet atlas(`meta` / `frames` / `animations`),並額外加上一個 `aispritejs` 的**輸入驅動**控制區塊:
131
-
132
- ```jsonc
133
- {
134
- "meta": { "image": "sheet.png", "size": { "w": 1024, "h": 1024 }, "scale": "1" },
135
- "frames": { /* PixiJS 原生:frame{x,y,w,h}、anchor、duration、trimmed... */ },
136
- "animations": { "idle": ["idle_0", "idle_1"], "walk": ["walk_0", "..."], "jump": ["jump_0", "..."] },
137
-
138
- "inputs": {
139
- "speed": { "type": "number", "default": 0 },
140
- "isGrounded": { "type": "boolean", "default": true },
141
- "jump": { "type": "trigger" }
142
- },
143
- "states": {
144
- "idle": { "animation": "idle", "loop": true },
145
- "walk": { "animation": "walk", "loop": true },
146
- "jump": { "animation": "jump", "loop": false }
36
+ animations: {
37
+ idle: ["idle_0"],
38
+ run: ["run_0", "run_1"],
39
+ jump: ["jump_0", "jump_1"],
147
40
  },
148
- "transitions": [
149
- { "from": "*", "to": "jump", "when": [{ "input": "jump", "op": "Trigger" }], "priority": 10 },
150
- { "from": "idle", "to": "walk", "when": [{ "input": "speed", "op": "GreaterThan", "value": 0 }] },
151
- { "from": "walk", "to": "idle", "when": [{ "input": "speed", "op": "Equals", "value": 0 }] }
152
- ]
153
- }
154
- ```
155
-
156
- 這個**輸入驅動**模型刻意與事件驅動 FSM 區隔。`aispritejs` 只吃通用的 `frames` / `animations`;`inputs` / `states` / `transitions` 屬於它自己。若某個 atlas 帶有來自其他工具的事件驅動 `states` 區塊,`aispritejs` 會忽略它。
157
-
158
- ## 載入 atlas —— `aispritejs/atlas`
41
+ });
159
42
 
160
- `aispritejs/atlas` 子路徑把已解析的 PixiJS v8 atlas 轉成 graph(或現成的 animator)。它是純函式且零相依。
161
-
162
- ```ts
163
- import { parseAtlas, loadAtlas } from "aispritejs/atlas";
164
-
165
- // 增強型 atlas(上述形狀,inputs/states/transitions 內嵌):
166
- const anim = loadAtlas(atlasJson);
167
-
168
- // 真實 atlas 的 `states` 是 foreign(事件驅動)或不存在 —— 另外傳入輸入驅動
169
- // 控制區塊;foreign 區塊會被忽略:
170
- 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);
171
47
  ```
172
48
 
173
- - `parseAtlas(atlas, control?)` → `SpriteGraph`;`loadAtlas(atlas, control?)` → `SpriteAnimator`(一步 parse + create,fail-fast)。
174
- - foreign 事件驅動 `states`(`{ initial, definitions }` 的 FSM 形狀)會被**偵測並忽略** —— 改傳 `aispritejs` 控制區塊。結構問題丟出 `InvalidAtlasError`;語意問題由核心丟出 `InvalidGraphError`。
175
- - 標準結構以 JSON Schema 發佈於 [`schemas/aispritejs-graph.schema.json`](schemas/aispritejs-graph.schema.json)(亦匯出為 `aispritejs/schema`),供編輯器與 CI 驗證;parser 以程式碼鏡像它,因此無 runtime schema-validator 相依。
176
-
177
- ## 核心 API
178
-
179
- 公開介面是單一工廠函式,加上型別與具名錯誤。**不匯出任何 class 建構子** —— `createSpriteAnimator` 回傳一個 `SpriteAnimator`。
49
+ ## PixiJS Adapter
180
50
 
181
51
  ```ts
182
- const anim = createSpriteAnimator(graph); // graph 不合法時丟出 InvalidGraphError
183
-
184
- anim.setInput(name, value); // Number | Boolean;丟出 Unknown/InputTypeError
185
- anim.fireTrigger(name); // 將某個 Trigger 標記為 pending
186
- anim.update(deltaMs); // 推進;評估轉移;推進影格
187
- anim.reset(); // 回到初始狀態與預設輸入(保留 buffer)
188
- anim.dispose(); // 冪等;之後呼叫 mutator 會丟錯
189
-
190
- const off = anim.onStateChange((to, from) => {}, { signal?, once? }); // → 取消訂閱
191
- const off2 = anim.onComplete((state) => {}, { signal?, once? });
52
+ import { createPixiSpriteAnimator } from "aispritejs/pixi";
192
53
 
193
- anim.activeState; // 當前狀態名稱
194
- anim.activeFrameKey; // 指向 atlas `frames` 的影格鍵 —— 交給渲染器
195
- anim.activeFrameIndex; // 在當前動畫中的索引
196
- anim.disposed; // boolean
54
+ const view = createPixiSpriteAnimator(sprite, graph, spritesheet);
55
+ view.update(deltaMs);
56
+ view.dispose(); // dispose core animator;不 destroy Pixi sprite
197
57
  ```
198
58
 
199
- 每個訂閱都回傳一個取消訂閱函式,並接受 `{ signal }`(用 `AbortSignal` 移除監聽)與 `{ once }`。
200
-
201
- ## 語意(精確規則)
202
-
203
- 這些規則是決定性的,並在 1.0 推出後對 1.x 凍結:
204
-
205
- - **`update(dt)` 順序** —— 以 `dt × speed` 推進計時器(非有限或非正值的 `dt` 夾到 `0`);評估轉移;重算當前影格;為播完的非循環片段觸發 `onComplete`,接著執行任何 `onEnd` 自動轉移。因此同一幀內,明確的輸入轉移優先於片段結束行為。
206
- - **轉移解析** —— 在離開當前狀態的轉移(加上 **Any-State** `from: "*"`)中,候選依 `priority`(遞減)再依宣告順序(遞增)排序;取**第一個有效**者。所有 `when` 條件須全部成立(邏輯 AND)。
207
- - **自我轉移規則** —— `to` 等於當前狀態的轉移,*只有在它消耗一個 Trigger 時才有效*。純 Number/Boolean 的自我迴圈會被跳過,因此不會每幀把片段重置回第 0 格。帶 trigger 的自我轉移會**重啟**片段(例如連續攻擊),但**不**觸發 `onStateChange`(狀態名稱沒變)。
208
- - **Triggers** —— `fireTrigger(name)` 把某 trigger 標記為 pending;它跨幀保持 pending,直到某個檢查它的轉移被採用而**消耗**它。一次 fire → 至多一次轉移。
209
- - **影格時序** —— 當前影格是累積時長首次超過已經過時間的那一格。循環片段在總時長處回繞;非循環片段停在最後一格並只觸發一次 `onComplete`。逐影格 `duration` 取自 atlas `frames`;沒有的影格採用 `defaultFrameDuration`(預設 `100` ms)。`speed` 是時間倍率(`2` = 兩倍速)。
210
- - **決定性** —— 相同的輸入加 `dt` 序列必產生相同的影格序列。無轉移的路徑不做任何配置。
211
-
212
- ## 錯誤
213
-
214
- 具名錯誤,從不裸 throw:
215
-
216
- - `InvalidGraphError` —— `createSpriteAnimator` 在 graph 驗證失敗時丟出(缺少動畫、未知的轉移目標、運算子/型別不符、非正的時長/速度、`onEnd` 配 `loop:true`…)。Fail-fast:不合法的 graph 絕不產出半成品動畫器。
217
- - `UnknownInputError` —— 對 `inputs` 未宣告的輸入呼叫 `setInput` / `fireTrigger`(帶有 `.input`)。
218
- - `InputTypeError` —— 值型別錯誤、對 Trigger 呼叫 `setInput`、或對非 Trigger 呼叫 `fireTrigger`(帶有 `.input`)。
219
- - `SpriteAnimatorDisposedError` —— `dispose()` 之後呼叫任何 mutator(`setInput` / `fireTrigger` / `update` / `reset`)。
220
-
221
- ## 解耦(P0)
59
+ `pixi.js` 是 optional peer dependency,adapter 只用 type-only import。root package 不 import Pixi、DOM 或 canvas API。
222
60
 
223
- - **零跨套件 import** —— `aispritejs` 不 import `aifsmjs`、`aieventjs` 或任何手足套件。它有自己最小的具名 emitter。
224
- - **核心與渲染器無關** —— 根進入點永不 import `pixi.js`。只有 `aispritejs/pixi` 會,而 `pixi.js` 是**選用的** `peerDependency`。
225
- - **僅是視覺動畫器** —— 用設定輸入的方式搭配遊戲邏輯層;它不假設你的邏輯如何組織。
61
+ ## Atlas and Schema
226
62
 
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
- | | aispritejs | Rive | aifsmjs | 原生 `AnimatedSprite` |
230
- |---|---|---|---|---|
231
- | 控制模型 | 輸入驅動(Number/Boolean/Trigger) | 輸入驅動 | 事件驅動(邏輯) | 手動 |
232
- | 範圍 | 視覺動畫 | 視覺動畫 | 遊戲邏輯 | 僅播放 |
233
- | Runtime | 輕巧 TS,無 wasm | wasm runtime | 輕巧 TS | — |
234
- | 渲染器 | 無關 + 轉接器 | 自帶 | n/a | PixiJS |
235
-
236
- `aispritejs` 與 `aifsmjs` 互補 —— 邏輯 FSM 設定輸入、視覺動畫器挑影格 —— 且永不耦合。
237
-
238
- ## 給 AI agent 的閱讀指南
239
-
240
- - **一次抓完整 context** —— [`llms-full.txt`](llms-full.txt) 串接了本 README、changelog、contributing 指南與範例索引。
241
- - **原始碼版面** —— 核心位於 [`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 任何渲染器。
242
- - **穩定度分級** —— 見 [STABILITY.md](STABILITY.md)。
243
-
244
- ## 測試
245
-
246
- `vitest` 行為測試涵蓋輸入、轉移、trigger 消耗、影格時序、`onComplete` / `onEnd`、訂閱(`signal` / `once`)、`dispose` / `reset` 與 graph 驗證。`fast-check` 屬性測試驗證**轉移決定性**(相同輸入加 `dt` 序列 ⇒ 相同影格軌跡)與 **trigger 消耗**(一次 fire ⇒ 一次進入)。覆蓋率以家族下限執行(≥95% statements / ≥90% branches / 100% functions 與 lines)。
247
-
248
- ```bash
249
- pnpm test # 跑一次
250
- pnpm coverage # 帶門檻
251
- pnpm example:platformer
252
- ```
68
+ ## 核心 API
253
69
 
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
- **v0.1.3 —— 文件修補。** 新增「何時你『不需要』aispritejs」門檻,以及一個完整、可執行、針對 `/pixi` 轉接器的 6 格爆炸(play-once FX)快速範例;原始碼與 API 無變動。v0.1.0 一併 ship 所有 roadmap 模組 1–4:與渲染器無關的核心(`.`)、PixiJS v8 轉接器(`aispritejs/pixi`)、atlas parser(`aispritejs/atlas`)、JSON Schema(`aispritejs/schema`);v0.1.1 加上 OIDC/SLSA 發佈 provenance,v0.1.2 強化驗證 —— 在編譯期拒絕非有限的 `speed` / `duration` / `defaultFrameDuration`,並在 `update()` 執行期把非有限或非正值(`dt <= 0`)的 `dt` 夾到 `0`。完整歷史請見 [CHANGELOG.md](CHANGELOG.md)。零執行期相依;根 import 圖不含 `pixi.js`;`pixi.js` 是選用、type-only 的 peer,僅 `/pixi` 子路徑用。
78
+ ## 注意事項
257
79
 
258
- `aispritejs` 是 **ai\*js** 家族中最新的套件,採用**自己獨立的版本線** —— `0.1.x` 系列反映的是這個套件自身的成熟度,而非與任何手足套件的版本號對齊。在某個遊戲裡使用率低(例如以靜態 sprite 為主的遊戲)是正常現象,並非缺陷。
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