aiquadtreejs 0.5.5 → 0.5.6

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
@@ -1,16 +1,16 @@
1
1
  # aiquadtreejs
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/aiquadtreejs.svg)](https://www.npmjs.com/package/aiquadtreejs)
4
- [![CI](https://github.com/yshengliao/aiquadtreejs/actions/workflows/ci.yml/badge.svg)](https://github.com/yshengliao/aiquadtreejs/actions/workflows/ci.yml)
4
+ [![CI](https://github.com/islumina/aiquadtreejs/actions/workflows/ci.yml/badge.svg)](https://github.com/islumina/aiquadtreejs/actions/workflows/ci.yml)
5
5
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
6
6
  [![AI Generated](https://img.shields.io/badge/AI_Generated-Claude_Code_Opus_4.7_Max-blueviolet.svg)](https://www.anthropic.com/claude-code)
7
7
  [![繁體中文](https://img.shields.io/badge/lang-繁體中文-red.svg)](README_ZHTW.md)
8
8
 
9
9
  > A 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.
10
10
 
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).
11
+ Part of the [ai\*js micro-runtime ecosystem](https://github.com/islumina) — see also [aifsmjs](https://github.com/islumina/aifsmjs) (FSM), [aiecsjs](https://github.com/islumina/aiecsjs) (ECS), [aibridgejs](https://github.com/islumina/aibridgejs) (cross-context RPC), [aieventjs](https://github.com/islumina/aieventjs) (event emitter), [aipooljs](https://github.com/islumina/aipooljs) (object pool), and [aiaudiojs](https://github.com/islumina/aiaudiojs) (Web Audio shell).
12
12
 
13
- > **Status: 0.5.1 published.** `insert()` now validates object geometry (non-finite coords, negative dimensions throw `QuadtreeError`); 22 new tests (J/K groups). `retrieveInto(region, target)` is a steady-state zero-allocation broadphase (reused internal scratch + caller buffer); property-based dedup invariants. ≥95% coverage, ≤2 KB gzip.
13
+ > **Status: 0.5.6.** `insert()` and `retrieve()` / `retrieveInto()` validate geometry (non-finite coords or negative dimensions throw `QuadtreeError`). `retrieveInto(region, target)` is a steady-state zero-allocation broadphase (reused internal scratch + caller buffer); property-based dedup invariants. ≥95% coverage, ≤2 KB gzip.
14
14
 
15
15
  ---
16
16
 
@@ -27,7 +27,7 @@ Naïve pairwise collision detection on `N` entities is `O(N²)`. At 1,000 entiti
27
27
 
28
28
  Why not just import `@timohausmann/quadtree-ts`? That library is solid and you should use it for stand-alone work. `aiquadtreejs` exists so that an ai*js stack can talk to entity IDs from `aiecsjs` without per-frame object adaptation — `insert({ id: eid, x, y, width, height })` lines up with the SoA columns you already maintain.
29
29
 
30
- > `aiquadtreejs` is one of the four 0.3-cycle siblings joining the family — alongside [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), `aieventjs` (typed events; self-built, not a `mitt` fork), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
30
+ > `aiquadtreejs` is one of the four 0.3-cycle siblings joining the family — alongside [aipooljs](https://github.com/islumina/aipooljs) (object pool), `aieventjs` (typed events; self-built, not a `mitt` fork), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
31
31
 
32
32
  ---
33
33
 
@@ -94,7 +94,9 @@ interface AABB {
94
94
  interface QuadtreeOptions {
95
95
  bounds: AABB;
96
96
  maxObjects?: number; // default 10
97
- maxLevels?: number; // default 4
97
+ maxLevels?: number; // default 4 — spanning objects replicate into every
98
+ // overlapping child; cost is ~4^L nodes in the worst
99
+ // case. Raise with caution (16 → ~4 B nodes).
98
100
  }
99
101
 
100
102
  interface Quadtree<T extends AABB> {
package/README_ZHTW.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # aiquadtreejs
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/aiquadtreejs.svg)](https://www.npmjs.com/package/aiquadtreejs)
4
- [![CI](https://github.com/yshengliao/aiquadtreejs/actions/workflows/ci.yml/badge.svg)](https://github.com/yshengliao/aiquadtreejs/actions/workflows/ci.yml)
4
+ [![CI](https://github.com/islumina/aiquadtreejs/actions/workflows/ci.yml/badge.svg)](https://github.com/islumina/aiquadtreejs/actions/workflows/ci.yml)
5
5
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
6
6
  [![AI Generated](https://img.shields.io/badge/AI_Generated-Claude_Code_Opus_4.7_Max-blueviolet.svg)](https://www.anthropic.com/claude-code)
7
7
  [![English](https://img.shields.io/badge/lang-English-blue.svg)](README.md)
8
8
 
9
9
  > 一個小型 2D quadtree,給 per-frame rebuild 的碰撞 broadphase 使用。`insert` AABB、`retrieve` 候選、`clear`,精確碰撞測試由呼叫方負責。瞄準 PixiJS 遊戲 500–10,000 個 active entity 的場景。
10
10
 
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 薄殼)。
11
+ 隸屬 [ai\*js micro-runtime 生態系](https://github.com/islumina) ─ 另見 [aifsmjs](https://github.com/islumina/aifsmjs)(FSM)、[aiecsjs](https://github.com/islumina/aiecsjs)(ECS)、[aibridgejs](https://github.com/islumina/aibridgejs)(cross-context RPC)、[aieventjs](https://github.com/islumina/aieventjs)(event emitter)、[aipooljs](https://github.com/islumina/aipooljs)(物件池)、[aiaudiojs](https://github.com/islumina/aiaudiojs)(Web Audio 薄殼)。
12
12
 
13
- > **狀態:0.5.1 已發佈。** `insert()` 現在驗證物件幾何(非 finite 座標或負維度拋出 `QuadtreeError`);22 項新測試(J/K 組)。`retrieveInto(region, target)` 為 steady-state 零分配 broadphase(內部 scratch 重用 + caller buffer)與 property-based 去重不變式。≥95% coverage,≤2 KB gzip。
13
+ > **狀態:0.5.6。** `insert()` 與 `retrieve()` / `retrieveInto()` 驗證幾何(非 finite 座標或負維度拋出 `QuadtreeError`)。`retrieveInto(region, target)` 為 steady-state 零分配 broadphase(內部 scratch 重用 + caller buffer)與 property-based 去重不變式。≥95% coverage,≤2 KB gzip。
14
14
 
15
15
  ---
16
16
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  那為什麼不直接用 `@timohausmann/quadtree-ts`?它做得很好,獨立場景直接用沒問題。`aiquadtreejs` 存在的理由是讓 ai*js stack 能直接接 `aiecsjs` 的 entity ID,不必每 frame 再轉一次物件 ── `insert({ id: eid, x, y, width, height })` 直接對齊你已經維護的 SoA 欄位。
29
29
 
30
- > `aiquadtreejs` 是 v0.3 cycle 四個新加入兄弟套件之一 ── 另外三個是 [aipooljs](https://github.com/yshengliao/aipooljs)(物件池)、`aieventjs`(typed event;**自寫不 fork mitt**)、`aiaudiojs`(Web Audio 薄殼,底層用 Howler.js 作 `peerDependency`)。
30
+ > `aiquadtreejs` 是 v0.3 cycle 四個新加入兄弟套件之一 ── 另外三個是 [aipooljs](https://github.com/islumina/aipooljs)(物件池)、`aieventjs`(typed event;**自寫不 fork mitt**)、`aiaudiojs`(Web Audio 薄殼,底層用 Howler.js 作 `peerDependency`)。
31
31
 
32
32
  ---
33
33
 
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var h=class extends Error{name="QuadtreeError"},b=class extends Error{name="QuadtreeDisposedError"};function m(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 x(e,t){let n=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,i=t.x<n,r=t.width===0?t.x>=n:t.x+t.width>n,l=t.y<s,c=t.height===0?t.y>=s:t.y+t.height>s,o=[];return l&&i&&o.push(0),l&&r&&o.push(1),c&&i&&o.push(2),c&&r&&o.push(3),o}function y(e){let t=e.bounds.width/2,n=e.bounds.height/2,s=e.bounds.x,i=e.bounds.y,r=e.level+1;e.children.push({bounds:{x:s,y:i,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s+t,y:i,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s,y:i+n,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s+t,y:i+n,width:t,height:n},level:r,objects:[],children:[]});for(let l of e.objects)for(let c of x(e,l)){let o=e.children[c];o!==void 0&&o.objects.push(l);}e.objects.length=0;}function w(e,t,n,s){if(!(e.level===0&&!m(e.bounds,t))){if(e.children.length===4){for(let i of x(e,t)){let r=e.children[i];r!==void 0&&w(r,t,n,s);}return}e.objects.push(t),e.objects.length>n&&e.level<s&&y(e);}}function A(e){e.objects.length=0;for(let t of e.children)A(t);e.children.length=0;}function N(e){let{bounds:t}=e,n=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(n)||n<=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 i={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:n,maxLevels:s,disposed:false};function r(){if(i.disposed)throw new b("aiquadtreejs: quadtree has been disposed")}function l(d){if(r(),!d||!Number.isFinite(d.x)||!Number.isFinite(d.y)||!Number.isFinite(d.width)||!Number.isFinite(d.height))throw new h("inserted object must be defined with finite numeric x, y, width and height");if(d.width<0)throw new h("inserted object width must be >= 0");if(d.height<0)throw new h("inserted object height must be >= 0");w(i.root,d,i.maxObjects,i.maxLevels);}let c=new Set,o=[];function a(d){for(c.clear(),o.length=0,o.push(i.root);o.length>0;){let u=o.pop();if(u!==void 0&&m(u.bounds,d)){for(let f of u.objects)c.add(f);for(let f of u.children)o.push(f);}}return c}function B(d){return r(),Array.from(a(d))}function v(d,u){r();let f=a(d);u.length=0;for(let T of f)u.push(T);return u}function g(){r(),A(i.root);}function p(){i.disposed||(i.disposed=true,i.root.objects.length=0,i.root.children.length=0,c.clear(),o.length=0);}return {insert:l,retrieve:B,retrieveInto:v,clear:g,dispose:p,get disposed(){return i.disposed}}}exports.QuadtreeDisposedError=b;exports.QuadtreeError=h;exports.createQuadtree=N;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var d=class extends Error{name="QuadtreeError"},x=class extends Error{name="QuadtreeDisposedError"};function B(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 g(e,t){let s=e.bounds.x+e.bounds.width/2,o=e.bounds.y+e.bounds.height/2,n=t.x<s,h=t.width===0?t.x>=s:t.x+t.width>s,u=t.y<o,c=t.height===0?t.y>=o:t.y+t.height>o,r=[];return u&&n&&r.push(0),u&&h&&r.push(1),c&&n&&r.push(2),c&&h&&r.push(3),r}function S(e){let t=e.bounds.width/2,s=e.bounds.height/2,o=e.bounds.x,n=e.bounds.y,h=e.level+1;e.children.push({bounds:{x:o,y:n,width:t,height:s},level:h,objects:[],children:[]},{bounds:{x:o+t,y:n,width:t,height:s},level:h,objects:[],children:[]},{bounds:{x:o,y:n+s,width:t,height:s},level:h,objects:[],children:[]},{bounds:{x:o+t,y:n+s,width:t,height:s},level:h,objects:[],children:[]});for(let u of e.objects)for(let c of g(e,u)){let r=e.children[c];r!==void 0&&r.objects.push(u);}e.objects.length=0;}function p(e,t,s,o){if(!(e.level===0&&!B(e.bounds,t))){if(e.children.length===4){for(let n of g(e,t)){let h=e.children[n];h!==void 0&&p(h,t,s,o);}return}e.objects.push(t),e.objects.length>s&&e.level<o&&S(e);}}function T(e){e.objects.length=0;for(let t of e.children)T(t);e.children.length=0;}function q(e){let{bounds:t}=e,s=e.maxObjects??10,o=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new d("bounds must contain finite numbers");if(t.width<=0)throw new d("bounds.width must be > 0");if(t.height<=0)throw new d("bounds.height must be > 0");if(!Number.isInteger(s)||s<=0)throw new d("maxObjects must be a positive integer");if(!Number.isInteger(o)||o<=0)throw new d("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:s,maxLevels:o,disposed:false};function h(){if(n.disposed)throw new x("aiquadtreejs: quadtree has been disposed")}function u(i){if(h(),!i||!Number.isFinite(i.x)||!Number.isFinite(i.y)||!Number.isFinite(i.width)||!Number.isFinite(i.height))throw new d("inserted object must be defined with finite numeric x, y, width and height");if(i.width<0)throw new d("inserted object width must be >= 0");if(i.height<0)throw new d("inserted object height must be >= 0");p(n.root,i,n.maxObjects,n.maxLevels);}let c=new Set,r=[],l={x:0,y:0,width:0,height:0};function v(i){let f=i.x,a=i.y,m=i.width,O=i.height;for(l.x=f,l.y=a,l.width=m,l.height=O,c.clear(),r.length=0,r.push(n.root);r.length>0;){let b=r.pop();if(b!==void 0&&B(b.bounds,l)){for(let w of b.objects)c.add(w);for(let w of b.children)r.push(w);}}return c}function A(i){if(!i||!Number.isFinite(i.x)||!Number.isFinite(i.y)||!Number.isFinite(i.width)||!Number.isFinite(i.height))throw new d("aiquadtreejs: retrieve region must have finite numeric x, y, width and height");if(i.width<0)throw new d("aiquadtreejs: retrieve region width must be >= 0");if(i.height<0)throw new d("aiquadtreejs: retrieve region height must be >= 0")}function y(i){return h(),A(i),Array.from(v(i))}function N(i,f){h(),A(i);let a=v(i);f.length=0;for(let m of a)f.push(m);return f}function F(){h(),T(n.root),c.clear(),r.length=0;}function j(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0,c.clear(),r.length=0);}return {insert:u,retrieve:y,retrieveInto:N,clear:F,dispose:j,get disposed(){return n.disposed}}}exports.QuadtreeDisposedError=x;exports.QuadtreeError=d;exports.createQuadtree=q;//# 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","scratchSet","scratchStack","retrieveSet","region","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.5.1: 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 * Right-open coordinate semantics: `x` / `y` are the top-left corner and\n * `x + width` / `y + height` are **exclusive**. A 32×32 box at `(0, 0)`\n * covers `[0, 32)` on both axes. (This matches the convention used by\n * renderers such as PixiJS `getBounds()`, but the type is renderer-agnostic.)\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 * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`\n * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or\n * `height` is negative. Zero-extent objects (points / lines) are valid.\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 * Allocation: in steady state this performs no per-call heap allocation.\n * The dedup `Set` and DFS stack are reused across calls (cleared, not\n * re-created), and results are written into the caller's `target` instead\n * of a fresh array. The first calls may grow the internal scratch; once\n * result sizes stabilise, allocation amortises to zero — the design goal\n * for per-frame broadphase loops issuing thousands of queries.\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 * After disposal, every method except `dispose` itself — `insert`,\n * `retrieve`, `retrieveInto`, `clear` — throws {@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 — thrown by `createQuadtree` for invalid\n * construction options and by `insert()` for precondition violations\n * (e.g. an inserted object with non-finite coordinates or negative\n * `width` / `height`).\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 if (\n !obj ||\n !Number.isFinite(obj.x) ||\n !Number.isFinite(obj.y) ||\n !Number.isFinite(obj.width) ||\n !Number.isFinite(obj.height)\n ) {\n throw new QuadtreeError(\n \"inserted object must be defined with finite numeric x, y, width and height\",\n );\n }\n if (obj.width < 0) {\n throw new QuadtreeError(\"inserted object width must be >= 0\");\n }\n if (obj.height < 0) {\n throw new QuadtreeError(\"inserted object height must be >= 0\");\n }\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n // Reusable scratch for retrieveSet, hoisted so steady-state queries\n // allocate nothing. Safe because the returned Set never escapes the\n // module: retrieve copies it out via Array.from and retrieveInto via a\n // push loop, both synchronously and fully before any subsequent call.\n const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n\n function retrieveSet(region: AABB): Set<T> {\n scratchSet.clear();\n scratchStack.length = 0;\n scratchStack.push(state.root);\n while (scratchStack.length > 0) {\n const node = scratchStack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, region)) continue;\n for (const obj of node.objects) scratchSet.add(obj);\n for (const child of node.children) scratchStack.push(child);\n }\n return scratchSet;\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 scratchSet.clear();\n scratchStack.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"]}
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","scratchSet","scratchStack","scratchRegion","retrieveSet","region","rx","ry","rw","rh","validateRegion","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.5.1: 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 * Right-open coordinate semantics: `x` / `y` are the top-left corner and\n * `x + width` / `y + height` are **exclusive**. A 32×32 box at `(0, 0)`\n * covers `[0, 32)` on both axes. (This matches the convention used by\n * renderers such as PixiJS `getBounds()`, but the type is renderer-agnostic.)\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 * **Spanning-object cost warning:** an object that spans multiple quadrant\n * boundaries is copied into every child node it overlaps. In the worst case\n * (an object covering the entire tree bounds) at depth `L`, up to `4^L`\n * nodes each hold a reference to that object. The default of `4` means at\n * most 256 leaf nodes; raising `maxLevels` to `10` allows ~1 M nodes, and\n * `20` allows ~10^12 — **OOM territory for dense inputs with spanning\n * objects**. Raise this value only when you understand the distribution of\n * large vs small objects in your scene. No upper-bound cap is applied\n * (the caller knows their workload); the default `4` is safe for typical\n * game scenes with 500–10,000 entities.\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 * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`\n * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or\n * `height` is negative. Zero-extent objects (points / lines) are valid.\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 * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid (they still query any overlapping node).\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 * Allocation: in steady state this performs no per-call heap allocation.\n * The dedup `Set` and DFS stack are reused across calls (cleared, not\n * re-created), and results are written into the caller's `target` instead\n * of a fresh array. The first calls may grow the internal scratch; once\n * result sizes stabilise, allocation amortises to zero — the design goal\n * for per-frame broadphase loops issuing thousands of queries.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid.\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 * Internal scratch buffers (dedup `Set` + DFS stack) are also drained on\n * clear(), matching the GC guarantee already provided by {@link dispose}.\n * This ensures a tree held alive but not queried after clear() does not\n * retain the previous query's object references.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * After disposal, every method except `dispose` itself — `insert`,\n * `retrieve`, `retrieveInto`, `clear` — throws {@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 — thrown by `createQuadtree` for invalid\n * construction options and by `insert()` for precondition violations\n * (e.g. an inserted object with non-finite coordinates or negative\n * `width` / `height`).\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 if (\n !obj ||\n !Number.isFinite(obj.x) ||\n !Number.isFinite(obj.y) ||\n !Number.isFinite(obj.width) ||\n !Number.isFinite(obj.height)\n ) {\n throw new QuadtreeError(\n \"inserted object must be defined with finite numeric x, y, width and height\",\n );\n }\n if (obj.width < 0) {\n throw new QuadtreeError(\"inserted object width must be >= 0\");\n }\n if (obj.height < 0) {\n throw new QuadtreeError(\"inserted object height must be >= 0\");\n }\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n // Reusable scratch for retrieveSet, hoisted so steady-state queries\n // allocate nothing. Safe because the returned Set never escapes the\n // module: retrieve copies it out via Array.from and retrieveInto via a\n // push loop, both synchronously and fully before any subsequent call.\n //\n // Plain-data assumption (tightened, QDT-B-02): region.x/y/width/height\n // are read once into locals at the top of retrieveSet, then written into\n // the reusable scratchRegion (no per-query allocation — the zero-alloc\n // contract of retrieveInto holds). This prevents a structurally-typed\n // region whose getter calls back into retrieve* from corrupting the shared\n // scratch mid-walk: any re-entrant call triggered by a getter completes\n // synchronously during the four reads, before this call touches scratch.\n // Adversarial-only: plain-object callers (all documented examples) are\n // unaffected.\n const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n const scratchRegion: AABB = { x: 0, y: 0, width: 0, height: 0 };\n\n function retrieveSet(region: AABB): Set<T> {\n // Snapshot region fields into locals once so that a getter-bearing\n // region cannot mutate the walk by re-entering retrieve* mid-DFS.\n const rx = region.x;\n const ry = region.y;\n const rw = region.width;\n const rh = region.height;\n scratchRegion.x = rx;\n scratchRegion.y = ry;\n scratchRegion.width = rw;\n scratchRegion.height = rh;\n scratchSet.clear();\n scratchStack.length = 0;\n scratchStack.push(state.root);\n while (scratchStack.length > 0) {\n const node = scratchStack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, scratchRegion)) continue;\n for (const obj of node.objects) scratchSet.add(obj);\n for (const child of node.children) scratchStack.push(child);\n }\n return scratchSet;\n }\n\n /**\n * Validate a region AABB for use in retrieve / retrieveInto.\n * Mirrors insert()'s 0.5.1 validation: non-finite coordinates or negative\n * dimensions throw QuadtreeError with an `aiquadtreejs: ` prefix message.\n */\n function validateRegion(region: AABB): void {\n if (\n !region ||\n !Number.isFinite(region.x) ||\n !Number.isFinite(region.y) ||\n !Number.isFinite(region.width) ||\n !Number.isFinite(region.height)\n ) {\n throw new QuadtreeError(\n \"aiquadtreejs: retrieve region must have finite numeric x, y, width and height\",\n );\n }\n if (region.width < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region width must be >= 0\");\n }\n if (region.height < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region height must be >= 0\");\n }\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n validateRegion(region);\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\n validateRegion(region);\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 // Drain internal scratch so that a tree held alive but not queried after\n // clear() does not pin the previous query's object references against GC.\n // (dispose() drains scratch for the same reason; clear() now provides the\n // same guarantee for the per-frame rebuild pattern.)\n scratchSet.clear();\n scratchStack.length = 0;\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 scratchSet.clear();\n scratchStack.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
@@ -35,6 +35,17 @@ interface QuadtreeOptions {
35
35
  /**
36
36
  * Maximum subdivision depth. Default `4`. Caps recursion so a very dense
37
37
  * cluster doesn't blow up into an unbounded tree.
38
+ *
39
+ * **Spanning-object cost warning:** an object that spans multiple quadrant
40
+ * boundaries is copied into every child node it overlaps. In the worst case
41
+ * (an object covering the entire tree bounds) at depth `L`, up to `4^L`
42
+ * nodes each hold a reference to that object. The default of `4` means at
43
+ * most 256 leaf nodes; raising `maxLevels` to `10` allows ~1 M nodes, and
44
+ * `20` allows ~10^12 — **OOM territory for dense inputs with spanning
45
+ * objects**. Raise this value only when you understand the distribution of
46
+ * large vs small objects in your scene. No upper-bound cap is applied
47
+ * (the caller knows their workload); the default `4` is safe for typical
48
+ * game scenes with 500–10,000 entities.
38
49
  */
39
50
  maxLevels?: number;
40
51
  }
@@ -64,6 +75,11 @@ interface Quadtree<T extends AABB> {
64
75
  * Return every inserted object whose containing node overlaps `region`,
65
76
  * deduplicated. The result is a **broadphase**: callers must still run
66
77
  * a precise AABB or pixel-level hit test on each candidate.
78
+ *
79
+ * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,
80
+ * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,
81
+ * `-Infinity`), or if `region.width` or `region.height` is negative.
82
+ * Zero-extent regions are valid (they still query any overlapping node).
67
83
  */
68
84
  retrieve(region: AABB): T[];
69
85
  /**
@@ -91,6 +107,11 @@ interface Quadtree<T extends AABB> {
91
107
  * of a fresh array. The first calls may grow the internal scratch; once
92
108
  * result sizes stabilise, allocation amortises to zero — the design goal
93
109
  * for per-frame broadphase loops issuing thousands of queries.
110
+ *
111
+ * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,
112
+ * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,
113
+ * `-Infinity`), or if `region.width` or `region.height` is negative.
114
+ * Zero-extent regions are valid.
94
115
  */
95
116
  retrieveInto(region: AABB, target: T[]): T[];
96
117
  /**
@@ -99,6 +120,11 @@ interface Quadtree<T extends AABB> {
99
120
  * time subdivision triggers. The per-frame churn is bounded by
100
121
  * `4 * (subdivided-internal-node-count)` and stays well inside V8's
101
122
  * young-generation budget for typical game-loop usage.
123
+ *
124
+ * Internal scratch buffers (dedup `Set` + DFS stack) are also drained on
125
+ * clear(), matching the GC guarantee already provided by {@link dispose}.
126
+ * This ensures a tree held alive but not queried after clear() does not
127
+ * retain the previous query's object references.
102
128
  */
103
129
  clear(): void;
104
130
  /**
package/dist/index.d.ts CHANGED
@@ -35,6 +35,17 @@ interface QuadtreeOptions {
35
35
  /**
36
36
  * Maximum subdivision depth. Default `4`. Caps recursion so a very dense
37
37
  * cluster doesn't blow up into an unbounded tree.
38
+ *
39
+ * **Spanning-object cost warning:** an object that spans multiple quadrant
40
+ * boundaries is copied into every child node it overlaps. In the worst case
41
+ * (an object covering the entire tree bounds) at depth `L`, up to `4^L`
42
+ * nodes each hold a reference to that object. The default of `4` means at
43
+ * most 256 leaf nodes; raising `maxLevels` to `10` allows ~1 M nodes, and
44
+ * `20` allows ~10^12 — **OOM territory for dense inputs with spanning
45
+ * objects**. Raise this value only when you understand the distribution of
46
+ * large vs small objects in your scene. No upper-bound cap is applied
47
+ * (the caller knows their workload); the default `4` is safe for typical
48
+ * game scenes with 500–10,000 entities.
38
49
  */
39
50
  maxLevels?: number;
40
51
  }
@@ -64,6 +75,11 @@ interface Quadtree<T extends AABB> {
64
75
  * Return every inserted object whose containing node overlaps `region`,
65
76
  * deduplicated. The result is a **broadphase**: callers must still run
66
77
  * a precise AABB or pixel-level hit test on each candidate.
78
+ *
79
+ * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,
80
+ * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,
81
+ * `-Infinity`), or if `region.width` or `region.height` is negative.
82
+ * Zero-extent regions are valid (they still query any overlapping node).
67
83
  */
68
84
  retrieve(region: AABB): T[];
69
85
  /**
@@ -91,6 +107,11 @@ interface Quadtree<T extends AABB> {
91
107
  * of a fresh array. The first calls may grow the internal scratch; once
92
108
  * result sizes stabilise, allocation amortises to zero — the design goal
93
109
  * for per-frame broadphase loops issuing thousands of queries.
110
+ *
111
+ * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,
112
+ * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,
113
+ * `-Infinity`), or if `region.width` or `region.height` is negative.
114
+ * Zero-extent regions are valid.
94
115
  */
95
116
  retrieveInto(region: AABB, target: T[]): T[];
96
117
  /**
@@ -99,6 +120,11 @@ interface Quadtree<T extends AABB> {
99
120
  * time subdivision triggers. The per-frame churn is bounded by
100
121
  * `4 * (subdivided-internal-node-count)` and stays well inside V8's
101
122
  * young-generation budget for typical game-loop usage.
123
+ *
124
+ * Internal scratch buffers (dedup `Set` + DFS stack) are also drained on
125
+ * clear(), matching the GC guarantee already provided by {@link dispose}.
126
+ * This ensures a tree held alive but not queried after clear() does not
127
+ * retain the previous query's object references.
102
128
  */
103
129
  clear(): void;
104
130
  /**
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- var h=class extends Error{name="QuadtreeError"},b=class extends Error{name="QuadtreeDisposedError"};function m(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 x(e,t){let n=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,i=t.x<n,r=t.width===0?t.x>=n:t.x+t.width>n,l=t.y<s,c=t.height===0?t.y>=s:t.y+t.height>s,o=[];return l&&i&&o.push(0),l&&r&&o.push(1),c&&i&&o.push(2),c&&r&&o.push(3),o}function y(e){let t=e.bounds.width/2,n=e.bounds.height/2,s=e.bounds.x,i=e.bounds.y,r=e.level+1;e.children.push({bounds:{x:s,y:i,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s+t,y:i,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s,y:i+n,width:t,height:n},level:r,objects:[],children:[]},{bounds:{x:s+t,y:i+n,width:t,height:n},level:r,objects:[],children:[]});for(let l of e.objects)for(let c of x(e,l)){let o=e.children[c];o!==void 0&&o.objects.push(l);}e.objects.length=0;}function w(e,t,n,s){if(!(e.level===0&&!m(e.bounds,t))){if(e.children.length===4){for(let i of x(e,t)){let r=e.children[i];r!==void 0&&w(r,t,n,s);}return}e.objects.push(t),e.objects.length>n&&e.level<s&&y(e);}}function A(e){e.objects.length=0;for(let t of e.children)A(t);e.children.length=0;}function N(e){let{bounds:t}=e,n=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(n)||n<=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 i={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:n,maxLevels:s,disposed:false};function r(){if(i.disposed)throw new b("aiquadtreejs: quadtree has been disposed")}function l(d){if(r(),!d||!Number.isFinite(d.x)||!Number.isFinite(d.y)||!Number.isFinite(d.width)||!Number.isFinite(d.height))throw new h("inserted object must be defined with finite numeric x, y, width and height");if(d.width<0)throw new h("inserted object width must be >= 0");if(d.height<0)throw new h("inserted object height must be >= 0");w(i.root,d,i.maxObjects,i.maxLevels);}let c=new Set,o=[];function a(d){for(c.clear(),o.length=0,o.push(i.root);o.length>0;){let u=o.pop();if(u!==void 0&&m(u.bounds,d)){for(let f of u.objects)c.add(f);for(let f of u.children)o.push(f);}}return c}function B(d){return r(),Array.from(a(d))}function v(d,u){r();let f=a(d);u.length=0;for(let T of f)u.push(T);return u}function g(){r(),A(i.root);}function p(){i.disposed||(i.disposed=true,i.root.objects.length=0,i.root.children.length=0,c.clear(),o.length=0);}return {insert:l,retrieve:B,retrieveInto:v,clear:g,dispose:p,get disposed(){return i.disposed}}}export{b as QuadtreeDisposedError,h as QuadtreeError,N as createQuadtree};//# sourceMappingURL=index.js.map
1
+ var d=class extends Error{name="QuadtreeError"},x=class extends Error{name="QuadtreeDisposedError"};function B(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 g(e,t){let s=e.bounds.x+e.bounds.width/2,o=e.bounds.y+e.bounds.height/2,n=t.x<s,h=t.width===0?t.x>=s:t.x+t.width>s,u=t.y<o,c=t.height===0?t.y>=o:t.y+t.height>o,r=[];return u&&n&&r.push(0),u&&h&&r.push(1),c&&n&&r.push(2),c&&h&&r.push(3),r}function S(e){let t=e.bounds.width/2,s=e.bounds.height/2,o=e.bounds.x,n=e.bounds.y,h=e.level+1;e.children.push({bounds:{x:o,y:n,width:t,height:s},level:h,objects:[],children:[]},{bounds:{x:o+t,y:n,width:t,height:s},level:h,objects:[],children:[]},{bounds:{x:o,y:n+s,width:t,height:s},level:h,objects:[],children:[]},{bounds:{x:o+t,y:n+s,width:t,height:s},level:h,objects:[],children:[]});for(let u of e.objects)for(let c of g(e,u)){let r=e.children[c];r!==void 0&&r.objects.push(u);}e.objects.length=0;}function p(e,t,s,o){if(!(e.level===0&&!B(e.bounds,t))){if(e.children.length===4){for(let n of g(e,t)){let h=e.children[n];h!==void 0&&p(h,t,s,o);}return}e.objects.push(t),e.objects.length>s&&e.level<o&&S(e);}}function T(e){e.objects.length=0;for(let t of e.children)T(t);e.children.length=0;}function q(e){let{bounds:t}=e,s=e.maxObjects??10,o=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new d("bounds must contain finite numbers");if(t.width<=0)throw new d("bounds.width must be > 0");if(t.height<=0)throw new d("bounds.height must be > 0");if(!Number.isInteger(s)||s<=0)throw new d("maxObjects must be a positive integer");if(!Number.isInteger(o)||o<=0)throw new d("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:s,maxLevels:o,disposed:false};function h(){if(n.disposed)throw new x("aiquadtreejs: quadtree has been disposed")}function u(i){if(h(),!i||!Number.isFinite(i.x)||!Number.isFinite(i.y)||!Number.isFinite(i.width)||!Number.isFinite(i.height))throw new d("inserted object must be defined with finite numeric x, y, width and height");if(i.width<0)throw new d("inserted object width must be >= 0");if(i.height<0)throw new d("inserted object height must be >= 0");p(n.root,i,n.maxObjects,n.maxLevels);}let c=new Set,r=[],l={x:0,y:0,width:0,height:0};function v(i){let f=i.x,a=i.y,m=i.width,O=i.height;for(l.x=f,l.y=a,l.width=m,l.height=O,c.clear(),r.length=0,r.push(n.root);r.length>0;){let b=r.pop();if(b!==void 0&&B(b.bounds,l)){for(let w of b.objects)c.add(w);for(let w of b.children)r.push(w);}}return c}function A(i){if(!i||!Number.isFinite(i.x)||!Number.isFinite(i.y)||!Number.isFinite(i.width)||!Number.isFinite(i.height))throw new d("aiquadtreejs: retrieve region must have finite numeric x, y, width and height");if(i.width<0)throw new d("aiquadtreejs: retrieve region width must be >= 0");if(i.height<0)throw new d("aiquadtreejs: retrieve region height must be >= 0")}function y(i){return h(),A(i),Array.from(v(i))}function N(i,f){h(),A(i);let a=v(i);f.length=0;for(let m of a)f.push(m);return f}function F(){h(),T(n.root),c.clear(),r.length=0;}function j(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0,c.clear(),r.length=0);}return {insert:u,retrieve:y,retrieveInto:N,clear:F,dispose:j,get disposed(){return n.disposed}}}export{x as QuadtreeDisposedError,d as QuadtreeError,q 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","scratchSet","scratchStack","retrieveSet","region","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.5.1: 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 * Right-open coordinate semantics: `x` / `y` are the top-left corner and\n * `x + width` / `y + height` are **exclusive**. A 32×32 box at `(0, 0)`\n * covers `[0, 32)` on both axes. (This matches the convention used by\n * renderers such as PixiJS `getBounds()`, but the type is renderer-agnostic.)\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 * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`\n * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or\n * `height` is negative. Zero-extent objects (points / lines) are valid.\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 * Allocation: in steady state this performs no per-call heap allocation.\n * The dedup `Set` and DFS stack are reused across calls (cleared, not\n * re-created), and results are written into the caller's `target` instead\n * of a fresh array. The first calls may grow the internal scratch; once\n * result sizes stabilise, allocation amortises to zero — the design goal\n * for per-frame broadphase loops issuing thousands of queries.\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 * After disposal, every method except `dispose` itself — `insert`,\n * `retrieve`, `retrieveInto`, `clear` — throws {@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 — thrown by `createQuadtree` for invalid\n * construction options and by `insert()` for precondition violations\n * (e.g. an inserted object with non-finite coordinates or negative\n * `width` / `height`).\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 if (\n !obj ||\n !Number.isFinite(obj.x) ||\n !Number.isFinite(obj.y) ||\n !Number.isFinite(obj.width) ||\n !Number.isFinite(obj.height)\n ) {\n throw new QuadtreeError(\n \"inserted object must be defined with finite numeric x, y, width and height\",\n );\n }\n if (obj.width < 0) {\n throw new QuadtreeError(\"inserted object width must be >= 0\");\n }\n if (obj.height < 0) {\n throw new QuadtreeError(\"inserted object height must be >= 0\");\n }\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n // Reusable scratch for retrieveSet, hoisted so steady-state queries\n // allocate nothing. Safe because the returned Set never escapes the\n // module: retrieve copies it out via Array.from and retrieveInto via a\n // push loop, both synchronously and fully before any subsequent call.\n const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n\n function retrieveSet(region: AABB): Set<T> {\n scratchSet.clear();\n scratchStack.length = 0;\n scratchStack.push(state.root);\n while (scratchStack.length > 0) {\n const node = scratchStack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, region)) continue;\n for (const obj of node.objects) scratchSet.add(obj);\n for (const child of node.children) scratchStack.push(child);\n }\n return scratchSet;\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 scratchSet.clear();\n scratchStack.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"]}
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","scratchSet","scratchStack","scratchRegion","retrieveSet","region","rx","ry","rw","rh","validateRegion","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.5.1: 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 * Right-open coordinate semantics: `x` / `y` are the top-left corner and\n * `x + width` / `y + height` are **exclusive**. A 32×32 box at `(0, 0)`\n * covers `[0, 32)` on both axes. (This matches the convention used by\n * renderers such as PixiJS `getBounds()`, but the type is renderer-agnostic.)\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 * **Spanning-object cost warning:** an object that spans multiple quadrant\n * boundaries is copied into every child node it overlaps. In the worst case\n * (an object covering the entire tree bounds) at depth `L`, up to `4^L`\n * nodes each hold a reference to that object. The default of `4` means at\n * most 256 leaf nodes; raising `maxLevels` to `10` allows ~1 M nodes, and\n * `20` allows ~10^12 — **OOM territory for dense inputs with spanning\n * objects**. Raise this value only when you understand the distribution of\n * large vs small objects in your scene. No upper-bound cap is applied\n * (the caller knows their workload); the default `4` is safe for typical\n * game scenes with 500–10,000 entities.\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 * @throws {@link QuadtreeError} if any of `x`, `y`, `width`, or `height`\n * is non-finite (`NaN`, `Infinity`, `-Infinity`), or if `width` or\n * `height` is negative. Zero-extent objects (points / lines) are valid.\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 * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid (they still query any overlapping node).\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 * Allocation: in steady state this performs no per-call heap allocation.\n * The dedup `Set` and DFS stack are reused across calls (cleared, not\n * re-created), and results are written into the caller's `target` instead\n * of a fresh array. The first calls may grow the internal scratch; once\n * result sizes stabilise, allocation amortises to zero — the design goal\n * for per-frame broadphase loops issuing thousands of queries.\n *\n * @throws {@link QuadtreeError} if any of `region.x`, `region.y`,\n * `region.width`, or `region.height` is non-finite (`NaN`, `Infinity`,\n * `-Infinity`), or if `region.width` or `region.height` is negative.\n * Zero-extent regions are valid.\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 * Internal scratch buffers (dedup `Set` + DFS stack) are also drained on\n * clear(), matching the GC guarantee already provided by {@link dispose}.\n * This ensures a tree held alive but not queried after clear() does not\n * retain the previous query's object references.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * After disposal, every method except `dispose` itself — `insert`,\n * `retrieve`, `retrieveInto`, `clear` — throws {@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 — thrown by `createQuadtree` for invalid\n * construction options and by `insert()` for precondition violations\n * (e.g. an inserted object with non-finite coordinates or negative\n * `width` / `height`).\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 if (\n !obj ||\n !Number.isFinite(obj.x) ||\n !Number.isFinite(obj.y) ||\n !Number.isFinite(obj.width) ||\n !Number.isFinite(obj.height)\n ) {\n throw new QuadtreeError(\n \"inserted object must be defined with finite numeric x, y, width and height\",\n );\n }\n if (obj.width < 0) {\n throw new QuadtreeError(\"inserted object width must be >= 0\");\n }\n if (obj.height < 0) {\n throw new QuadtreeError(\"inserted object height must be >= 0\");\n }\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n // Reusable scratch for retrieveSet, hoisted so steady-state queries\n // allocate nothing. Safe because the returned Set never escapes the\n // module: retrieve copies it out via Array.from and retrieveInto via a\n // push loop, both synchronously and fully before any subsequent call.\n //\n // Plain-data assumption (tightened, QDT-B-02): region.x/y/width/height\n // are read once into locals at the top of retrieveSet, then written into\n // the reusable scratchRegion (no per-query allocation — the zero-alloc\n // contract of retrieveInto holds). This prevents a structurally-typed\n // region whose getter calls back into retrieve* from corrupting the shared\n // scratch mid-walk: any re-entrant call triggered by a getter completes\n // synchronously during the four reads, before this call touches scratch.\n // Adversarial-only: plain-object callers (all documented examples) are\n // unaffected.\n const scratchSet = new Set<T>();\n const scratchStack: Node<T>[] = [];\n const scratchRegion: AABB = { x: 0, y: 0, width: 0, height: 0 };\n\n function retrieveSet(region: AABB): Set<T> {\n // Snapshot region fields into locals once so that a getter-bearing\n // region cannot mutate the walk by re-entering retrieve* mid-DFS.\n const rx = region.x;\n const ry = region.y;\n const rw = region.width;\n const rh = region.height;\n scratchRegion.x = rx;\n scratchRegion.y = ry;\n scratchRegion.width = rw;\n scratchRegion.height = rh;\n scratchSet.clear();\n scratchStack.length = 0;\n scratchStack.push(state.root);\n while (scratchStack.length > 0) {\n const node = scratchStack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, scratchRegion)) continue;\n for (const obj of node.objects) scratchSet.add(obj);\n for (const child of node.children) scratchStack.push(child);\n }\n return scratchSet;\n }\n\n /**\n * Validate a region AABB for use in retrieve / retrieveInto.\n * Mirrors insert()'s 0.5.1 validation: non-finite coordinates or negative\n * dimensions throw QuadtreeError with an `aiquadtreejs: ` prefix message.\n */\n function validateRegion(region: AABB): void {\n if (\n !region ||\n !Number.isFinite(region.x) ||\n !Number.isFinite(region.y) ||\n !Number.isFinite(region.width) ||\n !Number.isFinite(region.height)\n ) {\n throw new QuadtreeError(\n \"aiquadtreejs: retrieve region must have finite numeric x, y, width and height\",\n );\n }\n if (region.width < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region width must be >= 0\");\n }\n if (region.height < 0) {\n throw new QuadtreeError(\"aiquadtreejs: retrieve region height must be >= 0\");\n }\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n validateRegion(region);\n return Array.from(retrieveSet(region));\n }\n\n function retrieveInto(region: AABB, target: T[]): T[] {\n ck();\n validateRegion(region);\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 // Drain internal scratch so that a tree held alive but not queried after\n // clear() does not pin the previous query's object references against GC.\n // (dispose() drains scratch for the same reason; clear() now provides the\n // same guarantee for the per-frame rebuild pattern.)\n scratchSet.clear();\n scratchStack.length = 0;\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 scratchSet.clear();\n scratchStack.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
@@ -14,16 +14,16 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
14
14
  # aiquadtreejs
15
15
 
16
16
  [![npm version](https://img.shields.io/npm/v/aiquadtreejs.svg)](https://www.npmjs.com/package/aiquadtreejs)
17
- [![CI](https://github.com/yshengliao/aiquadtreejs/actions/workflows/ci.yml/badge.svg)](https://github.com/yshengliao/aiquadtreejs/actions/workflows/ci.yml)
17
+ [![CI](https://github.com/islumina/aiquadtreejs/actions/workflows/ci.yml/badge.svg)](https://github.com/islumina/aiquadtreejs/actions/workflows/ci.yml)
18
18
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
19
19
  [![AI Generated](https://img.shields.io/badge/AI_Generated-Claude_Code_Opus_4.7_Max-blueviolet.svg)](https://www.anthropic.com/claude-code)
20
20
  [![繁體中文](https://img.shields.io/badge/lang-繁體中文-red.svg)](README_ZHTW.md)
21
21
 
22
22
  > A 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.
23
23
 
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).
24
+ Part of the [ai\*js micro-runtime ecosystem](https://github.com/islumina) — see also [aifsmjs](https://github.com/islumina/aifsmjs) (FSM), [aiecsjs](https://github.com/islumina/aiecsjs) (ECS), [aibridgejs](https://github.com/islumina/aibridgejs) (cross-context RPC), [aieventjs](https://github.com/islumina/aieventjs) (event emitter), [aipooljs](https://github.com/islumina/aipooljs) (object pool), and [aiaudiojs](https://github.com/islumina/aiaudiojs) (Web Audio shell).
25
25
 
26
- > **Status: 0.5.1 published.** `insert()` now validates object geometry (non-finite coords, negative dimensions throw `QuadtreeError`); 22 new tests (J/K groups). `retrieveInto(region, target)` is a steady-state zero-allocation broadphase (reused internal scratch + caller buffer); property-based dedup invariants. ≥95% coverage, ≤2 KB gzip.
26
+ > **Status: 0.5.6.** `insert()` and `retrieve()` / `retrieveInto()` validate geometry (non-finite coords or negative dimensions throw `QuadtreeError`). `retrieveInto(region, target)` is a steady-state zero-allocation broadphase (reused internal scratch + caller buffer); property-based dedup invariants. ≥95% coverage, ≤2 KB gzip.
27
27
 
28
28
  ---
29
29
 
@@ -40,7 +40,7 @@ Naïve pairwise collision detection on `N` entities is `O(N²)`. At 1,000 entiti
40
40
 
41
41
  Why not just import `@timohausmann/quadtree-ts`? That library is solid and you should use it for stand-alone work. `aiquadtreejs` exists so that an ai*js stack can talk to entity IDs from `aiecsjs` without per-frame object adaptation — `insert({ id: eid, x, y, width, height })` lines up with the SoA columns you already maintain.
42
42
 
43
- > `aiquadtreejs` is one of the four 0.3-cycle siblings joining the family — alongside [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), `aieventjs` (typed events; self-built, not a `mitt` fork), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
43
+ > `aiquadtreejs` is one of the four 0.3-cycle siblings joining the family — alongside [aipooljs](https://github.com/islumina/aipooljs) (object pool), `aieventjs` (typed events; self-built, not a `mitt` fork), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
44
44
 
45
45
  ---
46
46
 
@@ -107,7 +107,9 @@ interface AABB {
107
107
  interface QuadtreeOptions {
108
108
  bounds: AABB;
109
109
  maxObjects?: number; // default 10
110
- maxLevels?: number; // default 4
110
+ maxLevels?: number; // default 4 — spanning objects replicate into every
111
+ // overlapping child; cost is ~4^L nodes in the worst
112
+ // case. Raise with caution (16 → ~4 B nodes).
111
113
  }
112
114
 
113
115
  interface Quadtree<T extends AABB> {
@@ -157,6 +159,22 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
157
159
 
158
160
  ## [Unreleased]
159
161
 
162
+ ## [0.5.6] - 2026-06-10
163
+
164
+ ### Fixed
165
+
166
+ - **`retrieve()` / `retrieveInto()` region validation** — both now throw `QuadtreeError` on non-finite coordinates (`NaN`, `±Infinity`) or negative dimensions, mirroring the `insert()` validation shipped in 0.5.1. Previously a `NaN` region silently returned `[]` and a negative-size region produced ghost candidates. Zero-extent regions remain valid. (Review wave 2026-06-10, QDT-S-01.)
167
+ - `clear()` now drains the internal dedup scratch and DFS stack, extending the GC guarantee `dispose()` already provided: a tree held alive but not queried after `clear()` no longer pins the previous query's object references. (QDT-B-01.)
168
+ - `retrieveSet` snapshots the query region's fields once into a reusable internal scratch region, so a structurally-typed region with re-entrant getters cannot corrupt a walk in progress; steady-state queries remain zero-allocation. (QDT-B-02.)
169
+
170
+ ### Changed
171
+
172
+ - Supply-chain and release hardening: CI/publish actions SHA-pinned, npm CLI pinned (`11.16.0`) in the OIDC publish job, `permissions: contents: read` on CI, job timeouts, tag↔package.json version guard, `npm publish --ignore-scripts`, and manual publish dispatch now defaults to dry-run. New `verify:docs` gate keeps the README status banners in lockstep with `package.json`. `typecheck` now also type-checks the test suite; `llms-full.txt` embeds `STABILITY.md`.
173
+
174
+ ### Docs
175
+
176
+ - README status banners refreshed (EN + ZHTW); `maxLevels` JSDoc/README now document the ~4^L spanning-replication cost trade-off; two steady-state test assertions strengthened from lower bounds to exact counts.
177
+
160
178
  ## [0.5.5] - 2026-06-08
161
179
 
162
180
  ### Changed
@@ -346,6 +364,125 @@ No runtime / source / API changes from 0.1.0. **0.1.1 is also the first version
346
364
  accidental npm release on tag push until 0.1.0.
347
365
 
348
366
 
367
+ ---
368
+
369
+ <!-- ===== STABILITY.md ===== -->
370
+
371
+ # aiquadtreejs Stability
372
+
373
+ This document tracks which public API is **stable** (subject to
374
+ semver-major break only) vs **experimental** (subject to change without
375
+ notice). Consumers should treat anything outside the Stable section as
376
+ unfit for production reliance.
377
+
378
+ ---
379
+
380
+ ## Stable
381
+
382
+ The following are stable (since v0.3.0) and, as of **v0.4.0**, formally
383
+ **frozen for the 1.x track** — they will not break before a major version
384
+ bump (v1.0.0+).
385
+
386
+ ### Exports
387
+
388
+ - `createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T>`
389
+ - `Quadtree<T extends AABB>` — the tree interface (`insert` / `retrieve` / `retrieveInto` / `clear` / `dispose` / `disposed`)
390
+ - `AABB` — `{ x, y, width, height }`, right-open semantics
391
+ - `QuadtreeOptions` — `{ bounds, maxObjects?, maxLevels? }`
392
+ - `QuadtreeError` — thrown by validation failures in `createQuadtree` and by `insert()` for invalid object geometry (non-finite coordinates, negative width/height)
393
+ - `QuadtreeDisposedError` — thrown by any method on a disposed tree
394
+
395
+ ### `Quadtree<T>` interface
396
+
397
+ | Member | Stability | Since |
398
+ |---|---|---|
399
+ | `insert(obj)` | Stable | 0.1.0 |
400
+ | `retrieve(region)` | Stable | 0.1.0 |
401
+ | `retrieveInto(region, target)` | Stable | 0.3.0 |
402
+ | `clear()` | Stable | 0.1.0 |
403
+ | `dispose()` | Stable | 0.1.0 |
404
+ | `disposed` (readonly) | Stable | 0.1.0 |
405
+
406
+ ### Behaviour guarantees
407
+
408
+ - Dedup: `retrieve` / `retrieveInto` return each candidate exactly once
409
+ even if it spans multiple quadrants.
410
+ - Right-open AABB: `x + width` and `y + height` are exclusive
411
+ (renderer-neutral; matches common conventions such as PixiJS
412
+ `getBounds()`).
413
+ - `insert()` validates the inserted object: throws `QuadtreeError` if any
414
+ of `x`, `y`, `width`, or `height` is non-finite (`NaN`, `Infinity`,
415
+ `-Infinity`), or if `width` or `height` is negative. Zero-extent objects
416
+ (points with `width=0` and/or `height=0`) are valid and accepted.
417
+ - `retrieve()` and `retrieveInto()` validate the query region with the same
418
+ rules: non-finite coordinates or negative dimensions throw `QuadtreeError`.
419
+ Zero-extent regions are valid (they still query any overlapping node).
420
+ - `maxLevels` has no upper-bound cap. Callers that raise it above the default
421
+ of `4` should be aware that spanning objects (those overlapping multiple
422
+ quadrant boundaries) are copied into every overlapping child node;
423
+ worst-case node count is ~4^maxLevels. The default is safe for typical game
424
+ scenes with 500–10,000 entities.
425
+ - `dispose()` is idempotent; subsequent public-method calls throw
426
+ `QuadtreeDisposedError`.
427
+ - All methods destructure cleanly: `const { insert } = qt; insert(obj)`
428
+ works without `this` binding.
429
+
430
+ ---
431
+
432
+ ## Experimental / Draft
433
+
434
+ These ideas are **not implemented** and have **no source code** in the
435
+ package. They are recorded here so consumers and contributors can see
436
+ the intended direction.
437
+
438
+ ### 3D octree variant (target: v0.6+)
439
+
440
+ A 3D variant for platformer / 2.5D broadphase queries.
441
+
442
+ ```typescript
443
+ // Draft only — no implementation in v0.3.x.
444
+ interface AABB3 {
445
+ x: number; y: number; z: number;
446
+ width: number; height: number; depth: number;
447
+ }
448
+
449
+ interface Octree<T extends AABB3> {
450
+ insert(obj: T): void;
451
+ retrieve(region: AABB3): T[];
452
+ retrieveInto(region: AABB3, target: T[]): T[];
453
+ clear(): void;
454
+ dispose(): void;
455
+ readonly disposed: boolean;
456
+ }
457
+
458
+ export function createOctree<T extends AABB3>(opts: {
459
+ bounds: AABB3;
460
+ maxObjects?: number;
461
+ maxLevels?: number;
462
+ }): Octree<T>;
463
+ ```
464
+
465
+ Open questions:
466
+ - Is a 2.5D quadtree with z-binning sufficient for typical platformer
467
+ collision (likely yes)?
468
+ - Acceptable size increase for the octree path (target ≤ 1500 B gzip
469
+ additional)?
470
+
471
+ Implementation is gated on a v0.5+ game actually needing it. Until
472
+ then, consumers requiring 3D broadphase should pull a dedicated
473
+ library (e.g. `octree-ts`).
474
+
475
+ ---
476
+
477
+ ## Out of scope (will not implement)
478
+
479
+ - Move-tracking (per-frame rebuild via `clear()` + `insert()` is the
480
+ intended pattern; see README "Why aiquadtreejs").
481
+ - Precise hit-test (broadphase only; caller does narrow-phase).
482
+ - Circle / Line / polygon primitives.
483
+ - Persistence / serialisation.
484
+ - KD-tree / R-tree variants.
485
+
349
486
  ---
350
487
 
351
488
  <!-- ===== CONTRIBUTING.md ===== -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiquadtreejs",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
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",
@@ -50,13 +50,14 @@
50
50
  "test:watch": "vitest",
51
51
  "lint": "biome check src test",
52
52
  "format": "biome format --write src test",
53
- "typecheck": "tsc --noEmit",
53
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.test.json --noEmit",
54
+ "verify:docs": "node scripts/verify-docs.mjs",
54
55
  "verify:exports": "node scripts/verify-exports.mjs",
55
56
  "check:size": "node scripts/check-size.mjs",
56
57
  "build:llms": "node scripts/build-llms-full.mjs",
57
58
  "verify:llms": "node scripts/build-llms-full.mjs --check",
58
59
  "coverage": "vitest run --coverage",
59
- "prepublishOnly": "pnpm typecheck && pnpm lint && pnpm coverage && pnpm build && pnpm verify:exports && pnpm verify:llms && pnpm check:size"
60
+ "prepublishOnly": "pnpm typecheck && pnpm lint && pnpm verify:docs && pnpm coverage && pnpm build && pnpm verify:exports && pnpm verify:llms && pnpm check:size"
60
61
  },
61
62
  "devDependencies": {
62
63
  "@biomejs/biome": "^1.9.0",