aispritejs 0.1.2 → 0.5.5

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 CHANGED
@@ -19,6 +19,17 @@ Part of the **ai\*js** family: zero cross-package dependencies, framework-agnost
19
19
  - **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.
20
20
  - **Tiny + fast.** O(1) input lookups, O(N) checks over transitions leaving the current state, no per-frame allocation.
21
21
 
22
+ ## When you DON'T need aispritejs
23
+
24
+ `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:
25
+
26
+ - **A single sprite / one static image** → a plain PixiJS [`Sprite`](https://pixijs.download/release/docs/scene.Sprite.html). No animation, no graph.
27
+ - **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.
28
+ - **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`).
29
+ - **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.
30
+
31
+ 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.
32
+
22
33
  ## Mental model
23
34
 
24
35
  ```
@@ -72,6 +83,50 @@ It swaps the texture only when the active frame changes, and honours per-frame `
72
83
 
73
84
  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.)
74
85
 
86
+ ### Complete example — a 6-frame explosion (play-once FX)
87
+
88
+ 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`).
89
+
90
+ ```ts
91
+ import { Assets, Sprite } from "pixi.js";
92
+ import { createPixiSpriteAnimator } from "aispritejs/pixi";
93
+
94
+ // A 6-frame explosion sprite sheet (PixiJS-v8 atlas): the `animations` block
95
+ // names the clip → its frame keys; `frames` carries per-frame durations.
96
+ const graph = {
97
+ animations: {
98
+ explosion: ["explosion_0", "explosion_1", "explosion_2", "explosion_3", "explosion_4", "explosion_5"],
99
+ idle: ["explosion_0"], // a 1-frame resting clip to hold between bursts
100
+ },
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" } },
106
+ states: {
107
+ idle: { animation: "idle", loop: true },
108
+ boom: { animation: "explosion", loop: false, onEnd: "idle" }, // play once → back to idle
109
+ },
110
+ transitions: [
111
+ { from: "*", to: "boom", when: [{ input: "detonate", op: "Trigger" }], priority: 10 },
112
+ ],
113
+ initial: "idle",
114
+ } as const;
115
+
116
+ // Load the packed sheet; its `.textures` cover every frame key the graph uses.
117
+ const sheet = await Assets.load("explosion.json"); // a PIXI.Spritesheet
118
+ const sprite = new Sprite();
119
+ const fx = createPixiSpriteAnimator(sprite, graph, sheet);
120
+
121
+ // Fire the one-shot trigger on impact:
122
+ fx.fireTrigger("detonate");
123
+
124
+ // Drive the burst from your PixiJS render loop (e.g. `app.ticker`), advancing by
125
+ // elapsed ms: `app.ticker.add((ticker) => fx.update(ticker.deltaMS))`.
126
+ ```
127
+
128
+ `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.
129
+
75
130
  ## Data format (atlas)
76
131
 
77
132
  `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:
@@ -155,7 +210,7 @@ Every subscription returns an unsubscribe function and accepts `{ signal }` (an
155
210
 
156
211
  These rules are deterministic and frozen for the 1.x line once 1.0 ships:
157
212
 
158
- - **`update(dt)` order** — advance the timer by `dt × speed` (negative `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.
213
+ - **`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.
159
214
  - **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).
160
215
  - **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).
161
216
  - **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.
@@ -195,12 +250,15 @@ Behavioural `vitest` suites cover inputs, transitions, trigger consumption, fram
195
250
  ```bash
196
251
  pnpm test # run once
197
252
  pnpm coverage # with thresholds
198
- pnpm example:platformer
253
+ pnpm example:platformer # renderer-free core demo
254
+ pnpm example:explosion # 6-frame play-once FX via the /pixi adapter
199
255
  ```
200
256
 
201
257
  ## Status
202
258
 
203
- **v0.1.1 — OIDC/SLSA publish.** The npm tarball now carries SLSA build provenance (OIDC trusted-publisher pipeline). No source or API changes from v0.1.0, which shipped all roadmap modules (1–4): the renderer-agnostic core (`.`), the PixiJS v8 adapter (`aispritejs/pixi`), the atlas parser (`aispritejs/atlas`), and the JSON Schema (`aispritejs/schema`). See [CHANGELOG.md](CHANGELOG.md) for the full history. Zero runtime dependencies; the root import graph contains no `pixi.js`; `pixi.js` is an optional, type-only peer used only by the `/pixi` subpath.
259
+ **v0.1.3 — docs patch.** Adds a "When you DON'T need aispritejs" threshold and a complete, runnable 6-frame explosion (play-once FX) quickstart for the `/pixi` adapter; no source or API changes. v0.1.0 shipped all roadmap modules (1–4): the renderer-agnostic core (`.`), the PixiJS v8 adapter (`aispritejs/pixi`), the atlas parser (`aispritejs/atlas`), and the JSON Schema (`aispritejs/schema`); v0.1.1 added OIDC/SLSA publish provenance and v0.1.2 hardened validation — compile-time rejection of non-finite `speed` / `duration` / `defaultFrameDuration`, plus a runtime clamp of non-finite or non-positive `dt` (`dt <= 0`) to `0` in `update()`. See [CHANGELOG.md](CHANGELOG.md) for the full history. Zero runtime dependencies; the root import graph contains no `pixi.js`; `pixi.js` is an optional, type-only peer used only by the `/pixi` subpath.
260
+
261
+ `aispritejs` is the newest package in the **ai\*js** family and follows its **own independent version line** — the `0.1.x` series reflects this package's own maturation, not alignment with any sibling's version number. Low usage in a given game (e.g. one built on static sprites) is expected, not a defect.
204
262
 
205
263
  ## Roadmap
206
264
 
package/README_ZHTW.md CHANGED
@@ -19,6 +19,17 @@
19
19
  - **視覺 ≠ 邏輯。** 這嚴格來說是一個*視覺*動畫器,**不是**遊戲邏輯 FSM,也**不**相依 `aifsmjs`。用任何邏輯層(純程式、FSM、ECS)來驅動它 —— 它們以慣例組合,從不以相依耦合。
20
20
  - **輕巧又快。** O(1) 的輸入查找、對離開當前狀態的轉移做 O(N) 檢查、無每幀配置。
21
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
+
22
33
  ## 心智模型
23
34
 
24
35
  ```
@@ -70,6 +81,50 @@ view.fireTrigger("jump");
70
81
 
71
82
  它只在當前影格改變時換 texture,並尊重逐影格 `duration`(透過核心)與非置中/腳底樞軸(透過 `texture.defaultAnchor`;傳 `{ applyAnchor: false }` 可自行管理 anchor)。`view.sprite` 是綁定的 sprite;`dispose()` 拆除核心但不銷毀 sprite。
72
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`)。
87
+
88
+ ```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 },
102
+ },
103
+ inputs: { detonate: { type: "trigger" } },
104
+ states: {
105
+ idle: { animation: "idle", loop: true },
106
+ boom: { animation: "explosion", loop: false, onEnd: "idle" }, // 播放一次 → 回到 idle
107
+ },
108
+ transitions: [
109
+ { from: "*", to: "boom", when: [{ input: "detonate", op: "Trigger" }], priority: 10 },
110
+ ],
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
+
73
128
  ## 資料格式(atlas)
74
129
 
75
130
  `aispritejs` 讀取 **PixiJS v8 原生**的 spritesheet atlas(`meta` / `frames` / `animations`),並額外加上一個 `aispritejs` 的**輸入驅動**控制區塊:
@@ -147,7 +202,7 @@ anim.disposed; // boolean
147
202
 
148
203
  這些規則是決定性的,並在 1.0 推出後對 1.x 凍結:
149
204
 
150
- - **`update(dt)` 順序** —— 以 `dt × speed` 推進計時器(負的 `dt` 夾到 `0`);評估轉移;重算當前影格;為播完的非循環片段觸發 `onComplete`,接著執行任何 `onEnd` 自動轉移。因此同一幀內,明確的輸入轉移優先於片段結束行為。
205
+ - **`update(dt)` 順序** —— 以 `dt × speed` 推進計時器(非有限或非正值的 `dt` 夾到 `0`);評估轉移;重算當前影格;為播完的非循環片段觸發 `onComplete`,接著執行任何 `onEnd` 自動轉移。因此同一幀內,明確的輸入轉移優先於片段結束行為。
151
206
  - **轉移解析** —— 在離開當前狀態的轉移(加上 **Any-State** `from: "*"`)中,候選依 `priority`(遞減)再依宣告順序(遞增)排序;取**第一個有效**者。所有 `when` 條件須全部成立(邏輯 AND)。
152
207
  - **自我轉移規則** —— `to` 等於當前狀態的轉移,*只有在它消耗一個 Trigger 時才有效*。純 Number/Boolean 的自我迴圈會被跳過,因此不會每幀把片段重置回第 0 格。帶 trigger 的自我轉移會**重啟**片段(例如連續攻擊),但**不**觸發 `onStateChange`(狀態名稱沒變)。
153
208
  - **Triggers** —— `fireTrigger(name)` 把某 trigger 標記為 pending;它跨幀保持 pending,直到某個檢查它的轉移被採用而**消耗**它。一次 fire → 至多一次轉移。
@@ -198,7 +253,9 @@ pnpm example:platformer
198
253
 
199
254
  ## 狀態
200
255
 
201
- **v0.1.1 —— OIDC/SLSA 發佈。** npm tarball 現在帶有 SLSA build provenance(OIDC 受信任發行者流水線)。原始碼與 API 相較 v0.1.0 無變動,v0.1.0 一併 ship 所有 roadmap 模組 1–4:與渲染器無關的核心(`.`)、PixiJS v8 轉接器(`aispritejs/pixi`)、atlas parser(`aispritejs/atlas`)、JSON Schema(`aispritejs/schema`)。完整歷史請見 [CHANGELOG.md](CHANGELOG.md)。零執行期相依;根 import 圖不含 `pixi.js`;`pixi.js` 是選用、type-only 的 peer,僅 `/pixi` 子路徑用。
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` 子路徑用。
257
+
258
+ `aispritejs` 是 **ai\*js** 家族中最新的套件,採用**自己獨立的版本線** —— `0.1.x` 系列反映的是這個套件自身的成熟度,而非與任何手足套件的版本號對齊。在某個遊戲裡使用率低(例如以靜態 sprite 為主的遊戲)是正常現象,並非缺陷。
202
259
 
203
260
  ## Roadmap
204
261
 
package/llms-full.txt CHANGED
@@ -32,6 +32,17 @@ Part of the **ai\*js** family: zero cross-package dependencies, framework-agnost
32
32
  - **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.
33
33
  - **Tiny + fast.** O(1) input lookups, O(N) checks over transitions leaving the current state, no per-frame allocation.
34
34
 
35
+ ## When you DON'T need aispritejs
36
+
37
+ `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:
38
+
39
+ - **A single sprite / one static image** → a plain PixiJS [`Sprite`](https://pixijs.download/release/docs/scene.Sprite.html). No animation, no graph.
40
+ - **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.
41
+ - **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`).
42
+ - **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.
43
+
44
+ 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.
45
+
35
46
  ## Mental model
36
47
 
37
48
  ```
@@ -85,6 +96,50 @@ It swaps the texture only when the active frame changes, and honours per-frame `
85
96
 
86
97
  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
98
 
99
+ ### Complete example — a 6-frame explosion (play-once FX)
100
+
101
+ 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`).
102
+
103
+ ```ts
104
+ import { Assets, Sprite } from "pixi.js";
105
+ import { createPixiSpriteAnimator } from "aispritejs/pixi";
106
+
107
+ // A 6-frame explosion sprite sheet (PixiJS-v8 atlas): the `animations` block
108
+ // names the clip → its frame keys; `frames` carries per-frame durations.
109
+ const graph = {
110
+ animations: {
111
+ explosion: ["explosion_0", "explosion_1", "explosion_2", "explosion_3", "explosion_4", "explosion_5"],
112
+ idle: ["explosion_0"], // a 1-frame resting clip to hold between bursts
113
+ },
114
+ frames: {
115
+ explosion_0: { duration: 40 }, explosion_1: { duration: 40 }, explosion_2: { duration: 40 },
116
+ explosion_3: { duration: 40 }, explosion_4: { duration: 40 }, explosion_5: { duration: 40 },
117
+ },
118
+ inputs: { detonate: { type: "trigger" } },
119
+ states: {
120
+ idle: { animation: "idle", loop: true },
121
+ boom: { animation: "explosion", loop: false, onEnd: "idle" }, // play once → back to idle
122
+ },
123
+ transitions: [
124
+ { from: "*", to: "boom", when: [{ input: "detonate", op: "Trigger" }], priority: 10 },
125
+ ],
126
+ initial: "idle",
127
+ } as const;
128
+
129
+ // Load the packed sheet; its `.textures` cover every frame key the graph uses.
130
+ const sheet = await Assets.load("explosion.json"); // a PIXI.Spritesheet
131
+ const sprite = new Sprite();
132
+ const fx = createPixiSpriteAnimator(sprite, graph, sheet);
133
+
134
+ // Fire the one-shot trigger on impact:
135
+ fx.fireTrigger("detonate");
136
+
137
+ // Drive the burst from your PixiJS render loop (e.g. `app.ticker`), advancing by
138
+ // elapsed ms: `app.ticker.add((ticker) => fx.update(ticker.deltaMS))`.
139
+ ```
140
+
141
+ `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.
142
+
88
143
  ## Data format (atlas)
89
144
 
90
145
  `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:
@@ -168,7 +223,7 @@ Every subscription returns an unsubscribe function and accepts `{ signal }` (an
168
223
 
169
224
  These rules are deterministic and frozen for the 1.x line once 1.0 ships:
170
225
 
171
- - **`update(dt)` order** — advance the timer by `dt × speed` (negative `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.
226
+ - **`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.
172
227
  - **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).
173
228
  - **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).
174
229
  - **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.
@@ -208,12 +263,15 @@ Behavioural `vitest` suites cover inputs, transitions, trigger consumption, fram
208
263
  ```bash
209
264
  pnpm test # run once
210
265
  pnpm coverage # with thresholds
211
- pnpm example:platformer
266
+ pnpm example:platformer # renderer-free core demo
267
+ pnpm example:explosion # 6-frame play-once FX via the /pixi adapter
212
268
  ```
213
269
 
214
270
  ## Status
215
271
 
216
- **v0.1.1 — OIDC/SLSA publish.** The npm tarball now carries SLSA build provenance (OIDC trusted-publisher pipeline). No source or API changes from v0.1.0, which shipped all roadmap modules (1–4): the renderer-agnostic core (`.`), the PixiJS v8 adapter (`aispritejs/pixi`), the atlas parser (`aispritejs/atlas`), and the JSON Schema (`aispritejs/schema`). See [CHANGELOG.md](CHANGELOG.md) for the full history. Zero runtime dependencies; the root import graph contains no `pixi.js`; `pixi.js` is an optional, type-only peer used only by the `/pixi` subpath.
272
+ **v0.1.3 — docs patch.** Adds a "When you DON'T need aispritejs" threshold and a complete, runnable 6-frame explosion (play-once FX) quickstart for the `/pixi` adapter; no source or API changes. v0.1.0 shipped all roadmap modules (1–4): the renderer-agnostic core (`.`), the PixiJS v8 adapter (`aispritejs/pixi`), the atlas parser (`aispritejs/atlas`), and the JSON Schema (`aispritejs/schema`); v0.1.1 added OIDC/SLSA publish provenance and v0.1.2 hardened validation — compile-time rejection of non-finite `speed` / `duration` / `defaultFrameDuration`, plus a runtime clamp of non-finite or non-positive `dt` (`dt <= 0`) to `0` in `update()`. See [CHANGELOG.md](CHANGELOG.md) for the full history. Zero runtime dependencies; the root import graph contains no `pixi.js`; `pixi.js` is an optional, type-only peer used only by the `/pixi` subpath.
273
+
274
+ `aispritejs` is the newest package in the **ai\*js** family and follows its **own independent version line** — the `0.1.x` series reflects this package's own maturation, not alignment with any sibling's version number. Low usage in a given game (e.g. one built on static sprites) is expected, not a defect.
217
275
 
218
276
  ## Roadmap
219
277
 
@@ -236,6 +294,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
236
294
 
237
295
  ## [Unreleased]
238
296
 
297
+ ## [0.5.5] - 2026-06-08
298
+
299
+ ### Changed
300
+
301
+ - Project home migrated to the [`islumina`](https://github.com/islumina) GitHub org; published from there via npm trusted publisher (OIDC + SLSA provenance). Version realigned from the `0.1.x` line to the shared ai\*js family version `0.5.5` — no API changes; the runtime is unchanged from `0.1.3`.
302
+
303
+ ## [0.1.3] - 2026-06-05
304
+
305
+ ### Added
306
+
307
+ - Docs: a "When you DON'T need aispritejs" threshold section and a complete, runnable 6-frame
308
+ explosion (play-once FX) `/pixi` quickstart (`examples/02-explosion-pixi`); no source/API changes.
309
+
239
310
  ## [0.1.2] - 2026-06-05
240
311
 
241
312
  ### Changed
@@ -362,7 +433,10 @@ parser, and JSON Schema (roadmap modules 1–4).
362
433
  / lines (above the family floor of 95 / 90 / 100 / 100). Core gzip ≈ 3.5 KB.
363
434
  - OIDC + SLSA provenance publish on tag-push.
364
435
 
365
- [Unreleased]: https://github.com/yshengliao/aispritejs/compare/v0.1.1...HEAD
436
+ [Unreleased]: https://github.com/islumina/aispritejs/compare/v0.5.5...HEAD
437
+ [0.5.5]: https://github.com/islumina/aispritejs/releases/tag/v0.5.5
438
+ [0.1.3]: https://github.com/yshengliao/aispritejs/compare/v0.1.2...v0.1.3
439
+ [0.1.2]: https://github.com/yshengliao/aispritejs/compare/v0.1.1...v0.1.2
366
440
  [0.1.1]: https://github.com/yshengliao/aispritejs/compare/v0.1.0...v0.1.1
367
441
  [0.1.0]: https://github.com/yshengliao/aispritejs/releases/tag/v0.1.0
368
442
 
@@ -472,4 +546,29 @@ classic `idle` / `walk` / `jump` graph and exercises every core idea:
472
546
  The same `graph` object would drive a PixiJS sprite unchanged via the
473
547
  `aispritejs/pixi` adapter — the core never knows a renderer exists.
474
548
 
549
+ ## 02 — explosion (PixiJS v8 adapter)
550
+
551
+ ```bash
552
+ pnpm example:explosion
553
+ ```
554
+
555
+ [`02-explosion-pixi/index.ts`](examples/02-explosion-pixi/index.ts) is the most-common
556
+ entry: a **6-frame explosion sprite sheet** (net-splash / hit FX) bound through
557
+ the real `aispritejs/pixi` adapter:
558
+
559
+ - the atlas `animations` block lists the six frame keys, with per-frame `frames`
560
+ durations (a snappy ~40 ms/frame burst);
561
+ - a **Trigger** (`detonate`) fires an Any-State transition into a **non-looping**
562
+ `boom` state that **plays once** and auto-returns to a resting `idle` frame via
563
+ `onEnd`;
564
+ - `createPixiSpriteAnimator(sprite, graph, textures)` binds a real `PIXI.Sprite`;
565
+ each `update(dt)` swaps `sprite.texture` to the active frame and applies that
566
+ frame's atlas `defaultAnchor`;
567
+ - it runs headlessly in Node — the adapter only touches `sprite.texture` /
568
+ `sprite.anchor` / `texture.defaultAnchor`, so plain `PIXI.Texture` / `Sprite`
569
+ instances exercise the real API (no GPU or canvas needed).
570
+
571
+ This is the play-once FX shape to reach for when you have input-driven,
572
+ multi-state visual switching backed by a texture atlas.
573
+
475
574
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aispritejs",
3
- "version": "0.1.2",
3
+ "version": "0.5.5",
4
4
  "description": "Input-driven, renderer-agnostic 2D sprite animation runtime — a tiny, Rive-like visual state machine driven by Number / Boolean / Trigger inputs. JSON transition graph, deterministic update(dt), zero runtime dependencies. Browser / Node / Bun / Deno / WebView / Worker friendly.",
5
5
  "keywords": [
6
6
  "sprite",
@@ -20,13 +20,13 @@
20
20
  ],
21
21
  "author": "yshengliao",
22
22
  "license": "MIT",
23
- "homepage": "https://github.com/yshengliao/aispritejs#readme",
23
+ "homepage": "https://github.com/islumina/aispritejs#readme",
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "git+https://github.com/yshengliao/aispritejs.git"
26
+ "url": "git+https://github.com/islumina/aispritejs.git"
27
27
  },
28
28
  "bugs": {
29
- "url": "https://github.com/yshengliao/aispritejs/issues"
29
+ "url": "https://github.com/islumina/aispritejs/issues"
30
30
  },
31
31
  "type": "module",
32
32
  "sideEffects": false,
@@ -73,6 +73,7 @@
73
73
  "verify:llms": "node scripts/build-llms-full.mjs --check",
74
74
  "coverage": "vitest run --coverage",
75
75
  "example:platformer": "tsx examples/01-platformer-inputs/index.ts",
76
+ "example:explosion": "tsx examples/02-explosion-pixi/index.ts",
76
77
  "prepublishOnly": "pnpm typecheck && pnpm lint && pnpm coverage && pnpm build && pnpm verify:exports && pnpm verify:llms && pnpm check:size"
77
78
  },
78
79
  "peerDependencies": {