aiquadtreejs 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  Part of the [ai\*js micro-runtime ecosystem](https://github.com/yshengliao) — see also [aifsmjs](https://github.com/yshengliao/aifsmjs) (FSM), [aiecsjs](https://github.com/yshengliao/aiecsjs) (ECS), [aibridgejs](https://github.com/yshengliao/aibridgejs) (cross-context RPC), [aieventjs](https://github.com/yshengliao/aieventjs) (event emitter), [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), and [aiaudiojs](https://github.com/yshengliao/aiaudiojs) (Web Audio shell).
12
12
 
13
- > **Status: 0.1.0 published.** Full implementation shipped: `createQuadtree`, `insert` / `retrieve` / `clear` / `dispose`, Set-based dedup, ≥95% coverage, ≤2 KB gzip.
13
+ > **Status: 0.3.0 published.** Adds `retrieveInto(region, target)` zero-allocation broadphase + property-based dedup invariants. ≥95% coverage, ≤2 KB gzip.
14
14
 
15
15
  ---
16
16
 
@@ -100,6 +100,7 @@ interface QuadtreeOptions {
100
100
  interface Quadtree<T extends AABB> {
101
101
  insert(obj: T): void;
102
102
  retrieve(region: AABB): T[];
103
+ retrieveInto(region: AABB, target: T[]): T[]; // ← new in 0.3.0
103
104
  clear(): void;
104
105
  dispose(): void;
105
106
  readonly disposed: boolean;
@@ -120,8 +121,8 @@ Full JSDoc lives in [`src/index.ts`](src/index.ts).
120
121
  | Version | Adds |
121
122
  | ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
122
123
  | **0.1.0** | `createQuadtree`, `insert` / `retrieve` / `clear` / `dispose`, Set-based dedup, ≥95% coverage, ≤2 KB gzip. |
123
- | **0.2.0** | Opt-in `retrieveInto(target: T[])` — write into a caller-supplied array to avoid allocating a fresh result every frame. |
124
- | **0.3+** | Evaluate zero-copy interop with `aiecsjs` queries (consume entity ID columns as `Uint32Array` views without an intermediate AABB obj). |
124
+ | **0.3.0** | `retrieveInto(region, target)` zero-alloc API; property-based tests (`fast-check`); STABILITY.md tracking. |
125
+ | **0.6+** | Evaluate 3D octree variant (`createOctree<T extends AABB3>`); see `STABILITY.md` for current draft. |
125
126
 
126
127
  ---
127
128
 
package/README_ZHTW.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  隸屬 [ai\*js micro-runtime 生態系](https://github.com/yshengliao) ─ 另見 [aifsmjs](https://github.com/yshengliao/aifsmjs)(FSM)、[aiecsjs](https://github.com/yshengliao/aiecsjs)(ECS)、[aibridgejs](https://github.com/yshengliao/aibridgejs)(cross-context RPC)、[aieventjs](https://github.com/yshengliao/aieventjs)(event emitter)、[aipooljs](https://github.com/yshengliao/aipooljs)(物件池)、[aiaudiojs](https://github.com/yshengliao/aiaudiojs)(Web Audio 薄殼)。
12
12
 
13
- > **狀態:0.0.1 scaffold。** 下方 API surface 已凍結;實作在 0.1.0 落地。目前 `createQuadtree` 被呼叫會直接 `throw "not implemented"`。
13
+ > **狀態:0.3.0 已發佈。** 新增 `retrieveInto(region, target)` 零分配 broadphase 與 property-based 去重不變式驗證。≥95% coverage,≤2 KB gzip。
14
14
 
15
15
  ---
16
16
 
@@ -100,6 +100,7 @@ interface QuadtreeOptions {
100
100
  interface Quadtree<T extends AABB> {
101
101
  insert(obj: T): void;
102
102
  retrieve(region: AABB): T[];
103
+ retrieveInto(region: AABB, target: T[]): T[]; // ← 0.3.0 新增
103
104
  clear(): void;
104
105
  dispose(): void;
105
106
  readonly disposed: boolean;
@@ -120,8 +121,8 @@ function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T>;
120
121
  | 版本 | 加入內容 |
121
122
  | ---------- | --------------------------------------------------------------------------------------------------------------------------------------- |
122
123
  | **0.1.0** | `createQuadtree`、`insert` / `retrieve` / `clear` / `dispose`、Set 去重、≥95% coverage、≤2 KB gzip。 |
123
- | **0.2.0** | opt-in `retrieveInto(target: T[])` ── 寫進呼叫方傳入的 array,省掉每 frame alloc 新 array。 |
124
- | **0.3+** | 評估與 `aiecsjs` query 的 zero-copy 介接(消費 entity ID 的 `Uint32Array` view,不必經 AABB plain object)。 |
124
+ | **0.3.0** | `retrieveInto(region, target)` 零分配 API;property-based 測試(`fast-check`);`STABILITY.md` API 穩定度追蹤。 |
125
+ | **0.6+** | 評估 3D octree 變體(`createOctree<T extends AABB3>`);現有草稿見 `STABILITY.md`。 |
125
126
 
126
127
  ---
127
128
 
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var c=class extends Error{name="QuadtreeError"},a=class extends Error{name="QuadtreeDisposedError"};function x(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function v(e,t){let i=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,n=t.x<i,o=t.width===0?t.x>=i:t.x+t.width>i,d=t.y<s,u=t.height===0?t.y>=s:t.y+t.height>s,r=[];return d&&n&&r.push(0),d&&o&&r.push(1),u&&n&&r.push(2),u&&o&&r.push(3),r}function g(e){let t=e.bounds.width/2,i=e.bounds.height/2,s=e.bounds.x,n=e.bounds.y,o=e.level+1;e.children.push({bounds:{x:s,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s,y:n+i,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n+i,width:t,height:i},level:o,objects:[],children:[]});for(let d of e.objects)for(let u of v(e,d)){let r=e.children[u];r!==void 0&&r.objects.push(d);}e.objects.length=0;}function A(e,t,i,s){if(!(e.level===0&&!x(e.bounds,t))){if(e.children.length===4){for(let n of v(e,t)){let o=e.children[n];o!==void 0&&A(o,t,i,s);}return}e.objects.push(t),e.objects.length>i&&e.level<s&&g(e);}}function B(e){e.objects.length=0;for(let t of e.children)B(t);e.children.length=0;}function w(e){let{bounds:t}=e,i=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new c("bounds must contain finite numbers");if(t.width<=0)throw new c("bounds.width must be > 0");if(t.height<=0)throw new c("bounds.height must be > 0");if(!Number.isInteger(i)||i<=0)throw new c("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new c("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:i,maxLevels:s,disposed:false};function o(){if(n.disposed)throw new a("aiquadtreejs: quadtree has been disposed")}function d(l){o(),A(n.root,l,n.maxObjects,n.maxLevels);}function u(l){o();let m=new Set,b=[n.root];for(;b.length>0;){let h=b.pop();if(h!==void 0&&x(h.bounds,l)){for(let f of h.objects)m.add(f);for(let f of h.children)b.push(f);}}return Array.from(m)}function r(){o(),B(n.root);}function p(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0);}return {insert:d,retrieve:u,clear:r,dispose:p,get disposed(){return n.disposed}}}exports.QuadtreeDisposedError=a;exports.QuadtreeError=c;exports.createQuadtree=w;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var h=class extends Error{name="QuadtreeError"},m=class extends Error{name="QuadtreeDisposedError"};function x(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function A(e,t){let i=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,n=t.x<i,o=t.width===0?t.x>=i:t.x+t.width>i,d=t.y<s,c=t.height===0?t.y>=s:t.y+t.height>s,r=[];return d&&n&&r.push(0),d&&o&&r.push(1),c&&n&&r.push(2),c&&o&&r.push(3),r}function w(e){let t=e.bounds.width/2,i=e.bounds.height/2,s=e.bounds.x,n=e.bounds.y,o=e.level+1;e.children.push({bounds:{x:s,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s,y:n+i,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n+i,width:t,height:i},level:o,objects:[],children:[]});for(let d of e.objects)for(let c of A(e,d)){let r=e.children[c];r!==void 0&&r.objects.push(d);}e.objects.length=0;}function B(e,t,i,s){if(!(e.level===0&&!x(e.bounds,t))){if(e.children.length===4){for(let n of A(e,t)){let o=e.children[n];o!==void 0&&B(o,t,i,s);}return}e.objects.push(t),e.objects.length>i&&e.level<s&&w(e);}}function v(e){e.objects.length=0;for(let t of e.children)v(t);e.children.length=0;}function y(e){let{bounds:t}=e,i=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new h("bounds must contain finite numbers");if(t.width<=0)throw new h("bounds.width must be > 0");if(t.height<=0)throw new h("bounds.height must be > 0");if(!Number.isInteger(i)||i<=0)throw new h("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new h("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:i,maxLevels:s,disposed:false};function o(){if(n.disposed)throw new m("aiquadtreejs: quadtree has been disposed")}function d(u){o(),B(n.root,u,n.maxObjects,n.maxLevels);}function c(u){let l=new Set,b=[n.root];for(;b.length>0;){let f=b.pop();if(f!==void 0&&x(f.bounds,u)){for(let a of f.objects)l.add(a);for(let a of f.children)b.push(a);}}return l}function r(u){return o(),Array.from(c(u))}function p(u,l){o();let b=c(u);l.length=0;for(let f of b)l.push(f);return l}function g(){o(),v(n.root);}function T(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0);}return {insert:d,retrieve:r,retrieveInto:p,clear:g,dispose:T,get disposed(){return n.disposed}}}exports.QuadtreeDisposedError=m;exports.QuadtreeError=h;exports.createQuadtree=y;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","retrieve","region","stack","clear","dispose"],"mappings":"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","file":"index.cjs","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// v0.1.0: full implementation. Plain-object nodes, iterative-DFS retrieve,\n// Set-based dedup, idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Axis-aligned bounding box.\n *\n * Coordinate semantics follow PixiJS `getBounds()`: `x` / `y` are the top-left\n * corner and `x + width` / `y + height` are **exclusive**. A 32×32 sprite at\n * `(0, 0)` covers pixels `[0, 32)` on both axes.\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n */\n retrieve(region: AABB): T[];\n\n /**\n * Reset every node back to empty. Internal node objects are reused\n * across frames so per-frame rebuild does not pressure the GC.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * Subsequent `insert` / `retrieve` / `clear` throw {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — currently unused at the public surface, but\n * reserved for future precondition violations (e.g. an inserted object with\n * `NaN` coordinates or negative `width`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n if (node.level === 0 && !rectsOverlap(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n const result = new Set<T>();\n const stack: Node<T>[] = [state.root];\n while (stack.length > 0) {\n const node = stack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, region)) continue;\n for (const obj of node.objects) {\n result.add(obj);\n }\n for (const child of node.children) {\n stack.push(child);\n }\n }\n return Array.from(result);\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n }\n\n return {\n insert,\n retrieve,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","retrieveSet","region","stack","retrieve","retrieveInto","target","set","v","clear","dispose"],"mappings":"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","file":"index.cjs","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// v0.1.0: full implementation. Plain-object nodes, iterative-DFS retrieve,\n// Set-based dedup, idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Axis-aligned bounding box.\n *\n * Coordinate semantics follow PixiJS `getBounds()`: `x` / `y` are the top-left\n * corner and `x + width` / `y + height` are **exclusive**. A 32×32 sprite at\n * `(0, 0)` covers pixels `[0, 32)` on both axes.\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n */\n retrieve(region: AABB): T[];\n\n /**\n * Zero-allocation variant of {@link retrieve}.\n *\n * Clears `target` (sets `target.length = 0`), walks the tree using the same\n * iterative DFS + Set-based dedup as {@link retrieve}, then writes every\n * deduplicated candidate into `target` and returns it.\n *\n * Designed for hot-path callers (per-frame broadphase queries in a game\n * loop) that hold a permanent `T[]` buffer and want to avoid allocating a\n * fresh result array on every call.\n *\n * @invariant `target` identity is preserved — only its contents are\n * replaced. `retrieveInto(r, buf) === buf` always holds.\n * @invariant After return, `target.length` equals the deduplicated\n * candidate count. No `undefined` / `null` holes.\n * @invariant Empty result set → `target.length === 0`.\n * @invariant Dedup semantics identical to {@link retrieve}: objects\n * spanning multiple quadrants appear exactly once.\n *\n * Note: `retrieveInto` is not strictly zero-allocation — the internal\n * dedup `Set<T>` and DFS stack are still allocated per call. What it\n * eliminates is the result `Array` allocation, which is the largest\n * frame-to-frame churn item.\n */\n retrieveInto(region: AABB, target: T[]): T[];\n\n /**\n * Reset the tree to empty. The root node object is reused across\n * frames; child nodes are released on clear() and re-created next\n * time subdivision triggers. The per-frame churn is bounded by\n * `4 * (subdivided-internal-node-count)` and stays well inside V8's\n * young-generation budget for typical game-loop usage.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * Subsequent `insert` / `retrieve` / `clear` throw {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — currently unused at the public surface, but\n * reserved for future precondition violations (e.g. an inserted object with\n * `NaN` coordinates or negative `width`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n if (node.level === 0 && !rectsOverlap(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n function retrieveSet(region: AABB): Set<T> {\n const result = new Set<T>();\n const stack: Node<T>[] = [state.root];\n while (stack.length > 0) {\n const node = stack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, region)) continue;\n for (const obj of node.objects) result.add(obj);\n for (const child of node.children) stack.push(child);\n }\n return result;\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\n const set = retrieveSet(region);\n target.length = 0;\n for (const v of set) target.push(v);\n return target;\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n }\n\n return {\n insert,\n retrieve,\n retrieveInto,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -62,8 +62,36 @@ interface Quadtree<T extends AABB> {
62
62
  */
63
63
  retrieve(region: AABB): T[];
64
64
  /**
65
- * Reset every node back to empty. Internal node objects are reused
66
- * across frames so per-frame rebuild does not pressure the GC.
65
+ * Zero-allocation variant of {@link retrieve}.
66
+ *
67
+ * Clears `target` (sets `target.length = 0`), walks the tree using the same
68
+ * iterative DFS + Set-based dedup as {@link retrieve}, then writes every
69
+ * deduplicated candidate into `target` and returns it.
70
+ *
71
+ * Designed for hot-path callers (per-frame broadphase queries in a game
72
+ * loop) that hold a permanent `T[]` buffer and want to avoid allocating a
73
+ * fresh result array on every call.
74
+ *
75
+ * @invariant `target` identity is preserved — only its contents are
76
+ * replaced. `retrieveInto(r, buf) === buf` always holds.
77
+ * @invariant After return, `target.length` equals the deduplicated
78
+ * candidate count. No `undefined` / `null` holes.
79
+ * @invariant Empty result set → `target.length === 0`.
80
+ * @invariant Dedup semantics identical to {@link retrieve}: objects
81
+ * spanning multiple quadrants appear exactly once.
82
+ *
83
+ * Note: `retrieveInto` is not strictly zero-allocation — the internal
84
+ * dedup `Set<T>` and DFS stack are still allocated per call. What it
85
+ * eliminates is the result `Array` allocation, which is the largest
86
+ * frame-to-frame churn item.
87
+ */
88
+ retrieveInto(region: AABB, target: T[]): T[];
89
+ /**
90
+ * Reset the tree to empty. The root node object is reused across
91
+ * frames; child nodes are released on clear() and re-created next
92
+ * time subdivision triggers. The per-frame churn is bounded by
93
+ * `4 * (subdivided-internal-node-count)` and stays well inside V8's
94
+ * young-generation budget for typical game-loop usage.
67
95
  */
68
96
  clear(): void;
69
97
  /**
package/dist/index.d.ts CHANGED
@@ -62,8 +62,36 @@ interface Quadtree<T extends AABB> {
62
62
  */
63
63
  retrieve(region: AABB): T[];
64
64
  /**
65
- * Reset every node back to empty. Internal node objects are reused
66
- * across frames so per-frame rebuild does not pressure the GC.
65
+ * Zero-allocation variant of {@link retrieve}.
66
+ *
67
+ * Clears `target` (sets `target.length = 0`), walks the tree using the same
68
+ * iterative DFS + Set-based dedup as {@link retrieve}, then writes every
69
+ * deduplicated candidate into `target` and returns it.
70
+ *
71
+ * Designed for hot-path callers (per-frame broadphase queries in a game
72
+ * loop) that hold a permanent `T[]` buffer and want to avoid allocating a
73
+ * fresh result array on every call.
74
+ *
75
+ * @invariant `target` identity is preserved — only its contents are
76
+ * replaced. `retrieveInto(r, buf) === buf` always holds.
77
+ * @invariant After return, `target.length` equals the deduplicated
78
+ * candidate count. No `undefined` / `null` holes.
79
+ * @invariant Empty result set → `target.length === 0`.
80
+ * @invariant Dedup semantics identical to {@link retrieve}: objects
81
+ * spanning multiple quadrants appear exactly once.
82
+ *
83
+ * Note: `retrieveInto` is not strictly zero-allocation — the internal
84
+ * dedup `Set<T>` and DFS stack are still allocated per call. What it
85
+ * eliminates is the result `Array` allocation, which is the largest
86
+ * frame-to-frame churn item.
87
+ */
88
+ retrieveInto(region: AABB, target: T[]): T[];
89
+ /**
90
+ * Reset the tree to empty. The root node object is reused across
91
+ * frames; child nodes are released on clear() and re-created next
92
+ * time subdivision triggers. The per-frame churn is bounded by
93
+ * `4 * (subdivided-internal-node-count)` and stays well inside V8's
94
+ * young-generation budget for typical game-loop usage.
67
95
  */
68
96
  clear(): void;
69
97
  /**
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var c=class extends Error{name="QuadtreeError"},a=class extends Error{name="QuadtreeDisposedError"};function x(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function v(e,t){let i=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,n=t.x<i,o=t.width===0?t.x>=i:t.x+t.width>i,d=t.y<s,u=t.height===0?t.y>=s:t.y+t.height>s,r=[];return d&&n&&r.push(0),d&&o&&r.push(1),u&&n&&r.push(2),u&&o&&r.push(3),r}function g(e){let t=e.bounds.width/2,i=e.bounds.height/2,s=e.bounds.x,n=e.bounds.y,o=e.level+1;e.children.push({bounds:{x:s,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s,y:n+i,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n+i,width:t,height:i},level:o,objects:[],children:[]});for(let d of e.objects)for(let u of v(e,d)){let r=e.children[u];r!==void 0&&r.objects.push(d);}e.objects.length=0;}function A(e,t,i,s){if(!(e.level===0&&!x(e.bounds,t))){if(e.children.length===4){for(let n of v(e,t)){let o=e.children[n];o!==void 0&&A(o,t,i,s);}return}e.objects.push(t),e.objects.length>i&&e.level<s&&g(e);}}function B(e){e.objects.length=0;for(let t of e.children)B(t);e.children.length=0;}function w(e){let{bounds:t}=e,i=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new c("bounds must contain finite numbers");if(t.width<=0)throw new c("bounds.width must be > 0");if(t.height<=0)throw new c("bounds.height must be > 0");if(!Number.isInteger(i)||i<=0)throw new c("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new c("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:i,maxLevels:s,disposed:false};function o(){if(n.disposed)throw new a("aiquadtreejs: quadtree has been disposed")}function d(l){o(),A(n.root,l,n.maxObjects,n.maxLevels);}function u(l){o();let m=new Set,b=[n.root];for(;b.length>0;){let h=b.pop();if(h!==void 0&&x(h.bounds,l)){for(let f of h.objects)m.add(f);for(let f of h.children)b.push(f);}}return Array.from(m)}function r(){o(),B(n.root);}function p(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0);}return {insert:d,retrieve:u,clear:r,dispose:p,get disposed(){return n.disposed}}}export{a as QuadtreeDisposedError,c as QuadtreeError,w as createQuadtree};//# sourceMappingURL=index.js.map
1
+ var h=class extends Error{name="QuadtreeError"},m=class extends Error{name="QuadtreeDisposedError"};function x(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function A(e,t){let i=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,n=t.x<i,o=t.width===0?t.x>=i:t.x+t.width>i,d=t.y<s,c=t.height===0?t.y>=s:t.y+t.height>s,r=[];return d&&n&&r.push(0),d&&o&&r.push(1),c&&n&&r.push(2),c&&o&&r.push(3),r}function w(e){let t=e.bounds.width/2,i=e.bounds.height/2,s=e.bounds.x,n=e.bounds.y,o=e.level+1;e.children.push({bounds:{x:s,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s,y:n+i,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n+i,width:t,height:i},level:o,objects:[],children:[]});for(let d of e.objects)for(let c of A(e,d)){let r=e.children[c];r!==void 0&&r.objects.push(d);}e.objects.length=0;}function B(e,t,i,s){if(!(e.level===0&&!x(e.bounds,t))){if(e.children.length===4){for(let n of A(e,t)){let o=e.children[n];o!==void 0&&B(o,t,i,s);}return}e.objects.push(t),e.objects.length>i&&e.level<s&&w(e);}}function v(e){e.objects.length=0;for(let t of e.children)v(t);e.children.length=0;}function y(e){let{bounds:t}=e,i=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new h("bounds must contain finite numbers");if(t.width<=0)throw new h("bounds.width must be > 0");if(t.height<=0)throw new h("bounds.height must be > 0");if(!Number.isInteger(i)||i<=0)throw new h("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new h("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:i,maxLevels:s,disposed:false};function o(){if(n.disposed)throw new m("aiquadtreejs: quadtree has been disposed")}function d(u){o(),B(n.root,u,n.maxObjects,n.maxLevels);}function c(u){let l=new Set,b=[n.root];for(;b.length>0;){let f=b.pop();if(f!==void 0&&x(f.bounds,u)){for(let a of f.objects)l.add(a);for(let a of f.children)b.push(a);}}return l}function r(u){return o(),Array.from(c(u))}function p(u,l){o();let b=c(u);l.length=0;for(let f of b)l.push(f);return l}function g(){o(),v(n.root);}function T(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0);}return {insert:d,retrieve:r,retrieveInto:p,clear:g,dispose:T,get disposed(){return n.disposed}}}export{m as QuadtreeDisposedError,h as QuadtreeError,y as createQuadtree};//# sourceMappingURL=index.js.map
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","retrieve","region","stack","clear","dispose"],"mappings":"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","file":"index.js","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// v0.1.0: full implementation. Plain-object nodes, iterative-DFS retrieve,\n// Set-based dedup, idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Axis-aligned bounding box.\n *\n * Coordinate semantics follow PixiJS `getBounds()`: `x` / `y` are the top-left\n * corner and `x + width` / `y + height` are **exclusive**. A 32×32 sprite at\n * `(0, 0)` covers pixels `[0, 32)` on both axes.\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n */\n retrieve(region: AABB): T[];\n\n /**\n * Reset every node back to empty. Internal node objects are reused\n * across frames so per-frame rebuild does not pressure the GC.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * Subsequent `insert` / `retrieve` / `clear` throw {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — currently unused at the public surface, but\n * reserved for future precondition violations (e.g. an inserted object with\n * `NaN` coordinates or negative `width`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n if (node.level === 0 && !rectsOverlap(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n const result = new Set<T>();\n const stack: Node<T>[] = [state.root];\n while (stack.length > 0) {\n const node = stack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, region)) continue;\n for (const obj of node.objects) {\n result.add(obj);\n }\n for (const child of node.children) {\n stack.push(child);\n }\n }\n return Array.from(result);\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n }\n\n return {\n insert,\n retrieve,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","retrieveSet","region","stack","retrieve","retrieveInto","target","set","v","clear","dispose"],"mappings":"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","file":"index.js","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// v0.1.0: full implementation. Plain-object nodes, iterative-DFS retrieve,\n// Set-based dedup, idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Axis-aligned bounding box.\n *\n * Coordinate semantics follow PixiJS `getBounds()`: `x` / `y` are the top-left\n * corner and `x + width` / `y + height` are **exclusive**. A 32×32 sprite at\n * `(0, 0)` covers pixels `[0, 32)` on both axes.\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n */\n retrieve(region: AABB): T[];\n\n /**\n * Zero-allocation variant of {@link retrieve}.\n *\n * Clears `target` (sets `target.length = 0`), walks the tree using the same\n * iterative DFS + Set-based dedup as {@link retrieve}, then writes every\n * deduplicated candidate into `target` and returns it.\n *\n * Designed for hot-path callers (per-frame broadphase queries in a game\n * loop) that hold a permanent `T[]` buffer and want to avoid allocating a\n * fresh result array on every call.\n *\n * @invariant `target` identity is preserved — only its contents are\n * replaced. `retrieveInto(r, buf) === buf` always holds.\n * @invariant After return, `target.length` equals the deduplicated\n * candidate count. No `undefined` / `null` holes.\n * @invariant Empty result set → `target.length === 0`.\n * @invariant Dedup semantics identical to {@link retrieve}: objects\n * spanning multiple quadrants appear exactly once.\n *\n * Note: `retrieveInto` is not strictly zero-allocation — the internal\n * dedup `Set<T>` and DFS stack are still allocated per call. What it\n * eliminates is the result `Array` allocation, which is the largest\n * frame-to-frame churn item.\n */\n retrieveInto(region: AABB, target: T[]): T[];\n\n /**\n * Reset the tree to empty. The root node object is reused across\n * frames; child nodes are released on clear() and re-created next\n * time subdivision triggers. The per-frame churn is bounded by\n * `4 * (subdivided-internal-node-count)` and stays well inside V8's\n * young-generation budget for typical game-loop usage.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * Subsequent `insert` / `retrieve` / `clear` throw {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — currently unused at the public surface, but\n * reserved for future precondition violations (e.g. an inserted object with\n * `NaN` coordinates or negative `width`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n if (node.level === 0 && !rectsOverlap(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n function retrieveSet(region: AABB): Set<T> {\n const result = new Set<T>();\n const stack: Node<T>[] = [state.root];\n while (stack.length > 0) {\n const node = stack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, region)) continue;\n for (const obj of node.objects) result.add(obj);\n for (const child of node.children) stack.push(child);\n }\n return result;\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\n const set = retrieveSet(region);\n target.length = 0;\n for (const v of set) target.push(v);\n return target;\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n }\n\n return {\n insert,\n retrieve,\n retrieveInto,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
package/llms-full.txt CHANGED
@@ -23,7 +23,7 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
23
23
 
24
24
  Part of the [ai\*js micro-runtime ecosystem](https://github.com/yshengliao) — see also [aifsmjs](https://github.com/yshengliao/aifsmjs) (FSM), [aiecsjs](https://github.com/yshengliao/aiecsjs) (ECS), [aibridgejs](https://github.com/yshengliao/aibridgejs) (cross-context RPC), [aieventjs](https://github.com/yshengliao/aieventjs) (event emitter), [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), and [aiaudiojs](https://github.com/yshengliao/aiaudiojs) (Web Audio shell).
25
25
 
26
- > **Status: 0.1.0 published.** Full implementation shipped: `createQuadtree`, `insert` / `retrieve` / `clear` / `dispose`, Set-based dedup, ≥95% coverage, ≤2 KB gzip.
26
+ > **Status: 0.3.0 published.** Adds `retrieveInto(region, target)` zero-allocation broadphase + property-based dedup invariants. ≥95% coverage, ≤2 KB gzip.
27
27
 
28
28
  ---
29
29
 
@@ -113,6 +113,7 @@ interface QuadtreeOptions {
113
113
  interface Quadtree<T extends AABB> {
114
114
  insert(obj: T): void;
115
115
  retrieve(region: AABB): T[];
116
+ retrieveInto(region: AABB, target: T[]): T[]; // ← new in 0.3.0
116
117
  clear(): void;
117
118
  dispose(): void;
118
119
  readonly disposed: boolean;
@@ -133,8 +134,8 @@ Full JSDoc lives in [`src/index.ts`](src/index.ts).
133
134
  | Version | Adds |
134
135
  | ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
135
136
  | **0.1.0** | `createQuadtree`, `insert` / `retrieve` / `clear` / `dispose`, Set-based dedup, ≥95% coverage, ≤2 KB gzip. |
136
- | **0.2.0** | Opt-in `retrieveInto(target: T[])` — write into a caller-supplied array to avoid allocating a fresh result every frame. |
137
- | **0.3+** | Evaluate zero-copy interop with `aiecsjs` queries (consume entity ID columns as `Uint32Array` views without an intermediate AABB obj). |
137
+ | **0.3.0** | `retrieveInto(region, target)` zero-alloc API; property-based tests (`fast-check`); STABILITY.md tracking. |
138
+ | **0.6+** | Evaluate 3D octree variant (`createOctree<T extends AABB3>`); see `STABILITY.md` for current draft. |
138
139
 
139
140
  ---
140
141
 
@@ -154,6 +155,42 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
154
155
 
155
156
  ## [Unreleased]
156
157
 
158
+ ## [0.3.0] - 2026-05-29
159
+
160
+ ### Added
161
+
162
+ - **`retrieveInto(region: AABB, target: T[]): T[]`** — zero-allocation
163
+ variant of `retrieve` for hot-path callers. Clears `target`, walks
164
+ the same iterative DFS + Set dedup as `retrieve`, writes results
165
+ into `target`, returns `target`. The returned reference equals the
166
+ argument — callers can hold a permanent buffer and pass it every
167
+ frame to eliminate the result-array allocation churn (5,000+ calls
168
+ per frame in typical bullet-hell broadphase loops).
169
+ - **`STABILITY.md`** — explicit stable vs experimental API tracking.
170
+ Includes a v0.6+ 3D octree draft (no source code in this release).
171
+ - **Property-based tests** via `fast-check` — 4 invariants covering
172
+ `retrieve` dedup and `retrieveInto` identity / length / content
173
+ equivalence. Adds `fast-check` to `devDependencies`.
174
+
175
+ ### Changed
176
+
177
+ - **`Quadtree.clear` JSDoc** — corrected the claim that "internal
178
+ node objects are reused across frames"; only the root node is
179
+ reused, child nodes are released on `clear()` and re-created when
180
+ subdivision next triggers. No runtime behaviour change.
181
+ - **README Roadmap / Status / API sketch** — synced to v0.3.0,
182
+ including the `retrieveInto(region, target)` signature (the
183
+ pre-0.2 Roadmap entry was a single-argument draft).
184
+
185
+ ### Notes
186
+
187
+ - `retrieve` behaviour is byte-for-byte identical to 0.1.1: the
188
+ internal refactor extracts a shared `retrieveSet` helper that both
189
+ `retrieve` and `retrieveInto` call, but `Set` insertion order →
190
+ `Array.from` order is preserved by spec.
191
+ - Bundle size: ≤ 2 KB gzip budget still ~50% headroom after this
192
+ release (expected ~1050-1090 B gzip).
193
+
157
194
  ## [0.1.1] - 2026-05-28
158
195
 
159
196
  ### Changed (CI)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiquadtreejs",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Tiny 2D quadtree for per-frame rebuild collision broadphase. Insert AABBs, retrieve candidates, clear. Caller does precise hit-testing. Designed for PixiJS games with 500–10,000 active entities.",
5
5
  "keywords": [
6
6
  "quadtree",
@@ -62,6 +62,7 @@
62
62
  "@biomejs/biome": "^1.9.0",
63
63
  "@types/node": "^22.0.0",
64
64
  "@vitest/coverage-v8": "^4.1.7",
65
+ "fast-check": "^3.23.0",
65
66
  "tsup": "^8.3.0",
66
67
  "tsx": "^4.22.3",
67
68
  "typescript": "^5.6.0",