aiecsjs 0.5.6 → 0.5.7

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 (63) hide show
  1. package/README.md +21 -12
  2. package/README_ZHTW.md +21 -12
  3. package/api.json +23 -0
  4. package/dist/chunk-22ICWJ5O.js +2 -0
  5. package/dist/chunk-22ICWJ5O.js.map +1 -0
  6. package/dist/chunk-2KCN5RVK.js +2 -0
  7. package/dist/chunk-2KCN5RVK.js.map +1 -0
  8. package/dist/chunk-5QWEV4VJ.cjs +2 -0
  9. package/dist/chunk-5QWEV4VJ.cjs.map +1 -0
  10. package/dist/chunk-AGWUE6JB.js +2 -0
  11. package/dist/chunk-AGWUE6JB.js.map +1 -0
  12. package/dist/chunk-P5GW7GKY.cjs +2 -0
  13. package/dist/chunk-P5GW7GKY.cjs.map +1 -0
  14. package/dist/chunk-SRX2MZPX.cjs +2 -0
  15. package/dist/chunk-SRX2MZPX.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 +49 -4
  25. package/dist/index.d.ts +49 -4
  26. package/dist/index.js +1 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/observers.cjs +1 -1
  29. package/dist/observers.d.cts +1 -1
  30. package/dist/observers.d.ts +1 -1
  31. package/dist/observers.js +1 -1
  32. package/dist/observers.js.map +1 -1
  33. package/dist/relations.cjs +1 -1
  34. package/dist/relations.d.cts +1 -1
  35. package/dist/relations.d.ts +1 -1
  36. package/dist/relations.js +1 -1
  37. package/dist/relations.js.map +1 -1
  38. package/dist/serialize.cjs +1 -1
  39. package/dist/serialize.d.cts +1 -1
  40. package/dist/serialize.d.ts +1 -1
  41. package/dist/serialize.js +1 -1
  42. package/dist/{types-BGEeHad-.d.cts → types-BeVLA7xG.d.cts} +11 -1
  43. package/dist/{types-BGEeHad-.d.ts → types-BeVLA7xG.d.ts} +11 -1
  44. package/dist/worker.cjs +1 -1
  45. package/dist/worker.cjs.map +1 -1
  46. package/dist/worker.d.cts +2 -2
  47. package/dist/worker.d.ts +2 -2
  48. package/dist/worker.js +1 -1
  49. package/dist/worker.js.map +1 -1
  50. package/llms-full.txt +50 -12
  51. package/package.json +7 -2
  52. package/dist/chunk-FZ5DSL4K.cjs +0 -2
  53. package/dist/chunk-FZ5DSL4K.cjs.map +0 -1
  54. package/dist/chunk-H3E365XN.cjs +0 -2
  55. package/dist/chunk-H3E365XN.cjs.map +0 -1
  56. package/dist/chunk-IFA7MVBB.js +0 -2
  57. package/dist/chunk-IFA7MVBB.js.map +0 -1
  58. package/dist/chunk-L3H4HVUJ.cjs +0 -2
  59. package/dist/chunk-L3H4HVUJ.cjs.map +0 -1
  60. package/dist/chunk-R67UJPKI.js +0 -2
  61. package/dist/chunk-R67UJPKI.js.map +0 -1
  62. package/dist/chunk-TKDLUVJR.js +0 -2
  63. package/dist/chunk-TKDLUVJR.js.map +0 -1
@@ -1,2 +1,2 @@
1
- 'use strict';var chunkL3H4HVUJ_cjs=require('./chunk-L3H4HVUJ.cjs');var x=1;function p(r){return {__kind:"relation",__id:x++,__exclusive:r?.exclusive??false,__hasData:false}}var _=p({exclusive:true});function v(r,l){let s=r.relationStorage.get(l.__id);return s||(s={rel:l,exclusive:l.__exclusive?new Int32Array(r.capacity).fill(-1):null,outgoing:new Map,data:new Map},r.relationStorage.set(l.__id,s),s)}function R(r,l,s,i,n){let t=chunkL3H4HVUJ_cjs.i(r);if(t.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");let e=v(t,s),o=l&t.options.indexMask,d=i&t.options.indexMask;if(e.exclusive){if(o>=e.exclusive.length){let c=new Int32Array(Math.max(o+1,e.exclusive.length*2)).fill(-1);c.set(e.exclusive),e.exclusive=c;}let a=e.exclusive[o]??-1;if(a>=0&&a!==d){let c=e.data.get(o);c?.delete(a),c&&c.size===0&&e.data.delete(o);}e.exclusive[o]=d;}else {let a=e.outgoing.get(o);a||(a=[],e.outgoing.set(o,a)),a.includes(d)||a.push(d);}if(n!==void 0){let a=e.data.get(o);a||(a=new Map,e.data.set(o,a)),a.set(d,n);}}function b(r,l,s,i){let n=chunkL3H4HVUJ_cjs.i(r);if(n.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");let t=n.relationStorage.get(s.__id);if(!t)return;let e=l&n.options.indexMask,o=i&n.options.indexMask;if(t.exclusive)e<t.exclusive.length&&t.exclusive[e]===o&&(t.exclusive[e]=-1);else {let a=t.outgoing.get(e);if(a){let c=a.indexOf(o);c>=0&&a.splice(c,1),a.length===0&&t.outgoing.delete(e);}}let d=t.data.get(e);d&&(d.delete(o),d.size===0&&t.data.delete(e));}function h(r,l,s){let i=chunkL3H4HVUJ_cjs.i(r),n=i.relationStorage.get(s.__id);if(!n)return [];let t=l&i.options.indexMask;if(n.exclusive){if(t<n.exclusive.length){let o=n.exclusive[t]??-1;if(o>=0){let d=i.generations[o]??0;return [chunkL3H4HVUJ_cjs.r(o,d,i.options)]}}return []}let e=n.outgoing.get(t);return e?e.map(o=>{let d=i.generations[o]??0;return chunkL3H4HVUJ_cjs.r(o,d,i.options)}):[]}function w(r,l,s,i){let n=chunkL3H4HVUJ_cjs.i(r),t=n.relationStorage.get(s.__id);if(!t)return;let e=l&n.options.indexMask,o=i&n.options.indexMask;return t.data.get(e)?.get(o)}chunkL3H4HVUJ_cjs.E((r,l)=>{let s=l&r.options.indexMask;for(let i of r.relationStorage.values()){if(i.exclusive){s<i.exclusive.length&&(i.exclusive[s]=-1);for(let n=0;n<i.exclusive.length;n++)i.exclusive[n]===s&&(i.exclusive[n]=-1);}i.outgoing.delete(s);for(let[n,t]of i.outgoing){let e=t.indexOf(s);e>=0&&(t.splice(e,1),t.length===0&&i.outgoing.delete(n));}i.data.delete(s);for(let[n,t]of i.data)t.delete(s),t.size===0&&i.data.delete(n);}});exports.ChildOf=_;exports.addRelation=R;exports.defineRelation=p;exports.getRelationData=w;exports.getRelationTargets=h;exports.removeRelation=b;//# sourceMappingURL=relations.cjs.map
1
+ 'use strict';var chunk5QWEV4VJ_cjs=require('./chunk-5QWEV4VJ.cjs');var x=1;function p(r){return {__kind:"relation",__id:x++,__exclusive:r?.exclusive??false,__hasData:false}}var _=p({exclusive:true});function v(r,l){let s=r.relationStorage.get(l.__id);return s||(s={rel:l,exclusive:l.__exclusive?new Int32Array(r.capacity).fill(-1):null,outgoing:new Map,data:new Map},r.relationStorage.set(l.__id,s),s)}function R(r,l,s,i,n){let t=chunk5QWEV4VJ_cjs.j(r);if(t.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");let e=v(t,s),o=l&t.options.indexMask,d=i&t.options.indexMask;if(e.exclusive){if(o>=e.exclusive.length){let c=new Int32Array(Math.max(o+1,e.exclusive.length*2)).fill(-1);c.set(e.exclusive),e.exclusive=c;}let a=e.exclusive[o]??-1;if(a>=0&&a!==d){let c=e.data.get(o);c?.delete(a),c&&c.size===0&&e.data.delete(o);}e.exclusive[o]=d;}else {let a=e.outgoing.get(o);a||(a=[],e.outgoing.set(o,a)),a.includes(d)||a.push(d);}if(n!==void 0){let a=e.data.get(o);a||(a=new Map,e.data.set(o,a)),a.set(d,n);}}function b(r,l,s,i){let n=chunk5QWEV4VJ_cjs.j(r);if(n.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");let t=n.relationStorage.get(s.__id);if(!t)return;let e=l&n.options.indexMask,o=i&n.options.indexMask;if(t.exclusive)e<t.exclusive.length&&t.exclusive[e]===o&&(t.exclusive[e]=-1);else {let a=t.outgoing.get(e);if(a){let c=a.indexOf(o);c>=0&&a.splice(c,1),a.length===0&&t.outgoing.delete(e);}}let d=t.data.get(e);d&&(d.delete(o),d.size===0&&t.data.delete(e));}function h(r,l,s){let i=chunk5QWEV4VJ_cjs.j(r),n=i.relationStorage.get(s.__id);if(!n)return [];let t=l&i.options.indexMask;if(n.exclusive){if(t<n.exclusive.length){let o=n.exclusive[t]??-1;if(o>=0){let d=i.generations[o]??0;return [chunk5QWEV4VJ_cjs.s(o,d,i.options)]}}return []}let e=n.outgoing.get(t);return e?e.map(o=>{let d=i.generations[o]??0;return chunk5QWEV4VJ_cjs.s(o,d,i.options)}):[]}function w(r,l,s,i){let n=chunk5QWEV4VJ_cjs.j(r),t=n.relationStorage.get(s.__id);if(!t)return;let e=l&n.options.indexMask,o=i&n.options.indexMask;return t.data.get(e)?.get(o)}chunk5QWEV4VJ_cjs.F((r,l)=>{let s=l&r.options.indexMask;for(let i of r.relationStorage.values()){if(i.exclusive){s<i.exclusive.length&&(i.exclusive[s]=-1);for(let n=0;n<i.exclusive.length;n++)i.exclusive[n]===s&&(i.exclusive[n]=-1);}i.outgoing.delete(s);for(let[n,t]of i.outgoing){let e=t.indexOf(s);e>=0&&(t.splice(e,1),t.length===0&&i.outgoing.delete(n));}i.data.delete(s);for(let[n,t]of i.data)t.delete(s),t.size===0&&i.data.delete(n);}});exports.ChildOf=_;exports.addRelation=R;exports.defineRelation=p;exports.getRelationData=w;exports.getRelationTargets=h;exports.removeRelation=b;//# sourceMappingURL=relations.cjs.map
2
2
  //# sourceMappingURL=relations.cjs.map
@@ -1,4 +1,4 @@
1
- import { R as Relation, W as World, E as EntityId } from './types-BGEeHad-.cjs';
1
+ import { R as Relation, W as World, E as EntityId } from './types-BeVLA7xG.cjs';
2
2
 
3
3
  declare function defineRelation<T = void>(options?: {
4
4
  exclusive?: boolean;
@@ -1,4 +1,4 @@
1
- import { R as Relation, W as World, E as EntityId } from './types-BGEeHad-.js';
1
+ import { R as Relation, W as World, E as EntityId } from './types-BeVLA7xG.js';
2
2
 
3
3
  declare function defineRelation<T = void>(options?: {
4
4
  exclusive?: boolean;
package/dist/relations.js CHANGED
@@ -1,2 +1,2 @@
1
- import {E,i,r}from'./chunk-TKDLUVJR.js';var x=1;function p(r){return {__kind:"relation",__id:x++,__exclusive:r?.exclusive??false,__hasData:false}}var _=p({exclusive:true});function v(r,l){let s=r.relationStorage.get(l.__id);return s||(s={rel:l,exclusive:l.__exclusive?new Int32Array(r.capacity).fill(-1):null,outgoing:new Map,data:new Map},r.relationStorage.set(l.__id,s),s)}function R(r,l,s,i$1,n){let t=i(r);if(t.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");let e=v(t,s),o=l&t.options.indexMask,d=i$1&t.options.indexMask;if(e.exclusive){if(o>=e.exclusive.length){let c=new Int32Array(Math.max(o+1,e.exclusive.length*2)).fill(-1);c.set(e.exclusive),e.exclusive=c;}let a=e.exclusive[o]??-1;if(a>=0&&a!==d){let c=e.data.get(o);c?.delete(a),c&&c.size===0&&e.data.delete(o);}e.exclusive[o]=d;}else {let a=e.outgoing.get(o);a||(a=[],e.outgoing.set(o,a)),a.includes(d)||a.push(d);}if(n!==void 0){let a=e.data.get(o);a||(a=new Map,e.data.set(o,a)),a.set(d,n);}}function b(r,l,s,i$1){let n=i(r);if(n.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");let t=n.relationStorage.get(s.__id);if(!t)return;let e=l&n.options.indexMask,o=i$1&n.options.indexMask;if(t.exclusive)e<t.exclusive.length&&t.exclusive[e]===o&&(t.exclusive[e]=-1);else {let a=t.outgoing.get(e);if(a){let c=a.indexOf(o);c>=0&&a.splice(c,1),a.length===0&&t.outgoing.delete(e);}}let d=t.data.get(e);d&&(d.delete(o),d.size===0&&t.data.delete(e));}function h(r$1,l,s){let i$1=i(r$1),n=i$1.relationStorage.get(s.__id);if(!n)return [];let t=l&i$1.options.indexMask;if(n.exclusive){if(t<n.exclusive.length){let o=n.exclusive[t]??-1;if(o>=0){let d=i$1.generations[o]??0;return [r(o,d,i$1.options)]}}return []}let e=n.outgoing.get(t);return e?e.map(o=>{let d=i$1.generations[o]??0;return r(o,d,i$1.options)}):[]}function w(r,l,s,i$1){let n=i(r),t=n.relationStorage.get(s.__id);if(!t)return;let e=l&n.options.indexMask,o=i$1&n.options.indexMask;return t.data.get(e)?.get(o)}E((r,l)=>{let s=l&r.options.indexMask;for(let i of r.relationStorage.values()){if(i.exclusive){s<i.exclusive.length&&(i.exclusive[s]=-1);for(let n=0;n<i.exclusive.length;n++)i.exclusive[n]===s&&(i.exclusive[n]=-1);}i.outgoing.delete(s);for(let[n,t]of i.outgoing){let e=t.indexOf(s);e>=0&&(t.splice(e,1),t.length===0&&i.outgoing.delete(n));}i.data.delete(s);for(let[n,t]of i.data)t.delete(s),t.size===0&&i.data.delete(n);}});export{_ as ChildOf,R as addRelation,p as defineRelation,w as getRelationData,h as getRelationTargets,b as removeRelation};//# sourceMappingURL=relations.js.map
1
+ import {F,j,s}from'./chunk-2KCN5RVK.js';var x=1;function p(r){return {__kind:"relation",__id:x++,__exclusive:r?.exclusive??false,__hasData:false}}var _=p({exclusive:true});function v(r,l){let s=r.relationStorage.get(l.__id);return s||(s={rel:l,exclusive:l.__exclusive?new Int32Array(r.capacity).fill(-1):null,outgoing:new Map,data:new Map},r.relationStorage.set(l.__id,s),s)}function R(r,l,s,i,n){let t=j(r);if(t.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");let e=v(t,s),o=l&t.options.indexMask,d=i&t.options.indexMask;if(e.exclusive){if(o>=e.exclusive.length){let c=new Int32Array(Math.max(o+1,e.exclusive.length*2)).fill(-1);c.set(e.exclusive),e.exclusive=c;}let a=e.exclusive[o]??-1;if(a>=0&&a!==d){let c=e.data.get(o);c?.delete(a),c&&c.size===0&&e.data.delete(o);}e.exclusive[o]=d;}else {let a=e.outgoing.get(o);a||(a=[],e.outgoing.set(o,a)),a.includes(d)||a.push(d);}if(n!==void 0){let a=e.data.get(o);a||(a=new Map,e.data.set(o,a)),a.set(d,n);}}function b(r,l,s,i){let n=j(r);if(n.readOnly)throw new Error("aiecsjs: cannot mutate a read-only world");let t=n.relationStorage.get(s.__id);if(!t)return;let e=l&n.options.indexMask,o=i&n.options.indexMask;if(t.exclusive)e<t.exclusive.length&&t.exclusive[e]===o&&(t.exclusive[e]=-1);else {let a=t.outgoing.get(e);if(a){let c=a.indexOf(o);c>=0&&a.splice(c,1),a.length===0&&t.outgoing.delete(e);}}let d=t.data.get(e);d&&(d.delete(o),d.size===0&&t.data.delete(e));}function h(r,l,s$1){let i=j(r),n=i.relationStorage.get(s$1.__id);if(!n)return [];let t=l&i.options.indexMask;if(n.exclusive){if(t<n.exclusive.length){let o=n.exclusive[t]??-1;if(o>=0){let d=i.generations[o]??0;return [s(o,d,i.options)]}}return []}let e=n.outgoing.get(t);return e?e.map(o=>{let d=i.generations[o]??0;return s(o,d,i.options)}):[]}function w(r,l,s,i){let n=j(r),t=n.relationStorage.get(s.__id);if(!t)return;let e=l&n.options.indexMask,o=i&n.options.indexMask;return t.data.get(e)?.get(o)}F((r,l)=>{let s=l&r.options.indexMask;for(let i of r.relationStorage.values()){if(i.exclusive){s<i.exclusive.length&&(i.exclusive[s]=-1);for(let n=0;n<i.exclusive.length;n++)i.exclusive[n]===s&&(i.exclusive[n]=-1);}i.outgoing.delete(s);for(let[n,t]of i.outgoing){let e=t.indexOf(s);e>=0&&(t.splice(e,1),t.length===0&&i.outgoing.delete(n));}i.data.delete(s);for(let[n,t]of i.data)t.delete(s),t.size===0&&i.data.delete(n);}});export{_ as ChildOf,R as addRelation,p as defineRelation,w as getRelationData,h as getRelationTargets,b as removeRelation};//# sourceMappingURL=relations.js.map
2
2
  //# sourceMappingURL=relations.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/relations.ts"],"names":["nextRelationId","defineRelation","options","ChildOf","getOrCreateStorage","state","rel","storage","addRelation","world","source","target","data","getWorldState","src","tgt","next","prevTgt","prevInner","list","inner","removeRelation","idx","getRelationTargets","gen","packEid","tIdx","getRelationData","registerRelationsCleanup","eid","e","i"],"mappings":"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","file":"relations.js","sourcesContent":["// aiecsjs/relations — entity-to-entity relations (experimental in 0.1).\n\nimport { packEid, registerRelationsCleanup } from './internal/entity.js'\nimport type { EntityId, Relation, RelationStorage, World, WorldState } from './internal/types.js'\nimport { getWorldState } from './internal/world.js'\n\nlet nextRelationId = 1\n\nexport function defineRelation<T = void>(options?: { exclusive?: boolean }): Relation<T> {\n const id = nextRelationId++\n return {\n __kind: 'relation',\n __id: id,\n __exclusive: options?.exclusive ?? false,\n __hasData: false,\n } as Relation<T>\n}\n\nexport const ChildOf: Relation = defineRelation({ exclusive: true })\n\nfunction getOrCreateStorage(state: WorldState, rel: Relation<any>): RelationStorage {\n let storage = state.relationStorage.get(rel.__id)\n if (storage) return storage\n storage = {\n rel,\n exclusive: rel.__exclusive ? new Int32Array(state.capacity).fill(-1) : null,\n outgoing: new Map<number, number[]>(),\n data: new Map<number, Map<number, unknown>>(),\n }\n state.relationStorage.set(rel.__id, storage)\n return storage\n}\n\nexport function addRelation<T>(\n world: World,\n source: EntityId,\n rel: Relation<T>,\n target: EntityId,\n data?: T,\n): void {\n const state = getWorldState(world)\n if (state.readOnly) throw new Error('aiecsjs: cannot mutate a read-only world')\n const storage = getOrCreateStorage(state, rel as Relation<any>)\n // Use raw idx as keys in relation storage so slot reuse invalidation is consistent\n const src = (source as number) & state.options.indexMask\n const tgt = (target as number) & state.options.indexMask\n\n if (storage.exclusive) {\n if (src >= storage.exclusive.length) {\n const next = new Int32Array(Math.max(src + 1, storage.exclusive.length * 2)).fill(-1)\n next.set(storage.exclusive)\n storage.exclusive = next\n }\n // Exclusive redirect: if this source already pointed at a different target,\n // drop that previous target's data entry so getRelationData stays consistent\n // with getRelationTargets, which reports only the current exclusive target.\n const prevTgt = storage.exclusive[src] ?? -1\n if (prevTgt >= 0 && prevTgt !== tgt) {\n const prevInner = storage.data.get(src)\n prevInner?.delete(prevTgt)\n if (prevInner && prevInner.size === 0) storage.data.delete(src)\n }\n storage.exclusive[src] = tgt\n } else {\n let list = storage.outgoing.get(src)\n if (!list) {\n list = []\n storage.outgoing.set(src, list)\n }\n if (!list.includes(tgt)) list.push(tgt)\n }\n\n if (data !== undefined) {\n let inner = storage.data.get(src)\n if (!inner) {\n inner = new Map<number, unknown>()\n storage.data.set(src, inner)\n }\n inner.set(tgt, data)\n }\n}\n\nexport function removeRelation(\n world: World,\n source: EntityId,\n rel: Relation,\n target: EntityId,\n): void {\n const state = getWorldState(world)\n if (state.readOnly) throw new Error('aiecsjs: cannot mutate a read-only world')\n const storage = state.relationStorage.get(rel.__id)\n if (!storage) return\n const src = (source as number) & state.options.indexMask\n const tgt = (target as number) & state.options.indexMask\n if (storage.exclusive) {\n if (src < storage.exclusive.length && storage.exclusive[src] === tgt) {\n storage.exclusive[src] = -1\n }\n } else {\n const list = storage.outgoing.get(src)\n if (list) {\n const idx = list.indexOf(tgt)\n if (idx >= 0) list.splice(idx, 1)\n if (list.length === 0) storage.outgoing.delete(src)\n }\n }\n const inner = storage.data.get(src)\n if (inner) {\n inner.delete(tgt)\n if (inner.size === 0) storage.data.delete(src)\n }\n}\n\nexport function getRelationTargets(\n world: World,\n source: EntityId,\n rel: Relation,\n): readonly EntityId[] {\n const state = getWorldState(world)\n const storage = state.relationStorage.get(rel.__id)\n if (!storage) return []\n const src = (source as number) & state.options.indexMask\n if (storage.exclusive) {\n if (src < storage.exclusive.length) {\n const tgt = storage.exclusive[src] ?? -1\n if (tgt >= 0) {\n const gen = state.generations[tgt] ?? 0\n return [packEid(tgt, gen, state.options)]\n }\n }\n return []\n }\n const list = storage.outgoing.get(src)\n if (!list) return []\n return list.map((tIdx) => {\n const gen = state.generations[tIdx] ?? 0\n return packEid(tIdx, gen, state.options)\n })\n}\n\n/**\n * Read the data payload attached to a relation edge.\n *\n * Returns the `data` value that was supplied to {@link addRelation} for the\n * `(source, rel, target)` triple, or `undefined` when:\n * - the relation has never been stored for this world,\n * - no edge from `source` to `target` via `rel` exists, or\n * - the edge was added without a data argument.\n *\n * **Slot-keying semantic (ABA caveat):** relation storage keys edges by raw\n * entity slot index (`entityId & indexMask`), not by the full packed EntityId\n * that includes the generation counter. This means that if entity A is\n * destroyed and a *different* entity B is later created in the same slot, B\n * will inherit any edges that A had — unless the destroy cleanup hook ran\n * (which it does when `destroyEntity` is called). Callers that cache a\n * source/target EntityId should validate liveness with `entityExists` before\n * calling `getRelationData` if slot recycling is a concern.\n */\nexport function getRelationData<T>(\n world: World,\n source: EntityId,\n rel: Relation<T>,\n target: EntityId,\n): T | undefined {\n const state = getWorldState(world)\n const storage = state.relationStorage.get(rel.__id)\n if (!storage) return undefined\n const src = (source as number) & state.options.indexMask\n const tgt = (target as number) & state.options.indexMask\n return storage.data.get(src)?.get(tgt) as T | undefined\n}\n\n// Cleanup hook: when an entity is destroyed, remove all relations involving it.\nregisterRelationsCleanup((state: WorldState, eid: EntityId) => {\n const e = (eid as number) & state.options.indexMask\n for (const storage of state.relationStorage.values()) {\n if (storage.exclusive) {\n if (e < storage.exclusive.length) storage.exclusive[e] = -1\n // Also clear any references TO this entity as target\n for (let i = 0; i < storage.exclusive.length; i++) {\n if (storage.exclusive[i] === e) storage.exclusive[i] = -1\n }\n }\n storage.outgoing.delete(e)\n for (const [src, list] of storage.outgoing) {\n const idx = list.indexOf(e)\n if (idx >= 0) {\n list.splice(idx, 1)\n if (list.length === 0) storage.outgoing.delete(src)\n }\n }\n // Clear data entries involving this entity. The outer key is the source\n // eid; entries whose source IS this entity drop entirely. Entries that\n // reference this entity as a target are pruned from each inner map.\n storage.data.delete(e)\n for (const [src, inner] of storage.data) {\n inner.delete(e)\n if (inner.size === 0) storage.data.delete(src)\n }\n }\n})\n"]}
1
+ {"version":3,"sources":["../src/relations.ts"],"names":["nextRelationId","defineRelation","options","ChildOf","getOrCreateStorage","state","rel","storage","addRelation","world","source","target","data","getWorldState","src","tgt","next","prevTgt","prevInner","list","inner","removeRelation","idx","getRelationTargets","gen","packEid","tIdx","getRelationData","registerRelationsCleanup","eid","e","i"],"mappings":"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","file":"relations.js","sourcesContent":["// aiecsjs/relations — entity-to-entity relations (experimental in 0.1).\n\nimport { packEid, registerRelationsCleanup } from './internal/entity.js'\nimport type { EntityId, Relation, RelationStorage, World, WorldState } from './internal/types.js'\nimport { getWorldState } from './internal/world.js'\n\nlet nextRelationId = 1\n\nexport function defineRelation<T = void>(options?: { exclusive?: boolean }): Relation<T> {\n const id = nextRelationId++\n return {\n __kind: 'relation',\n __id: id,\n __exclusive: options?.exclusive ?? false,\n __hasData: false,\n } as Relation<T>\n}\n\nexport const ChildOf: Relation = defineRelation({ exclusive: true })\n\nfunction getOrCreateStorage(state: WorldState, rel: Relation<any>): RelationStorage {\n let storage = state.relationStorage.get(rel.__id)\n if (storage) return storage\n storage = {\n rel,\n exclusive: rel.__exclusive ? new Int32Array(state.capacity).fill(-1) : null,\n outgoing: new Map<number, number[]>(),\n data: new Map<number, Map<number, unknown>>(),\n }\n state.relationStorage.set(rel.__id, storage)\n return storage\n}\n\nexport function addRelation<T>(\n world: World,\n source: EntityId,\n rel: Relation<T>,\n target: EntityId,\n data?: T,\n): void {\n const state = getWorldState(world)\n if (state.readOnly) throw new Error('aiecsjs: cannot mutate a read-only world')\n const storage = getOrCreateStorage(state, rel as Relation<any>)\n // Use raw idx as keys in relation storage so slot reuse invalidation is consistent\n const src = (source as number) & state.options.indexMask\n const tgt = (target as number) & state.options.indexMask\n\n if (storage.exclusive) {\n if (src >= storage.exclusive.length) {\n const next = new Int32Array(Math.max(src + 1, storage.exclusive.length * 2)).fill(-1)\n next.set(storage.exclusive)\n storage.exclusive = next\n }\n // Exclusive redirect: if this source already pointed at a different target,\n // drop that previous target's data entry so getRelationData stays consistent\n // with getRelationTargets, which reports only the current exclusive target.\n const prevTgt = storage.exclusive[src] ?? -1\n if (prevTgt >= 0 && prevTgt !== tgt) {\n const prevInner = storage.data.get(src)\n prevInner?.delete(prevTgt)\n if (prevInner && prevInner.size === 0) storage.data.delete(src)\n }\n storage.exclusive[src] = tgt\n } else {\n let list = storage.outgoing.get(src)\n if (!list) {\n list = []\n storage.outgoing.set(src, list)\n }\n if (!list.includes(tgt)) list.push(tgt)\n }\n\n if (data !== undefined) {\n let inner = storage.data.get(src)\n if (!inner) {\n inner = new Map<number, unknown>()\n storage.data.set(src, inner)\n }\n inner.set(tgt, data)\n }\n}\n\nexport function removeRelation(\n world: World,\n source: EntityId,\n rel: Relation,\n target: EntityId,\n): void {\n const state = getWorldState(world)\n if (state.readOnly) throw new Error('aiecsjs: cannot mutate a read-only world')\n const storage = state.relationStorage.get(rel.__id)\n if (!storage) return\n const src = (source as number) & state.options.indexMask\n const tgt = (target as number) & state.options.indexMask\n if (storage.exclusive) {\n if (src < storage.exclusive.length && storage.exclusive[src] === tgt) {\n storage.exclusive[src] = -1\n }\n } else {\n const list = storage.outgoing.get(src)\n if (list) {\n const idx = list.indexOf(tgt)\n if (idx >= 0) list.splice(idx, 1)\n if (list.length === 0) storage.outgoing.delete(src)\n }\n }\n const inner = storage.data.get(src)\n if (inner) {\n inner.delete(tgt)\n if (inner.size === 0) storage.data.delete(src)\n }\n}\n\nexport function getRelationTargets(\n world: World,\n source: EntityId,\n rel: Relation,\n): readonly EntityId[] {\n const state = getWorldState(world)\n const storage = state.relationStorage.get(rel.__id)\n if (!storage) return []\n const src = (source as number) & state.options.indexMask\n if (storage.exclusive) {\n if (src < storage.exclusive.length) {\n const tgt = storage.exclusive[src] ?? -1\n if (tgt >= 0) {\n const gen = state.generations[tgt] ?? 0\n return [packEid(tgt, gen, state.options)]\n }\n }\n return []\n }\n const list = storage.outgoing.get(src)\n if (!list) return []\n return list.map((tIdx) => {\n const gen = state.generations[tIdx] ?? 0\n return packEid(tIdx, gen, state.options)\n })\n}\n\n/**\n * Read the data payload attached to a relation edge.\n *\n * Returns the `data` value that was supplied to {@link addRelation} for the\n * `(source, rel, target)` triple, or `undefined` when:\n * - the relation has never been stored for this world,\n * - no edge from `source` to `target` via `rel` exists, or\n * - the edge was added without a data argument.\n *\n * **Slot-keying semantic (ABA caveat):** relation storage keys edges by raw\n * entity slot index (`entityId & indexMask`), not by the full packed EntityId\n * that includes the generation counter. This means that if entity A is\n * destroyed and a *different* entity B is later created in the same slot, B\n * will inherit any edges that A had — unless the destroy cleanup hook ran\n * (which it does when `destroyEntity` is called). Callers that cache a\n * source/target EntityId should validate liveness with `entityExists` before\n * calling `getRelationData` if slot recycling is a concern.\n */\nexport function getRelationData<T>(\n world: World,\n source: EntityId,\n rel: Relation<T>,\n target: EntityId,\n): T | undefined {\n const state = getWorldState(world)\n const storage = state.relationStorage.get(rel.__id)\n if (!storage) return undefined\n const src = (source as number) & state.options.indexMask\n const tgt = (target as number) & state.options.indexMask\n return storage.data.get(src)?.get(tgt) as T | undefined\n}\n\n// Cleanup hook: when an entity is destroyed, remove all relations involving it.\nregisterRelationsCleanup((state: WorldState, eid: EntityId) => {\n const e = (eid as number) & state.options.indexMask\n for (const storage of state.relationStorage.values()) {\n if (storage.exclusive) {\n if (e < storage.exclusive.length) storage.exclusive[e] = -1\n // Also clear any references TO this entity as target\n for (let i = 0; i < storage.exclusive.length; i++) {\n if (storage.exclusive[i] === e) storage.exclusive[i] = -1\n }\n }\n storage.outgoing.delete(e)\n for (const [src, list] of storage.outgoing) {\n const idx = list.indexOf(e)\n if (idx >= 0) {\n list.splice(idx, 1)\n if (list.length === 0) storage.outgoing.delete(src)\n }\n }\n // Clear data entries involving this entity. The outer key is the source\n // eid; entries whose source IS this entity drop entirely. Entries that\n // reference this entity as a target are pruned from each inner map.\n storage.data.delete(e)\n for (const [src, inner] of storage.data) {\n inner.delete(e)\n if (inner.size === 0) storage.data.delete(src)\n }\n }\n})\n"]}
@@ -1,2 +1,2 @@
1
- 'use strict';var chunkFZ5DSL4K_cjs=require('./chunk-FZ5DSL4K.cjs');require('./chunk-L3H4HVUJ.cjs');Object.defineProperty(exports,"createDeltaSerializer",{enumerable:true,get:function(){return chunkFZ5DSL4K_cjs.e}});Object.defineProperty(exports,"deserializeWorld",{enumerable:true,get:function(){return chunkFZ5DSL4K_cjs.b}});Object.defineProperty(exports,"fromJSON",{enumerable:true,get:function(){return chunkFZ5DSL4K_cjs.d}});Object.defineProperty(exports,"serializeWorld",{enumerable:true,get:function(){return chunkFZ5DSL4K_cjs.a}});Object.defineProperty(exports,"toJSON",{enumerable:true,get:function(){return chunkFZ5DSL4K_cjs.c}});//# sourceMappingURL=serialize.cjs.map
1
+ 'use strict';var chunkP5GW7GKY_cjs=require('./chunk-P5GW7GKY.cjs');require('./chunk-5QWEV4VJ.cjs');Object.defineProperty(exports,"createDeltaSerializer",{enumerable:true,get:function(){return chunkP5GW7GKY_cjs.e}});Object.defineProperty(exports,"deserializeWorld",{enumerable:true,get:function(){return chunkP5GW7GKY_cjs.b}});Object.defineProperty(exports,"fromJSON",{enumerable:true,get:function(){return chunkP5GW7GKY_cjs.d}});Object.defineProperty(exports,"serializeWorld",{enumerable:true,get:function(){return chunkP5GW7GKY_cjs.a}});Object.defineProperty(exports,"toJSON",{enumerable:true,get:function(){return chunkP5GW7GKY_cjs.c}});//# sourceMappingURL=serialize.cjs.map
2
2
  //# sourceMappingURL=serialize.cjs.map
@@ -1,4 +1,4 @@
1
- import { W as World, S as SerializeOptions, D as DeltaSerializer, e as DeserializeOptions, p as WorldSnapshot } from './types-BGEeHad-.cjs';
1
+ import { W as World, S as SerializeOptions, D as DeltaSerializer, e as DeserializeOptions, p as WorldSnapshot } from './types-BeVLA7xG.cjs';
2
2
 
3
3
  declare function serializeWorld(world: World, options?: SerializeOptions): Uint8Array;
4
4
  declare function deserializeWorld(bytes: Uint8Array, options?: DeserializeOptions): World;
@@ -1,4 +1,4 @@
1
- import { W as World, S as SerializeOptions, D as DeltaSerializer, e as DeserializeOptions, p as WorldSnapshot } from './types-BGEeHad-.js';
1
+ import { W as World, S as SerializeOptions, D as DeltaSerializer, e as DeserializeOptions, p as WorldSnapshot } from './types-BeVLA7xG.js';
2
2
 
3
3
  declare function serializeWorld(world: World, options?: SerializeOptions): Uint8Array;
4
4
  declare function deserializeWorld(bytes: Uint8Array, options?: DeserializeOptions): World;
package/dist/serialize.js CHANGED
@@ -1,2 +1,2 @@
1
- export{e as createDeltaSerializer,b as deserializeWorld,d as fromJSON,a as serializeWorld,c as toJSON}from'./chunk-IFA7MVBB.js';import'./chunk-TKDLUVJR.js';//# sourceMappingURL=serialize.js.map
1
+ export{e as createDeltaSerializer,b as deserializeWorld,d as fromJSON,a as serializeWorld,c as toJSON}from'./chunk-AGWUE6JB.js';import'./chunk-2KCN5RVK.js';//# sourceMappingURL=serialize.js.map
2
2
  //# sourceMappingURL=serialize.js.map
@@ -56,7 +56,17 @@ interface WorldOptions {
56
56
  maxEntities?: number;
57
57
  indexBits?: number;
58
58
  generationBits?: number;
59
+ /**
60
+ * RESERVED / UNIMPLEMENTED (0.x). Setting this has **no effect** today: the
61
+ * world always allocates its own column storage and never reads or writes a
62
+ * caller-supplied SAB. The 0.x Worker handoff is a snapshot-copy transport —
63
+ * post `transferableSnapshot(world)` and rebuild via `adoptSnapshot` from
64
+ * `aiecsjs/worker`; do not pre-allocate a SAB here. The field is kept so the
65
+ * shape is forward-compatible with the true shared-column backing targeted
66
+ * for 0.3+ (see STABILITY.md, `aiecsjs/worker`).
67
+ */
59
68
  buffer?: SharedArrayBuffer;
69
+ /** RESERVED / UNIMPLEMENTED (0.x). Paired with {@link WorldOptions.buffer}; no effect today. */
60
70
  bufferByteOffset?: number;
61
71
  }
62
72
  interface World {
@@ -88,7 +98,7 @@ interface WorldMeta {
88
98
  }>;
89
99
  }
90
100
  interface TransferableSnapshot {
91
- buffer: SharedArrayBuffer;
101
+ buffer: SharedArrayBuffer | ArrayBuffer;
92
102
  meta: WorldMeta;
93
103
  }
94
104
  interface SerializeOptions {
@@ -56,7 +56,17 @@ interface WorldOptions {
56
56
  maxEntities?: number;
57
57
  indexBits?: number;
58
58
  generationBits?: number;
59
+ /**
60
+ * RESERVED / UNIMPLEMENTED (0.x). Setting this has **no effect** today: the
61
+ * world always allocates its own column storage and never reads or writes a
62
+ * caller-supplied SAB. The 0.x Worker handoff is a snapshot-copy transport —
63
+ * post `transferableSnapshot(world)` and rebuild via `adoptSnapshot` from
64
+ * `aiecsjs/worker`; do not pre-allocate a SAB here. The field is kept so the
65
+ * shape is forward-compatible with the true shared-column backing targeted
66
+ * for 0.3+ (see STABILITY.md, `aiecsjs/worker`).
67
+ */
59
68
  buffer?: SharedArrayBuffer;
69
+ /** RESERVED / UNIMPLEMENTED (0.x). Paired with {@link WorldOptions.buffer}; no effect today. */
60
70
  bufferByteOffset?: number;
61
71
  }
62
72
  interface World {
@@ -88,7 +98,7 @@ interface WorldMeta {
88
98
  }>;
89
99
  }
90
100
  interface TransferableSnapshot {
91
- buffer: SharedArrayBuffer;
101
+ buffer: SharedArrayBuffer | ArrayBuffer;
92
102
  meta: WorldMeta;
93
103
  }
94
104
  interface SerializeOptions {
package/dist/worker.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var chunkFZ5DSL4K_cjs=require('./chunk-FZ5DSL4K.cjs'),chunkL3H4HVUJ_cjs=require('./chunk-L3H4HVUJ.cjs');var l=1095320899;function W(r){let e=chunkL3H4HVUJ_cjs.i(r),o=chunkFZ5DSL4K_cjs.a(r);if(typeof SharedArrayBuffer>"u"){let n=new ArrayBuffer(o.byteLength);return new Uint8Array(n).set(o),{buffer:n,meta:p(e)}}let t=new SharedArrayBuffer(o.byteLength);return new Uint8Array(t).set(o),{buffer:t,meta:p(e)}}function b(r){m(r.meta);let e=new Uint8Array(r.buffer);return chunkFZ5DSL4K_cjs.b(e)}function S(r,e){let o=new Uint8Array(r),t=chunkFZ5DSL4K_cjs.b(o);if(e?.readOnly){let n=chunkL3H4HVUJ_cjs.i(t);n.readOnly=true;}return t}function w(r){chunkL3H4HVUJ_cjs.j(r.id)&&chunkL3H4HVUJ_cjs.l(r);}function p(r){let e=[];for(let o of r.componentInfoByBit)o&&e.push({id:o.id,kind:o.kind,schema:o.schema});return {magic:l,formatVersion:1,aiecsjsVersion:chunkL3H4HVUJ_cjs.a,indexBits:r.options.indexBits,generationBits:r.options.generationBits,maxComponents:r.options.maxComponents,maskWordCount:r.options.maskWordCount,capacity:r.capacity,componentSchemas:e}}function m(r){if(r.magic!==l)throw new Error("aiecsjs: invalid snapshot meta (wrong magic)");if(r.formatVersion!==1)throw new Error(`aiecsjs: unsupported snapshot format version ${r.formatVersion}`)}exports.adoptSnapshot=b;exports.attachWorld=S;exports.detachWorld=w;exports.transferableSnapshot=W;//# sourceMappingURL=worker.cjs.map
1
+ 'use strict';var chunkP5GW7GKY_cjs=require('./chunk-P5GW7GKY.cjs'),chunk5QWEV4VJ_cjs=require('./chunk-5QWEV4VJ.cjs');var l=1095320899;function W(r){let e=chunk5QWEV4VJ_cjs.j(r),o=chunkP5GW7GKY_cjs.a(r);if(typeof SharedArrayBuffer>"u"){let n=new ArrayBuffer(o.byteLength);return new Uint8Array(n).set(o),{buffer:n,meta:p(e)}}let t=new SharedArrayBuffer(o.byteLength);return new Uint8Array(t).set(o),{buffer:t,meta:p(e)}}function b(r){m(r.meta);let e=new Uint8Array(r.buffer);return chunkP5GW7GKY_cjs.b(e)}function S(r,e){let o=new Uint8Array(r),t=chunkP5GW7GKY_cjs.b(o);if(e?.readOnly){let n=chunk5QWEV4VJ_cjs.j(t);n.readOnly=true;}return t}function B(r){chunk5QWEV4VJ_cjs.k(r.id)&&chunk5QWEV4VJ_cjs.m(r);}function p(r){let e=[];for(let o of r.componentInfoByBit)o&&e.push({id:o.id,kind:o.kind,schema:o.schema});return {magic:l,formatVersion:1,aiecsjsVersion:chunk5QWEV4VJ_cjs.a,indexBits:r.options.indexBits,generationBits:r.options.generationBits,maxComponents:r.options.maxComponents,maskWordCount:r.options.maskWordCount,capacity:r.capacity,componentSchemas:e}}function m(r){if(r.magic!==l)throw new Error("aiecsjs: invalid snapshot meta (wrong magic)");if(r.formatVersion!==1)throw new Error(`aiecsjs: unsupported snapshot format version ${r.formatVersion}`)}exports.adoptSnapshot=b;exports.attachWorld=S;exports.detachWorld=B;exports.transferableSnapshot=W;//# sourceMappingURL=worker.cjs.map
2
2
  //# sourceMappingURL=worker.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/worker.ts"],"names":["MAGIC","transferableSnapshot","world","state","getWorldState","bytes","serializeWorld","ab","buildMeta","sab","adoptSnapshot","snap","validateMeta","deserializeWorld","attachWorld","buffer","options","detachWorld","isWorldRegistered","destroyWorld","componentSchemas","info","VERSION","meta"],"mappings":"qHAkBA,IAAMA,CAAAA,CAAQ,WAEP,SAASC,CAAAA,CAAqBC,EAAoC,CACvE,IAAMC,CAAAA,CAAQC,mBAAAA,CAAcF,CAAK,CAAA,CAC3BG,EAAQC,mBAAAA,CAAeJ,CAAK,EAElC,GAAI,OAAO,kBAAsB,GAAA,CAAa,CAE5C,IAAMK,CAAAA,CAAK,IAAI,WAAA,CAAYF,EAAM,UAAU,CAAA,CAC3C,WAAI,UAAA,CAAWE,CAAE,EAAE,GAAA,CAAIF,CAAK,CAAA,CACrB,CACL,MAAA,CAAQE,CAAAA,CACR,KAAMC,CAAAA,CAAUL,CAAK,CACvB,CACF,CAEA,IAAMM,EAAM,IAAI,iBAAA,CAAkBJ,CAAAA,CAAM,UAAU,CAAA,CAClD,OAAA,IAAI,WAAWI,CAAG,CAAA,CAAE,IAAIJ,CAAK,CAAA,CACtB,CAAE,MAAA,CAAQI,CAAAA,CAAK,IAAA,CAAMD,CAAAA,CAAUL,CAAK,CAAE,CAC/C,CAaO,SAASO,CAAAA,CAAcC,CAAAA,CAAmC,CAC/DC,CAAAA,CAAaD,EAAK,IAAI,CAAA,CACtB,IAAMN,CAAAA,CAAQ,IAAI,UAAA,CAAWM,EAAK,MAAM,CAAA,CACxC,OAAOE,mBAAAA,CAAiBR,CAAK,CAC/B,CAaO,SAASS,CAAAA,CAAYC,CAAAA,CAA2BC,CAAAA,CAAyC,CAC9F,IAAMX,CAAAA,CAAQ,IAAI,UAAA,CAAWU,CAAM,CAAA,CAC7Bb,CAAAA,CAAQW,oBAAiBR,CAAK,CAAA,CACpC,GAAIW,CAAAA,EAAS,QAAA,CAAU,CACrB,IAAMb,CAAAA,CAAQC,mBAAAA,CAAcF,CAAK,CAAA,CACjCC,CAAAA,CAAM,SAAW,KACnB,CACA,OAAOD,CACT,CAEO,SAASe,EAAYf,CAAAA,CAAoB,CAC1CgB,mBAAAA,CAAkBhB,CAAAA,CAAM,EAAE,CAAA,EAC5BiB,oBAAajB,CAAK,EAEtB,CAEA,SAASM,CAAAA,CAAUL,CAAAA,CAA8B,CAC/C,IAAMiB,CAAAA,CAAkD,EAAC,CACzD,IAAA,IAAWC,KAAQlB,CAAAA,CAAM,kBAAA,CAClBkB,CAAAA,EACLD,CAAAA,CAAiB,IAAA,CAAK,CAAE,GAAIC,CAAAA,CAAK,EAAA,CAAI,IAAA,CAAMA,CAAAA,CAAK,IAAA,CAAM,MAAA,CAAQA,EAAK,MAAO,CAAC,CAAA,CAE7E,OAAO,CACL,KAAA,CAAOrB,EACP,aAAA,CAAe,CAAA,CACf,eAAgBsB,mBAAAA,CAChB,SAAA,CAAWnB,EAAM,OAAA,CAAQ,SAAA,CACzB,cAAA,CAAgBA,CAAAA,CAAM,OAAA,CAAQ,cAAA,CAC9B,cAAeA,CAAAA,CAAM,OAAA,CAAQ,aAAA,CAC7B,aAAA,CAAeA,CAAAA,CAAM,OAAA,CAAQ,cAC7B,QAAA,CAAUA,CAAAA,CAAM,QAAA,CAChB,gBAAA,CAAAiB,CACF,CACF,CAEA,SAASR,CAAAA,CAAaW,EAAuB,CAC3C,GAAIA,EAAK,KAAA,GAAUvB,CAAAA,CACjB,MAAM,IAAI,KAAA,CAAM,8CAA8C,EAEhE,GAAIuB,CAAAA,CAAK,aAAA,GAAkB,CAAA,CACzB,MAAM,IAAI,MAAM,CAAA,6CAAA,EAAgDA,CAAAA,CAAK,aAAa,CAAA,CAAE,CAExF","file":"worker.cjs","sourcesContent":["// aiecsjs/worker — SharedArrayBuffer helpers (experimental, snapshot-copy).\n//\n// Note: 0.x implements SAB as a transferable snapshot pattern rather than true\n// shared-memory aliasing. The world is serialized into the SAB, and the worker\n// reconstructs a fresh world from those bytes via adoptSnapshot/attachWorld.\n// True shared-column memory is targeted for a future stable release.\n// The API matches the documented contract and survives postMessage cleanly.\n//\n// EntityRef is in-memory only — not preserved across worker boundaries.\n// Generation counters reset on adoptSnapshot/attachWorld. Pass `EntityRef.id`\n// (the packed EntityId) across the worker boundary only if you understand that\n// the generation portion will be stale after a round-trip snapshot.\n\nimport type { TransferableSnapshot, World, WorldMeta, WorldState } from './internal/types.js'\nimport { destroyWorld, getWorldState, isWorldRegistered } from './internal/world.js'\nimport { deserializeWorld, serializeWorld } from './serialize.js'\nimport { VERSION } from './version.js'\n\nconst MAGIC = 0x41494543 // 'AIEC' little-endian as uint32\n\nexport function transferableSnapshot(world: World): TransferableSnapshot {\n const state = getWorldState(world)\n const bytes = serializeWorld(world)\n\n if (typeof SharedArrayBuffer === 'undefined') {\n // Fallback: ArrayBuffer wrapped to look like a SAB at runtime\n const ab = new ArrayBuffer(bytes.byteLength)\n new Uint8Array(ab).set(bytes)\n return {\n buffer: ab as unknown as SharedArrayBuffer,\n meta: buildMeta(state),\n }\n }\n\n const sab = new SharedArrayBuffer(bytes.byteLength)\n new Uint8Array(sab).set(bytes)\n return { buffer: sab, meta: buildMeta(state) }\n}\n\n/**\n * Adopt a snapshot previously produced by `transferableSnapshot`.\n *\n * SECURITY: same trust expectation as `attachWorld` — the sender of the\n * `TransferableSnapshot` (typically a Web Worker) must be trusted. The\n * function runs `validateMeta` and `deserializeWorld`, which enforce magic\n * + format version + length bounds on the binary header; but the inner\n * JSON payload, once decoded, is fed to `addComponent` and reaches AoS\n * components. For untrusted senders, use `aibridgejs` + `toJSON(world)` at\n * the application boundary instead.\n */\nexport function adoptSnapshot(snap: TransferableSnapshot): World {\n validateMeta(snap.meta)\n const bytes = new Uint8Array(snap.buffer)\n return deserializeWorld(bytes)\n}\n\n/**\n * Adopt a SharedArrayBuffer-backed snapshot produced by `transferableSnapshot`.\n *\n * SECURITY: the SAB sender must be trusted. `attachWorld` performs the same\n * magic + version + length-bounds checks as `deserializeWorld`, but the JSON\n * payload itself is deserialised into AoS components via `addComponent`. If\n * the sender is untrusted (e.g. a third-party Web Worker that you do not\n * audit), prefer the higher-level `aibridgejs` channel + `toJSON(world)`\n * pattern, which lets you validate the shape at the application boundary\n * before constructing entities.\n */\nexport function attachWorld(buffer: SharedArrayBuffer, options?: { readOnly?: boolean }): World {\n const bytes = new Uint8Array(buffer)\n const world = deserializeWorld(bytes)\n if (options?.readOnly) {\n const state = getWorldState(world)\n state.readOnly = true\n }\n return world\n}\n\nexport function detachWorld(world: World): void {\n if (isWorldRegistered(world.id)) {\n destroyWorld(world)\n }\n}\n\nfunction buildMeta(state: WorldState): WorldMeta {\n const componentSchemas: WorldMeta['componentSchemas'] = []\n for (const info of state.componentInfoByBit) {\n if (!info) continue\n componentSchemas.push({ id: info.id, kind: info.kind, schema: info.schema })\n }\n return {\n magic: MAGIC,\n formatVersion: 1,\n aiecsjsVersion: VERSION,\n indexBits: state.options.indexBits,\n generationBits: state.options.generationBits,\n maxComponents: state.options.maxComponents,\n maskWordCount: state.options.maskWordCount,\n capacity: state.capacity,\n componentSchemas,\n }\n}\n\nfunction validateMeta(meta: WorldMeta): void {\n if (meta.magic !== MAGIC) {\n throw new Error('aiecsjs: invalid snapshot meta (wrong magic)')\n }\n if (meta.formatVersion !== 1) {\n throw new Error(`aiecsjs: unsupported snapshot format version ${meta.formatVersion}`)\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/worker.ts"],"names":["MAGIC","transferableSnapshot","world","state","getWorldState","bytes","serializeWorld","ab","buildMeta","sab","adoptSnapshot","snap","validateMeta","deserializeWorld","attachWorld","buffer","options","detachWorld","isWorldRegistered","destroyWorld","componentSchemas","info","VERSION","meta"],"mappings":"qHAkBA,IAAMA,CAAAA,CAAQ,WAEP,SAASC,CAAAA,CAAqBC,EAAoC,CACvE,IAAMC,CAAAA,CAAQC,mBAAAA,CAAcF,CAAK,CAAA,CAC3BG,EAAQC,mBAAAA,CAAeJ,CAAK,EAElC,GAAI,OAAO,kBAAsB,GAAA,CAAa,CAI5C,IAAMK,CAAAA,CAAK,IAAI,WAAA,CAAYF,EAAM,UAAU,CAAA,CAC3C,WAAI,UAAA,CAAWE,CAAE,EAAE,GAAA,CAAIF,CAAK,CAAA,CACrB,CACL,MAAA,CAAQE,CAAAA,CACR,KAAMC,CAAAA,CAAUL,CAAK,CACvB,CACF,CAEA,IAAMM,EAAM,IAAI,iBAAA,CAAkBJ,CAAAA,CAAM,UAAU,CAAA,CAClD,OAAA,IAAI,WAAWI,CAAG,CAAA,CAAE,IAAIJ,CAAK,CAAA,CACtB,CAAE,MAAA,CAAQI,CAAAA,CAAK,IAAA,CAAMD,CAAAA,CAAUL,CAAK,CAAE,CAC/C,CAaO,SAASO,CAAAA,CAAcC,CAAAA,CAAmC,CAC/DC,CAAAA,CAAaD,EAAK,IAAI,CAAA,CACtB,IAAMN,CAAAA,CAAQ,IAAI,UAAA,CAAWM,EAAK,MAAM,CAAA,CACxC,OAAOE,mBAAAA,CAAiBR,CAAK,CAC/B,CAaO,SAASS,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACO,CACP,IAAMX,CAAAA,CAAQ,IAAI,UAAA,CAAWU,CAAM,CAAA,CAC7Bb,CAAAA,CAAQW,oBAAiBR,CAAK,CAAA,CACpC,GAAIW,CAAAA,EAAS,QAAA,CAAU,CACrB,IAAMb,CAAAA,CAAQC,mBAAAA,CAAcF,CAAK,CAAA,CACjCC,CAAAA,CAAM,SAAW,KACnB,CACA,OAAOD,CACT,CAEO,SAASe,EAAYf,CAAAA,CAAoB,CAC1CgB,mBAAAA,CAAkBhB,CAAAA,CAAM,EAAE,CAAA,EAC5BiB,oBAAajB,CAAK,EAEtB,CAEA,SAASM,CAAAA,CAAUL,CAAAA,CAA8B,CAC/C,IAAMiB,CAAAA,CAAkD,EAAC,CACzD,IAAA,IAAWC,KAAQlB,CAAAA,CAAM,kBAAA,CAClBkB,CAAAA,EACLD,CAAAA,CAAiB,IAAA,CAAK,CAAE,GAAIC,CAAAA,CAAK,EAAA,CAAI,IAAA,CAAMA,CAAAA,CAAK,IAAA,CAAM,MAAA,CAAQA,EAAK,MAAO,CAAC,CAAA,CAE7E,OAAO,CACL,KAAA,CAAOrB,EACP,aAAA,CAAe,CAAA,CACf,eAAgBsB,mBAAAA,CAChB,SAAA,CAAWnB,EAAM,OAAA,CAAQ,SAAA,CACzB,cAAA,CAAgBA,CAAAA,CAAM,OAAA,CAAQ,cAAA,CAC9B,cAAeA,CAAAA,CAAM,OAAA,CAAQ,aAAA,CAC7B,aAAA,CAAeA,CAAAA,CAAM,OAAA,CAAQ,cAC7B,QAAA,CAAUA,CAAAA,CAAM,QAAA,CAChB,gBAAA,CAAAiB,CACF,CACF,CAEA,SAASR,CAAAA,CAAaW,EAAuB,CAC3C,GAAIA,EAAK,KAAA,GAAUvB,CAAAA,CACjB,MAAM,IAAI,KAAA,CAAM,8CAA8C,EAEhE,GAAIuB,CAAAA,CAAK,aAAA,GAAkB,CAAA,CACzB,MAAM,IAAI,MAAM,CAAA,6CAAA,EAAgDA,CAAAA,CAAK,aAAa,CAAA,CAAE,CAExF","file":"worker.cjs","sourcesContent":["// aiecsjs/worker — SharedArrayBuffer helpers (experimental, snapshot-copy).\n//\n// Note: 0.x implements SAB as a transferable snapshot pattern rather than true\n// shared-memory aliasing. The world is serialized into the SAB, and the worker\n// reconstructs a fresh world from those bytes via adoptSnapshot/attachWorld.\n// True shared-column memory is targeted for a future stable release.\n// The API matches the documented contract and survives postMessage cleanly.\n//\n// EntityRef is in-memory only — not preserved across worker boundaries.\n// Generation counters reset on adoptSnapshot/attachWorld. Pass `EntityRef.id`\n// (the packed EntityId) across the worker boundary only if you understand that\n// the generation portion will be stale after a round-trip snapshot.\n\nimport type { TransferableSnapshot, World, WorldMeta, WorldState } from './internal/types.js'\nimport { destroyWorld, getWorldState, isWorldRegistered } from './internal/world.js'\nimport { deserializeWorld, serializeWorld } from './serialize.js'\nimport { VERSION } from './version.js'\n\nconst MAGIC = 0x41494543 // 'AIEC' little-endian as uint32\n\nexport function transferableSnapshot(world: World): TransferableSnapshot {\n const state = getWorldState(world)\n const bytes = serializeWorld(world)\n\n if (typeof SharedArrayBuffer === 'undefined') {\n // Fallback: a plain ArrayBuffer. TransferableSnapshot.buffer is typed\n // `SharedArrayBuffer | ArrayBuffer`, so this needs no cast — the type tells\n // the truth instead of pretending the fallback is a SAB.\n const ab = new ArrayBuffer(bytes.byteLength)\n new Uint8Array(ab).set(bytes)\n return {\n buffer: ab,\n meta: buildMeta(state),\n }\n }\n\n const sab = new SharedArrayBuffer(bytes.byteLength)\n new Uint8Array(sab).set(bytes)\n return { buffer: sab, meta: buildMeta(state) }\n}\n\n/**\n * Adopt a snapshot previously produced by `transferableSnapshot`.\n *\n * SECURITY: same trust expectation as `attachWorld` — the sender of the\n * `TransferableSnapshot` (typically a Web Worker) must be trusted. The\n * function runs `validateMeta` and `deserializeWorld`, which enforce magic\n * + format version + length bounds on the binary header; but the inner\n * JSON payload, once decoded, is fed to `addComponent` and reaches AoS\n * components. For untrusted senders, use `aibridgejs` + `toJSON(world)` at\n * the application boundary instead.\n */\nexport function adoptSnapshot(snap: TransferableSnapshot): World {\n validateMeta(snap.meta)\n const bytes = new Uint8Array(snap.buffer)\n return deserializeWorld(bytes)\n}\n\n/**\n * Adopt a SharedArrayBuffer-backed snapshot produced by `transferableSnapshot`.\n *\n * SECURITY: the SAB sender must be trusted. `attachWorld` performs the same\n * magic + version + length-bounds checks as `deserializeWorld`, but the JSON\n * payload itself is deserialised into AoS components via `addComponent`. If\n * the sender is untrusted (e.g. a third-party Web Worker that you do not\n * audit), prefer the higher-level `aibridgejs` channel + `toJSON(world)`\n * pattern, which lets you validate the shape at the application boundary\n * before constructing entities.\n */\nexport function attachWorld(\n buffer: SharedArrayBuffer | ArrayBuffer,\n options?: { readOnly?: boolean },\n): World {\n const bytes = new Uint8Array(buffer)\n const world = deserializeWorld(bytes)\n if (options?.readOnly) {\n const state = getWorldState(world)\n state.readOnly = true\n }\n return world\n}\n\nexport function detachWorld(world: World): void {\n if (isWorldRegistered(world.id)) {\n destroyWorld(world)\n }\n}\n\nfunction buildMeta(state: WorldState): WorldMeta {\n const componentSchemas: WorldMeta['componentSchemas'] = []\n for (const info of state.componentInfoByBit) {\n if (!info) continue\n componentSchemas.push({ id: info.id, kind: info.kind, schema: info.schema })\n }\n return {\n magic: MAGIC,\n formatVersion: 1,\n aiecsjsVersion: VERSION,\n indexBits: state.options.indexBits,\n generationBits: state.options.generationBits,\n maxComponents: state.options.maxComponents,\n maskWordCount: state.options.maskWordCount,\n capacity: state.capacity,\n componentSchemas,\n }\n}\n\nfunction validateMeta(meta: WorldMeta): void {\n if (meta.magic !== MAGIC) {\n throw new Error('aiecsjs: invalid snapshot meta (wrong magic)')\n }\n if (meta.formatVersion !== 1) {\n throw new Error(`aiecsjs: unsupported snapshot format version ${meta.formatVersion}`)\n }\n}\n"]}
package/dist/worker.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { m as TransferableSnapshot, W as World } from './types-BGEeHad-.cjs';
1
+ import { m as TransferableSnapshot, W as World } from './types-BeVLA7xG.cjs';
2
2
 
3
3
  declare function transferableSnapshot(world: World): TransferableSnapshot;
4
4
  /**
@@ -24,7 +24,7 @@ declare function adoptSnapshot(snap: TransferableSnapshot): World;
24
24
  * pattern, which lets you validate the shape at the application boundary
25
25
  * before constructing entities.
26
26
  */
27
- declare function attachWorld(buffer: SharedArrayBuffer, options?: {
27
+ declare function attachWorld(buffer: SharedArrayBuffer | ArrayBuffer, options?: {
28
28
  readOnly?: boolean;
29
29
  }): World;
30
30
  declare function detachWorld(world: World): void;
package/dist/worker.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { m as TransferableSnapshot, W as World } from './types-BGEeHad-.js';
1
+ import { m as TransferableSnapshot, W as World } from './types-BeVLA7xG.js';
2
2
 
3
3
  declare function transferableSnapshot(world: World): TransferableSnapshot;
4
4
  /**
@@ -24,7 +24,7 @@ declare function adoptSnapshot(snap: TransferableSnapshot): World;
24
24
  * pattern, which lets you validate the shape at the application boundary
25
25
  * before constructing entities.
26
26
  */
27
- declare function attachWorld(buffer: SharedArrayBuffer, options?: {
27
+ declare function attachWorld(buffer: SharedArrayBuffer | ArrayBuffer, options?: {
28
28
  readOnly?: boolean;
29
29
  }): World;
30
30
  declare function detachWorld(world: World): void;
package/dist/worker.js CHANGED
@@ -1,2 +1,2 @@
1
- import {a,b as b$1}from'./chunk-IFA7MVBB.js';import {i,j,l as l$1,a as a$1}from'./chunk-TKDLUVJR.js';var l=1095320899;function W(r){let e=i(r),o=a(r);if(typeof SharedArrayBuffer>"u"){let n=new ArrayBuffer(o.byteLength);return new Uint8Array(n).set(o),{buffer:n,meta:p(e)}}let t=new SharedArrayBuffer(o.byteLength);return new Uint8Array(t).set(o),{buffer:t,meta:p(e)}}function b(r){m(r.meta);let e=new Uint8Array(r.buffer);return b$1(e)}function S(r,e){let o=new Uint8Array(r),t=b$1(o);if(e?.readOnly){let n=i(t);n.readOnly=true;}return t}function w(r){j(r.id)&&l$1(r);}function p(r){let e=[];for(let o of r.componentInfoByBit)o&&e.push({id:o.id,kind:o.kind,schema:o.schema});return {magic:l,formatVersion:1,aiecsjsVersion:a$1,indexBits:r.options.indexBits,generationBits:r.options.generationBits,maxComponents:r.options.maxComponents,maskWordCount:r.options.maskWordCount,capacity:r.capacity,componentSchemas:e}}function m(r){if(r.magic!==l)throw new Error("aiecsjs: invalid snapshot meta (wrong magic)");if(r.formatVersion!==1)throw new Error(`aiecsjs: unsupported snapshot format version ${r.formatVersion}`)}export{b as adoptSnapshot,S as attachWorld,w as detachWorld,W as transferableSnapshot};//# sourceMappingURL=worker.js.map
1
+ import {a,b as b$1}from'./chunk-AGWUE6JB.js';import {j,k,m as m$1,a as a$1}from'./chunk-2KCN5RVK.js';var l=1095320899;function W(r){let e=j(r),o=a(r);if(typeof SharedArrayBuffer>"u"){let n=new ArrayBuffer(o.byteLength);return new Uint8Array(n).set(o),{buffer:n,meta:p(e)}}let t=new SharedArrayBuffer(o.byteLength);return new Uint8Array(t).set(o),{buffer:t,meta:p(e)}}function b(r){m(r.meta);let e=new Uint8Array(r.buffer);return b$1(e)}function S(r,e){let o=new Uint8Array(r),t=b$1(o);if(e?.readOnly){let n=j(t);n.readOnly=true;}return t}function B(r){k(r.id)&&m$1(r);}function p(r){let e=[];for(let o of r.componentInfoByBit)o&&e.push({id:o.id,kind:o.kind,schema:o.schema});return {magic:l,formatVersion:1,aiecsjsVersion:a$1,indexBits:r.options.indexBits,generationBits:r.options.generationBits,maxComponents:r.options.maxComponents,maskWordCount:r.options.maskWordCount,capacity:r.capacity,componentSchemas:e}}function m(r){if(r.magic!==l)throw new Error("aiecsjs: invalid snapshot meta (wrong magic)");if(r.formatVersion!==1)throw new Error(`aiecsjs: unsupported snapshot format version ${r.formatVersion}`)}export{b as adoptSnapshot,S as attachWorld,B as detachWorld,W as transferableSnapshot};//# sourceMappingURL=worker.js.map
2
2
  //# sourceMappingURL=worker.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/worker.ts"],"names":["MAGIC","transferableSnapshot","world","state","getWorldState","bytes","serializeWorld","ab","buildMeta","sab","adoptSnapshot","snap","validateMeta","deserializeWorld","attachWorld","buffer","options","detachWorld","isWorldRegistered","destroyWorld","componentSchemas","info","VERSION","meta"],"mappings":"qGAkBA,IAAMA,CAAAA,CAAQ,WAEP,SAASC,CAAAA,CAAqBC,EAAoC,CACvE,IAAMC,CAAAA,CAAQC,CAAAA,CAAcF,CAAK,CAAA,CAC3BG,EAAQC,CAAAA,CAAeJ,CAAK,EAElC,GAAI,OAAO,kBAAsB,GAAA,CAAa,CAE5C,IAAMK,CAAAA,CAAK,IAAI,WAAA,CAAYF,EAAM,UAAU,CAAA,CAC3C,WAAI,UAAA,CAAWE,CAAE,EAAE,GAAA,CAAIF,CAAK,CAAA,CACrB,CACL,MAAA,CAAQE,CAAAA,CACR,KAAMC,CAAAA,CAAUL,CAAK,CACvB,CACF,CAEA,IAAMM,EAAM,IAAI,iBAAA,CAAkBJ,CAAAA,CAAM,UAAU,CAAA,CAClD,OAAA,IAAI,WAAWI,CAAG,CAAA,CAAE,IAAIJ,CAAK,CAAA,CACtB,CAAE,MAAA,CAAQI,CAAAA,CAAK,IAAA,CAAMD,CAAAA,CAAUL,CAAK,CAAE,CAC/C,CAaO,SAASO,CAAAA,CAAcC,CAAAA,CAAmC,CAC/DC,CAAAA,CAAaD,EAAK,IAAI,CAAA,CACtB,IAAMN,CAAAA,CAAQ,IAAI,UAAA,CAAWM,EAAK,MAAM,CAAA,CACxC,OAAOE,GAAAA,CAAiBR,CAAK,CAC/B,CAaO,SAASS,CAAAA,CAAYC,CAAAA,CAA2BC,CAAAA,CAAyC,CAC9F,IAAMX,CAAAA,CAAQ,IAAI,UAAA,CAAWU,CAAM,CAAA,CAC7Bb,CAAAA,CAAQW,IAAiBR,CAAK,CAAA,CACpC,GAAIW,CAAAA,EAAS,QAAA,CAAU,CACrB,IAAMb,CAAAA,CAAQC,CAAAA,CAAcF,CAAK,CAAA,CACjCC,CAAAA,CAAM,SAAW,KACnB,CACA,OAAOD,CACT,CAEO,SAASe,EAAYf,CAAAA,CAAoB,CAC1CgB,CAAAA,CAAkBhB,CAAAA,CAAM,EAAE,CAAA,EAC5BiB,IAAajB,CAAK,EAEtB,CAEA,SAASM,CAAAA,CAAUL,CAAAA,CAA8B,CAC/C,IAAMiB,CAAAA,CAAkD,EAAC,CACzD,IAAA,IAAWC,KAAQlB,CAAAA,CAAM,kBAAA,CAClBkB,CAAAA,EACLD,CAAAA,CAAiB,IAAA,CAAK,CAAE,GAAIC,CAAAA,CAAK,EAAA,CAAI,IAAA,CAAMA,CAAAA,CAAK,IAAA,CAAM,MAAA,CAAQA,EAAK,MAAO,CAAC,CAAA,CAE7E,OAAO,CACL,KAAA,CAAOrB,EACP,aAAA,CAAe,CAAA,CACf,eAAgBsB,GAAAA,CAChB,SAAA,CAAWnB,EAAM,OAAA,CAAQ,SAAA,CACzB,cAAA,CAAgBA,CAAAA,CAAM,OAAA,CAAQ,cAAA,CAC9B,cAAeA,CAAAA,CAAM,OAAA,CAAQ,aAAA,CAC7B,aAAA,CAAeA,CAAAA,CAAM,OAAA,CAAQ,cAC7B,QAAA,CAAUA,CAAAA,CAAM,QAAA,CAChB,gBAAA,CAAAiB,CACF,CACF,CAEA,SAASR,CAAAA,CAAaW,EAAuB,CAC3C,GAAIA,EAAK,KAAA,GAAUvB,CAAAA,CACjB,MAAM,IAAI,KAAA,CAAM,8CAA8C,EAEhE,GAAIuB,CAAAA,CAAK,aAAA,GAAkB,CAAA,CACzB,MAAM,IAAI,MAAM,CAAA,6CAAA,EAAgDA,CAAAA,CAAK,aAAa,CAAA,CAAE,CAExF","file":"worker.js","sourcesContent":["// aiecsjs/worker — SharedArrayBuffer helpers (experimental, snapshot-copy).\n//\n// Note: 0.x implements SAB as a transferable snapshot pattern rather than true\n// shared-memory aliasing. The world is serialized into the SAB, and the worker\n// reconstructs a fresh world from those bytes via adoptSnapshot/attachWorld.\n// True shared-column memory is targeted for a future stable release.\n// The API matches the documented contract and survives postMessage cleanly.\n//\n// EntityRef is in-memory only — not preserved across worker boundaries.\n// Generation counters reset on adoptSnapshot/attachWorld. Pass `EntityRef.id`\n// (the packed EntityId) across the worker boundary only if you understand that\n// the generation portion will be stale after a round-trip snapshot.\n\nimport type { TransferableSnapshot, World, WorldMeta, WorldState } from './internal/types.js'\nimport { destroyWorld, getWorldState, isWorldRegistered } from './internal/world.js'\nimport { deserializeWorld, serializeWorld } from './serialize.js'\nimport { VERSION } from './version.js'\n\nconst MAGIC = 0x41494543 // 'AIEC' little-endian as uint32\n\nexport function transferableSnapshot(world: World): TransferableSnapshot {\n const state = getWorldState(world)\n const bytes = serializeWorld(world)\n\n if (typeof SharedArrayBuffer === 'undefined') {\n // Fallback: ArrayBuffer wrapped to look like a SAB at runtime\n const ab = new ArrayBuffer(bytes.byteLength)\n new Uint8Array(ab).set(bytes)\n return {\n buffer: ab as unknown as SharedArrayBuffer,\n meta: buildMeta(state),\n }\n }\n\n const sab = new SharedArrayBuffer(bytes.byteLength)\n new Uint8Array(sab).set(bytes)\n return { buffer: sab, meta: buildMeta(state) }\n}\n\n/**\n * Adopt a snapshot previously produced by `transferableSnapshot`.\n *\n * SECURITY: same trust expectation as `attachWorld` — the sender of the\n * `TransferableSnapshot` (typically a Web Worker) must be trusted. The\n * function runs `validateMeta` and `deserializeWorld`, which enforce magic\n * + format version + length bounds on the binary header; but the inner\n * JSON payload, once decoded, is fed to `addComponent` and reaches AoS\n * components. For untrusted senders, use `aibridgejs` + `toJSON(world)` at\n * the application boundary instead.\n */\nexport function adoptSnapshot(snap: TransferableSnapshot): World {\n validateMeta(snap.meta)\n const bytes = new Uint8Array(snap.buffer)\n return deserializeWorld(bytes)\n}\n\n/**\n * Adopt a SharedArrayBuffer-backed snapshot produced by `transferableSnapshot`.\n *\n * SECURITY: the SAB sender must be trusted. `attachWorld` performs the same\n * magic + version + length-bounds checks as `deserializeWorld`, but the JSON\n * payload itself is deserialised into AoS components via `addComponent`. If\n * the sender is untrusted (e.g. a third-party Web Worker that you do not\n * audit), prefer the higher-level `aibridgejs` channel + `toJSON(world)`\n * pattern, which lets you validate the shape at the application boundary\n * before constructing entities.\n */\nexport function attachWorld(buffer: SharedArrayBuffer, options?: { readOnly?: boolean }): World {\n const bytes = new Uint8Array(buffer)\n const world = deserializeWorld(bytes)\n if (options?.readOnly) {\n const state = getWorldState(world)\n state.readOnly = true\n }\n return world\n}\n\nexport function detachWorld(world: World): void {\n if (isWorldRegistered(world.id)) {\n destroyWorld(world)\n }\n}\n\nfunction buildMeta(state: WorldState): WorldMeta {\n const componentSchemas: WorldMeta['componentSchemas'] = []\n for (const info of state.componentInfoByBit) {\n if (!info) continue\n componentSchemas.push({ id: info.id, kind: info.kind, schema: info.schema })\n }\n return {\n magic: MAGIC,\n formatVersion: 1,\n aiecsjsVersion: VERSION,\n indexBits: state.options.indexBits,\n generationBits: state.options.generationBits,\n maxComponents: state.options.maxComponents,\n maskWordCount: state.options.maskWordCount,\n capacity: state.capacity,\n componentSchemas,\n }\n}\n\nfunction validateMeta(meta: WorldMeta): void {\n if (meta.magic !== MAGIC) {\n throw new Error('aiecsjs: invalid snapshot meta (wrong magic)')\n }\n if (meta.formatVersion !== 1) {\n throw new Error(`aiecsjs: unsupported snapshot format version ${meta.formatVersion}`)\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/worker.ts"],"names":["MAGIC","transferableSnapshot","world","state","getWorldState","bytes","serializeWorld","ab","buildMeta","sab","adoptSnapshot","snap","validateMeta","deserializeWorld","attachWorld","buffer","options","detachWorld","isWorldRegistered","destroyWorld","componentSchemas","info","VERSION","meta"],"mappings":"qGAkBA,IAAMA,CAAAA,CAAQ,WAEP,SAASC,CAAAA,CAAqBC,EAAoC,CACvE,IAAMC,CAAAA,CAAQC,CAAAA,CAAcF,CAAK,CAAA,CAC3BG,EAAQC,CAAAA,CAAeJ,CAAK,EAElC,GAAI,OAAO,kBAAsB,GAAA,CAAa,CAI5C,IAAMK,CAAAA,CAAK,IAAI,WAAA,CAAYF,EAAM,UAAU,CAAA,CAC3C,WAAI,UAAA,CAAWE,CAAE,EAAE,GAAA,CAAIF,CAAK,CAAA,CACrB,CACL,MAAA,CAAQE,CAAAA,CACR,KAAMC,CAAAA,CAAUL,CAAK,CACvB,CACF,CAEA,IAAMM,EAAM,IAAI,iBAAA,CAAkBJ,CAAAA,CAAM,UAAU,CAAA,CAClD,OAAA,IAAI,WAAWI,CAAG,CAAA,CAAE,IAAIJ,CAAK,CAAA,CACtB,CAAE,MAAA,CAAQI,CAAAA,CAAK,IAAA,CAAMD,CAAAA,CAAUL,CAAK,CAAE,CAC/C,CAaO,SAASO,CAAAA,CAAcC,CAAAA,CAAmC,CAC/DC,CAAAA,CAAaD,EAAK,IAAI,CAAA,CACtB,IAAMN,CAAAA,CAAQ,IAAI,UAAA,CAAWM,EAAK,MAAM,CAAA,CACxC,OAAOE,GAAAA,CAAiBR,CAAK,CAC/B,CAaO,SAASS,CAAAA,CACdC,CAAAA,CACAC,CAAAA,CACO,CACP,IAAMX,CAAAA,CAAQ,IAAI,UAAA,CAAWU,CAAM,CAAA,CAC7Bb,CAAAA,CAAQW,IAAiBR,CAAK,CAAA,CACpC,GAAIW,CAAAA,EAAS,QAAA,CAAU,CACrB,IAAMb,CAAAA,CAAQC,CAAAA,CAAcF,CAAK,CAAA,CACjCC,CAAAA,CAAM,SAAW,KACnB,CACA,OAAOD,CACT,CAEO,SAASe,EAAYf,CAAAA,CAAoB,CAC1CgB,CAAAA,CAAkBhB,CAAAA,CAAM,EAAE,CAAA,EAC5BiB,IAAajB,CAAK,EAEtB,CAEA,SAASM,CAAAA,CAAUL,CAAAA,CAA8B,CAC/C,IAAMiB,CAAAA,CAAkD,EAAC,CACzD,IAAA,IAAWC,KAAQlB,CAAAA,CAAM,kBAAA,CAClBkB,CAAAA,EACLD,CAAAA,CAAiB,IAAA,CAAK,CAAE,GAAIC,CAAAA,CAAK,EAAA,CAAI,IAAA,CAAMA,CAAAA,CAAK,IAAA,CAAM,MAAA,CAAQA,EAAK,MAAO,CAAC,CAAA,CAE7E,OAAO,CACL,KAAA,CAAOrB,EACP,aAAA,CAAe,CAAA,CACf,eAAgBsB,GAAAA,CAChB,SAAA,CAAWnB,EAAM,OAAA,CAAQ,SAAA,CACzB,cAAA,CAAgBA,CAAAA,CAAM,OAAA,CAAQ,cAAA,CAC9B,cAAeA,CAAAA,CAAM,OAAA,CAAQ,aAAA,CAC7B,aAAA,CAAeA,CAAAA,CAAM,OAAA,CAAQ,cAC7B,QAAA,CAAUA,CAAAA,CAAM,QAAA,CAChB,gBAAA,CAAAiB,CACF,CACF,CAEA,SAASR,CAAAA,CAAaW,EAAuB,CAC3C,GAAIA,EAAK,KAAA,GAAUvB,CAAAA,CACjB,MAAM,IAAI,KAAA,CAAM,8CAA8C,EAEhE,GAAIuB,CAAAA,CAAK,aAAA,GAAkB,CAAA,CACzB,MAAM,IAAI,MAAM,CAAA,6CAAA,EAAgDA,CAAAA,CAAK,aAAa,CAAA,CAAE,CAExF","file":"worker.js","sourcesContent":["// aiecsjs/worker — SharedArrayBuffer helpers (experimental, snapshot-copy).\n//\n// Note: 0.x implements SAB as a transferable snapshot pattern rather than true\n// shared-memory aliasing. The world is serialized into the SAB, and the worker\n// reconstructs a fresh world from those bytes via adoptSnapshot/attachWorld.\n// True shared-column memory is targeted for a future stable release.\n// The API matches the documented contract and survives postMessage cleanly.\n//\n// EntityRef is in-memory only — not preserved across worker boundaries.\n// Generation counters reset on adoptSnapshot/attachWorld. Pass `EntityRef.id`\n// (the packed EntityId) across the worker boundary only if you understand that\n// the generation portion will be stale after a round-trip snapshot.\n\nimport type { TransferableSnapshot, World, WorldMeta, WorldState } from './internal/types.js'\nimport { destroyWorld, getWorldState, isWorldRegistered } from './internal/world.js'\nimport { deserializeWorld, serializeWorld } from './serialize.js'\nimport { VERSION } from './version.js'\n\nconst MAGIC = 0x41494543 // 'AIEC' little-endian as uint32\n\nexport function transferableSnapshot(world: World): TransferableSnapshot {\n const state = getWorldState(world)\n const bytes = serializeWorld(world)\n\n if (typeof SharedArrayBuffer === 'undefined') {\n // Fallback: a plain ArrayBuffer. TransferableSnapshot.buffer is typed\n // `SharedArrayBuffer | ArrayBuffer`, so this needs no cast — the type tells\n // the truth instead of pretending the fallback is a SAB.\n const ab = new ArrayBuffer(bytes.byteLength)\n new Uint8Array(ab).set(bytes)\n return {\n buffer: ab,\n meta: buildMeta(state),\n }\n }\n\n const sab = new SharedArrayBuffer(bytes.byteLength)\n new Uint8Array(sab).set(bytes)\n return { buffer: sab, meta: buildMeta(state) }\n}\n\n/**\n * Adopt a snapshot previously produced by `transferableSnapshot`.\n *\n * SECURITY: same trust expectation as `attachWorld` — the sender of the\n * `TransferableSnapshot` (typically a Web Worker) must be trusted. The\n * function runs `validateMeta` and `deserializeWorld`, which enforce magic\n * + format version + length bounds on the binary header; but the inner\n * JSON payload, once decoded, is fed to `addComponent` and reaches AoS\n * components. For untrusted senders, use `aibridgejs` + `toJSON(world)` at\n * the application boundary instead.\n */\nexport function adoptSnapshot(snap: TransferableSnapshot): World {\n validateMeta(snap.meta)\n const bytes = new Uint8Array(snap.buffer)\n return deserializeWorld(bytes)\n}\n\n/**\n * Adopt a SharedArrayBuffer-backed snapshot produced by `transferableSnapshot`.\n *\n * SECURITY: the SAB sender must be trusted. `attachWorld` performs the same\n * magic + version + length-bounds checks as `deserializeWorld`, but the JSON\n * payload itself is deserialised into AoS components via `addComponent`. If\n * the sender is untrusted (e.g. a third-party Web Worker that you do not\n * audit), prefer the higher-level `aibridgejs` channel + `toJSON(world)`\n * pattern, which lets you validate the shape at the application boundary\n * before constructing entities.\n */\nexport function attachWorld(\n buffer: SharedArrayBuffer | ArrayBuffer,\n options?: { readOnly?: boolean },\n): World {\n const bytes = new Uint8Array(buffer)\n const world = deserializeWorld(bytes)\n if (options?.readOnly) {\n const state = getWorldState(world)\n state.readOnly = true\n }\n return world\n}\n\nexport function detachWorld(world: World): void {\n if (isWorldRegistered(world.id)) {\n destroyWorld(world)\n }\n}\n\nfunction buildMeta(state: WorldState): WorldMeta {\n const componentSchemas: WorldMeta['componentSchemas'] = []\n for (const info of state.componentInfoByBit) {\n if (!info) continue\n componentSchemas.push({ id: info.id, kind: info.kind, schema: info.schema })\n }\n return {\n magic: MAGIC,\n formatVersion: 1,\n aiecsjsVersion: VERSION,\n indexBits: state.options.indexBits,\n generationBits: state.options.generationBits,\n maxComponents: state.options.maxComponents,\n maskWordCount: state.options.maskWordCount,\n capacity: state.capacity,\n componentSchemas,\n }\n}\n\nfunction validateMeta(meta: WorldMeta): void {\n if (meta.magic !== MAGIC) {\n throw new Error('aiecsjs: invalid snapshot meta (wrong magic)')\n }\n if (meta.formatVersion !== 1) {\n throw new Error(`aiecsjs: unsupported snapshot format version ${meta.formatVersion}`)\n }\n}\n"]}
package/llms-full.txt CHANGED
@@ -43,7 +43,7 @@ pipe(movement)(world, 1/60)
43
43
 
44
44
  > **Use `forEachEntityIndexed` for column iteration.** Its callback is `(e, i, ...cols)`: `e` is the packed `EntityId` (use it directly for `destroyEntity`, `hasComponent`, `getComponent`, command buffers), and `i` is the **safe column subscript** — index every SoA column with it (`pos.x[i]`). The packed `e` is **not** a column index: it only equals the index until a slot is recycled (generation 0); after any `destroyEntity` recycles a slot, `e !== i` and indexing a column with `e` reads the wrong slot (out of bounds → `undefined`/`NaN`). `forEachEntityIndexed` hands you the correct `i` so this footgun is closed in code. Use `forEachEntity` when you only need the `EntityId` (and index columns via `getEntityIndex(e)` if you must).
45
45
 
46
- > **Status: experimental (v0.5.x).** The API surface in `STABILITY.md` is committed for the 0.x line, but expect adjustments. A stable 1.0 freeze is targeted after community feedback.
46
+ > **Status: 0.5.7 — experimental (0.5.x line).** The API surface in `STABILITY.md` is committed for the 0.x line, but expect adjustments. A stable 1.0 freeze is targeted after community feedback.
47
47
 
48
48
  ## Table of contents
49
49
 
@@ -68,7 +68,7 @@ pipe(movement)(world, 1/60)
68
68
  ## Why aiecsjs?
69
69
 
70
70
  - **Archetype-first storage** — entities sharing the same component set live in one contiguous table; queries walk straight `for` loops over parallel TypedArrays. Iteration is cache-friendly by construction.
71
- - **Zero-config TypeScript inference** — `defineQuery([Position, Velocity])` returns an iterator that yields `(eid, posCols, velCols)` with the correct TypedArray types. No manual generics.
71
+ - **First-class TypeScript** — typed `EntityId`, components, worlds, and queries with no manual generics. Iteration helpers pass the SoA column views positionally (`forEachEntityIndexed(w, q, (e, i, pos, vel) => …)`); the **column arguments are `any`-typed** today — statically tuple-typed columns are future work — while `e` (`EntityId`) and `i` (`number`) are fully typed.
72
72
  - **AI-first documentation contract** — every public export has a stability tag and a `since` version. Ships `llms.txt`, `llms-full.txt`, and `api.json` so LLM tools can read the API surface directly.
73
73
 
74
74
  ### Comparison
@@ -77,7 +77,7 @@ pipe(movement)(world, 1/60)
77
77
  |---|---|---|---|---|
78
78
  | Storage | Archetype + SoA columns | SparseSet + bitmask + SoA/AoS | Archetype + JS objects | Configurable (packed/sparse/compact) + ArrayBuffer |
79
79
  | API style | Functional + `pipe` | Functional + `pipe` | Chainable OO | Decorator classes |
80
- | TS inference on query | Tuple-aware columns | Manual | Predicate inference | Class-based |
80
+ | TS inference on query | Typed `e`/`i`; columns `any` | Manual | Predicate inference | Class-based |
81
81
  | Multi-thread | SAB snapshot transport (0.x); true shared cols planned 0.3+ | SAB-ready, scheduling DIY | Single-thread | Roadmap (not shipped) |
82
82
  | AI docs | `llms.txt` + `llms-full.txt` + `api.json` | No | No | No |
83
83
  | Maintenance | Active (new) | Active | Slowed (~3y since npm release) | Active |
@@ -349,7 +349,7 @@ Observers fire synchronously inside the mutation call. Use them for side effects
349
349
 
350
350
  ### Command buffers — when and why
351
351
 
352
- The golden rule: **do not add or remove components on entities you're currently iterating over.** Doing so can skip or double-process entities because the archetype membership changes mid-walk. Use a command buffer to defer:
352
+ The golden rule: **add or remove components via a command buffer while iterating** — never mutate an iterated entity's component set inline. Adding or removing a component changes archetype membership mid-walk, which can skip or double-process entities. In-loop `destroyEntity` **is** safe (the iteration re-reads the live row count each step, so the callback never sees the reserved eid 0 and never a destroyed entity), but with two caveats: a surviving entity swapped into a freed row is **deferred to the next pass** (not visited again this pass), and iteration order after a destroy is not guaranteed. Use a command buffer to defer component mutations:
353
353
 
354
354
  ```ts
355
355
  import { withCommandBuffer } from 'aiecsjs/commands'
@@ -414,11 +414,13 @@ type WorldOptions = {
414
414
  maxEntities?: number // default 1_000_000
415
415
  indexBits?: 20 | 24 // default 24 → 16M entities
416
416
  generationBits?: 8 | 12 | 16 // default 8 → 256 recycles
417
- buffer?: SharedArrayBuffer // opt-in SAB backing
418
- bufferByteOffset?: number // when sharing one SAB across worlds
417
+ buffer?: SharedArrayBuffer // RESERVED — no effect in 0.x (see note below)
418
+ bufferByteOffset?: number // RESERVED — paired with buffer; no effect in 0.x
419
419
  }
420
420
  ```
421
421
 
422
+ > **`buffer` / `bufferByteOffset` are reserved and currently unimplemented.** Setting them has no effect: a world always allocates its own column storage and never reads a caller-supplied SAB. For Worker handoff use the snapshot-copy transport — post `transferableSnapshot(world)` and rebuild via `adoptSnapshot` from `aiecsjs/worker` (see [Multi-threading Guide](#multi-threading-guide)). The fields are kept for forward-compatibility with the true shared-column backing targeted for 0.3+.
423
+
422
424
  ### Entity — `aiecsjs`
423
425
 
424
426
  | Function | Signature | Stability |
@@ -598,7 +600,7 @@ These tips are derived from public ECS benchmarks (noctjs/ecs-benchmark, ddmills
598
600
 
599
601
  ## Multi-threading Guide
600
602
 
601
- aiecsjs is **SharedArrayBuffer-ready**: a world's archetype columns can live in shared memory and a Worker can iterate them in parallel.
603
+ aiecsjs hands a world to a Worker via a **transferable snapshot**: the world is serialized into a `SharedArrayBuffer` (a plain `ArrayBuffer` when SAB is unavailable) and the Worker reconstructs a fresh world from it. In 0.x this is a snapshot-**copy** transport, not true shared-memory column aliasing — the two worlds do not see each other's writes after the handoff. True shared columns are targeted for 0.3+ (see [`STABILITY.md`](./STABILITY.md), `aiecsjs/worker`).
602
604
 
603
605
  ### Capability detection
604
606
 
@@ -613,21 +615,28 @@ In browsers, `SharedArrayBuffer` requires the page to be **cross-origin isolated
613
615
 
614
616
  ### Main thread
615
617
 
618
+ Post the whole `transferableSnapshot(world)` — it already carries `{ buffer, meta }`. Do **not** allocate your own SAB and pass it to `createWorld`; `WorldOptions.buffer` is reserved and currently has no effect (see [Entity — `aiecsjs`](#entity--aiecsjs)).
619
+
616
620
  ```ts
617
- const buffer = new SharedArrayBuffer(64 * 1024 * 1024) // 64 MB
618
- const world = createWorld({ buffer })
621
+ import { createWorld } from 'aiecsjs'
622
+ import { transferableSnapshot } from 'aiecsjs/worker'
623
+
624
+ const world = createWorld()
619
625
 
620
626
  // populate world...
621
627
 
622
628
  const worker = new Worker(new URL('./sim-worker.ts', import.meta.url), { type: 'module' })
623
- worker.postMessage({ buffer, meta: transferableSnapshot(world).meta })
629
+ worker.postMessage(transferableSnapshot(world))
624
630
  ```
625
631
 
626
632
  ### Worker thread
627
633
 
634
+ `adoptSnapshot` is imported from the **`aiecsjs/worker`** sub-path (it is not exported from the root entry).
635
+
628
636
  ```ts
629
637
  // sim-worker.ts
630
- import { adoptSnapshot, defineComponent, defineQuery, forEachEntityIndexed, Types } from 'aiecsjs'
638
+ import { defineComponent, defineQuery, forEachEntityIndexed, Types } from 'aiecsjs'
639
+ import { adoptSnapshot } from 'aiecsjs/worker'
631
640
 
632
641
  const Position = defineComponent({ x: Types.f32, y: Types.f32 })
633
642
  const Velocity = defineComponent({ x: Types.f32, y: Types.f32 })
@@ -952,7 +961,7 @@ A: It will be on first stable publish. Until then, the docs are the contract.
952
961
  - **AoS components** not SAB-shareable across workers.
953
962
  - **Network delta serializer** wire format is experimental in 0.1; may change.
954
963
  - **WebGPU integration is one-way** (CPU → GPU). No compute-shader system generation.
955
- - **Limited dev-mode validation.** Production builds skip invariant checks for speed; dev builds (`process.env.NODE_ENV !== 'production'`) include argument-order and entity-existence checks.
964
+ - **No build-mode-gated validation.** There is no `process.env.NODE_ENV` branching — the same checks run in every build. Entity-liveness is validated unconditionally on the mutation paths (`addComponent` / `setComponent` throw on a dead entity), so there is no separate "dev build" with extra runtime guards. Argument order is enforced statically by the TypeScript types, not by a runtime check.
956
965
 
957
966
  ## Contributing
958
967
 
@@ -978,6 +987,32 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
978
987
 
979
988
  ## [Unreleased]
980
989
 
990
+ ## [0.5.7] - 2026-06-10
991
+
992
+ ### Fixed
993
+
994
+ - **In-loop `destroyEntity` no longer hands the callback the reserved eid 0** — `forEachEntity` / `forEachEntityIndexed` re-read `arch.size` every iteration instead of caching it, so the swap-pop of the visited row can't walk into zeroed tail slots. The README Quick Start pattern (destroy inside the loop) is now exercised verbatim by the integration suite, which had been rewritten to defer destroys and thereby masked the defect. Semantics pinned by tests: the callback never sees eid 0 or a dead eid; a survivor swapped into the destroyed row is visited on the *next* pass; destroy+create interleaving never exposes eid 0. (Review wave 2026-06-10, ECS-B-01.)
995
+ - **Hostile snapshot `capacity` is clamped** — `fromJSON` / `deserializeWorld` cap the restored world's initial capacity at the actual entity count (floor 1024), so a ~100-byte payload with an inflated `capacity` can no longer force a ~590 MB TypedArray allocation (browser-tab OOM DoS) through the documented localStorage/network restore flows. Restored worlds still grow on demand; no data is lost. (ECS-S-01.)
996
+ - `TransferableSnapshot.buffer` is typed `SharedArrayBuffer | ArrayBuffer` (the non-SAB fallback returns a plain `ArrayBuffer`; the old type lied via a cast). (ECS-B-04.)
997
+
998
+ ### Added
999
+
1000
+ - `EcsError` — named error class for core invariant failures; the seven bare `Error` throws in `world.ts` now use it (messages unchanged, `aiecsjs:` prefix retained). Exported from the root; recorded in `STABILITY.md` / `api.json`. (FAM-C-04.)
1001
+ - `homepage` / `bugs` package metadata (islumina org), matching the rest of the family. (FAM-C-13.)
1002
+
1003
+ ### Changed
1004
+
1005
+ - `tsconfig`: `exactOptionalPropertyTypes` and `verbatimModuleSyntax` enabled (both compile clean); the coverage-threshold rationale in `vitest.config.ts` now states the actual flag set and records the measured cost (30 errors) of the four still-disabled strict flags as the deferral basis. (FAM-B-05.)
1006
+ - Dead `attachState` scaffolding removed from `commands.ts`. (ECS-C-02.)
1007
+ - Supply-chain and release hardening: CI/publish actions SHA-pinned, npm CLI pinned (`11.16.0`), tag↔package.json version guard, `npm publish --ignore-scripts` (gates run as explicit steps), workflow_dispatch input added (defaults to dry-run; manual dispatch previously performed a real publish), job timeouts, new `verify:docs` banner gate that also locks `src/version.ts` to `package.json`.
1008
+
1009
+ ### Docs
1010
+
1011
+ - **Multi-threading Guide rewritten to the working handoff** (`transferableSnapshot(world)` + `aiecsjs/worker` adoption — the old guide passed a dead `createWorld({ buffer })` option and imported a non-existent root export; a test now exercises the documented postMessage shape). `WorldOptions.buffer` is marked reserved/unimplemented. (ECS-B-02.)
1012
+ - README over-claims corrected: no NODE_ENV-gated dev checks exist; `forEach*` column parameters are `any[]`, not inferred tuples. (ECS-B-03.)
1013
+ - `enterQuery` / `exitQuery` must-drain contract documented (unbounded by design; read every view you create). (ECS-R-01.)
1014
+ - Status banner switched to the exact-version family format (EN + ZHTW); golden rule rewritten — in-loop destroy is safe, add/removeComponent still goes through the command buffer.
1015
+
981
1016
  ### Planned
982
1017
 
983
1018
  - Add `pipeAsync` for async system composition.
@@ -1370,6 +1405,7 @@ The **root** entry (`aiecsjs`) is the stable core: world, entity, component, que
1370
1405
  | `deref` | stable | 0.3.0 | Returns null for stale / cross-world refs; never throws. |
1371
1406
  | `aliveRef` | stable | 0.3.0 | Boolean guard form of `deref`; never throws. |
1372
1407
  | `EntityRef` (type) | stable | 0.3.0 | In-memory only; not serializable. |
1408
+ | `EcsError` | stable | 0.5.6 | Base error for core invariant failures (bad world options, destroyed/unknown world, exhausted component slots, capacity overflow). `instanceof`-catchable; `aiecsjs:`-prefixed message. |
1373
1409
  | `EntityNotAliveError` | stable | 0.3.0 | Thrown only by `refOf`. |
1374
1410
  | `defineComponent` | stable | 0.1.0 | |
1375
1411
  | `defineTag` | stable | 0.1.0 | |
@@ -1393,6 +1429,8 @@ The **root** entry (`aiecsjs`) is the stable core: world, entity, component, que
1393
1429
  | `isWorld` | stable | 0.1.0 | |
1394
1430
  | `isEntity` | stable | 0.1.0 | |
1395
1431
 
1432
+ **Reactive query must-drain contract (`enterQuery` / `exitQuery`).** The enter and exit buffers are **unbounded** — there is no cap and no drop-oldest policy. Each structural change that flips an entity into (enter) or out of (exit) a query pushes exactly one id; the buffer shrinks only when the reactive view is read (`runQuery`, `iterQuery`, `forEachEntity`, `forEachEntityIndexed`). A view that is created but never read — a disabled system, or reading only one of the enter/exit pair — accumulates one number per matching event for the lifetime of the world, which is an unbounded memory leak under churn. **Read every reactive view you create, once per frame.** Capping is deliberately omitted: silently dropping ids would break enter/exit symmetry, so draining is the caller's contract, not the library's.
1433
+
1396
1434
  ### `aiecsjs/loop` (utility sub-path)
1397
1435
 
1398
1436
  Fixed-timestep accumulator loop. Drop this sub-path if you already drive frame updates yourself (PixiJS `Ticker`, requestAnimationFrame, server-side simulation).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiecsjs",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "TypeScript-first archetype ECS with TypedArray SoA, SAB-ready snapshot transport, and AI-readable docs.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -59,10 +59,14 @@
59
59
  },
60
60
  "license": "MIT",
61
61
  "author": "yshengliao",
62
+ "homepage": "https://github.com/islumina/aiecsjs#readme",
62
63
  "repository": {
63
64
  "type": "git",
64
65
  "url": "git+https://github.com/islumina/aiecsjs.git"
65
66
  },
67
+ "bugs": {
68
+ "url": "https://github.com/islumina/aiecsjs/issues"
69
+ },
66
70
  "keywords": [
67
71
  "ecs",
68
72
  "entity-component-system",
@@ -80,13 +84,14 @@
80
84
  "typecheck": "tsc --noEmit",
81
85
  "lint": "biome check src tests",
82
86
  "format": "biome format --write src tests",
87
+ "verify:docs": "node scripts/verify-docs.mjs",
83
88
  "verify:exports": "node scripts/verify-exports.mjs",
84
89
  "verify:dist": "node scripts/check-dist-subpaths.mjs",
85
90
  "build:llms": "node scripts/build-llms-full.mjs",
86
91
  "verify:llms": "node scripts/build-llms-full.mjs --check",
87
92
  "check:size": "node scripts/check-size.mjs",
88
93
  "coverage": "vitest run --coverage",
89
- "prepublishOnly": "pnpm typecheck && pnpm lint && pnpm coverage && pnpm build && pnpm verify:dist && pnpm verify:exports && pnpm verify:llms && pnpm check:size"
94
+ "prepublishOnly": "pnpm typecheck && pnpm lint && pnpm verify:docs && pnpm coverage && pnpm build && pnpm verify:dist && pnpm verify:exports && pnpm verify:llms && pnpm check:size"
90
95
  },
91
96
  "devDependencies": {
92
97
  "@biomejs/biome": "^1.9.4",
@@ -1,2 +0,0 @@
1
- 'use strict';var chunkL3H4HVUJ_cjs=require('./chunk-L3H4HVUJ.cjs');var p="AIEC",B=1;function P(n,o){let e=D(n);return o?.components&&(e.entities=e.entities.map(t=>({eid:t.eid,components:t.components.filter(i=>o.components.some(c=>c.__id===i.id))}))),x(e)}function Q(n,o){let e=N(n,o);return J(e)}function D(n){let o=chunkL3H4HVUJ_cjs.i(n),e=[];for(let t=1;t<o.capacity;t++){let i=o.entityArchetype[t]??0;if(i===0)continue;let c=o.archetypes[i];if(!c)continue;let r=o.generations[t]??0,a=chunkL3H4HVUJ_cjs.r(t,r,o.options);if(!c.entityRow.has(a))continue;let s=o.options.maskWordCount,f=t*s,l=[];for(let d=0;d<s;d++){let m=o.entityMask[f+d]??0;for(;m!==0;){let b=m&-m,A=(d<<5)+(31-Math.clz32(b)),h=o.componentInfoByBit[A];if(h){let S=o.componentStorageByBit[A],w=null;if(h.kind==="soa"&&S?.soa){let y={};for(let u of h.fields){let g=S.soa[u.name];if(g)if(u.vectorLen===1)y[u.name]=g[t];else {let _=[],M=t*u.vectorLen;for(let C=0;C<u.vectorLen;C++)_.push(g[M+C]??0);y[u.name]=_;}}w=y;}else h.kind==="aos"&&S?.aos?w=S.aos[t]??null:w=true;l.push({kind:h.kind,id:h.id,data:w});}m&=m-1;}}e.push({eid:t,components:l});}return {version:o.version,capacity:o.capacity,entities:e}}function J(n){let o=chunkL3H4HVUJ_cjs.k({initialCapacity:n.capacity}),e=new Map;for(let t of n.entities){let i=chunkL3H4HVUJ_cjs.u(o);e.set(t.eid,i);}for(let t of n.entities){let i=e.get(t.eid);for(let c of t.components){let r=chunkL3H4HVUJ_cjs.Q(c.id);if(!r)continue;let a=v(r);a&&chunkL3H4HVUJ_cjs.J(o,i,a,c.data);}}return o}function v(n){if(n.kind==="soa")return {__kind:"soa",__id:n.id,__schema:n.schema??{}};if(n.kind==="aos"){let o=n.factory??(()=>({}));return {__kind:"aos",__id:n.id,__factory:o}}return {__kind:"tag",__id:n.id}}function x(n){let o=JSON.stringify(n),e=new TextEncoder().encode(o),t=new TextEncoder().encode(chunkL3H4HVUJ_cjs.a),c=12+t.length+4+e.length,r=new Uint8Array(c),a=new DataView(r.buffer),s=0;return r[s++]=p.charCodeAt(0),r[s++]=p.charCodeAt(1),r[s++]=p.charCodeAt(2),r[s++]=p.charCodeAt(3),a.setUint32(s,B,true),s+=4,a.setUint32(s,t.length,true),s+=4,r.set(t,s),s+=t.length,a.setUint32(s,e.length,true),s+=4,r.set(e,s),r}function N(n,o){if(n.length<12)throw new Error("aiecsjs: bytes too short to be a valid snapshot");let e=new DataView(n.buffer,n.byteOffset,n.byteLength);if(n[0]!==p.charCodeAt(0)||n[1]!==p.charCodeAt(1)||n[2]!==p.charCodeAt(2)||n[3]!==p.charCodeAt(3))throw new Error("aiecsjs: invalid magic bytes");let t=4,i=e.getUint32(t,true);t+=4;let c=o?.onUnknownVersion??"throw";if(i!==B&&c==="throw")throw new Error(`aiecsjs: format version ${i} not supported`);let r=64*1024*1024;if(t+4>n.length)throw new Error("aiecsjs: snapshot truncated before verLen");let a=e.getUint32(t,true);if(t+=4,a>r||t+a>n.length)throw new Error(`aiecsjs: snapshot verLen=${a} out of bounds`);if(t+=a,t+4>n.length)throw new Error("aiecsjs: snapshot truncated before jsonLen");let s=e.getUint32(t,true);if(t+=4,s>r||t+s>n.length)throw new Error(`aiecsjs: snapshot jsonLen=${s} out of bounds`);let f=n.subarray(t,t+s),l=new TextDecoder().decode(f);try{return JSON.parse(l)}catch(d){throw new Error("aiecsjs: snapshot body is not valid JSON",{cause:d})}}function Y(n,o){let e={world:n,components:o?.components??chunkL3H4HVUJ_cjs.R().map(t=>v(t)).filter(Boolean),lastSnapshot:null};return {capture(){let t=D(e.world),i;return e.lastSnapshot?i=V(e.lastSnapshot,t):i=t,e.lastSnapshot=t,x(i)},apply(t,i){let c=N(i),r=chunkL3H4HVUJ_cjs.i(t);for(let a of c.entities){if(a.eid<=0||a.eid>=r.options.maxEntities)continue;let s=chunkL3H4HVUJ_cjs.v(r,a.eid);for(let f of a.components){let l=chunkL3H4HVUJ_cjs.Q(f.id);if(!l)continue;let d=v(l);d&&chunkL3H4HVUJ_cjs.J(t,s,d,f.data);}}},reset(){e.lastSnapshot=null;}}}function V(n,o){let e=new Map(n.entities.map(i=>[i.eid,i])),t=[];for(let i of o.entities){let c=e.get(i.eid);if(!c){t.push(i);continue}let r=JSON.stringify(c.components),a=JSON.stringify(i.components);r!==a&&t.push(i);}return {version:o.version,capacity:o.capacity,entities:t}}exports.a=P;exports.b=Q;exports.c=D;exports.d=J;exports.e=Y;//# sourceMappingURL=chunk-FZ5DSL4K.cjs.map
2
- //# sourceMappingURL=chunk-FZ5DSL4K.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/serialize.ts"],"names":["MAGIC","FORMAT_VERSION","serializeWorld","world","options","snapshot","toJSON","e","c","comp","packBinary","deserializeWorld","bytes","unpackBinary","fromJSON","state","getWorldState","entities","idx","archId","arch","gen","packedEid","packEid","w","base","components","wi","word","lsb","bit","info","storage","data","obj","f","col","arr","baseI","i","createWorld","eidMap","eid","createEntity","getComponentByInternalId","handle","getComponentHandle","addComponent","factory","json","jsonBytes","versionBytes","VERSION","total","out","view","off","formatVersion","onUnknown","MAX_FIELD_LEN","verLen","jsonLen","cause","createDeltaSerializer","listAllComponents","current","delta","computeDelta","targetWorld","deltaBytes","targetState","ensureEntityAtSlot","prev","curr","prevByEid","changed","prevE","prevSig","currSig"],"mappings":"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","file":"chunk-FZ5DSL4K.cjs","sourcesContent":["// aiecsjs/serialize — binary, JSON, and delta serializers.\n\nimport { testBit } from './internal/bitmask.js'\nimport {\n addComponent,\n defineComponent,\n defineObjectComponent,\n defineTag,\n getComponentByInternalId,\n getComponentInfo,\n listAllComponents,\n} from './internal/component.js'\nimport { createEntity, destroyEntity, ensureEntityAtSlot, packEid } from './internal/entity.js'\nimport type {\n ComponentInfo,\n ComponentLike,\n DeltaSerializer,\n DeserializeOptions,\n EntityId,\n SerializeOptions,\n World,\n WorldSnapshot,\n} from './internal/types.js'\nimport { createWorld, getWorldState } from './internal/world.js'\nimport { VERSION } from './version.js'\n\nconst MAGIC = 'AIEC'\nconst FORMAT_VERSION = 1\n\nexport function serializeWorld(world: World, options?: SerializeOptions): Uint8Array {\n const snapshot = toJSON(world)\n if (options?.components) {\n snapshot.entities = snapshot.entities.map((e) => ({\n eid: e.eid,\n components: e.components.filter((c) =>\n options.components!.some((comp) => comp.__id === c.id),\n ),\n }))\n }\n return packBinary(snapshot)\n}\n\nexport function deserializeWorld(bytes: Uint8Array, options?: DeserializeOptions): World {\n const snapshot = unpackBinary(bytes, options)\n return fromJSON(snapshot)\n}\n\n/**\n * Serialize a world snapshot to a plain JSON-compatible object.\n *\n * Note: `EntityRef` is in-memory only — not preserved across serialize/deserialize.\n * Generation counters reset on world load. Stale refs from before serialization\n * will deref to null after loading the snapshot into a new world.\n */\nexport function toJSON(world: World): WorldSnapshot {\n const state = getWorldState(world)\n const entities: WorldSnapshot['entities'] = []\n // Iterate by raw slot index; snapshot stores raw idx in the `eid` field\n // (wire format unchanged — idx is used for load-side entity re-creation).\n for (let idx = 1; idx < state.capacity; idx++) {\n const archId = state.entityArchetype[idx] ?? 0\n if (archId === 0) continue // slot unused\n\n const arch = state.archetypes[archId]\n if (!arch) continue\n\n // Build the packed eid from idx + current generation to do alive check\n const gen = state.generations[idx] ?? 0\n const packedEid = packEid(idx, gen, state.options)\n if (!arch.entityRow.has(packedEid)) continue\n\n const w = state.options.maskWordCount\n const base = idx * w\n const components: WorldSnapshot['entities'][0]['components'] = []\n for (let wi = 0; wi < w; wi++) {\n let word = state.entityMask[base + wi] ?? 0\n while (word !== 0) {\n const lsb = word & -word\n const bit = (wi << 5) + (31 - Math.clz32(lsb))\n const info = state.componentInfoByBit[bit]\n if (info) {\n const storage = state.componentStorageByBit[bit]\n let data: unknown = null\n if (info.kind === 'soa' && storage?.soa) {\n const obj: Record<string, unknown> = {}\n for (const f of info.fields) {\n const col = storage.soa[f.name]\n if (!col) continue\n if (f.vectorLen === 1) {\n obj[f.name] = col[idx]\n } else {\n const arr: number[] = []\n const baseI = idx * f.vectorLen\n for (let i = 0; i < f.vectorLen; i++) arr.push(col[baseI + i] ?? 0)\n obj[f.name] = arr\n }\n }\n data = obj\n } else if (info.kind === 'aos' && storage?.aos) {\n data = storage.aos[idx] ?? null\n } else {\n data = true\n }\n components.push({ kind: info.kind, id: info.id, data })\n }\n word &= word - 1\n }\n }\n // Wire format stores raw idx (not packed) for cross-session portability\n entities.push({ eid: idx, components })\n }\n return {\n version: state.version,\n capacity: state.capacity,\n entities,\n }\n}\n\nexport function fromJSON(snapshot: WorldSnapshot): World {\n const world = createWorld({ initialCapacity: snapshot.capacity })\n const eidMap = new Map<number, EntityId>()\n for (const e of snapshot.entities) {\n const eid = createEntity(world)\n eidMap.set(e.eid, eid)\n }\n for (const e of snapshot.entities) {\n const eid = eidMap.get(e.eid)!\n for (const comp of e.components) {\n const info = getComponentByInternalId(comp.id)\n if (!info) {\n // Component missing — silently skip; future version may throw based on options\n continue\n }\n const handle = getComponentHandle(info)\n if (handle) {\n addComponent(world, eid, handle, comp.data as any)\n }\n }\n }\n return world\n}\n\n// Reconstruct a component handle from its ComponentInfo. Since defineComponent\n// returns plain handles { __kind, __id, __schema }, we can synthesize them.\nfunction getComponentHandle(info: ComponentInfo): ComponentLike | null {\n if (info.kind === 'soa') {\n return { __kind: 'soa', __id: info.id, __schema: info.schema ?? {} } as ComponentLike\n }\n if (info.kind === 'aos') {\n const factory = info.factory ?? (() => ({}))\n return { __kind: 'aos', __id: info.id, __factory: factory } as ComponentLike\n }\n return { __kind: 'tag', __id: info.id } as ComponentLike\n}\n\n// --- Binary packing (wrapped JSON for 0.1) ---\n\nfunction packBinary(snapshot: WorldSnapshot): Uint8Array {\n const json = JSON.stringify(snapshot)\n const jsonBytes = new TextEncoder().encode(json)\n const versionBytes = new TextEncoder().encode(VERSION)\n const headerSize = 4 + 4 + 4 + versionBytes.length + 4\n const total = headerSize + jsonBytes.length\n const out = new Uint8Array(total)\n const view = new DataView(out.buffer)\n let off = 0\n out[off++] = MAGIC.charCodeAt(0)\n out[off++] = MAGIC.charCodeAt(1)\n out[off++] = MAGIC.charCodeAt(2)\n out[off++] = MAGIC.charCodeAt(3)\n view.setUint32(off, FORMAT_VERSION, true)\n off += 4\n view.setUint32(off, versionBytes.length, true)\n off += 4\n out.set(versionBytes, off)\n off += versionBytes.length\n view.setUint32(off, jsonBytes.length, true)\n off += 4\n out.set(jsonBytes, off)\n return out\n}\n\nfunction unpackBinary(bytes: Uint8Array, options?: DeserializeOptions): WorldSnapshot {\n if (bytes.length < 12) throw new Error('aiecsjs: bytes too short to be a valid snapshot')\n const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)\n if (\n bytes[0] !== MAGIC.charCodeAt(0) ||\n bytes[1] !== MAGIC.charCodeAt(1) ||\n bytes[2] !== MAGIC.charCodeAt(2) ||\n bytes[3] !== MAGIC.charCodeAt(3)\n ) {\n throw new Error('aiecsjs: invalid magic bytes')\n }\n let off = 4\n const formatVersion = view.getUint32(off, true)\n off += 4\n const onUnknown = options?.onUnknownVersion ?? 'throw'\n if (formatVersion !== FORMAT_VERSION && onUnknown === 'throw') {\n throw new Error(`aiecsjs: format version ${formatVersion} not supported`)\n }\n\n // SECURITY: explicit bounds checks on every length field. DataView itself\n // throws on out-of-range reads, but an attacker who controls the bytes can\n // still craft a `verLen` that skips past data of their choosing — we make\n // each step's invariant explicit and impose a sane cap to short-circuit\n // pathological payloads early.\n const MAX_FIELD_LEN = 64 * 1024 * 1024 // 64 MiB\n if (off + 4 > bytes.length) {\n throw new Error('aiecsjs: snapshot truncated before verLen')\n }\n const verLen = view.getUint32(off, true)\n off += 4\n if (verLen > MAX_FIELD_LEN || off + verLen > bytes.length) {\n throw new Error(`aiecsjs: snapshot verLen=${verLen} out of bounds`)\n }\n off += verLen // skip the aiecsjs version string\n\n if (off + 4 > bytes.length) {\n throw new Error('aiecsjs: snapshot truncated before jsonLen')\n }\n const jsonLen = view.getUint32(off, true)\n off += 4\n if (jsonLen > MAX_FIELD_LEN || off + jsonLen > bytes.length) {\n throw new Error(`aiecsjs: snapshot jsonLen=${jsonLen} out of bounds`)\n }\n const jsonBytes = bytes.subarray(off, off + jsonLen)\n const json = new TextDecoder().decode(jsonBytes)\n // Wrap the parse so a malformed body (e.g. a truncated/garbled payload reached\n // under onUnknownVersion:'best-effort') surfaces a namespaced `aiecsjs:` error\n // instead of leaking a raw SyntaxError — consistent with the rest of the module.\n // Bounds checks above already guarantee `json` covers exactly the declared body.\n try {\n return JSON.parse(json) as WorldSnapshot\n } catch (cause) {\n throw new Error('aiecsjs: snapshot body is not valid JSON', { cause })\n }\n}\n\n// --- Delta serializer ---\n\ninterface DeltaState {\n world: World\n components: ComponentLike[]\n lastSnapshot: WorldSnapshot | null\n}\n\nexport function createDeltaSerializer(world: World, options?: SerializeOptions): DeltaSerializer {\n const state: DeltaState = {\n world,\n components:\n options?.components ??\n listAllComponents()\n .map((i) => getComponentHandle(i)!)\n .filter(Boolean),\n lastSnapshot: null,\n }\n return {\n capture(): Uint8Array {\n const current = toJSON(state.world)\n let delta: WorldSnapshot\n if (!state.lastSnapshot) {\n delta = current\n } else {\n // Compute simple delta: entities with changed components\n delta = computeDelta(state.lastSnapshot, current)\n }\n state.lastSnapshot = current\n return packBinary(delta)\n },\n apply(targetWorld: World, deltaBytes: Uint8Array): void {\n const snapshot = unpackBinary(deltaBytes)\n // Materialise each snapshot entity at the SAME slot index the source used\n // (the wire stores raw slot indices in `eid`). ensureEntityAtSlot reuses a\n // live slot, reclaims a freed one, or advances the frontier — so apply() is\n // sound on a non-pristine / independently-churned replica (it uses the\n // slot's CURRENT generation, never gen=0) and never spawns phantom padding\n // entities for holes in the source's slot range.\n //\n // CAVEAT: deltas carry added/changed entities only (see computeDelta) —\n // entity REMOVALS are not represented, so a replica is not pruned when the\n // source destroys an entity, and a component removed on the source is not\n // removed on the replica. apply() is additive/updating.\n const targetState = getWorldState(targetWorld)\n for (const e of snapshot.entities) {\n if (e.eid <= 0 || e.eid >= targetState.options.maxEntities) continue\n const eid = ensureEntityAtSlot(targetState, e.eid)\n for (const comp of e.components) {\n const info = getComponentByInternalId(comp.id)\n if (!info) continue\n const handle = getComponentHandle(info)\n if (handle) addComponent(targetWorld, eid, handle, comp.data as any)\n }\n }\n },\n reset(): void {\n state.lastSnapshot = null\n },\n }\n}\n\nfunction computeDelta(prev: WorldSnapshot, curr: WorldSnapshot): WorldSnapshot {\n const prevByEid = new Map(prev.entities.map((e) => [e.eid, e]))\n const changed: WorldSnapshot['entities'] = []\n for (const e of curr.entities) {\n const prevE = prevByEid.get(e.eid)\n if (!prevE) {\n changed.push(e)\n continue\n }\n const prevSig = JSON.stringify(prevE.components)\n const currSig = JSON.stringify(e.components)\n if (prevSig !== currSig) changed.push(e)\n }\n return { version: curr.version, capacity: curr.capacity, entities: changed }\n}\n"]}