aiecsjs 0.5.8 → 0.6.0

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.
Files changed (72) hide show
  1. package/README.md +46 -12
  2. package/README_ZHTW.md +46 -12
  3. package/api.json +171 -48
  4. package/dist/chunk-27JX7WHY.js +2 -0
  5. package/dist/chunk-27JX7WHY.js.map +1 -0
  6. package/dist/chunk-GJ6U2TIU.js +2 -0
  7. package/dist/chunk-GJ6U2TIU.js.map +1 -0
  8. package/dist/chunk-LNZZ4WWC.js +2 -0
  9. package/dist/chunk-LNZZ4WWC.js.map +1 -0
  10. package/dist/chunk-MVBFYRHY.cjs +2 -0
  11. package/dist/chunk-MVBFYRHY.cjs.map +1 -0
  12. package/dist/chunk-NPNWRCB5.cjs +2 -0
  13. package/dist/chunk-NPNWRCB5.cjs.map +1 -0
  14. package/dist/chunk-O4UVAWFU.cjs +2 -0
  15. package/dist/chunk-O4UVAWFU.cjs.map +1 -0
  16. package/dist/commands.cjs +1 -1
  17. package/dist/commands.cjs.map +1 -1
  18. package/dist/commands.d.cts +1 -1
  19. package/dist/commands.d.ts +1 -1
  20. package/dist/commands.js +1 -1
  21. package/dist/commands.js.map +1 -1
  22. package/dist/index.cjs +1 -1
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +79 -13
  25. package/dist/index.d.ts +79 -13
  26. package/dist/index.js +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/loop.cjs +1 -1
  29. package/dist/loop.cjs.map +1 -1
  30. package/dist/loop.d.cts +12 -0
  31. package/dist/loop.d.ts +12 -0
  32. package/dist/loop.js +1 -1
  33. package/dist/loop.js.map +1 -1
  34. package/dist/observers.cjs +1 -1
  35. package/dist/observers.cjs.map +1 -1
  36. package/dist/observers.d.cts +7 -1
  37. package/dist/observers.d.ts +7 -1
  38. package/dist/observers.js +1 -1
  39. package/dist/observers.js.map +1 -1
  40. package/dist/relations.cjs +1 -1
  41. package/dist/relations.cjs.map +1 -1
  42. package/dist/relations.d.cts +18 -9
  43. package/dist/relations.d.ts +18 -9
  44. package/dist/relations.js +1 -1
  45. package/dist/relations.js.map +1 -1
  46. package/dist/serialize.cjs +1 -1
  47. package/dist/serialize.d.cts +30 -5
  48. package/dist/serialize.d.ts +30 -5
  49. package/dist/serialize.js +1 -1
  50. package/dist/{types-BeVLA7xG.d.cts → types-C6TkQ7HW.d.cts} +33 -0
  51. package/dist/{types-BeVLA7xG.d.ts → types-C6TkQ7HW.d.ts} +33 -0
  52. package/dist/worker.cjs +1 -1
  53. package/dist/worker.cjs.map +1 -1
  54. package/dist/worker.d.cts +10 -3
  55. package/dist/worker.d.ts +10 -3
  56. package/dist/worker.js +1 -1
  57. package/dist/worker.js.map +1 -1
  58. package/llms-full.txt +120 -18
  59. package/llms.txt +1 -0
  60. package/package.json +57 -22
  61. package/dist/chunk-3FV6UMUS.cjs +0 -2
  62. package/dist/chunk-3FV6UMUS.cjs.map +0 -1
  63. package/dist/chunk-3ZLNR4JX.cjs +0 -2
  64. package/dist/chunk-3ZLNR4JX.cjs.map +0 -1
  65. package/dist/chunk-N5OETPTK.js +0 -2
  66. package/dist/chunk-N5OETPTK.js.map +0 -1
  67. package/dist/chunk-TNZMD7E5.js +0 -2
  68. package/dist/chunk-TNZMD7E5.js.map +0 -1
  69. package/dist/chunk-YFTCD2UG.js +0 -2
  70. package/dist/chunk-YFTCD2UG.js.map +0 -1
  71. package/dist/chunk-ZIZSWIHW.cjs +0 -2
  72. package/dist/chunk-ZIZSWIHW.cjs.map +0 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  TypeScript-first archetype ECS with TypedArray SoA components, command buffers, relations, serialization, and SAB-ready snapshot transport.
4
4
 
5
- > **Status: 0.5.8 - stable 1.0-track core.** Root ECS APIs are stable; worker transport remains adapter-shaped and environment-dependent.
5
+ > **Status: 0.6.0 - stable 1.0-track core.** Root ECS APIs are stable; worker transport remains adapter-shaped and environment-dependent. 0.6.0 has breaking changes (snapshot format 2, stricter validation): see the [CHANGELOG](CHANGELOG.md).
6
6
 
7
7
  ## Install
8
8
 
@@ -17,8 +17,8 @@ import {
17
17
  createEntity,
18
18
  createWorld,
19
19
  defineComponent,
20
- forEachEntity,
21
- getComponent,
20
+ defineQuery,
21
+ forEachEntityIndexed,
22
22
  } from "aiecsjs";
23
23
  ```
24
24
 
@@ -33,15 +33,15 @@ const e = createEntity(world);
33
33
  addComponent(world, e, Position, { x: 0, y: 0 });
34
34
  addComponent(world, e, Velocity, { x: 1, y: 0 });
35
35
 
36
- forEachEntity(world, [Position, Velocity], (entity) => {
37
- const pos = getComponent(world, entity, Position);
38
- const vel = getComponent(world, entity, Velocity);
39
- pos.x += vel.x;
40
- pos.y += vel.y;
36
+ // SoA columns are TypedArrays; index them with `i`, the entity's slot index.
37
+ const movers = defineQuery([Position, Velocity]);
38
+ forEachEntityIndexed(world, movers, (entity, i, pos, vel) => {
39
+ pos.x[i] += vel.x[i];
40
+ pos.y[i] += vel.y[i];
41
41
  });
42
42
  ```
43
43
 
44
- Use `defineTag()` for marker components and `defineObjectComponent()` when you need object references instead of TypedArray storage.
44
+ Use `defineTag()` for marker components and `defineObjectComponent()` when you need object references instead of TypedArray storage. Do not index columns with the packed `entity` id: it stops matching the slot once a slot is recycled.
45
45
 
46
46
  ## Public Surface
47
47
 
@@ -55,15 +55,49 @@ Use `defineTag()` for marker components and `defineObjectComponent()` when you n
55
55
  | `aiecsjs/worker` | Transfer/adopt/attach helpers for worker snapshots. |
56
56
  | `aiecsjs/relations` | `defineRelation`, `ChildOf`, relation add/remove/read helpers. |
57
57
 
58
+ ## Snapshots
59
+
60
+ `toJSON` / `serializeWorld` write snapshot format 2: entity data plus a table of the components it uses (stable key, kind, SoA fields). Give every component you save a `key`, so a loading session can define its components in any order:
61
+
62
+ ```ts
63
+ const Position = defineComponent({ x: Types.f32, y: Types.f32 }, { key: "position" });
64
+ const Player = defineTag({ key: "player" });
65
+
66
+ const bytes = serializeWorld(world); // aiecsjs/serialize
67
+ const restored = deserializeWorld(bytes); // or fromJSON(toJSON(world))
68
+ ```
69
+
70
+ - Loading resolves every component before it creates the world: by key, or by creation-order id for keyless components. It throws `EcsError` when a component is not defined in this process (pass `onUnknownComponent: "skip"` to drop its data) or when its kind or SoA fields differ.
71
+ - 0.5.x snapshots are rejected with `EcsError` by default; `{ onUnknownVersion: "best-effort" }` loads them by id with a kind check. See [0.5.x -> 0.6.0 snapshots](docs/MIGRATION.md#05x---060-snapshots).
72
+ - Restored entities get fresh ids in snapshot order (holes in the slot range close up), so EntityIds stored inside component data are not remapped. Delta `apply()` keeps slot indices.
73
+
74
+ ## Relations
75
+
76
+ ```ts
77
+ import { ChildOf, addRelation, getRelationTargets } from "aiecsjs/relations";
78
+
79
+ addRelation(world, child, ChildOf, parent);
80
+ getRelationTargets(world, child, ChildOf); // [parent]
81
+ ```
82
+
83
+ - `addRelation` throws `EcsError` for a dead source or target, mirroring `addComponent`. Check `entityExists` first when an endpoint may be stale.
84
+ - `destroyEntity` and `resetWorld` drop every edge of the entities they remove; `removeRelation` is a no-op for dead endpoints.
85
+
86
+ ## Errors
87
+
88
+ - Misuse and invariant failures throw `EcsError` (message `aiecsjs: ...`) before any change is made: non-integer world options, dead entities, unknown components, invalid component keys, non-function callbacks (observer handlers, `forEachEntity`, `withCommandBuffer`, `pipe` systems, AoS factories) and snapshot errors.
89
+ - A world from `attachWorld(buffer, { readOnly: true })` rejects every mutator with `EcsError`: `createEntity`, `destroyEntity`, `addComponent`, `removeComponent`, `setComponent`, `resetWorld`, `addRelation`, `removeRelation` and delta `apply()`.
90
+ - `defineComponent` field-declaration errors and non-Query input to query functions throw `TypeError`. `createLoop` (`aiecsjs/loop` has no error class) throws `TypeError` / `RangeError`.
91
+
58
92
  ## Sharp Edges
59
93
 
60
94
  - Structural mutation during a query loop is allowed by the library, but app systems should prefer `withCommandBuffer()` when adding/removing/destroying entities from inside iteration.
61
95
  - Reactive query buffers are unbounded until drained. Poll and clear them every frame or event tick.
62
- - Query registration currently uses a global module cache; many worlds/components can make structural changes scan more query metadata than expected.
63
- - Exclusive relation cleanup scans relation capacity on destroy. Large sparse relation tables can make destroy cost visible.
96
+ - Queries are cached process-wide, and a structural change visits every reactive (enter/exit) source query that references the changed component, whichever world registered it.
97
+ - Exclusive relation cleanup is `O(incoming)` on destroy for the exclusive-slot reverse index, but every destroy also walks all relation `data` payload sources and all `outgoing` (non-exclusive) edge lists — cost is `O(incoming + data sources + outgoing sources)` across all relations, even for entities with no relations at all.
64
98
  - Serialization restores capacity with safety clamps, but snapshots from untrusted sources should still be treated as hostile input.
65
99
  - Worker/SAB helpers depend on the runtime environment. Feature-detect `SharedArrayBuffer` and cross-origin isolation in browsers.
66
- - `pnpm lint` currently reports many `noExplicitAny` warnings. They are not release-blocking, but they add AI-review noise.
100
+ - `pnpm lint` reports 84 `noExplicitAny` warnings, mostly casts in tests plus the public query callback types. They are not release-blocking.
67
101
 
68
102
  ## AI Context
69
103
 
package/README_ZHTW.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  TypeScript-first archetype ECS,提供 TypedArray SoA component、command buffer、relations、serialization,以及 SAB-ready snapshot transport。
4
4
 
5
- > **狀態:0.5.8 - 穩定 1.0 軌道核心。** Root ECS API 穩定;worker transport 仍取決於執行環境。
5
+ > **狀態:0.6.0 - 穩定 1.0 軌道核心。** Root ECS API 穩定;worker transport 仍取決於執行環境。0.6.0 含破壞性變更(snapshot format 2、更嚴格的參數驗證),請見 [CHANGELOG](CHANGELOG.md)。
6
6
 
7
7
  ## 安裝
8
8
 
@@ -17,8 +17,8 @@ import {
17
17
  createEntity,
18
18
  createWorld,
19
19
  defineComponent,
20
- forEachEntity,
21
- getComponent,
20
+ defineQuery,
21
+ forEachEntityIndexed,
22
22
  } from "aiecsjs";
23
23
  ```
24
24
 
@@ -33,15 +33,15 @@ const e = createEntity(world);
33
33
  addComponent(world, e, Position, { x: 0, y: 0 });
34
34
  addComponent(world, e, Velocity, { x: 1, y: 0 });
35
35
 
36
- forEachEntity(world, [Position, Velocity], (entity) => {
37
- const pos = getComponent(world, entity, Position);
38
- const vel = getComponent(world, entity, Velocity);
39
- pos.x += vel.x;
40
- pos.y += vel.y;
36
+ // SoA columns are TypedArrays; index them with `i`, the entity's slot index.
37
+ const movers = defineQuery([Position, Velocity]);
38
+ forEachEntityIndexed(world, movers, (entity, i, pos, vel) => {
39
+ pos.x[i] += vel.x[i];
40
+ pos.y[i] += vel.y[i];
41
41
  });
42
42
  ```
43
43
 
44
- marker component 用 `defineTag()`;需要物件參照而非 TypedArray storage 時用 `defineObjectComponent()`。
44
+ marker component 用 `defineTag()`;需要物件參照而非 TypedArray storage 時用 `defineObjectComponent()`。SoA 欄位是 TypedArray,請用 slot 索引 `i` 存取;不要用封裝後的 `entity` id 索引欄位,slot 被回收後它就不再等於 slot 索引。
45
45
 
46
46
  ## Public Surface
47
47
 
@@ -55,15 +55,49 @@ marker component 用 `defineTag()`;需要物件參照而非 TypedArray storage
55
55
  | `aiecsjs/worker` | worker snapshot 的 transfer / adopt / attach helpers。 |
56
56
  | `aiecsjs/relations` | `defineRelation`、`ChildOf` 與 relation add/remove/read helpers。 |
57
57
 
58
+ ## Snapshot
59
+
60
+ `toJSON` / `serializeWorld` 會寫出 snapshot format 2:entity 資料,加上這些資料用到的 component 表(穩定 key、kind、SoA 欄位)。請為每個要存檔的 component 指定 `key`,載入端就能以任意順序定義 component:
61
+
62
+ ```ts
63
+ const Position = defineComponent({ x: Types.f32, y: Types.f32 }, { key: "position" });
64
+ const Player = defineTag({ key: "player" });
65
+
66
+ const bytes = serializeWorld(world); // aiecsjs/serialize
67
+ const restored = deserializeWorld(bytes); // or fromJSON(toJSON(world))
68
+ ```
69
+
70
+ - 載入時會先解析所有 component,再建立 world:有 key 的依 key 對應,沒有 key 的依建立順序 id 對應。component 在目前行程中未定義時丟出 `EcsError`(傳入 `onUnknownComponent: "skip"` 可略過該 component 的資料);kind 或 SoA 欄位不符時也丟出 `EcsError`。
71
+ - 0.5.x snapshot 預設以 `EcsError` 拒絕;`{ onUnknownVersion: "best-effort" }` 會依 id 載入並檢查 kind。請見 [0.5.x -> 0.6.0 snapshots](docs/MIGRATION_ZHTW.md#05x---060-snapshots)。
72
+ - 還原後的 entity 依 snapshot 順序取得新 id(slot 範圍中的空洞會被補齊),因此存在 component 資料中的 EntityId 不會重新對應。Delta `apply()` 會保留 slot 索引。
73
+
74
+ ## Relations
75
+
76
+ ```ts
77
+ import { ChildOf, addRelation, getRelationTargets } from "aiecsjs/relations";
78
+
79
+ addRelation(world, child, ChildOf, parent);
80
+ getRelationTargets(world, child, ChildOf); // [parent]
81
+ ```
82
+
83
+ - `addRelation` 在 source 或 target 已不存在時丟出 `EcsError`,與 `addComponent` 一致。端點可能已過期時,請先用 `entityExists` 檢查。
84
+ - `destroyEntity` 與 `resetWorld` 會移除被刪除 entity 的所有 edge;`removeRelation` 對已不存在的端點不做任何事。
85
+
86
+ ## 錯誤
87
+
88
+ - 誤用與不變式違反會在做任何變更前丟出 `EcsError`(訊息為 `aiecsjs: ...`):非整數的 world 選項、已不存在的 entity、未知的 component、無效的 component key、不是函式的 callback(observer handler、`forEachEntity`、`withCommandBuffer`、`pipe` system、AoS factory),以及 snapshot 錯誤。
89
+ - 由 `attachWorld(buffer, { readOnly: true })` 取得的 world 會以 `EcsError` 拒絕所有 mutator:`createEntity`、`destroyEntity`、`addComponent`、`removeComponent`、`setComponent`、`resetWorld`、`addRelation`、`removeRelation` 與 delta `apply()`。
90
+ - `defineComponent` 欄位宣告錯誤,以及傳給 query 函式的非 Query 輸入,會丟出 `TypeError`。`createLoop`(`aiecsjs/loop` 沒有錯誤類別)會丟出 `TypeError` / `RangeError`。
91
+
58
92
  ## 注意事項
59
93
 
60
94
  - Query loop 期間可以 structural mutation,但在 system 內 add/remove/destroy entity 時建議用 `withCommandBuffer()`。
61
95
  - Reactive query buffers 在 drain 前沒有上限。請每 frame 或每 event tick poll 並清空。
62
- - Query registration 目前使用全域 module cache;大量 worlds/components 會讓 structural change 掃描較多 query metadata。
63
- - Exclusive relation cleanup 在 destroy 時掃 relation capacity;大型稀疏 relation table 會讓 destroy 成本變明顯。
96
+ - Query 在整個行程中共用快取;structural change 會走訪所有參照到該 component 的 reactive(enter/exit)source query,不論它由哪個 world 註冊。
97
+ - Exclusive relation cleanup 的 exclusive-slot reverse index 部分在 destroy 時為 `O(incoming)`,但每次 destroy 仍會掃過所有 relation 的 `data` payload sources 與 `outgoing`(非 exclusive)edge lists——整體成本是 `O(incoming + data sources + outgoing sources)`(跨所有 relations 加總),即使該 entity 根本沒有任何 relation 也一樣。
64
98
  - Serialization restore capacity 有安全 clamp,但不可信 snapshot 仍應視為 hostile input。
65
99
  - Worker/SAB helper 取決於環境。瀏覽器中請 feature-detect `SharedArrayBuffer` 與 cross-origin isolation。
66
- - `pnpm lint` 目前仍有大量 `noExplicitAny` warnings;不阻擋 release,但會增加 AI review 雜訊。
100
+ - `pnpm lint` 目前回報 84 個 `noExplicitAny` warnings,多數是測試中的轉型,另有公開的 query callback 型別;不阻擋 release。
67
101
 
68
102
  ## AI Context
69
103