aiquadtreejs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ysl
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # aiquadtreejs
2
+
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)
5
+ [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
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
+ [![繁體中文](https://img.shields.io/badge/lang-繁體中文-red.svg)](README_ZHTW.md)
8
+
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
+
11
+ Part of the [ai\*js micro-runtime ecosystem](https://github.com/yshengliao) — see also [aifsmjs](https://github.com/yshengliao/aifsmjs) (FSM), [aiecsjs](https://github.com/yshengliao/aiecsjs) (ECS), [aibridgejs](https://github.com/yshengliao/aibridgejs) (cross-context RPC), [aieventjs](https://github.com/yshengliao/aieventjs) (event emitter), [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), and [aiaudiojs](https://github.com/yshengliao/aiaudiojs) (Web Audio shell).
12
+
13
+ > **Status: 0.1.0 published.** Full implementation shipped: `createQuadtree`, `insert` / `retrieve` / `clear` / `dispose`, Set-based dedup, ≥95% coverage, ≤2 KB gzip.
14
+
15
+ ---
16
+
17
+ ## Why aiquadtreejs
18
+
19
+ Naïve pairwise collision detection on `N` entities is `O(N²)`. At 1,000 entities that's a million comparisons per frame, which at 60 Hz is already a budget killer; at 10,000 entities it's a non-starter. A quadtree replaces the dense outer loop with a spatial filter: each entity asks "who could I possibly collide with?" and the tree returns a small candidate set — sub-linear on average for well-distributed inputs (worst case is `O(N)` when every object overlaps the query region). The precise hit test then runs only on candidates, dropping the actual comparisons by one or two orders of magnitude in typical game distributions.
20
+
21
+ `aiquadtreejs` makes four deliberate trade-offs:
22
+
23
+ - **Per-frame rebuild, not move-tracking.** Tracking which leaf an entity migrated into between frames is doable but error-prone; `clear()` + re-`insert()` is faster in practice and easier to reason about. This mirrors the Kontra.js philosophy. (`clear()` resets the root node and drops the child array; subdividing fresh next frame is cheap because the inner subdivision logic touches at most `maxLevels = 4` levels.)
24
+ - **Set-based dedup on `retrieve`.** An AABB that straddles a quadrant boundary lands in multiple leaves. Without dedup the caller sees the same candidate two or four times and pays double for the precise hit test. The Set guarantees each candidate appears once.
25
+ - **2D AABB only — no 3D, no R-tree, no KD-tree, no Circle / Line primitives.** Those are real techniques for real problems but they sit in a different size class. Keep this library at ≤ 2 KB gzipped and let user-land bring in heavier broadphase when needed.
26
+ - **No precise hit-test.** Broadphase libraries that also do collision response always grow. The contract here ends at "here are the candidates"; pixel-perfect or shape-specific tests belong to whatever physics layer you already have.
27
+
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
+
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 — see the [evaluation in LEARNINGS.md](../LEARNINGS.md)), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
31
+
32
+ ---
33
+
34
+ ## Quick Start
35
+
36
+ ```bash
37
+ pnpm add aiquadtreejs
38
+ ```
39
+
40
+ ```typescript
41
+ import { createQuadtree, type AABB } from "aiquadtreejs";
42
+
43
+ type Body = { id: number } & AABB;
44
+
45
+ // 1. Build the tree once.
46
+ const qt = createQuadtree<Body>({
47
+ bounds: { x: 0, y: 0, width: 800, height: 600 },
48
+ maxObjects: 10,
49
+ maxLevels: 4,
50
+ });
51
+
52
+ // 2. Per frame: clear and re-insert.
53
+ function rebuild(entities: Body[]) {
54
+ qt.clear();
55
+ for (const e of entities) qt.insert(e);
56
+ }
57
+
58
+ // 3. Per query: broadphase, then precise hit-test.
59
+ function nearbyEnemies(player: Body, enemies: Body[]): Body[] {
60
+ const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };
61
+ const candidates = qt.retrieve(region);
62
+ // Caller filters with precise AABB / pixel test:
63
+ return candidates.filter((c) => aabbOverlap(c, region));
64
+ }
65
+ ```
66
+
67
+ Coordinate semantics match PixiJS `getBounds()`: `x + width` and `y + height` are exclusive.
68
+
69
+ ---
70
+
71
+ ## Capabilities / Limitations
72
+
73
+ | Will do (v1) | Won't do |
74
+ | --------------------------------------------------------- | ----------------------------------------------------- |
75
+ | 2D rectangle quadtree | 3D octree / R-tree / KD-tree |
76
+ | `insert()` / `retrieve()` / `clear()` / `dispose()` | Move-tracking (use `clear()` + re-`insert()`) |
77
+ | Set-based dedup on `retrieve` (each candidate once) | Precise hit-test (broadphase only) |
78
+ | `maxObjects` + `maxLevels` knobs | Auto-rebalance / dynamic depth growth |
79
+ | Reuse the root node across frames; resubdivide cheaply | Circle / Line / polygon primitives |
80
+ | `dispose()` idempotent; post-dispose calls throw | Persistence / snapshot / serialise (out of scope) |
81
+
82
+ ---
83
+
84
+ ## API sketch
85
+
86
+ ```typescript
87
+ interface AABB {
88
+ x: number;
89
+ y: number;
90
+ width: number; // x + width is exclusive
91
+ height: number; // y + height is exclusive
92
+ }
93
+
94
+ interface QuadtreeOptions {
95
+ bounds: AABB;
96
+ maxObjects?: number; // default 10
97
+ maxLevels?: number; // default 4
98
+ }
99
+
100
+ interface Quadtree<T extends AABB> {
101
+ insert(obj: T): void;
102
+ retrieve(region: AABB): T[];
103
+ clear(): void;
104
+ dispose(): void;
105
+ readonly disposed: boolean;
106
+ }
107
+
108
+ class QuadtreeError extends Error {}
109
+ class QuadtreeDisposedError extends Error {}
110
+
111
+ function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T>;
112
+ ```
113
+
114
+ Full JSDoc lives in [`src/index.ts`](src/index.ts).
115
+
116
+ ---
117
+
118
+ ## Roadmap
119
+
120
+ | Version | Adds |
121
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
122
+ | **0.1.0** | `createQuadtree`, `insert` / `retrieve` / `clear` / `dispose`, Set-based dedup, ≥95% coverage, ≤2 KB gzip. |
123
+ | **0.2.0** | Opt-in `retrieveInto(target: T[])` — write into a caller-supplied array to avoid allocating a fresh result every frame. |
124
+ | **0.3+** | Evaluate zero-copy interop with `aiecsjs` queries (consume entity ID columns as `Uint32Array` views without an intermediate AABB obj). |
125
+
126
+ ---
127
+
128
+ ## License
129
+
130
+ [MIT](LICENSE).
package/README_ZHTW.md ADDED
@@ -0,0 +1,130 @@
1
+ # aiquadtreejs
2
+
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)
5
+ [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
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
+ [![English](https://img.shields.io/badge/lang-English-blue.svg)](README.md)
8
+
9
+ > 一個小型 2D quadtree,給 per-frame rebuild 的碰撞 broadphase 使用。`insert` AABB、`retrieve` 候選、`clear`,精確碰撞測試由呼叫方負責。瞄準 PixiJS 遊戲 500–10,000 個 active entity 的場景。
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 薄殼)。
12
+
13
+ > **狀態:0.0.1 scaffold。** 下方 API surface 已凍結;實作在 0.1.0 落地。目前 `createQuadtree` 被呼叫會直接 `throw "not implemented"`。
14
+
15
+ ---
16
+
17
+ ## 為什麼有 aiquadtreejs
18
+
19
+ 對 `N` 個 entity 做 N² 兩兩碰撞比對,1,000 個 entity 就是百萬次比對,在 60 Hz 已經吃光預算;10,000 個 entity 直接破表。Quadtree 把那個密集外層 loop 換成空間過濾器:每個 entity 問「我可能會撞到誰?」,樹回傳一個小的候選集,平均 `O(log N)`。精確碰撞測試只跑在候選上,實際比較數量降一到兩個量級。
20
+
21
+ `aiquadtreejs` 刻意做這四個取捨:
22
+
23
+ - **Per-frame rebuild,不追蹤移動。** 追蹤 entity 跨幀在哪個 leaf 之間遷移,能做但容易出錯;對重用 node object 的樹做 `clear()` + re-`insert()`,實測更快、心智模型更乾淨。這跟 Kontra.js 哲學一致。
24
+ - **`retrieve` 用 Set 去重。** 跨象限的 AABB 會落進多個 leaf;不去重的話呼叫方會看到同一個候選 2 或 4 次,精確碰撞測試成本翻倍。Set 保證每個候選只出現一次。
25
+ - **只做 2D AABB ── 不做 3D、不做 R-tree / KD-tree、不做 Circle / Line。** 那些是針對特定問題的好技術,但屬於不同 size class。這個套件壓在 ≤ 2 KB gzip,更重的 broadphase 由 user-land 帶。
26
+ - **不做精確碰撞測試。** Broadphase + 碰撞回應綁在一起的套件永遠在膨脹。本套件契約止於「這是候選名單」;pixel-perfect 或特殊形狀的精確測試交給你已經有的 physics layer。
27
+
28
+ 那為什麼不直接用 `@timohausmann/quadtree-ts`?它做得很好,獨立場景直接用沒問題。`aiquadtreejs` 存在的理由是讓 ai*js stack 能直接接 `aiecsjs` 的 entity ID,不必每 frame 再轉一次物件 ── `insert({ id: eid, x, y, width, height })` 直接對齊你已經維護的 SoA 欄位。
29
+
30
+ > `aiquadtreejs` 是 v0.3 cycle 四個新加入兄弟套件之一 ── 另外三個是 [aipooljs](https://github.com/yshengliao/aipooljs)(物件池)、`aieventjs`(typed event;**自寫不 fork mitt**,理由見 [LEARNINGS.md 的評估](../LEARNINGS.md))、`aiaudiojs`(Web Audio 薄殼,底層用 Howler.js 作 `peerDependency`)。
31
+
32
+ ---
33
+
34
+ ## Quick Start
35
+
36
+ ```bash
37
+ pnpm add aiquadtreejs
38
+ ```
39
+
40
+ ```typescript
41
+ import { createQuadtree, type AABB } from "aiquadtreejs";
42
+
43
+ type Body = { id: number } & AABB;
44
+
45
+ // 1. 建一次。
46
+ const qt = createQuadtree<Body>({
47
+ bounds: { x: 0, y: 0, width: 800, height: 600 },
48
+ maxObjects: 10,
49
+ maxLevels: 4,
50
+ });
51
+
52
+ // 2. 每 frame:clear 然後重新 insert。
53
+ function rebuild(entities: Body[]) {
54
+ qt.clear();
55
+ for (const e of entities) qt.insert(e);
56
+ }
57
+
58
+ // 3. 每次查詢:broadphase 再精確碰撞。
59
+ function nearbyEnemies(player: Body, enemies: Body[]): Body[] {
60
+ const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };
61
+ const candidates = qt.retrieve(region);
62
+ // 呼叫方做精確 AABB / pixel 測試:
63
+ return candidates.filter((c) => aabbOverlap(c, region));
64
+ }
65
+ ```
66
+
67
+ 座標語意對齊 PixiJS `getBounds()`:`x + width` 與 `y + height` 都是**右開**(不含)。
68
+
69
+ ---
70
+
71
+ ## 能做 / 不做
72
+
73
+ | 會做(v1) | 不會做 |
74
+ | --------------------------------------------------------- | ----------------------------------------------------- |
75
+ | 2D rectangle quadtree | 3D octree / R-tree / KD-tree |
76
+ | `insert()` / `retrieve()` / `clear()` / `dispose()` | 追蹤移動(用 `clear()` + re-`insert()`) |
77
+ | `retrieve` Set 去重(每候選只出現一次) | 精確碰撞測試(只做 broadphase) |
78
+ | `maxObjects` + `maxLevels` 旋鈕 | Auto-rebalance / 動態深度成長 |
79
+ | 跨 frame 重用 node slot(零 GC churn) | Circle / Line / Polygon 原型 |
80
+ | `dispose()` 冪等;dispose 後呼叫拋錯 | Persistence / snapshot / serialise(不在範圍內) |
81
+
82
+ ---
83
+
84
+ ## API 草稿
85
+
86
+ ```typescript
87
+ interface AABB {
88
+ x: number;
89
+ y: number;
90
+ width: number; // x + width 為右開(不含)
91
+ height: number; // y + height 為右開(不含)
92
+ }
93
+
94
+ interface QuadtreeOptions {
95
+ bounds: AABB;
96
+ maxObjects?: number; // 預設 10
97
+ maxLevels?: number; // 預設 4
98
+ }
99
+
100
+ interface Quadtree<T extends AABB> {
101
+ insert(obj: T): void;
102
+ retrieve(region: AABB): T[];
103
+ clear(): void;
104
+ dispose(): void;
105
+ readonly disposed: boolean;
106
+ }
107
+
108
+ class QuadtreeError extends Error {}
109
+ class QuadtreeDisposedError extends Error {}
110
+
111
+ function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T>;
112
+ ```
113
+
114
+ 完整 JSDoc 在 [`src/index.ts`](src/index.ts)。
115
+
116
+ ---
117
+
118
+ ## Roadmap
119
+
120
+ | 版本 | 加入內容 |
121
+ | ---------- | --------------------------------------------------------------------------------------------------------------------------------------- |
122
+ | **0.1.0** | `createQuadtree`、`insert` / `retrieve` / `clear` / `dispose`、Set 去重、≥95% coverage、≤2 KB gzip。 |
123
+ | **0.2.0** | opt-in `retrieveInto(target: T[])` ── 寫進呼叫方傳入的 array,省掉每 frame alloc 新 array。 |
124
+ | **0.3+** | 評估與 `aiecsjs` query 的 zero-copy 介接(消費 entity ID 的 `Uint32Array` view,不必經 AABB plain object)。 |
125
+
126
+ ---
127
+
128
+ ## License
129
+
130
+ [MIT](LICENSE)。
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var c=class extends Error{name="QuadtreeError"},a=class extends Error{name="QuadtreeDisposedError"};function x(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function v(e,t){let i=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,n=t.x<i,o=t.width===0?t.x>=i:t.x+t.width>i,d=t.y<s,u=t.height===0?t.y>=s:t.y+t.height>s,r=[];return d&&n&&r.push(0),d&&o&&r.push(1),u&&n&&r.push(2),u&&o&&r.push(3),r}function g(e){let t=e.bounds.width/2,i=e.bounds.height/2,s=e.bounds.x,n=e.bounds.y,o=e.level+1;e.children.push({bounds:{x:s,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s,y:n+i,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n+i,width:t,height:i},level:o,objects:[],children:[]});for(let d of e.objects)for(let u of v(e,d)){let r=e.children[u];r!==void 0&&r.objects.push(d);}e.objects.length=0;}function A(e,t,i,s){if(!(e.level===0&&!x(e.bounds,t))){if(e.children.length===4){for(let n of v(e,t)){let o=e.children[n];o!==void 0&&A(o,t,i,s);}return}e.objects.push(t),e.objects.length>i&&e.level<s&&g(e);}}function B(e){e.objects.length=0;for(let t of e.children)B(t);e.children.length=0;}function w(e){let{bounds:t}=e,i=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new c("bounds must contain finite numbers");if(t.width<=0)throw new c("bounds.width must be > 0");if(t.height<=0)throw new c("bounds.height must be > 0");if(!Number.isInteger(i)||i<=0)throw new c("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new c("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:i,maxLevels:s,disposed:false};function o(){if(n.disposed)throw new a("aiquadtreejs: quadtree has been disposed")}function d(l){o(),A(n.root,l,n.maxObjects,n.maxLevels);}function u(l){o();let m=new Set,b=[n.root];for(;b.length>0;){let h=b.pop();if(h!==void 0&&x(h.bounds,l)){for(let f of h.objects)m.add(f);for(let f of h.children)b.push(f);}}return Array.from(m)}function r(){o(),B(n.root);}function p(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0);}return {insert:d,retrieve:u,clear:r,dispose:p,get disposed(){return n.disposed}}}exports.QuadtreeDisposedError=a;exports.QuadtreeError=c;exports.createQuadtree=w;//# sourceMappingURL=index.cjs.map
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","retrieve","region","stack","clear","dispose"],"mappings":"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","file":"index.cjs","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// v0.1.0: full implementation. Plain-object nodes, iterative-DFS retrieve,\n// Set-based dedup, idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Axis-aligned bounding box.\n *\n * Coordinate semantics follow PixiJS `getBounds()`: `x` / `y` are the top-left\n * corner and `x + width` / `y + height` are **exclusive**. A 32×32 sprite at\n * `(0, 0)` covers pixels `[0, 32)` on both axes.\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n */\n retrieve(region: AABB): T[];\n\n /**\n * Reset every node back to empty. Internal node objects are reused\n * across frames so per-frame rebuild does not pressure the GC.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * Subsequent `insert` / `retrieve` / `clear` throw {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — currently unused at the public surface, but\n * reserved for future precondition violations (e.g. an inserted object with\n * `NaN` coordinates or negative `width`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n if (node.level === 0 && !rectsOverlap(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n const result = new Set<T>();\n const stack: Node<T>[] = [state.root];\n while (stack.length > 0) {\n const node = stack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, region)) continue;\n for (const obj of node.objects) {\n result.add(obj);\n }\n for (const child of node.children) {\n stack.push(child);\n }\n }\n return Array.from(result);\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n }\n\n return {\n insert,\n retrieve,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Axis-aligned bounding box.
3
+ *
4
+ * Coordinate semantics follow PixiJS `getBounds()`: `x` / `y` are the top-left
5
+ * corner and `x + width` / `y + height` are **exclusive**. A 32×32 sprite at
6
+ * `(0, 0)` covers pixels `[0, 32)` on both axes.
7
+ *
8
+ * @public
9
+ */
10
+ interface AABB {
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ }
16
+ /**
17
+ * Configuration for {@link createQuadtree}.
18
+ *
19
+ * @public
20
+ */
21
+ interface QuadtreeOptions {
22
+ /**
23
+ * Outer bounds. Objects partially outside `bounds` still insert into
24
+ * whichever child nodes they overlap; objects fully outside are ignored
25
+ * by `retrieve()` because no node overlaps them.
26
+ */
27
+ bounds: AABB;
28
+ /**
29
+ * Threshold above which a node subdivides. Default `10`. Lower values
30
+ * mean deeper trees and fewer candidates per `retrieve()`; higher values
31
+ * mean shallower trees and cheaper `insert()`.
32
+ */
33
+ maxObjects?: number;
34
+ /**
35
+ * Maximum subdivision depth. Default `4`. Caps recursion so a very dense
36
+ * cluster doesn't blow up into an unbounded tree.
37
+ */
38
+ maxLevels?: number;
39
+ }
40
+ /**
41
+ * Quadtree storing objects that extend {@link AABB}. `T` may carry any
42
+ * payload (entity ID, sprite reference, user data) alongside the geometry.
43
+ *
44
+ * The expected usage pattern is **per-frame rebuild**: at the start of each
45
+ * frame, call `clear()` and re-`insert()` every active object. This is
46
+ * cheaper than tracking movements through the tree and gives correct results
47
+ * regardless of how objects moved.
48
+ *
49
+ * @public
50
+ */
51
+ interface Quadtree<T extends AABB> {
52
+ /**
53
+ * Insert an object. The same object reference may legitimately appear
54
+ * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`
55
+ * deduplicates with a `Set` so the caller sees it exactly once.
56
+ */
57
+ insert(obj: T): void;
58
+ /**
59
+ * Return every inserted object whose containing node overlaps `region`,
60
+ * deduplicated. The result is a **broadphase**: callers must still run
61
+ * a precise AABB or pixel-level hit test on each candidate.
62
+ */
63
+ retrieve(region: AABB): T[];
64
+ /**
65
+ * Reset every node back to empty. Internal node objects are reused
66
+ * across frames so per-frame rebuild does not pressure the GC.
67
+ */
68
+ clear(): void;
69
+ /**
70
+ * Idempotent teardown. Drops references so the GC can reclaim everything.
71
+ * Subsequent `insert` / `retrieve` / `clear` throw {@link QuadtreeDisposedError}.
72
+ */
73
+ dispose(): void;
74
+ /** `true` once {@link dispose} has been called. */
75
+ readonly disposed: boolean;
76
+ }
77
+ /**
78
+ * Recoverable quadtree error — currently unused at the public surface, but
79
+ * reserved for future precondition violations (e.g. an inserted object with
80
+ * `NaN` coordinates or negative `width`).
81
+ *
82
+ * @public
83
+ */
84
+ declare class QuadtreeError extends Error {
85
+ readonly name = "QuadtreeError";
86
+ }
87
+ /**
88
+ * Thrown by any quadtree method called after {@link Quadtree.dispose}.
89
+ *
90
+ * @public
91
+ */
92
+ declare class QuadtreeDisposedError extends Error {
93
+ readonly name = "QuadtreeDisposedError";
94
+ }
95
+ /**
96
+ * Construct a 2D quadtree.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * import { createQuadtree, type AABB } from "aiquadtreejs";
101
+ *
102
+ * interface Body extends AABB {
103
+ * id: number;
104
+ * }
105
+ *
106
+ * const entities: Body[] = [
107
+ * { id: 1, x: 100, y: 100, width: 32, height: 32 },
108
+ * { id: 2, x: 400, y: 250, width: 32, height: 32 },
109
+ * ];
110
+ * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };
111
+ *
112
+ * const qt = createQuadtree<Body>({
113
+ * bounds: { x: 0, y: 0, width: 800, height: 600 },
114
+ * maxObjects: 10,
115
+ * maxLevels: 4,
116
+ * });
117
+ *
118
+ * // Per-frame:
119
+ * qt.clear();
120
+ * for (const e of entities) qt.insert(e);
121
+ *
122
+ * // Broadphase lookup near the player:
123
+ * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };
124
+ * const candidates = qt.retrieve(region);
125
+ * // Caller runs a precise hit test on `candidates`.
126
+ * ```
127
+ *
128
+ * @public
129
+ */
130
+ declare function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T>;
131
+
132
+ export { type AABB, type Quadtree, QuadtreeDisposedError, QuadtreeError, type QuadtreeOptions, createQuadtree };
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Axis-aligned bounding box.
3
+ *
4
+ * Coordinate semantics follow PixiJS `getBounds()`: `x` / `y` are the top-left
5
+ * corner and `x + width` / `y + height` are **exclusive**. A 32×32 sprite at
6
+ * `(0, 0)` covers pixels `[0, 32)` on both axes.
7
+ *
8
+ * @public
9
+ */
10
+ interface AABB {
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ }
16
+ /**
17
+ * Configuration for {@link createQuadtree}.
18
+ *
19
+ * @public
20
+ */
21
+ interface QuadtreeOptions {
22
+ /**
23
+ * Outer bounds. Objects partially outside `bounds` still insert into
24
+ * whichever child nodes they overlap; objects fully outside are ignored
25
+ * by `retrieve()` because no node overlaps them.
26
+ */
27
+ bounds: AABB;
28
+ /**
29
+ * Threshold above which a node subdivides. Default `10`. Lower values
30
+ * mean deeper trees and fewer candidates per `retrieve()`; higher values
31
+ * mean shallower trees and cheaper `insert()`.
32
+ */
33
+ maxObjects?: number;
34
+ /**
35
+ * Maximum subdivision depth. Default `4`. Caps recursion so a very dense
36
+ * cluster doesn't blow up into an unbounded tree.
37
+ */
38
+ maxLevels?: number;
39
+ }
40
+ /**
41
+ * Quadtree storing objects that extend {@link AABB}. `T` may carry any
42
+ * payload (entity ID, sprite reference, user data) alongside the geometry.
43
+ *
44
+ * The expected usage pattern is **per-frame rebuild**: at the start of each
45
+ * frame, call `clear()` and re-`insert()` every active object. This is
46
+ * cheaper than tracking movements through the tree and gives correct results
47
+ * regardless of how objects moved.
48
+ *
49
+ * @public
50
+ */
51
+ interface Quadtree<T extends AABB> {
52
+ /**
53
+ * Insert an object. The same object reference may legitimately appear
54
+ * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`
55
+ * deduplicates with a `Set` so the caller sees it exactly once.
56
+ */
57
+ insert(obj: T): void;
58
+ /**
59
+ * Return every inserted object whose containing node overlaps `region`,
60
+ * deduplicated. The result is a **broadphase**: callers must still run
61
+ * a precise AABB or pixel-level hit test on each candidate.
62
+ */
63
+ retrieve(region: AABB): T[];
64
+ /**
65
+ * Reset every node back to empty. Internal node objects are reused
66
+ * across frames so per-frame rebuild does not pressure the GC.
67
+ */
68
+ clear(): void;
69
+ /**
70
+ * Idempotent teardown. Drops references so the GC can reclaim everything.
71
+ * Subsequent `insert` / `retrieve` / `clear` throw {@link QuadtreeDisposedError}.
72
+ */
73
+ dispose(): void;
74
+ /** `true` once {@link dispose} has been called. */
75
+ readonly disposed: boolean;
76
+ }
77
+ /**
78
+ * Recoverable quadtree error — currently unused at the public surface, but
79
+ * reserved for future precondition violations (e.g. an inserted object with
80
+ * `NaN` coordinates or negative `width`).
81
+ *
82
+ * @public
83
+ */
84
+ declare class QuadtreeError extends Error {
85
+ readonly name = "QuadtreeError";
86
+ }
87
+ /**
88
+ * Thrown by any quadtree method called after {@link Quadtree.dispose}.
89
+ *
90
+ * @public
91
+ */
92
+ declare class QuadtreeDisposedError extends Error {
93
+ readonly name = "QuadtreeDisposedError";
94
+ }
95
+ /**
96
+ * Construct a 2D quadtree.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * import { createQuadtree, type AABB } from "aiquadtreejs";
101
+ *
102
+ * interface Body extends AABB {
103
+ * id: number;
104
+ * }
105
+ *
106
+ * const entities: Body[] = [
107
+ * { id: 1, x: 100, y: 100, width: 32, height: 32 },
108
+ * { id: 2, x: 400, y: 250, width: 32, height: 32 },
109
+ * ];
110
+ * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };
111
+ *
112
+ * const qt = createQuadtree<Body>({
113
+ * bounds: { x: 0, y: 0, width: 800, height: 600 },
114
+ * maxObjects: 10,
115
+ * maxLevels: 4,
116
+ * });
117
+ *
118
+ * // Per-frame:
119
+ * qt.clear();
120
+ * for (const e of entities) qt.insert(e);
121
+ *
122
+ * // Broadphase lookup near the player:
123
+ * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };
124
+ * const candidates = qt.retrieve(region);
125
+ * // Caller runs a precise hit test on `candidates`.
126
+ * ```
127
+ *
128
+ * @public
129
+ */
130
+ declare function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T>;
131
+
132
+ export { type AABB, type Quadtree, QuadtreeDisposedError, QuadtreeError, type QuadtreeOptions, createQuadtree };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ var c=class extends Error{name="QuadtreeError"},a=class extends Error{name="QuadtreeDisposedError"};function x(e,t){return e.x<t.x+t.width&&e.x+e.width>t.x&&e.y<t.y+t.height&&e.y+e.height>t.y}function v(e,t){let i=e.bounds.x+e.bounds.width/2,s=e.bounds.y+e.bounds.height/2,n=t.x<i,o=t.width===0?t.x>=i:t.x+t.width>i,d=t.y<s,u=t.height===0?t.y>=s:t.y+t.height>s,r=[];return d&&n&&r.push(0),d&&o&&r.push(1),u&&n&&r.push(2),u&&o&&r.push(3),r}function g(e){let t=e.bounds.width/2,i=e.bounds.height/2,s=e.bounds.x,n=e.bounds.y,o=e.level+1;e.children.push({bounds:{x:s,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s,y:n+i,width:t,height:i},level:o,objects:[],children:[]},{bounds:{x:s+t,y:n+i,width:t,height:i},level:o,objects:[],children:[]});for(let d of e.objects)for(let u of v(e,d)){let r=e.children[u];r!==void 0&&r.objects.push(d);}e.objects.length=0;}function A(e,t,i,s){if(!(e.level===0&&!x(e.bounds,t))){if(e.children.length===4){for(let n of v(e,t)){let o=e.children[n];o!==void 0&&A(o,t,i,s);}return}e.objects.push(t),e.objects.length>i&&e.level<s&&g(e);}}function B(e){e.objects.length=0;for(let t of e.children)B(t);e.children.length=0;}function w(e){let{bounds:t}=e,i=e.maxObjects??10,s=e.maxLevels??4;if(!Number.isFinite(t.x)||!Number.isFinite(t.y)||!Number.isFinite(t.width)||!Number.isFinite(t.height))throw new c("bounds must contain finite numbers");if(t.width<=0)throw new c("bounds.width must be > 0");if(t.height<=0)throw new c("bounds.height must be > 0");if(!Number.isInteger(i)||i<=0)throw new c("maxObjects must be a positive integer");if(!Number.isInteger(s)||s<=0)throw new c("maxLevels must be a positive integer");let n={root:{bounds:{...t},level:0,objects:[],children:[]},maxObjects:i,maxLevels:s,disposed:false};function o(){if(n.disposed)throw new a("aiquadtreejs: quadtree has been disposed")}function d(l){o(),A(n.root,l,n.maxObjects,n.maxLevels);}function u(l){o();let m=new Set,b=[n.root];for(;b.length>0;){let h=b.pop();if(h!==void 0&&x(h.bounds,l)){for(let f of h.objects)m.add(f);for(let f of h.children)b.push(f);}}return Array.from(m)}function r(){o(),B(n.root);}function p(){n.disposed||(n.disposed=true,n.root.objects.length=0,n.root.children.length=0);}return {insert:d,retrieve:u,clear:r,dispose:p,get disposed(){return n.disposed}}}export{a as QuadtreeDisposedError,c as QuadtreeError,w as createQuadtree};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["QuadtreeError","QuadtreeDisposedError","rectsOverlap","a","b","quadrantIndices","node","obj","midX","midY","inLeft","inRight","inTop","inBottom","result","subdivide","w","h","x","y","lvl","i","child","insertNode","maxObjects","maxLevels","clearNode","createQuadtree","opts","bounds","state","ck","insert","retrieve","region","stack","clear","dispose"],"mappings":"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","file":"index.js","sourcesContent":["// aiquadtreejs — 2D quadtree for per-frame rebuild collision broadphase.\n//\n// v0.1.0: full implementation. Plain-object nodes, iterative-DFS retrieve,\n// Set-based dedup, idempotent dispose, destructurable methods (no `this`).\n\n/**\n * Axis-aligned bounding box.\n *\n * Coordinate semantics follow PixiJS `getBounds()`: `x` / `y` are the top-left\n * corner and `x + width` / `y + height` are **exclusive**. A 32×32 sprite at\n * `(0, 0)` covers pixels `[0, 32)` on both axes.\n *\n * @public\n */\nexport interface AABB {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/**\n * Configuration for {@link createQuadtree}.\n *\n * @public\n */\nexport interface QuadtreeOptions {\n /**\n * Outer bounds. Objects partially outside `bounds` still insert into\n * whichever child nodes they overlap; objects fully outside are ignored\n * by `retrieve()` because no node overlaps them.\n */\n bounds: AABB;\n\n /**\n * Threshold above which a node subdivides. Default `10`. Lower values\n * mean deeper trees and fewer candidates per `retrieve()`; higher values\n * mean shallower trees and cheaper `insert()`.\n */\n maxObjects?: number;\n\n /**\n * Maximum subdivision depth. Default `4`. Caps recursion so a very dense\n * cluster doesn't blow up into an unbounded tree.\n */\n maxLevels?: number;\n}\n\n/**\n * Quadtree storing objects that extend {@link AABB}. `T` may carry any\n * payload (entity ID, sprite reference, user data) alongside the geometry.\n *\n * The expected usage pattern is **per-frame rebuild**: at the start of each\n * frame, call `clear()` and re-`insert()` every active object. This is\n * cheaper than tracking movements through the tree and gives correct results\n * regardless of how objects moved.\n *\n * @public\n */\nexport interface Quadtree<T extends AABB> {\n /**\n * Insert an object. The same object reference may legitimately appear\n * in multiple leaf nodes when it spans quadrant boundaries; `retrieve()`\n * deduplicates with a `Set` so the caller sees it exactly once.\n */\n insert(obj: T): void;\n\n /**\n * Return every inserted object whose containing node overlaps `region`,\n * deduplicated. The result is a **broadphase**: callers must still run\n * a precise AABB or pixel-level hit test on each candidate.\n */\n retrieve(region: AABB): T[];\n\n /**\n * Reset every node back to empty. Internal node objects are reused\n * across frames so per-frame rebuild does not pressure the GC.\n */\n clear(): void;\n\n /**\n * Idempotent teardown. Drops references so the GC can reclaim everything.\n * Subsequent `insert` / `retrieve` / `clear` throw {@link QuadtreeDisposedError}.\n */\n dispose(): void;\n\n /** `true` once {@link dispose} has been called. */\n readonly disposed: boolean;\n}\n\n/**\n * Recoverable quadtree error — currently unused at the public surface, but\n * reserved for future precondition violations (e.g. an inserted object with\n * `NaN` coordinates or negative `width`).\n *\n * @public\n */\nexport class QuadtreeError extends Error {\n override readonly name = \"QuadtreeError\";\n}\n\n/**\n * Thrown by any quadtree method called after {@link Quadtree.dispose}.\n *\n * @public\n */\nexport class QuadtreeDisposedError extends Error {\n override readonly name = \"QuadtreeDisposedError\";\n}\n\n// ---------------------------------------------------------------------------\n// Internal types\n// ---------------------------------------------------------------------------\n\ninterface Node<T extends AABB> {\n bounds: AABB;\n level: number;\n objects: T[];\n children: Node<T>[];\n}\n\ninterface State<T extends AABB> {\n root: Node<T>;\n maxObjects: number;\n maxLevels: number;\n disposed: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction rectsOverlap(a: AABB, b: AABB): boolean {\n return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n}\n\nfunction quadrantIndices<T extends AABB>(node: Node<T>, obj: AABB): number[] {\n const midX = node.bounds.x + node.bounds.width / 2;\n const midY = node.bounds.y + node.bounds.height / 2;\n // Zero-extent objects (points) sitting exactly on midX / midY would fall\n // through both `<` and `>` checks; treat the point as belonging to the\n // right/bottom side so it doesn't silently disappear.\n const inLeft = obj.x < midX;\n const inRight = obj.width === 0 ? obj.x >= midX : obj.x + obj.width > midX;\n const inTop = obj.y < midY;\n const inBottom = obj.height === 0 ? obj.y >= midY : obj.y + obj.height > midY;\n const result: number[] = [];\n if (inTop && inLeft) result.push(0);\n if (inTop && inRight) result.push(1);\n if (inBottom && inLeft) result.push(2);\n if (inBottom && inRight) result.push(3);\n return result;\n}\n\nfunction subdivide<T extends AABB>(node: Node<T>): void {\n const w = node.bounds.width / 2;\n const h = node.bounds.height / 2;\n const x = node.bounds.x;\n const y = node.bounds.y;\n const lvl = node.level + 1;\n node.children.push(\n { bounds: { x, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n { bounds: { x: x + w, y: y + h, width: w, height: h }, level: lvl, objects: [], children: [] },\n );\n for (const obj of node.objects) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) child.objects.push(obj);\n }\n }\n node.objects.length = 0;\n}\n\nfunction insertNode<T extends AABB>(\n node: Node<T>,\n obj: T,\n maxObjects: number,\n maxLevels: number,\n): void {\n // Reject objects entirely outside the root bounds; for inner nodes we\n // trust `quadrantIndices` to route correctly (it has zero-extent fallback\n // logic that `rectsOverlap` does not, so the strict check is too tight\n // at child level for points sitting on a child boundary).\n if (node.level === 0 && !rectsOverlap(node.bounds, obj)) return;\n if (node.children.length === 4) {\n for (const i of quadrantIndices(node, obj)) {\n const child = node.children[i];\n if (child !== undefined) insertNode(child, obj, maxObjects, maxLevels);\n }\n return;\n }\n node.objects.push(obj);\n if (node.objects.length > maxObjects && node.level < maxLevels) {\n subdivide(node);\n }\n}\n\nfunction clearNode<T extends AABB>(node: Node<T>): void {\n node.objects.length = 0;\n for (const child of node.children) {\n clearNode(child);\n }\n node.children.length = 0;\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Construct a 2D quadtree.\n *\n * @example\n * ```ts\n * import { createQuadtree, type AABB } from \"aiquadtreejs\";\n *\n * interface Body extends AABB {\n * id: number;\n * }\n *\n * const entities: Body[] = [\n * { id: 1, x: 100, y: 100, width: 32, height: 32 },\n * { id: 2, x: 400, y: 250, width: 32, height: 32 },\n * ];\n * const player: Body = { id: 0, x: 200, y: 200, width: 32, height: 32 };\n *\n * const qt = createQuadtree<Body>({\n * bounds: { x: 0, y: 0, width: 800, height: 600 },\n * maxObjects: 10,\n * maxLevels: 4,\n * });\n *\n * // Per-frame:\n * qt.clear();\n * for (const e of entities) qt.insert(e);\n *\n * // Broadphase lookup near the player:\n * const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };\n * const candidates = qt.retrieve(region);\n * // Caller runs a precise hit test on `candidates`.\n * ```\n *\n * @public\n */\nexport function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T> {\n const { bounds } = opts;\n const maxObjects = opts.maxObjects ?? 10;\n const maxLevels = opts.maxLevels ?? 4;\n\n if (\n !Number.isFinite(bounds.x) ||\n !Number.isFinite(bounds.y) ||\n !Number.isFinite(bounds.width) ||\n !Number.isFinite(bounds.height)\n ) {\n throw new QuadtreeError(\"bounds must contain finite numbers\");\n }\n if (bounds.width <= 0) {\n throw new QuadtreeError(\"bounds.width must be > 0\");\n }\n if (bounds.height <= 0) {\n throw new QuadtreeError(\"bounds.height must be > 0\");\n }\n if (!Number.isInteger(maxObjects) || maxObjects <= 0) {\n throw new QuadtreeError(\"maxObjects must be a positive integer\");\n }\n if (!Number.isInteger(maxLevels) || maxLevels <= 0) {\n throw new QuadtreeError(\"maxLevels must be a positive integer\");\n }\n\n const state: State<T> = {\n root: {\n bounds: { ...bounds },\n level: 0,\n objects: [],\n children: [],\n },\n maxObjects,\n maxLevels,\n disposed: false,\n };\n\n function ck(): void {\n if (state.disposed) throw new QuadtreeDisposedError(\"aiquadtreejs: quadtree has been disposed\");\n }\n\n function insert(obj: T): void {\n ck();\n insertNode(state.root, obj, state.maxObjects, state.maxLevels);\n }\n\n function retrieve(region: AABB): T[] {\n ck();\n const result = new Set<T>();\n const stack: Node<T>[] = [state.root];\n while (stack.length > 0) {\n const node = stack.pop();\n if (node === undefined) continue;\n if (!rectsOverlap(node.bounds, region)) continue;\n for (const obj of node.objects) {\n result.add(obj);\n }\n for (const child of node.children) {\n stack.push(child);\n }\n }\n return Array.from(result);\n }\n\n function clear(): void {\n ck();\n clearNode(state.root);\n }\n\n function dispose(): void {\n if (state.disposed) return;\n state.disposed = true;\n state.root.objects.length = 0;\n state.root.children.length = 0;\n }\n\n return {\n insert,\n retrieve,\n clear,\n dispose,\n get disposed() {\n return state.disposed;\n },\n };\n}\n"]}
package/llms-full.txt ADDED
@@ -0,0 +1,275 @@
1
+ # aiquadtreejs — full LLM context
2
+
3
+ This file is auto-generated by `scripts/build-llms-full.mjs`. Do not edit
4
+ manually; instead edit the underlying source documents and re-run the
5
+ script. The file concatenates the canonical English documentation surface
6
+ so an LLM agent can ingest the full project context in a single fetch.
7
+
8
+ The short index lives at `llms.txt` (see https://llmstxt.org/).
9
+
10
+ ---
11
+
12
+ <!-- ===== README.md ===== -->
13
+
14
+ # aiquadtreejs
15
+
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)
18
+ [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
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
+ [![繁體中文](https://img.shields.io/badge/lang-繁體中文-red.svg)](README_ZHTW.md)
21
+
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
+
24
+ Part of the [ai\*js micro-runtime ecosystem](https://github.com/yshengliao) — see also [aifsmjs](https://github.com/yshengliao/aifsmjs) (FSM), [aiecsjs](https://github.com/yshengliao/aiecsjs) (ECS), [aibridgejs](https://github.com/yshengliao/aibridgejs) (cross-context RPC), [aieventjs](https://github.com/yshengliao/aieventjs) (event emitter), [aipooljs](https://github.com/yshengliao/aipooljs) (object pool), and [aiaudiojs](https://github.com/yshengliao/aiaudiojs) (Web Audio shell).
25
+
26
+ > **Status: 0.1.0 published.** Full implementation shipped: `createQuadtree`, `insert` / `retrieve` / `clear` / `dispose`, Set-based dedup, ≥95% coverage, ≤2 KB gzip.
27
+
28
+ ---
29
+
30
+ ## Why aiquadtreejs
31
+
32
+ Naïve pairwise collision detection on `N` entities is `O(N²)`. At 1,000 entities that's a million comparisons per frame, which at 60 Hz is already a budget killer; at 10,000 entities it's a non-starter. A quadtree replaces the dense outer loop with a spatial filter: each entity asks "who could I possibly collide with?" and the tree returns a small candidate set — sub-linear on average for well-distributed inputs (worst case is `O(N)` when every object overlaps the query region). The precise hit test then runs only on candidates, dropping the actual comparisons by one or two orders of magnitude in typical game distributions.
33
+
34
+ `aiquadtreejs` makes four deliberate trade-offs:
35
+
36
+ - **Per-frame rebuild, not move-tracking.** Tracking which leaf an entity migrated into between frames is doable but error-prone; `clear()` + re-`insert()` is faster in practice and easier to reason about. This mirrors the Kontra.js philosophy. (`clear()` resets the root node and drops the child array; subdividing fresh next frame is cheap because the inner subdivision logic touches at most `maxLevels = 4` levels.)
37
+ - **Set-based dedup on `retrieve`.** An AABB that straddles a quadrant boundary lands in multiple leaves. Without dedup the caller sees the same candidate two or four times and pays double for the precise hit test. The Set guarantees each candidate appears once.
38
+ - **2D AABB only — no 3D, no R-tree, no KD-tree, no Circle / Line primitives.** Those are real techniques for real problems but they sit in a different size class. Keep this library at ≤ 2 KB gzipped and let user-land bring in heavier broadphase when needed.
39
+ - **No precise hit-test.** Broadphase libraries that also do collision response always grow. The contract here ends at "here are the candidates"; pixel-perfect or shape-specific tests belong to whatever physics layer you already have.
40
+
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
+
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 — see the [evaluation in LEARNINGS.md](../LEARNINGS.md)), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
44
+
45
+ ---
46
+
47
+ ## Quick Start
48
+
49
+ ```bash
50
+ pnpm add aiquadtreejs
51
+ ```
52
+
53
+ ```typescript
54
+ import { createQuadtree, type AABB } from "aiquadtreejs";
55
+
56
+ type Body = { id: number } & AABB;
57
+
58
+ // 1. Build the tree once.
59
+ const qt = createQuadtree<Body>({
60
+ bounds: { x: 0, y: 0, width: 800, height: 600 },
61
+ maxObjects: 10,
62
+ maxLevels: 4,
63
+ });
64
+
65
+ // 2. Per frame: clear and re-insert.
66
+ function rebuild(entities: Body[]) {
67
+ qt.clear();
68
+ for (const e of entities) qt.insert(e);
69
+ }
70
+
71
+ // 3. Per query: broadphase, then precise hit-test.
72
+ function nearbyEnemies(player: Body, enemies: Body[]): Body[] {
73
+ const region: AABB = { x: player.x - 50, y: player.y - 50, width: 100, height: 100 };
74
+ const candidates = qt.retrieve(region);
75
+ // Caller filters with precise AABB / pixel test:
76
+ return candidates.filter((c) => aabbOverlap(c, region));
77
+ }
78
+ ```
79
+
80
+ Coordinate semantics match PixiJS `getBounds()`: `x + width` and `y + height` are exclusive.
81
+
82
+ ---
83
+
84
+ ## Capabilities / Limitations
85
+
86
+ | Will do (v1) | Won't do |
87
+ | --------------------------------------------------------- | ----------------------------------------------------- |
88
+ | 2D rectangle quadtree | 3D octree / R-tree / KD-tree |
89
+ | `insert()` / `retrieve()` / `clear()` / `dispose()` | Move-tracking (use `clear()` + re-`insert()`) |
90
+ | Set-based dedup on `retrieve` (each candidate once) | Precise hit-test (broadphase only) |
91
+ | `maxObjects` + `maxLevels` knobs | Auto-rebalance / dynamic depth growth |
92
+ | Reuse the root node across frames; resubdivide cheaply | Circle / Line / polygon primitives |
93
+ | `dispose()` idempotent; post-dispose calls throw | Persistence / snapshot / serialise (out of scope) |
94
+
95
+ ---
96
+
97
+ ## API sketch
98
+
99
+ ```typescript
100
+ interface AABB {
101
+ x: number;
102
+ y: number;
103
+ width: number; // x + width is exclusive
104
+ height: number; // y + height is exclusive
105
+ }
106
+
107
+ interface QuadtreeOptions {
108
+ bounds: AABB;
109
+ maxObjects?: number; // default 10
110
+ maxLevels?: number; // default 4
111
+ }
112
+
113
+ interface Quadtree<T extends AABB> {
114
+ insert(obj: T): void;
115
+ retrieve(region: AABB): T[];
116
+ clear(): void;
117
+ dispose(): void;
118
+ readonly disposed: boolean;
119
+ }
120
+
121
+ class QuadtreeError extends Error {}
122
+ class QuadtreeDisposedError extends Error {}
123
+
124
+ function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T>;
125
+ ```
126
+
127
+ Full JSDoc lives in [`src/index.ts`](src/index.ts).
128
+
129
+ ---
130
+
131
+ ## Roadmap
132
+
133
+ | Version | Adds |
134
+ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
135
+ | **0.1.0** | `createQuadtree`, `insert` / `retrieve` / `clear` / `dispose`, Set-based dedup, ≥95% coverage, ≤2 KB gzip. |
136
+ | **0.2.0** | Opt-in `retrieveInto(target: T[])` — write into a caller-supplied array to avoid allocating a fresh result every frame. |
137
+ | **0.3+** | Evaluate zero-copy interop with `aiecsjs` queries (consume entity ID columns as `Uint32Array` views without an intermediate AABB obj). |
138
+
139
+ ---
140
+
141
+ ## License
142
+
143
+ [MIT](LICENSE).
144
+
145
+ ---
146
+
147
+ <!-- ===== CHANGELOG.md ===== -->
148
+
149
+ # Changelog
150
+
151
+ All notable changes to this project are documented here. The format follows
152
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
153
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
154
+
155
+ ## [Unreleased]
156
+
157
+ ## [0.1.0] - 2026-05-28
158
+
159
+ ### Added
160
+
161
+ - `createQuadtree({ bounds, maxObjects, maxLevels })` factory — 2D AABB broadphase.
162
+ - `insert(obj)` / `retrieve(region)` / `clear()` / `dispose()` lifecycle.
163
+ - Set-based dedup on `retrieve()` so objects spanning multiple quadrants are returned once.
164
+ - Per-frame rebuild model — `clear()` reuses internal node objects (zero GC churn frame-to-frame).
165
+ - `disposed` read-only flag; post-dispose calls throw `QuadtreeDisposedError`.
166
+ - Test coverage ≥95% statements / lines / functions / ≥90% branches (~30 it() blocks, groups A–H).
167
+ - Size budget: ≤2 KB gzip.
168
+ - Dual ESM + CJS build via `tsup` with `minify: true`; `sideEffects: false`; zero runtime dependencies.
169
+
170
+ ## [0.0.1] - 2026-05-28
171
+
172
+ ### Added (scaffold)
173
+
174
+ - Full package scaffold landed (`package.json`, `tsconfig.json`,
175
+ `tsconfig.test.json`, `tsup.config.ts`, `vitest.config.ts`, `biome.json`,
176
+ `scripts/{verify-exports,check-size,build-llms-full}.mjs`,
177
+ `test/scaffold.test.ts`, `examples/.gitkeep`, `.github/workflows/{ci,publish}.yml`,
178
+ `llms.txt`, `llms-full.txt`).
179
+ - `src/index.ts` remains a `throw` stub exposing the frozen 0.1.0 API surface
180
+ (`createQuadtree`, `Quadtree<T>`, `QuadtreeOptions`, `AABB`,
181
+ `QuadtreeError`, `QuadtreeDisposedError`).
182
+ - `pnpm typecheck && pnpm lint && pnpm coverage && pnpm build &&
183
+ pnpm verify:exports && pnpm verify:llms && pnpm check:size` walks clean
184
+ against a single placeholder test.
185
+ - Coverage thresholds temporarily set to `0/0/0/0`; tightened to
186
+ `95/90/100/100` in 0.1.0 with real tests.
187
+ - Size budget temporarily set to 3 KB gzip; tightened to the 2 KB README
188
+ target in 0.1.0.
189
+ - Publish workflow exists but trigger is `workflow_dispatch` only — no
190
+ accidental npm release on tag push until 0.1.0.
191
+
192
+
193
+ ---
194
+
195
+ <!-- ===== CONTRIBUTING.md ===== -->
196
+
197
+ # Contributing to aiquadtreejs
198
+
199
+ Thanks for taking the time to look. aiquadtreejs is a deliberately small
200
+ library (target ≤ 2 KB gzip); contributions that keep the surface narrow
201
+ are easier to accept than ones that expand it.
202
+
203
+ ## Quick start
204
+
205
+ ```bash
206
+ pnpm install
207
+ pnpm test # vitest
208
+ pnpm coverage # vitest with v0.1.0 thresholds (95/90/100/100)
209
+ pnpm typecheck # tsc --noEmit on strict mode
210
+ pnpm lint # biome check
211
+ pnpm build # tsup; dual ESM/CJS + .d.ts
212
+ pnpm verify:exports # ensures package.json#exports matches dist/
213
+ pnpm verify:llms # ensures llms-full.txt is in sync with README + CHANGELOG
214
+ pnpm check:size # gzip per subpath against the size budget
215
+ ```
216
+
217
+ The full pre-publish gate is `pnpm prepublishOnly`, which runs typecheck,
218
+ lint, coverage (with thresholds), build, exports verification, llms drift
219
+ check, and size budget check — in that order.
220
+
221
+ ## What gets in easily
222
+
223
+ - Bug fixes with a failing test added first
224
+ - README / typing corrections
225
+ - Tests that lock down existing behaviour (especially the Set-dedup
226
+ invariant on `retrieve`)
227
+ - Performance work that keeps per-frame `clear()` + bulk `insert()` zero-GC
228
+
229
+ ## What needs discussion first
230
+
231
+ - Anything that changes the public surface (`createQuadtree`, `Quadtree<T>`,
232
+ `AABB`, `QuadtreeOptions`, error classes)
233
+ - Move-tracking / dynamic update (explicit non-goal — `clear()` + re-insert
234
+ is faster in practice for ≤ 10k entities)
235
+ - Circle / Line / Polygon shape primitives (out of 2D AABB broadphase
236
+ scope; bring your own user-land wrapper)
237
+ - 3D octree / R-tree / KD-tree (different size class; out of scope)
238
+ - Anything that pushes the core gzip past 2 KB
239
+
240
+ ## Design principles
241
+
242
+ aiquadtreejs follows the ai*js library-core priority order:
243
+
244
+ > Security > Correctness > Simplicity > YAGNI > Performance
245
+
246
+ Key invariants:
247
+
248
+ - `retrieve()` deduplicates with a `Set` so each candidate appears once
249
+ regardless of how many quadrants it spans.
250
+ - `clear()` reuses internal node objects across frames; no per-frame node
251
+ allocation.
252
+ - `x + width` and `y + height` are **right-open** (exclusive), matching
253
+ PixiJS `getBounds()` semantics.
254
+ - `dispose()` is idempotent.
255
+
256
+ ## Commit & PR style
257
+
258
+ - Commit messages: imperative subject under 70 chars; body explains *why*.
259
+ - PRs: keep scope to one topic. Link the issue if any.
260
+ - Tests required for any behaviour change. Property-based tests welcome
261
+ for invariants (especially the Set-dedup property).
262
+
263
+ ## Reporting issues
264
+
265
+ - Minimal reproduction welcome (paste the smallest insert / retrieve
266
+ sequence that shows the bug).
267
+ - For security issues, please email the maintainer rather than filing
268
+ publicly.
269
+
270
+ ## License
271
+
272
+ By contributing, you agree your changes will be licensed under the MIT
273
+ license that covers this project.
274
+
275
+ ---
package/llms.txt ADDED
@@ -0,0 +1,5 @@
1
+ # aiquadtreejs
2
+
3
+ > A tiny 2D quadtree for per-frame rebuild collision broadphase. Insert AABBs, retrieve candidates with Set-based dedup, clear, dispose. Caller does precise hit-testing. Designed for PixiJS games with 500–10,000 active entities. Zero runtime dependencies. Part of the ai*js micro-runtime ecosystem.
4
+
5
+ For the full LLM context (README + CHANGELOG + CONTRIBUTING concatenated), fetch [llms-full.txt](llms-full.txt).
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "aiquadtreejs",
3
+ "version": "0.1.0",
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
+ "keywords": [
6
+ "quadtree",
7
+ "spatial-index",
8
+ "broadphase",
9
+ "collision",
10
+ "game-dev",
11
+ "pixijs",
12
+ "svelte",
13
+ "ai-readable",
14
+ "typescript",
15
+ "esm"
16
+ ],
17
+ "author": "ysl <ysl@sheng.page>",
18
+ "license": "MIT",
19
+ "homepage": "https://github.com/yshengliao/aiquadtreejs#readme",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/yshengliao/aiquadtreejs.git"
23
+ },
24
+ "bugs": {
25
+ "url": "https://github.com/yshengliao/aiquadtreejs/issues"
26
+ },
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js",
36
+ "require": "./dist/index.cjs"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md",
42
+ "README_ZHTW.md",
43
+ "LICENSE",
44
+ "llms.txt",
45
+ "llms-full.txt"
46
+ ],
47
+ "devDependencies": {
48
+ "@biomejs/biome": "^1.9.0",
49
+ "@types/node": "^22.0.0",
50
+ "@vitest/coverage-v8": "^4.1.7",
51
+ "tsup": "^8.3.0",
52
+ "tsx": "^4.22.3",
53
+ "typescript": "^5.6.0",
54
+ "vite": "^8.0.14",
55
+ "vitest": "^4.1.7"
56
+ },
57
+ "engines": {
58
+ "node": ">=18.0.0"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "scripts": {
64
+ "build": "tsup",
65
+ "test": "vitest run",
66
+ "test:watch": "vitest",
67
+ "lint": "biome check src test",
68
+ "format": "biome format --write src test",
69
+ "typecheck": "tsc --noEmit",
70
+ "verify:exports": "node scripts/verify-exports.mjs",
71
+ "check:size": "node scripts/check-size.mjs",
72
+ "build:llms": "node scripts/build-llms-full.mjs",
73
+ "verify:llms": "node scripts/build-llms-full.mjs --check",
74
+ "coverage": "vitest run --coverage"
75
+ }
76
+ }