aiquadtreejs 0.5.6 → 0.5.9
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 +47 -105
- package/README_ZHTW.md +47 -103
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/llms-full.txt +94 -474
- package/llms.txt +6 -2
- package/package.json +1 -1
package/llms-full.txt
CHANGED
|
@@ -13,139 +13,81 @@ The short index lives at `llms.txt` (see https://llmstxt.org/).
|
|
|
13
13
|
|
|
14
14
|
# aiquadtreejs
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
[](https://github.com/islumina/aiquadtreejs/actions/workflows/ci.yml)
|
|
18
|
-
[](LICENSE)
|
|
19
|
-
[](https://www.anthropic.com/claude-code)
|
|
20
|
-
[](README_ZHTW.md)
|
|
16
|
+
Tiny 2D quadtree for per-frame rebuild collision broadphase. Insert AABBs, retrieve candidates, then run precise collision checks yourself.
|
|
21
17
|
|
|
22
|
-
>
|
|
18
|
+
> **Status: 0.5.9 - stable 1.0-track surface.** The root entry is the public API.
|
|
23
19
|
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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/islumina/aipooljs) (object pool), `aieventjs` (typed events; self-built, not a `mitt` fork), and `aiaudiojs` (Web Audio shell over a Howler.js `peerDependency`).
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Quick Start
|
|
20
|
+
## Install
|
|
48
21
|
|
|
49
22
|
```bash
|
|
50
23
|
pnpm add aiquadtreejs
|
|
51
24
|
```
|
|
52
25
|
|
|
53
|
-
```
|
|
26
|
+
```ts
|
|
54
27
|
import { createQuadtree, type AABB } from "aiquadtreejs";
|
|
28
|
+
```
|
|
55
29
|
|
|
56
|
-
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
interface Body extends AABB {
|
|
34
|
+
id: number;
|
|
35
|
+
}
|
|
57
36
|
|
|
58
|
-
|
|
59
|
-
const qt = createQuadtree<Body>({
|
|
37
|
+
const tree = createQuadtree<Body>({
|
|
60
38
|
bounds: { x: 0, y: 0, width: 800, height: 600 },
|
|
61
39
|
maxObjects: 10,
|
|
62
40
|
maxLevels: 4,
|
|
63
41
|
});
|
|
64
42
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
43
|
+
const bodies: Body[] = [
|
|
44
|
+
{ id: 1, x: 100, y: 100, width: 32, height: 32 },
|
|
45
|
+
{ id: 2, x: 400, y: 250, width: 32, height: 32 },
|
|
46
|
+
];
|
|
70
47
|
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
```
|
|
48
|
+
tree.clear();
|
|
49
|
+
for (const body of bodies) tree.insert(body);
|
|
79
50
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
## Capabilities / Limitations
|
|
51
|
+
const candidates = tree.retrieve({ x: 80, y: 80, width: 120, height: 120 });
|
|
52
|
+
```
|
|
85
53
|
|
|
86
|
-
|
|
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) |
|
|
54
|
+
## Core API
|
|
94
55
|
|
|
95
|
-
|
|
56
|
+
- `createQuadtree<T extends AABB>({ bounds, maxObjects?, maxLevels? })` creates a tree.
|
|
57
|
+
- `insert(obj)` stores an object reference in overlapping nodes.
|
|
58
|
+
- `retrieve(region)` returns a deduplicated broadphase candidate array.
|
|
59
|
+
- `retrieveInto(region, target)` reuses a caller-owned result array.
|
|
60
|
+
- `clear()` empties the tree for the next frame and clears scratch buffers.
|
|
61
|
+
- `dispose()` is idempotent permanent teardown.
|
|
62
|
+
- Errors: `QuadtreeError`, `QuadtreeDisposedError`.
|
|
96
63
|
|
|
97
|
-
##
|
|
64
|
+
## Model
|
|
98
65
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
height: number; // y + height is exclusive
|
|
105
|
-
}
|
|
66
|
+
- Coordinates are right-open: `{ x, y, width, height }` covers `[x, x + width)` and `[y, y + height)`.
|
|
67
|
+
- This is a broadphase only. Returned candidates may not actually overlap the query region.
|
|
68
|
+
- Expected usage is per-frame rebuild: `clear()`, insert active bodies, query.
|
|
69
|
+
- Objects spanning quadrant boundaries can be stored in multiple child nodes; results are deduplicated.
|
|
70
|
+
- `maxLevels` has no hard cap. Very high values plus spanning objects can create huge node counts.
|
|
106
71
|
|
|
107
|
-
|
|
108
|
-
bounds: AABB;
|
|
109
|
-
maxObjects?: number; // default 10
|
|
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).
|
|
113
|
-
}
|
|
72
|
+
## Sharp Edges
|
|
114
73
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
dispose(): void;
|
|
121
|
-
readonly disposed: boolean;
|
|
122
|
-
}
|
|
74
|
+
- Zero-size points (width = 0, height = 0) follow right-open `[x, x+width)` semantics: a point on the minimum `x/y` boundary is **inclusive** and is inserted/retrieved correctly; a point at the exclusive maximum edge is outside the root and is ignored. (Was a bug before 0.5.8; fixed.)
|
|
75
|
+
- Fully outside objects are ignored by retrieval.
|
|
76
|
+
- Negative width/height and non-finite coordinates throw.
|
|
77
|
+
- `retrieveInto()` clears the target array before writing results.
|
|
78
|
+
- After `dispose()`, all methods except `dispose()` throw `QuadtreeDisposedError`.
|
|
123
79
|
|
|
124
|
-
|
|
125
|
-
class QuadtreeDisposedError extends Error {}
|
|
126
|
-
|
|
127
|
-
function createQuadtree<T extends AABB>(opts: QuadtreeOptions): Quadtree<T>;
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Full JSDoc lives in [`src/index.ts`](src/index.ts).
|
|
131
|
-
|
|
132
|
-
---
|
|
80
|
+
## AI Context
|
|
133
81
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
| **0.3.0** | `retrieveInto(region, target)` zero-alloc API; property-based tests (`fast-check`); STABILITY.md tracking. |
|
|
140
|
-
| **0.4.0** | Dependency hygiene (removed unused `tsx`, aligned `fast-check`); 0.3.x public surface frozen for the 1.x track. No runtime API change. |
|
|
141
|
-
| **0.5.1** | `insert()` input validation: throws `QuadtreeError` for non-finite coords or negative dimensions; 22 new tests (J1–J15, K1–K7). |
|
|
142
|
-
| **0.6+** | Evaluate 3D octree variant (`createOctree<T extends AABB3>`); see `STABILITY.md` for current draft. |
|
|
143
|
-
|
|
144
|
-
---
|
|
82
|
+
- Short index: [`llms.txt`](llms.txt)
|
|
83
|
+
- Full generated context: [`llms-full.txt`](llms-full.txt)
|
|
84
|
+
- Stability contract: [`STABILITY.md`](STABILITY.md)
|
|
85
|
+
- Current review backlog: [`REVIEW.md`](REVIEW.md)
|
|
86
|
+
- Release history: [`CHANGELOG.md`](CHANGELOG.md)
|
|
145
87
|
|
|
146
88
|
## License
|
|
147
89
|
|
|
148
|
-
|
|
90
|
+
MIT
|
|
149
91
|
|
|
150
92
|
---
|
|
151
93
|
|
|
@@ -153,216 +95,30 @@ Full JSDoc lives in [`src/index.ts`](src/index.ts).
|
|
|
153
95
|
|
|
154
96
|
# Changelog
|
|
155
97
|
|
|
156
|
-
All notable changes to
|
|
157
|
-
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
158
|
-
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
98
|
+
All notable changes to aiquadtreejs are summarized here.
|
|
159
99
|
|
|
160
100
|
## [Unreleased]
|
|
161
101
|
|
|
162
|
-
## [0.5.
|
|
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.
|
|
102
|
+
## [0.5.9] - 2026-06-29
|
|
177
103
|
|
|
178
|
-
|
|
104
|
+
- Docs: corrected the zero-size root boundary description — a point on the root min boundary is inclusive and the max boundary is exclusive (right-open `[x, x+width)`), shipped in 0.5.8; removed the stale "known bug" wording and version tokens in source comments.
|
|
179
105
|
|
|
180
|
-
|
|
106
|
+
## [0.5.8] - 2026-06-14
|
|
181
107
|
|
|
182
|
-
-
|
|
108
|
+
- Fixed: a zero-size point on the root left/top minimum boundary (`{ x: bounds.x, y: bounds.y, width: 0, height: 0 }`) is now inserted and retrievable. The root insert gate's right-open overlap test dropped it; a new `rootContains` check is inclusive on the minimum edge and exclusive on the maximum edge, preserving right-open `[x, x+width)` semantics. Boundary regression tests added.
|
|
109
|
+
- Documentation-only slimming pass across README, stability notes, review backlog, and LLM context.
|
|
183
110
|
|
|
184
|
-
## [0.5.
|
|
185
|
-
|
|
186
|
-
### Docs
|
|
187
|
-
|
|
188
|
-
- Review-driven documentation fixes (`README.md`, `README_ZHTW.md`, `llms-full.txt`) and corrected a stale version reference in the `src/index.ts` header comment. No functional change; `dist` byte-identical to 0.5.1.
|
|
189
|
-
|
|
190
|
-
## [0.5.1] - 2026-06-02
|
|
191
|
-
|
|
192
|
-
### Fixed
|
|
193
|
-
|
|
194
|
-
- **`insert()` input validation** — `insert()` now throws `QuadtreeError`
|
|
195
|
-
when the inserted object contains a non-finite coordinate (`NaN`,
|
|
196
|
-
`Infinity`, `-Infinity` in any of `x`, `y`, `width`, `height`) or a
|
|
197
|
-
negative dimension (`width < 0` or `height < 0`). Previously, such
|
|
198
|
-
objects were silently mishandled (kept before subdivide, silently dropped
|
|
199
|
-
after). Zero-extent objects (points / lines with `width=0` and/or
|
|
200
|
-
`height=0`) remain valid and are not rejected. The `QuadtreeError`
|
|
201
|
-
JSDoc already noted this use was reserved; the behaviour is now
|
|
202
|
-
implemented and documented in `STABILITY.md`.
|
|
203
|
-
|
|
204
|
-
### Tests
|
|
205
|
-
|
|
206
|
-
- Added J-group (J1–J15): `insert()` throws `QuadtreeError` for
|
|
207
|
-
`width<0`, `height<0`, `NaN`/`Infinity` in any of `x/y/width/height`, and
|
|
208
|
-
for `null`/`undefined` objects (J14/J15); valid zero-extent objects
|
|
209
|
-
(width=0, height=0, mixed) are accepted.
|
|
210
|
-
- Added K-group (K1–K7): oversized object appears exactly once in
|
|
211
|
-
full-region and per-quadrant sub-queries after subdivide; `retrieveInto`
|
|
212
|
-
buffer identity preserved across 60 clear+insert+retrieveInto frames;
|
|
213
|
-
negative-origin bounds with negative-coord objects and queries; zero-
|
|
214
|
-
extent point at a non-midpoint survives deep subdivision (maxLevels=4)
|
|
215
|
-
and is retrievable with a tight region; maxLevels=1 with many same-
|
|
216
|
-
quadrant objects — all retrievable, zero duplicates; non-origin bounds
|
|
217
|
-
insert+retrieve correctness.
|
|
218
|
-
|
|
219
|
-
## [0.4.0] - 2026-05-29
|
|
220
|
-
|
|
221
|
-
Dependency hygiene + stability freeze. **No runtime API addition or behaviour
|
|
222
|
-
change** — `dist/index.js` is byte-identical to 0.3.1; consumers see no
|
|
223
|
-
difference. This release unifies the ai*js family version line at 0.4.0 and
|
|
224
|
-
formally freezes the public surface for the 1.x track.
|
|
225
|
-
|
|
226
|
-
### Changed
|
|
227
|
-
|
|
228
|
-
- **devDependencies** — removed unused `tsx` (no script, config, or example in
|
|
229
|
-
this package invoked it) and aligned `fast-check` `^3.23.0` → `^4.8.0` to
|
|
230
|
-
match the rest of the ai*js family. Both are dev-only and absent from the
|
|
231
|
-
published tarball (`files` ships `dist` + docs only), so consumers are
|
|
232
|
-
unaffected.
|
|
233
|
-
|
|
234
|
-
### Stability
|
|
235
|
-
|
|
236
|
-
- The 0.3.x public surface — `createQuadtree`, the `Quadtree<T>` interface and
|
|
237
|
-
all its members (`insert` / `retrieve` / `retrieveInto` / `clear` /
|
|
238
|
-
`dispose` / `disposed`), `AABB`, `QuadtreeOptions`, `QuadtreeError`,
|
|
239
|
-
`QuadtreeDisposedError` — is declared **frozen for the 1.x track**: it will
|
|
240
|
-
not break before a 1.0.0+ major. The 3D octree variant remains a draft
|
|
241
|
-
(target v0.6+).
|
|
242
|
-
|
|
243
|
-
### Internal (not shipped to npm)
|
|
244
|
-
|
|
245
|
-
- `pnpm audit` reports no known vulnerabilities; `pnpm-lock.yaml` regenerated
|
|
246
|
-
after the devDependency changes. Property tests re-run green under
|
|
247
|
-
fast-check v4.
|
|
248
|
-
|
|
249
|
-
## [0.3.1] - 2026-05-29
|
|
250
|
-
|
|
251
|
-
### Changed
|
|
111
|
+
## [0.5.6] - 2026-06-10
|
|
252
112
|
|
|
253
|
-
-
|
|
254
|
-
|
|
255
|
-
reused across calls (cleared, not re-created) instead of being allocated
|
|
256
|
-
per query. Combined with the caller-owned `target` buffer, a per-frame
|
|
257
|
-
broadphase loop issuing thousands of `retrieveInto` queries now allocates
|
|
258
|
-
nothing once result sizes stabilise — the design goal stated in 0.3.0.
|
|
259
|
-
Purely internal: no API, signature, or observable-behaviour change.
|
|
260
|
-
`retrieve` still returns a fresh array each call.
|
|
261
|
-
- **`dispose()`** now also clears the internal scratch, preserving the
|
|
262
|
-
"drops references so the GC can reclaim everything" guarantee.
|
|
263
|
-
- **Docs** — `AABB` JSDoc leads with a renderer-neutral right-open
|
|
264
|
-
definition (PixiJS `getBounds()` demoted to a compatibility note);
|
|
265
|
-
`dispose()` JSDoc now lists `retrieveInto` among the post-dispose
|
|
266
|
-
throwers; the 0.3.0 changelog note reworded ("observationally identical"
|
|
267
|
-
rather than "byte-for-byte"); `STABILITY.md` exports list now includes
|
|
268
|
-
the `Quadtree<T>` interface.
|
|
113
|
+
- Hardened retrieve validation, scratch cleanup, and size-budget docs.
|
|
114
|
+
- Kept root quadtree API stable and regenerated generated LLM context.
|
|
269
115
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
- Property test `prop2` uses `fc.uniqueArray` keyed on `id` instead of an
|
|
273
|
-
early-return guard that skipped ~39% of runs on id collisions; the
|
|
274
|
-
id-uniqueness invariant now runs on every case. Property generators mix
|
|
275
|
-
in zero-extent midpoint points (`fc.oneof`) so the dedup invariants
|
|
276
|
-
exercise the G4 regression shape, not just deterministic fixtures.
|
|
277
|
-
- Tightened `I4` (`toBe(1)`); added `I13` (retrieve fresh-array vs
|
|
278
|
-
retrieveInto buffer-reuse contrast — backward-compat lock) and `I14`
|
|
279
|
-
(interleaved retrieve / retrieveInto correctness — shared-scratch guard).
|
|
280
|
-
|
|
281
|
-
### Notes
|
|
282
|
-
|
|
283
|
-
- `dist/index.js` runtime differs from 0.3.0 only by the scratch-hoist
|
|
284
|
-
optimisation; the public API and all observable behaviour are unchanged.
|
|
285
|
-
|
|
286
|
-
## [0.3.0] - 2026-05-29
|
|
287
|
-
|
|
288
|
-
### Added
|
|
289
|
-
|
|
290
|
-
- **`retrieveInto(region: AABB, target: T[]): T[]`** — zero-allocation
|
|
291
|
-
variant of `retrieve` for hot-path callers. Clears `target`, walks
|
|
292
|
-
the same iterative DFS + Set dedup as `retrieve`, writes results
|
|
293
|
-
into `target`, returns `target`. The returned reference equals the
|
|
294
|
-
argument — callers can hold a permanent buffer and pass it every
|
|
295
|
-
frame to eliminate the result-array allocation churn (5,000+ calls
|
|
296
|
-
per frame in typical bullet-hell broadphase loops).
|
|
297
|
-
- **`STABILITY.md`** — explicit stable vs experimental API tracking.
|
|
298
|
-
Includes a v0.6+ 3D octree draft (no source code in this release).
|
|
299
|
-
- **Property-based tests** via `fast-check` — 4 invariants covering
|
|
300
|
-
`retrieve` dedup and `retrieveInto` identity / length / content
|
|
301
|
-
equivalence. Adds `fast-check` to `devDependencies`.
|
|
302
|
-
|
|
303
|
-
### Changed
|
|
304
|
-
|
|
305
|
-
- **`Quadtree.clear` JSDoc** — corrected the claim that "internal
|
|
306
|
-
node objects are reused across frames"; only the root node is
|
|
307
|
-
reused, child nodes are released on `clear()` and re-created when
|
|
308
|
-
subdivision next triggers. No runtime behaviour change.
|
|
309
|
-
- **README Roadmap / Status / API sketch** — synced to v0.3.0,
|
|
310
|
-
including the `retrieveInto(region, target)` signature (the
|
|
311
|
-
pre-0.2 Roadmap entry was a single-argument draft).
|
|
312
|
-
|
|
313
|
-
### Notes
|
|
314
|
-
|
|
315
|
-
- `retrieve` behaviour is observationally identical to 0.1.1: the
|
|
316
|
-
internal refactor extracts a shared `retrieveSet` helper that both
|
|
317
|
-
`retrieve` and `retrieveInto` call, but `Set` insertion order →
|
|
318
|
-
`Array.from` order is preserved by spec.
|
|
319
|
-
- Bundle size: ≤ 2 KB gzip budget still ~50% headroom after this
|
|
320
|
-
release (expected ~1050-1090 B gzip).
|
|
321
|
-
|
|
322
|
-
## [0.1.1] - 2026-05-28
|
|
323
|
-
|
|
324
|
-
### Changed (CI)
|
|
325
|
-
|
|
326
|
-
- **`publish.yml` now triggers on `push: tags: ["v*"]`** (was `workflow_dispatch` only). Aligns with the trigger used by `aifsmjs` / `aiecsjs` / `aibridgejs`. Tag push now automatically runs the OIDC trusted publish.
|
|
327
|
-
- **`npm publish --provenance --access public`** — the workflow now emits a [sigstore provenance attestation](https://docs.npmjs.com/generating-provenance-statements) so consumers can verify the tarball was built by this workflow on this commit.
|
|
328
|
-
|
|
329
|
-
No runtime / source / API changes from 0.1.0. **0.1.1 is also the first version to actually land on npm — 0.1.0 was tagged in git but never published to npm.** Production bundles are byte-identical to the 0.1.0 git tag.
|
|
330
|
-
|
|
331
|
-
## [0.1.0] - 2026-05-28
|
|
332
|
-
|
|
333
|
-
### Added
|
|
334
|
-
|
|
335
|
-
- `createQuadtree({ bounds, maxObjects, maxLevels })` factory — 2D AABB broadphase.
|
|
336
|
-
- `insert(obj)` / `retrieve(region)` / `clear()` / `dispose()` lifecycle.
|
|
337
|
-
- Set-based dedup on `retrieve()` so objects spanning multiple quadrants are returned once.
|
|
338
|
-
- Per-frame rebuild model — `clear()` reuses internal node objects (zero GC churn frame-to-frame).
|
|
339
|
-
- `disposed` read-only flag; post-dispose calls throw `QuadtreeDisposedError`.
|
|
340
|
-
- Test coverage ≥95% statements / lines / functions / ≥90% branches (~30 it() blocks, groups A–H).
|
|
341
|
-
- Size budget: ≤2 KB gzip.
|
|
342
|
-
- Dual ESM + CJS build via `tsup` with `minify: true`; `sideEffects: false`; zero runtime dependencies.
|
|
343
|
-
|
|
344
|
-
## [0.0.1] - 2026-05-28
|
|
345
|
-
|
|
346
|
-
### Added (scaffold)
|
|
347
|
-
|
|
348
|
-
- Full package scaffold landed (`package.json`, `tsconfig.json`,
|
|
349
|
-
`tsconfig.test.json`, `tsup.config.ts`, `vitest.config.ts`, `biome.json`,
|
|
350
|
-
`scripts/{verify-exports,check-size,build-llms-full}.mjs`,
|
|
351
|
-
`test/scaffold.test.ts`, `examples/.gitkeep`, `.github/workflows/{ci,publish}.yml`,
|
|
352
|
-
`llms.txt`, `llms-full.txt`).
|
|
353
|
-
- `src/index.ts` remains a `throw` stub exposing the frozen 0.1.0 API surface
|
|
354
|
-
(`createQuadtree`, `Quadtree<T>`, `QuadtreeOptions`, `AABB`,
|
|
355
|
-
`QuadtreeError`, `QuadtreeDisposedError`).
|
|
356
|
-
- `pnpm typecheck && pnpm lint && pnpm coverage && pnpm build &&
|
|
357
|
-
pnpm verify:exports && pnpm verify:llms && pnpm check:size` walks clean
|
|
358
|
-
against a single placeholder test.
|
|
359
|
-
- Coverage thresholds temporarily set to `0/0/0/0`; tightened to
|
|
360
|
-
`95/90/100/100` in 0.1.0 with real tests.
|
|
361
|
-
- Size budget temporarily set to 3 KB gzip; tightened to the 2 KB README
|
|
362
|
-
target in 0.1.0.
|
|
363
|
-
- Publish workflow exists but trigger is `workflow_dispatch` only — no
|
|
364
|
-
accidental npm release on tag push until 0.1.0.
|
|
116
|
+
## Older releases
|
|
365
117
|
|
|
118
|
+
- `0.5.5` through `0.5.1` focused on release hygiene, docs accuracy, and validation/retrieve regressions.
|
|
119
|
+
- `0.4.0` declared the stable ai*js quadtree surface.
|
|
120
|
+
- `0.3.x` added `retrieveInto()` and zero-allocation query paths.
|
|
121
|
+
- `0.1.x` introduced `createQuadtree`, `AABB`, `Quadtree`, and error classes.
|
|
366
122
|
|
|
367
123
|
---
|
|
368
124
|
|
|
@@ -370,118 +126,31 @@ No runtime / source / API changes from 0.1.0. **0.1.1 is also the first version
|
|
|
370
126
|
|
|
371
127
|
# aiquadtreejs Stability
|
|
372
128
|
|
|
373
|
-
|
|
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.
|
|
129
|
+
## Stable Surface
|
|
377
130
|
|
|
378
|
-
|
|
131
|
+
| Surface | Status | Notes |
|
|
132
|
+
| --- | --- | --- |
|
|
133
|
+
| `createQuadtree()` | Stable | Root factory. |
|
|
134
|
+
| `AABB`, `QuadtreeOptions`, `Quadtree<T>` | Stable | Public types. |
|
|
135
|
+
| `insert`, `retrieve`, `retrieveInto`, `clear`, `dispose` | Stable | Main methods. |
|
|
136
|
+
| Error classes | Stable | `QuadtreeError`, `QuadtreeDisposedError`. |
|
|
379
137
|
|
|
380
|
-
##
|
|
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.
|
|
138
|
+
## Behavioral Contract
|
|
429
139
|
|
|
430
|
-
|
|
140
|
+
- Bounds use right-open coordinates.
|
|
141
|
+
- Inserted object references are not cloned.
|
|
142
|
+
- Retrieval is broadphase and deduplicated.
|
|
143
|
+
- `retrieveInto()` preserves target array identity and clears it first.
|
|
144
|
+
- `clear()` drains node contents and scratch references.
|
|
145
|
+
- `dispose()` is idempotent and permanent.
|
|
431
146
|
|
|
432
|
-
##
|
|
147
|
+
## Zero-Size Point Boundary Semantics
|
|
433
148
|
|
|
434
|
-
|
|
435
|
-
package. They are recorded here so consumers and contributors can see
|
|
436
|
-
the intended direction.
|
|
149
|
+
Zero-size points (width = 0, height = 0) follow right-open `[x, x+width)` semantics on the root boundary: a point exactly on the minimum `x/y` edge is **inclusive** and will be inserted and retrieved correctly. A point at the exclusive maximum edge (`bounds.x + bounds.width`, `bounds.y + bounds.height`) is outside the root and is ignored. This was a known bug in versions before 0.5.8; it is fixed and covered by tests as of 0.5.8.
|
|
437
150
|
|
|
438
|
-
|
|
151
|
+
## Out of Scope
|
|
439
152
|
|
|
440
|
-
|
|
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.
|
|
153
|
+
Precise collision checks, physics integration, spatial hashing, and 3D octrees are outside the current package.
|
|
485
154
|
|
|
486
155
|
---
|
|
487
156
|
|
|
@@ -489,80 +158,31 @@ library (e.g. `octree-ts`).
|
|
|
489
158
|
|
|
490
159
|
# Contributing to aiquadtreejs
|
|
491
160
|
|
|
492
|
-
|
|
493
|
-
library (target ≤ 2 KB gzip); contributions that keep the surface narrow
|
|
494
|
-
are easier to accept than ones that expand it.
|
|
161
|
+
Keep the tree small, allocation-aware, and explicit about broadphase semantics.
|
|
495
162
|
|
|
496
|
-
##
|
|
163
|
+
## Local workflow
|
|
497
164
|
|
|
498
165
|
```bash
|
|
499
166
|
pnpm install
|
|
500
|
-
pnpm
|
|
501
|
-
pnpm
|
|
502
|
-
pnpm
|
|
503
|
-
pnpm
|
|
504
|
-
pnpm
|
|
505
|
-
pnpm
|
|
506
|
-
pnpm verify:llms # ensures llms-full.txt is in sync with README + CHANGELOG
|
|
507
|
-
pnpm check:size # gzip per subpath against the size budget
|
|
167
|
+
pnpm typecheck
|
|
168
|
+
pnpm test
|
|
169
|
+
pnpm verify:docs
|
|
170
|
+
pnpm build:llms
|
|
171
|
+
pnpm verify:llms
|
|
172
|
+
pnpm check:size
|
|
508
173
|
```
|
|
509
174
|
|
|
510
|
-
|
|
511
|
-
lint, coverage (with thresholds), build, exports verification, llms drift
|
|
512
|
-
check, and size budget check — in that order.
|
|
513
|
-
|
|
514
|
-
## What gets in easily
|
|
515
|
-
|
|
516
|
-
- Bug fixes with a failing test added first
|
|
517
|
-
- README / typing corrections
|
|
518
|
-
- Tests that lock down existing behaviour (especially the Set-dedup
|
|
519
|
-
invariant on `retrieve`)
|
|
520
|
-
- Performance work that keeps per-frame `clear()` + bulk `insert()` zero-GC
|
|
521
|
-
|
|
522
|
-
## What needs discussion first
|
|
523
|
-
|
|
524
|
-
- Anything that changes the public surface (`createQuadtree`, `Quadtree<T>`,
|
|
525
|
-
`AABB`, `QuadtreeOptions`, error classes)
|
|
526
|
-
- Move-tracking / dynamic update (explicit non-goal — `clear()` + re-insert
|
|
527
|
-
is faster in practice for ≤ 10k entities)
|
|
528
|
-
- Circle / Line / Polygon shape primitives (out of 2D AABB broadphase
|
|
529
|
-
scope; bring your own user-land wrapper)
|
|
530
|
-
- 3D octree / R-tree / KD-tree (different size class; out of scope)
|
|
531
|
-
- Anything that pushes the core gzip past 2 KB
|
|
532
|
-
|
|
533
|
-
## Design principles
|
|
534
|
-
|
|
535
|
-
aiquadtreejs follows the ai*js library-core priority order:
|
|
536
|
-
|
|
537
|
-
> Security > Correctness > Simplicity > YAGNI > Performance
|
|
538
|
-
|
|
539
|
-
Key invariants:
|
|
540
|
-
|
|
541
|
-
- `retrieve()` deduplicates with a `Set` so each candidate appears once
|
|
542
|
-
regardless of how many quadrants it spans.
|
|
543
|
-
- `clear()` reuses internal node objects across frames; no per-frame node
|
|
544
|
-
allocation.
|
|
545
|
-
- `x + width` and `y + height` are **right-open** (exclusive), matching
|
|
546
|
-
PixiJS `getBounds()` semantics.
|
|
547
|
-
- `dispose()` is idempotent.
|
|
548
|
-
|
|
549
|
-
## Commit & PR style
|
|
550
|
-
|
|
551
|
-
- Commit messages: imperative subject under 70 chars; body explains *why*.
|
|
552
|
-
- PRs: keep scope to one topic. Link the issue if any.
|
|
553
|
-
- Tests required for any behaviour change. Property-based tests welcome
|
|
554
|
-
for invariants (especially the Set-dedup property).
|
|
175
|
+
Run `pnpm lint` before PRs. If docs change, regenerate `llms-full.txt`.
|
|
555
176
|
|
|
556
|
-
##
|
|
177
|
+
## Rules
|
|
557
178
|
|
|
558
|
-
-
|
|
559
|
-
|
|
560
|
-
-
|
|
561
|
-
|
|
179
|
+
- Preserve right-open coordinate semantics.
|
|
180
|
+
- Add tests for root edges, quadrant boundaries, zero-size objects, `retrieveInto()`, `clear()`, and dispose.
|
|
181
|
+
- Do not turn broadphase results into precise collision promises.
|
|
182
|
+
- Keep allocation behavior visible in docs and tests.
|
|
562
183
|
|
|
563
184
|
## License
|
|
564
185
|
|
|
565
|
-
|
|
566
|
-
license that covers this project.
|
|
186
|
+
MIT
|
|
567
187
|
|
|
568
188
|
---
|