@flighthq/spatial 0.3.0-next.1032.e2b99fc → 0.3.0-next.1220.6982c21

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.
@@ -1,3 +1,5 @@
1
+ export * from './explainSpatialIndexing';
2
+ export * from './formatSpatialIndexingNotice';
1
3
  export * from './spatialIndex';
2
4
  export * from './uniformGrid';
3
5
  //# sourceMappingURL=contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
package/dist/contract.js CHANGED
@@ -1,3 +1,5 @@
1
+ export * from './explainSpatialIndexing';
2
+ export * from './formatSpatialIndexingNotice';
1
3
  export * from './spatialIndex';
2
4
  export * from './uniformGrid';
3
5
  //# sourceMappingURL=contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SpatialIndex, SpatialIndexingExplanation, SpatialObjectId } from '@flighthq/types/contract';
2
+ export declare function explainSpatialIndexing(index: Readonly<SpatialIndex>, id: SpatialObjectId): SpatialIndexingExplanation;
3
+ //# sourceMappingURL=explainSpatialIndexing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explainSpatialIndexing.d.ts","sourceRoot":"","sources":["../src/explainSpatialIndexing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAW1G,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,eAAe,GAAG,0BAA0B,CAErH"}
@@ -0,0 +1,13 @@
1
+ // Reports how the index is currently holding `id`, as plain data. Pure: reads only, indexes nothing,
2
+ // never throws on an unknown id, and retains no reference to the backend. Import it to answer "why is
3
+ // this object not in my query results?" — `absent` means it was never inserted or was removed,
4
+ // `declined` means its bounds were non-finite and it is deliberately not held, `overflow` means it is
5
+ // held but too large to bucket, and `cells` reports the bucket count the per-object bound caps.
6
+ //
7
+ // This is the pull half of the diagnostics convention, and the measurement seam a regression test
8
+ // asserts the cost bound against: `bucketCount` is the number that goes unbounded when the bound is
9
+ // removed, so a test can pin it without timing anything.
10
+ export function explainSpatialIndexing(index, id) {
11
+ return index.runtime.backend.explainSpatialIndexing(id);
12
+ }
13
+ //# sourceMappingURL=explainSpatialIndexing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explainSpatialIndexing.js","sourceRoot":"","sources":["../src/explainSpatialIndexing.ts"],"names":[],"mappings":"AAEA,qGAAqG;AACrG,sGAAsG;AACtG,+FAA+F;AAC/F,sGAAsG;AACtG,gGAAgG;AAChG,EAAE;AACF,kGAAkG;AAClG,oGAAoG;AACpG,yDAAyD;AACzD,MAAM,UAAU,sBAAsB,CAAC,KAA6B,EAAE,EAAmB;IACvF,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,EAAE,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { SpatialIndexingNotice } from '@flighthq/types/contract';
2
+ export declare function formatSpatialIndexingNotice(notice: Readonly<SpatialIndexingNotice>): string;
3
+ //# sourceMappingURL=formatSpatialIndexingNotice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatSpatialIndexingNotice.d.ts","sourceRoot":"","sources":["../src/formatSpatialIndexingNotice.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AActE,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,QAAQ,CAAC,qBAAqB,CAAC,GAAG,MAAM,CAU3F"}
@@ -0,0 +1,23 @@
1
+ import { MAX_INDEXED_CELLS_PER_OBJECT } from './uniformGrid';
2
+ // Renders an indexing notice as a caller-facing sentence, and is the only place in this package that
3
+ // holds one. Import it to turn the structured record a SpatialIndexingGuard receives into something a
4
+ // developer reads; leave it unimported and neither the text nor its cost reaches a bundle.
5
+ //
6
+ // The text lives here rather than inside an `enableSpatialGuards` that logs for you because
7
+ // `@flighthq/spatial` is a core-layer package and may not depend on `@flighthq/log` — the same shape
8
+ // `@flighthq/importdiagnostics` uses, where core carries the record and a separate module carries the
9
+ // words. Wiring the two together is three lines at the application layer, which can reach both:
10
+ //
11
+ // setSpatialIndexingGuard((notice) => logWarn({ message: formatSpatialIndexingNotice(notice) }, 'spatial'));
12
+ export function formatSpatialIndexingNotice(notice) {
13
+ if (notice.mode === 'declined') {
14
+ return `insertSpatialObject(${notice.id}): the bounds are not finite, so the object was not indexed and no query will return it. Insert returns false for this — check the sentinel, and check what produced NaN/Infinity bounds upstream.`;
15
+ }
16
+ if (notice.mode === 'overflow') {
17
+ return `insertSpatialObject(${notice.id}): the bounds span ${notice.wouldOccupyBucketCount} cells, over the ${MAX_INDEXED_CELLS_PER_OBJECT} per-object budget, so the object is held in the flat overflow list instead of the grid. Results are unaffected. If this is not a one-off outlier, the grid's cellSize is too small for the objects being indexed — size it to a typical object.`;
18
+ }
19
+ // `cells` and `absent` are ordinary outcomes no guard reports, so reaching here means a backend
20
+ // notified about something this text does not cover. Say exactly that rather than inventing advice.
21
+ return `insertSpatialObject(${notice.id}): indexed as '${notice.mode}', which carries no caller-facing advice.`;
22
+ }
23
+ //# sourceMappingURL=formatSpatialIndexingNotice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatSpatialIndexingNotice.js","sourceRoot":"","sources":["../src/formatSpatialIndexingNotice.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAE7D,qGAAqG;AACrG,sGAAsG;AACtG,2FAA2F;AAC3F,EAAE;AACF,4FAA4F;AAC5F,qGAAqG;AACrG,sGAAsG;AACtG,gGAAgG;AAChG,EAAE;AACF,+GAA+G;AAC/G,MAAM,UAAU,2BAA2B,CAAC,MAAuC;IACjF,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,OAAO,uBAAuB,MAAM,CAAC,EAAE,oMAAoM,CAAC;IAC9O,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAC/B,OAAO,uBAAuB,MAAM,CAAC,EAAE,sBAAsB,MAAM,CAAC,sBAAsB,oBAAoB,4BAA4B,kPAAkP,CAAC;IAC/X,CAAC;IACD,gGAAgG;IAChG,oGAAoG;IACpG,OAAO,uBAAuB,MAAM,CAAC,EAAE,kBAAkB,MAAM,CAAC,IAAI,2CAA2C,CAAC;AAClH,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export { clearSpatialIndex, createSpatialIndex, createUniformGridSpatialBackend, insertSpatialObject, querySpatialPairs, querySpatialPoint, querySpatialRay, querySpatialRegion, removeSpatialObject, updateSpatialObject, } from './contract';
1
+ export { MAX_INDEXED_CELLS_PER_OBJECT, clearSpatialIndex, createSpatialIndex, createUniformGridSpatialBackend, explainSpatialIndexing, formatSpatialIndexingNotice, insertSpatialObject, querySpatialPairs, querySpatialPoint, querySpatialRay, querySpatialRegion, removeSpatialObject, setSpatialIndexingGuard, updateSpatialObject, } from './contract';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,+BAA+B,EAC/B,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,iBAAiB,EACjB,kBAAkB,EAClB,+BAA+B,EAC/B,sBAAsB,EACtB,2BAA2B,EAC3B,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { clearSpatialIndex, createSpatialIndex, createUniformGridSpatialBackend, insertSpatialObject, querySpatialPairs, querySpatialPoint, querySpatialRay, querySpatialRegion, removeSpatialObject, updateSpatialObject, } from './contract';
1
+ export { MAX_INDEXED_CELLS_PER_OBJECT, clearSpatialIndex, createSpatialIndex, createUniformGridSpatialBackend, explainSpatialIndexing, formatSpatialIndexingNotice, insertSpatialObject, querySpatialPairs, querySpatialPoint, querySpatialRay, querySpatialRegion, removeSpatialObject, setSpatialIndexingGuard, updateSpatialObject, } from './contract';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,+BAA+B,EAC/B,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,iBAAiB,EACjB,kBAAkB,EAClB,+BAA+B,EAC/B,sBAAsB,EACtB,2BAA2B,EAC3B,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,GACpB,MAAM,YAAY,CAAC"}
@@ -1,11 +1,11 @@
1
1
  import type { SpatialAabb, SpatialIndex, SpatialIndexBackend, SpatialObjectId, SpatialPair } from '@flighthq/types/contract';
2
2
  export declare function clearSpatialIndex(index: Readonly<SpatialIndex>): void;
3
3
  export declare function createSpatialIndex(backend?: SpatialIndexBackend): SpatialIndex;
4
- export declare function insertSpatialObject(index: Readonly<SpatialIndex>, id: SpatialObjectId, bounds: Readonly<SpatialAabb>): void;
4
+ export declare function insertSpatialObject(index: Readonly<SpatialIndex>, id: SpatialObjectId, bounds: Readonly<SpatialAabb>): boolean;
5
5
  export declare function querySpatialPairs(index: Readonly<SpatialIndex>, out: SpatialPair[]): void;
6
6
  export declare function querySpatialPoint(index: Readonly<SpatialIndex>, x: number, y: number, out: SpatialObjectId[]): void;
7
7
  export declare function querySpatialRay(index: Readonly<SpatialIndex>, x: number, y: number, dx: number, dy: number, out: SpatialObjectId[]): void;
8
8
  export declare function querySpatialRegion(index: Readonly<SpatialIndex>, region: Readonly<SpatialAabb>, out: SpatialObjectId[]): void;
9
9
  export declare function removeSpatialObject(index: Readonly<SpatialIndex>, id: SpatialObjectId): void;
10
- export declare function updateSpatialObject(index: Readonly<SpatialIndex>, id: SpatialObjectId, bounds: Readonly<SpatialAabb>): void;
10
+ export declare function updateSpatialObject(index: Readonly<SpatialIndex>, id: SpatialObjectId, bounds: Readonly<SpatialAabb>): boolean;
11
11
  //# sourceMappingURL=spatialIndex.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spatialIndex.d.ts","sourceRoot":"","sources":["../src/spatialIndex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAErE;AAMD,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAM9E;AAID,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC7B,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,GAC5B,IAAI,CAEN;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,IAAI,CAEzF;AAGD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,EAAE,GAAG,IAAI,CAEnH;AAID,wBAAgB,eAAe,CAC7B,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC7B,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,eAAe,EAAE,GACrB,IAAI,CAEN;AAGD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC7B,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC7B,GAAG,EAAE,eAAe,EAAE,GACrB,IAAI,CAEN;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,eAAe,GAAG,IAAI,CAE5F;AAGD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC7B,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,GAC5B,IAAI,CAEN"}
1
+ {"version":3,"file":"spatialIndex.d.ts","sourceRoot":"","sources":["../src/spatialIndex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,mBAAmB,EACnB,eAAe,EACf,WAAW,EACZ,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,GAAG,IAAI,CAErE;AAMD,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,YAAY,CAM9E;AAOD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC7B,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,GAC5B,OAAO,CAET;AAKD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,GAAG,IAAI,CAEzF;AAGD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,eAAe,EAAE,GAAG,IAAI,CAEnH;AAID,wBAAgB,eAAe,CAC7B,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC7B,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,EAAE,EAAE,MAAM,EACV,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,eAAe,EAAE,GACrB,IAAI,CAEN;AAGD,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC7B,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,EAC7B,GAAG,EAAE,eAAe,EAAE,GACrB,IAAI,CAEN;AAGD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,eAAe,GAAG,IAAI,CAE5F;AAMD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC7B,EAAE,EAAE,eAAe,EACnB,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,GAC5B,OAAO,CAET"}
@@ -15,9 +15,12 @@ export function createSpatialIndex(backend) {
15
15
  };
16
16
  }
17
17
  // Adds an object to the index under `id` with its current bounds. The bounds are copied; the caller
18
- // may reuse its own value afterward.
18
+ // may reuse its own value afterward. Returns false when the bounds cannot be indexed at all
19
+ // (non-finite) — the object is then absent from every query rather than present at a nonsense
20
+ // position. Oversized-but-finite bounds return true and stay fully queryable; the backend decides how
21
+ // to hold them. explainSpatialIndexing reports which happened.
19
22
  export function insertSpatialObject(index, id, bounds) {
20
- index.runtime.backend.insertSpatialObject(id, bounds);
23
+ return index.runtime.backend.insertSpatialObject(id, bounds);
21
24
  }
22
25
  // Fills `out` (cleared first) with every deduplicated candidate pair — each unordered pair at most
23
26
  // once, never an object with itself. A pair is a broadphase candidate: the two objects are close
@@ -43,8 +46,11 @@ export function removeSpatialObject(index, id) {
43
46
  index.runtime.backend.removeSpatialObject(id);
44
47
  }
45
48
  // Moves an already-inserted object to new bounds. Inserting a not-yet-present id behaves as insert.
49
+ // Returns the same sentinel as insertSpatialObject. A declined update removes the object rather than
50
+ // leaving it at its old bounds, so a caller that ignores the sentinel can never read a stale position
51
+ // as a current one.
46
52
  export function updateSpatialObject(index, id, bounds) {
47
- index.runtime.backend.updateSpatialObject(id, bounds);
53
+ return index.runtime.backend.updateSpatialObject(id, bounds);
48
54
  }
49
55
  // The default uniform-grid cell size when createSpatialIndex is called without an explicit backend —
50
56
  // a middle-of-the-road choice; a workload with a known typical object size should pass its own grid.
@@ -1 +1 @@
1
- {"version":3,"file":"spatialIndex.js","sourceRoot":"","sources":["../src/spatialIndex.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,+BAA+B,EAAE,MAAM,eAAe,CAAC;AAEhE,gFAAgF;AAChF,MAAM,UAAU,iBAAiB,CAAC,KAA6B;IAC7D,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;AAC5C,CAAC;AAED,yFAAyF;AACzF,kGAAkG;AAClG,mGAAmG;AACnG,4DAA4D;AAC5D,MAAM,UAAU,kBAAkB,CAAC,OAA6B;IAC9D,OAAO;QACL,OAAO,EAAE;YACP,OAAO,EAAE,OAAO,IAAI,+BAA+B,CAAC,yBAAyB,CAAC;SAC/E;KACF,CAAC;AACJ,CAAC;AAED,oGAAoG;AACpG,qCAAqC;AACrC,MAAM,UAAU,mBAAmB,CACjC,KAA6B,EAC7B,EAAmB,EACnB,MAA6B;IAE7B,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AACxD,CAAC;AAED,mGAAmG;AACnG,iGAAiG;AACjG,8FAA8F;AAC9F,MAAM,UAAU,iBAAiB,CAAC,KAA6B,EAAE,GAAkB;IACjF,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,iBAAiB,CAAC,KAA6B,EAAE,CAAS,EAAE,CAAS,EAAE,GAAsB;IAC3G,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;AACrD,CAAC;AAED,iGAAiG;AACjG,kGAAkG;AAClG,MAAM,UAAU,eAAe,CAC7B,KAA6B,EAC7B,CAAS,EACT,CAAS,EACT,EAAU,EACV,EAAU,EACV,GAAsB;IAEtB,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,kBAAkB,CAChC,KAA6B,EAC7B,MAA6B,EAC7B,GAAsB;IAEtB,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACxD,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,mBAAmB,CAAC,KAA6B,EAAE,EAAmB;IACpF,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,oGAAoG;AACpG,MAAM,UAAU,mBAAmB,CACjC,KAA6B,EAC7B,EAAmB,EACnB,MAA6B;IAE7B,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AACxD,CAAC;AAED,qGAAqG;AACrG,qGAAqG;AACrG,MAAM,yBAAyB,GAAG,GAAG,CAAC"}
1
+ {"version":3,"file":"spatialIndex.js","sourceRoot":"","sources":["../src/spatialIndex.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,+BAA+B,EAAE,MAAM,eAAe,CAAC;AAEhE,gFAAgF;AAChF,MAAM,UAAU,iBAAiB,CAAC,KAA6B;IAC7D,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;AAC5C,CAAC;AAED,yFAAyF;AACzF,kGAAkG;AAClG,mGAAmG;AACnG,4DAA4D;AAC5D,MAAM,UAAU,kBAAkB,CAAC,OAA6B;IAC9D,OAAO;QACL,OAAO,EAAE;YACP,OAAO,EAAE,OAAO,IAAI,+BAA+B,CAAC,yBAAyB,CAAC;SAC/E;KACF,CAAC;AACJ,CAAC;AAED,oGAAoG;AACpG,4FAA4F;AAC5F,8FAA8F;AAC9F,sGAAsG;AACtG,+DAA+D;AAC/D,MAAM,UAAU,mBAAmB,CACjC,KAA6B,EAC7B,EAAmB,EACnB,MAA6B;IAE7B,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AAC/D,CAAC;AAED,mGAAmG;AACnG,iGAAiG;AACjG,8FAA8F;AAC9F,MAAM,UAAU,iBAAiB,CAAC,KAA6B,EAAE,GAAkB;IACjF,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,iBAAiB,CAAC,KAA6B,EAAE,CAAS,EAAE,CAAS,EAAE,GAAsB;IAC3G,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;AACrD,CAAC;AAED,iGAAiG;AACjG,kGAAkG;AAClG,MAAM,UAAU,eAAe,CAC7B,KAA6B,EAC7B,CAAS,EACT,CAAS,EACT,EAAU,EACV,EAAU,EACV,GAAsB;IAEtB,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,kBAAkB,CAChC,KAA6B,EAC7B,MAA6B,EAC7B,GAAsB;IAEtB,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACxD,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,mBAAmB,CAAC,KAA6B,EAAE,EAAmB;IACpF,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,oGAAoG;AACpG,qGAAqG;AACrG,sGAAsG;AACtG,oBAAoB;AACpB,MAAM,UAAU,mBAAmB,CACjC,KAA6B,EAC7B,EAAmB,EACnB,MAA6B;IAE7B,OAAO,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;AAC/D,CAAC;AAED,qGAAqG;AACrG,qGAAqG;AACrG,MAAM,yBAAyB,GAAG,GAAG,CAAC"}
@@ -1,3 +1,5 @@
1
- import type { SpatialIndexBackend } from '@flighthq/types/contract';
1
+ import type { SpatialIndexBackend, SpatialIndexingGuard } from '@flighthq/types/contract';
2
+ export declare const MAX_INDEXED_CELLS_PER_OBJECT = 1024;
2
3
  export declare function createUniformGridSpatialBackend(cellSize: number): SpatialIndexBackend;
4
+ export declare function setSpatialIndexingGuard(guard: SpatialIndexingGuard | null): void;
3
5
  //# sourceMappingURL=uniformGrid.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"uniformGrid.d.ts","sourceRoot":"","sources":["../src/uniformGrid.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,mBAAmB,EAGpB,MAAM,0BAA0B,CAAC;AASlC,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CA0CrF"}
1
+ {"version":3,"file":"uniformGrid.d.ts","sourceRoot":"","sources":["../src/uniformGrid.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,mBAAmB,EAEnB,oBAAoB,EAGrB,MAAM,0BAA0B,CAAC;AAiBlC,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAajD,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,CA+CrF;AAOD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,GAAG,IAAI,CAEhF"}
@@ -1,4 +1,20 @@
1
1
  import { containsRectanglePointXY, intersectsRectangle } from '@flighthq/geometry/contract';
2
+ // The per-object cell budget. An object whose AABB covers more cells than this is held in the flat
3
+ // overflow list instead of being written into every cell it spans.
4
+ //
5
+ // This bound is what makes insert cost independent of object size. Without it the cell walk is
6
+ // proportional to extent ÷ cellSize *squared*, so cost is set by the largest object rather than by
7
+ // the object count: measured on this grid at one cell per unit, a 200-unit box costs 40k cell writes
8
+ // (~28 ms) and a 2000-unit box costs 4M (~4.7 s), while an AABB a trillion units wide — reachable
9
+ // from any diverging simulation — never returns at all. A hang is worse than a throw because it is
10
+ // uncatchable and takes the caller with it.
11
+ //
12
+ // Overflow is not a degraded result: an object that spans a thousand cells is a co-occupant of nearly
13
+ // everything, so the cell index tells the queries almost nothing and a linear scan answers the same
14
+ // question at a bounded cost. 1024 cells is a 32×32 block — an object 32× the cell size on each axis,
15
+ // far past the "cell size ≈ typical object size" the grid is built around. An object over the budget
16
+ // is a signal the cell size is wrong for the workload, which is what enableSpatialGuards reports.
17
+ export const MAX_INDEXED_CELLS_PER_OBJECT = 1024;
2
18
  // Builds a uniform-grid (spatial-hash) backend: an object's AABB is mapped to the rectangular block
3
19
  // of fixed-size cells it covers, and each cell holds the ids overlapping it. Co-located objects share
4
20
  // a cell, so candidate pairs and region/point/ray hits are found by looking only at the relevant
@@ -6,25 +22,30 @@ import { containsRectanglePointXY, intersectsRectangle } from '@flighthq/geometr
6
22
  // good value is roughly the size of a typical object (too small over-spans large objects across many
7
23
  // cells, too large lumps unrelated objects together). No import-time side effect — the caller
8
24
  // constructs a grid explicitly, and createSpatialIndex uses this as its default index.
25
+ //
26
+ // Object size does not set the cost: an AABB spanning more than MAX_INDEXED_CELLS_PER_OBJECT cells
27
+ // goes to a flat overflow list that every query scans, and non-finite bounds are declined outright
28
+ // with a false sentinel. Both are visible through explainSpatialIndexing.
9
29
  export function createUniformGridSpatialBackend(cellSize) {
10
30
  const grid = {
11
31
  cellSize,
12
32
  cells: new Map(),
13
33
  bounds: new Map(),
34
+ overflow: new Set(),
35
+ declined: new Set(),
14
36
  minCellX: 0,
15
37
  minCellY: 0,
16
38
  maxCellX: 0,
17
39
  maxCellY: 0,
18
- empty: true,
19
40
  seen: new Set(),
20
41
  };
21
42
  return {
22
43
  insertSpatialObject(id, bounds) {
23
- _insertIntoGrid(grid, id, bounds);
44
+ return _insertIntoGrid(grid, id, bounds);
24
45
  },
25
46
  updateSpatialObject(id, bounds) {
26
47
  _removeFromGrid(grid, id);
27
- _insertIntoGrid(grid, id, bounds);
48
+ return _insertIntoGrid(grid, id, bounds);
28
49
  },
29
50
  removeSpatialObject(id) {
30
51
  _removeFromGrid(grid, id);
@@ -32,8 +53,12 @@ export function createUniformGridSpatialBackend(cellSize) {
32
53
  clearSpatialIndex() {
33
54
  grid.cells.clear();
34
55
  grid.bounds.clear();
56
+ grid.overflow.clear();
57
+ grid.declined.clear();
35
58
  grid.seen.clear();
36
- grid.empty = true;
59
+ },
60
+ explainSpatialIndexing(id) {
61
+ return _explainGridIndexing(grid, id);
37
62
  },
38
63
  querySpatialPairs(out) {
39
64
  _queryGridPairs(grid, out);
@@ -49,6 +74,14 @@ export function createUniformGridSpatialBackend(cellSize) {
49
74
  },
50
75
  };
51
76
  }
77
+ // Installs the indexing guard consulted whenever an object is declined or routed to overflow; null
78
+ // uninstalls it. This is the diagnostics seam, not the caller-facing entry point — use
79
+ // enableSpatialGuards, which installs the @flighthq/log reporter through here. Module-scoped rather
80
+ // than per-grid: it is a development diagnostic, and an application debugging a missing object wants
81
+ // it on for every index at once.
82
+ export function setSpatialIndexingGuard(guard) {
83
+ _indexingGuard = guard;
84
+ }
52
85
  // Maps a world coordinate to its cell index along one axis. Uses floor so negative coordinates map
53
86
  // to consistently decreasing cell indices (world 0 is the boundary between cell -1 and cell 0).
54
87
  function _cellIndex(coord, cellSize) {
@@ -69,15 +102,62 @@ function _fillRectFromAabb(out, aabb) {
69
102
  out.width = aabb.maxX - aabb.minX;
70
103
  out.height = aabb.maxY - aabb.minY;
71
104
  }
72
- // Adds an object to every cell its AABB covers, storing a private copy of the bounds and expanding
73
- // the occupied cell range. The caller may safely mutate or reuse the passed bounds afterward.
105
+ // Reports how `id` is currently held. Pure: reads only, allocates only the returned record.
106
+ function _explainGridIndexing(grid, id) {
107
+ if (grid.declined.has(id))
108
+ return { bucketCount: 0, id, mode: 'declined', reason: 'non-finite-bounds' };
109
+ if (grid.overflow.has(id))
110
+ return { bucketCount: 0, id, mode: 'overflow', reason: null };
111
+ const bounds = grid.bounds.get(id);
112
+ if (bounds === undefined)
113
+ return { bucketCount: 0, id, mode: 'absent', reason: null };
114
+ return { bucketCount: _spannedCellCount(grid.cellSize, bounds), id, mode: 'cells', reason: null };
115
+ }
116
+ // Adds an object to the index, storing a private copy of the bounds; the caller may safely mutate or
117
+ // reuse the passed bounds afterward. Returns false only for bounds that cannot be indexed at all.
118
+ //
119
+ // Three outcomes, and which one applies is decided *before* any cell is touched — that ordering is
120
+ // the whole point, since deciding afterward would mean walking the cells to find out they were too
121
+ // many:
122
+ // - non-finite bounds are declined. There is no cell range to compute, and storing them would let a
123
+ // NaN leak into every later overlap test, so the object is left out of the index entirely and the
124
+ // caller gets a false sentinel rather than an exception or a silent no-op.
125
+ // - bounds spanning more than MAX_INDEXED_CELLS_PER_OBJECT cells go to the overflow list, which
126
+ // every query scans. Bounded by the object count instead of the object's size.
127
+ // - everything else takes the ordinary path: one entry in each cell it covers.
74
128
  function _insertIntoGrid(grid, id, bounds) {
129
+ if (!Number.isFinite(bounds.minX) ||
130
+ !Number.isFinite(bounds.minY) ||
131
+ !Number.isFinite(bounds.maxX) ||
132
+ !Number.isFinite(bounds.maxY)) {
133
+ grid.declined.add(id);
134
+ if (_indexingGuard !== null) {
135
+ _indexingGuard({ id, mode: 'declined', reason: 'non-finite-bounds', wouldOccupyBucketCount: 0 });
136
+ }
137
+ return false;
138
+ }
75
139
  const cs = grid.cellSize;
76
- const cx0 = _cellIndex(bounds.minX, cs);
77
- const cx1 = _cellIndex(bounds.maxX, cs);
78
- const cy0 = _cellIndex(bounds.minY, cs);
79
- const cy1 = _cellIndex(bounds.maxY, cs);
80
- grid.bounds.set(id, { minX: bounds.minX, minY: bounds.minY, maxX: bounds.maxX, maxY: bounds.maxY });
140
+ const copy = { minX: bounds.minX, minY: bounds.minY, maxX: bounds.maxX, maxY: bounds.maxY };
141
+ const spanned = _spannedCellCount(cs, copy);
142
+ // Written as a negated `<=` so a NaN span — a zero or non-finite cellSize makes every cell index
143
+ // NaN — falls to overflow rather than through to a loop whose bounds are NaN and which therefore
144
+ // never runs, silently indexing nothing.
145
+ if (!(spanned <= MAX_INDEXED_CELLS_PER_OBJECT)) {
146
+ grid.bounds.set(id, copy);
147
+ grid.overflow.add(id);
148
+ if (_indexingGuard !== null) {
149
+ _indexingGuard({ id, mode: 'overflow', reason: null, wouldOccupyBucketCount: spanned });
150
+ }
151
+ return true;
152
+ }
153
+ const cx0 = _cellIndex(copy.minX, cs);
154
+ const cx1 = _cellIndex(copy.maxX, cs);
155
+ const cy0 = _cellIndex(copy.minY, cs);
156
+ const cy1 = _cellIndex(copy.maxY, cs);
157
+ // Whether a cell range exists yet, read *before* this object's cells are added. A first celled
158
+ // object seeds the range; a later one widens it.
159
+ const hadCells = grid.cells.size !== 0;
160
+ grid.bounds.set(id, copy);
81
161
  for (let cy = cy0; cy <= cy1; cy++) {
82
162
  for (let cx = cx0; cx <= cx1; cx++) {
83
163
  const key = _cellKey(cx, cy);
@@ -89,12 +169,11 @@ function _insertIntoGrid(grid, id, bounds) {
89
169
  cell.ids.add(id);
90
170
  }
91
171
  }
92
- if (grid.empty) {
172
+ if (!hadCells) {
93
173
  grid.minCellX = cx0;
94
174
  grid.maxCellX = cx1;
95
175
  grid.minCellY = cy0;
96
176
  grid.maxCellY = cy1;
97
- grid.empty = false;
98
177
  }
99
178
  else {
100
179
  if (cx0 < grid.minCellX)
@@ -106,6 +185,7 @@ function _insertIntoGrid(grid, id, bounds) {
106
185
  if (cy1 > grid.maxCellY)
107
186
  grid.maxCellY = cy1;
108
187
  }
188
+ return true;
109
189
  }
110
190
  // Reports whether an AABB contains the point (`x`,`y`), reusing the geometry rectangle helper.
111
191
  function _isSpatialAabbContainsPoint(aabb, x, y) {
@@ -165,11 +245,18 @@ function _rayBoxEntryT(ox, oy, dx, dy, minX, minY, maxX, maxY) {
165
245
  }
166
246
  // Removes an object from every cell its stored AABB covered and drops now-empty cells. The occupied
167
247
  // cell range is intentionally not shrunk (only reset when the grid fully empties). A no-op for an
168
- // unknown id.
248
+ // unknown id. Overflowed and declined ids leave through their own sets without a cell walk — remove
249
+ // re-derives the cell range from the stored bounds, so an overflowed object taking the ordinary path
250
+ // here would reintroduce exactly the unbounded walk the insert bound removed.
169
251
  function _removeFromGrid(grid, id) {
252
+ grid.declined.delete(id);
170
253
  const bounds = grid.bounds.get(id);
171
254
  if (bounds === undefined)
172
255
  return;
256
+ if (grid.overflow.delete(id)) {
257
+ grid.bounds.delete(id);
258
+ return;
259
+ }
173
260
  const cs = grid.cellSize;
174
261
  const cx0 = _cellIndex(bounds.minX, cs);
175
262
  const cx1 = _cellIndex(bounds.maxX, cs);
@@ -187,8 +274,30 @@ function _removeFromGrid(grid, id) {
187
274
  }
188
275
  }
189
276
  grid.bounds.delete(id);
190
- if (grid.bounds.size === 0)
191
- grid.empty = true;
277
+ }
278
+ // Appends the pairs involving overflowed objects. An overflowed object occupies no cell, so the cell
279
+ // walk in _queryGridPairs can never emit a pair for it and no dedup against that walk is needed. Its
280
+ // pairs are enumerated here instead: against every other indexed object, which is what "spans nearly
281
+ // the whole world" already means. Unlike the cell path these are filtered by a real AABB overlap test
282
+ // rather than emitted as bare locality candidates — the result stays a superset of the true overlaps
283
+ // either way, and an object this large would otherwise nominate every other object every frame.
284
+ function _queryGridOverflowPairs(grid, out) {
285
+ for (const id of grid.overflow) {
286
+ const bounds = grid.bounds.get(id);
287
+ if (bounds === undefined)
288
+ continue;
289
+ for (const [otherId, otherBounds] of grid.bounds) {
290
+ if (otherId === id)
291
+ continue;
292
+ // Each overflow×overflow pair would otherwise be emitted from both sides; taking it only from
293
+ // the lower id emits it once. An overflow×celled pair reaches this test from one side only.
294
+ if (grid.overflow.has(otherId) && otherId < id)
295
+ continue;
296
+ if (!_isSpatialAabbOverlapping(bounds, otherBounds))
297
+ continue;
298
+ out.push(id < otherId ? { a: id, b: otherId } : { a: otherId, b: id });
299
+ }
300
+ }
192
301
  }
193
302
  // Enumerates candidate pairs. Within each cell every co-occupant pair is a candidate, but a pair may
194
303
  // share several cells; to emit it exactly once, a pair is emitted only from its canonical cell — the
@@ -223,16 +332,25 @@ function _queryGridPairs(grid, out) {
223
332
  }
224
333
  }
225
334
  }
335
+ if (grid.overflow.size !== 0)
336
+ _queryGridOverflowPairs(grid, out);
226
337
  }
227
338
  // Gathers the ids in the cell containing the point, then confirms each against its real bounds. A
228
- // single cell holds each id at most once, so no dedup pass is needed.
339
+ // single cell holds each id at most once, so no dedup pass is needed. Overflowed objects hold no cell
340
+ // and are tested directly; they cannot collide with the cell gather, so no dedup is needed there
341
+ // either.
229
342
  function _queryGridPoint(grid, x, y, out) {
230
343
  out.length = 0;
231
344
  const cs = grid.cellSize;
232
345
  const cell = grid.cells.get(_cellKey(_cellIndex(x, cs), _cellIndex(y, cs)));
233
- if (cell === undefined)
234
- return;
235
- for (const id of cell.ids) {
346
+ if (cell !== undefined) {
347
+ for (const id of cell.ids) {
348
+ const bounds = grid.bounds.get(id);
349
+ if (bounds !== undefined && _isSpatialAabbContainsPoint(bounds, x, y))
350
+ out.push(id);
351
+ }
352
+ }
353
+ for (const id of grid.overflow) {
236
354
  const bounds = grid.bounds.get(id);
237
355
  if (bounds !== undefined && _isSpatialAabbContainsPoint(bounds, x, y))
238
356
  out.push(id);
@@ -244,8 +362,6 @@ function _queryGridPoint(grid, x, y, out) {
244
362
  // point query at the origin. An empty grid returns nothing.
245
363
  function _queryGridRay(grid, ox, oy, dx, dy, out) {
246
364
  out.length = 0;
247
- if (grid.empty)
248
- return;
249
365
  const cs = grid.cellSize;
250
366
  const seen = grid.seen;
251
367
  seen.clear();
@@ -253,6 +369,23 @@ function _queryGridRay(grid, ox, oy, dx, dy, out) {
253
369
  _queryGridPoint(grid, ox, oy, out);
254
370
  return;
255
371
  }
372
+ // Overflowed objects are outside the cell range the DDA walks — deliberately, so one huge AABB
373
+ // cannot stretch that range and make every ray traverse it — so they are slab-tested directly.
374
+ for (const id of grid.overflow) {
375
+ const bounds = grid.bounds.get(id);
376
+ if (bounds !== undefined &&
377
+ _rayBoxEntryT(ox, oy, dx, dy, bounds.minX, bounds.minY, bounds.maxX, bounds.maxY) >= 0) {
378
+ out.push(id);
379
+ }
380
+ }
381
+ // The cell range describes the CELLS, so the cells decide whether it means anything. Reading a
382
+ // separately-maintained `empty` flag here was the defect: it was set from `bounds.size`, which counts
383
+ // overflowed objects too, so removing the last CELLED object while any overflowed object remained
384
+ // left the flag false and the min/max range stale — and this walk then stepped across that stale
385
+ // range, unbounded, with no cells left to find. Deriving the fact removes the class: there is no
386
+ // longer a flag that can disagree with the structure it describes, at any transition.
387
+ if (grid.cells.size === 0)
388
+ return;
256
389
  const boxMinX = grid.minCellX * cs;
257
390
  const boxMinY = grid.minCellY * cs;
258
391
  const boxMaxX = (grid.maxCellX + 1) * cs;
@@ -316,12 +449,25 @@ function _queryGridRay(grid, ox, oy, dx, dy, out) {
316
449
  }
317
450
  // Gathers the ids in every cell the region covers (deduplicated via the reused scratch set), then
318
451
  // confirms each against the region's real bounds — so a false cell-mate whose bounds miss the region
319
- // is dropped.
452
+ // is dropped. Overflowed objects hold no cell and are tested directly.
453
+ //
454
+ // The region is caller-supplied, so it carries the same unbounded-walk hazard the insert bound closes
455
+ // and needs its own guard: a query region wider than the world costs extent ÷ cellSize squared cell
456
+ // lookups against a grid that may hold one object. When the region spans more cells than the grid has
457
+ // occupied, walking the objects is both cheaper and exact, so the walk flips — a region query is
458
+ // never more expensive than a full scan.
320
459
  function _queryGridRegion(grid, region, out) {
321
460
  out.length = 0;
322
461
  const cs = grid.cellSize;
323
462
  const seen = grid.seen;
324
463
  seen.clear();
464
+ if (!(_spannedCellCount(cs, region) <= grid.cells.size)) {
465
+ for (const [id, bounds] of grid.bounds) {
466
+ if (_isSpatialAabbOverlapping(bounds, region))
467
+ out.push(id);
468
+ }
469
+ return;
470
+ }
325
471
  const cx0 = _cellIndex(region.minX, cs);
326
472
  const cx1 = _cellIndex(region.maxX, cs);
327
473
  const cy0 = _cellIndex(region.minY, cs);
@@ -341,7 +487,28 @@ function _queryGridRegion(grid, region, out) {
341
487
  }
342
488
  }
343
489
  }
490
+ for (const id of grid.overflow) {
491
+ const bounds = grid.bounds.get(id);
492
+ if (bounds !== undefined && _isSpatialAabbOverlapping(bounds, region))
493
+ out.push(id);
494
+ }
495
+ }
496
+ // How many cells an AABB's span covers, as a count rather than a walk — the number the per-object
497
+ // budget is compared against, computed before any cell is touched. Returns NaN for bounds or a cell
498
+ // size that make the cell indices non-finite; callers test it with a negated `<=` so NaN falls to the
499
+ // bounded path rather than through it.
500
+ function _spannedCellCount(cellSize, aabb) {
501
+ const cx0 = _cellIndex(aabb.minX, cellSize);
502
+ const cx1 = _cellIndex(aabb.maxX, cellSize);
503
+ const cy0 = _cellIndex(aabb.minY, cellSize);
504
+ const cy1 = _cellIndex(aabb.maxY, cellSize);
505
+ return (cx1 - cx0 + 1) * (cy1 - cy0 + 1);
344
506
  }
507
+ // Diagnostics seam: enableSpatialGuards (separately imported, so its message text and its
508
+ // @flighthq/log dependency shake out of a production bundle) fills this, and the insert path reaches
509
+ // it through the null check. Not enabling guards costs one comparison per declined-or-overflowed
510
+ // insert — and nothing at all on the ordinary path.
511
+ let _indexingGuard = null;
345
512
  // Reused scratch rectangles for the geometry overlap/containment helpers. Only ever read+written
346
513
  // within a single non-nested helper call, so sharing them across the module allocates nothing per
347
514
  // query without aliasing hazard.
@@ -1 +1 @@
1
- {"version":3,"file":"uniformGrid.js","sourceRoot":"","sources":["../src/uniformGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAS5F,oGAAoG;AACpG,sGAAsG;AACtG,iGAAiG;AACjG,mGAAmG;AACnG,qGAAqG;AACrG,8FAA8F;AAC9F,uFAAuF;AACvF,MAAM,UAAU,+BAA+B,CAAC,QAAgB;IAC9D,MAAM,IAAI,GAAgB;QACxB,QAAQ;QACR,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,MAAM,EAAE,IAAI,GAAG,EAAE;QACjB,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;QACX,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,IAAI,GAAG,EAAE;KAChB,CAAC;IACF,OAAO;QACL,mBAAmB,CAAC,EAAE,EAAE,MAAM;YAC5B,eAAe,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;QACD,mBAAmB,CAAC,EAAE,EAAE,MAAM;YAC5B,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC1B,eAAe,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;QACpC,CAAC;QACD,mBAAmB,CAAC,EAAE;YACpB,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5B,CAAC;QACD,iBAAiB;YACf,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAClB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;QACD,iBAAiB,CAAC,GAAG;YACnB,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC7B,CAAC;QACD,kBAAkB,CAAC,MAAM,EAAE,GAAG;YAC5B,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC;QACD,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG;YACzB,eAAe,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG;YAC/B,aAAa,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC;AA6BD,mGAAmG;AACnG,gGAAgG;AAChG,SAAS,UAAU,CAAC,KAAa,EAAE,QAAgB;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED,+FAA+F;AAC/F,+FAA+F;AAC/F,SAAS,QAAQ,CAAC,EAAU,EAAE,EAAU;IACtC,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC;AACvB,CAAC;AAED,+FAA+F;AAC/F,oGAAoG;AACpG,mGAAmG;AACnG,yDAAyD;AACzD,SAAS,iBAAiB,CAAC,GAAkB,EAAE,IAA2B;IACxE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IAClB,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IAClB,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IAClC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;AACrC,CAAC;AAED,mGAAmG;AACnG,8FAA8F;AAC9F,SAAS,eAAe,CAAC,IAAiB,EAAE,EAAmB,EAAE,MAA6B;IAC5F,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACpG,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;QACnC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC7B,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;gBAClC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC5B,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QAC7C,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QAC7C,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QAC7C,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;IAC/C,CAAC;AACH,CAAC;AAED,+FAA+F;AAC/F,SAAS,2BAA2B,CAAC,IAA2B,EAAE,CAAS,EAAE,CAAS;IACpF,iBAAiB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACvC,OAAO,wBAAwB,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,kGAAkG;AAClG,qDAAqD;AACrD,SAAS,yBAAyB,CAAC,CAAwB,EAAE,CAAwB;IACnF,iBAAiB,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACpC,iBAAiB,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACpC,OAAO,mBAAmB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;AAC3D,CAAC;AAED,qGAAqG;AACrG,mGAAmG;AACnG,wFAAwF;AACxF,SAAS,aAAa,CACpB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,IAAY,EACZ,IAAY,EACZ,IAAY,EACZ,IAAY;IAEZ,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC;IACrB,IAAI,IAAI,GAAG,QAAQ,CAAC;IACpB,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;QACnB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC;QAC3B,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC;QAC3B,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,EAAE,CAAC;YACb,EAAE,GAAG,EAAE,CAAC;YACR,EAAE,GAAG,CAAC,CAAC;QACT,CAAC;QACD,IAAI,EAAE,GAAG,IAAI;YAAE,IAAI,GAAG,EAAE,CAAC;QACzB,IAAI,EAAE,GAAG,IAAI;YAAE,IAAI,GAAG,EAAE,CAAC;IAC3B,CAAC;SAAM,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;QAClC,OAAO,CAAC,CAAC,CAAC;IACZ,CAAC;IACD,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;QACnB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC;QAC3B,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC;QAC3B,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,EAAE,CAAC;YACb,EAAE,GAAG,EAAE,CAAC;YACR,EAAE,GAAG,CAAC,CAAC;QACT,CAAC;QACD,IAAI,EAAE,GAAG,IAAI;YAAE,IAAI,GAAG,EAAE,CAAC;QACzB,IAAI,EAAE,GAAG,IAAI;YAAE,IAAI,GAAG,EAAE,CAAC;IAC3B,CAAC;SAAM,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;QAClC,OAAO,CAAC,CAAC,CAAC;IACZ,CAAC;IACD,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC;IACvC,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,oGAAoG;AACpG,kGAAkG;AAClG,cAAc;AACd,SAAS,eAAe,CAAC,IAAiB,EAAE,EAAmB;IAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACnC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO;IACjC,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;QACnC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,IAAI,KAAK,SAAS;gBAAE,SAAS;YACjC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACpB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACvB,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;AAChD,CAAC;AAED,qGAAqG;AACrG,qGAAqG;AACrG,mGAAmG;AACnG,kGAAkG;AAClG,sGAAsG;AACtG,SAAS,eAAe,CAAC,IAAiB,EAAE,GAAkB;IAC5D,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QACrB,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC;YAAE,SAAS;QAC3B,MAAM,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBAChB,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBAChB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACV,MAAM,CAAC,GAAG,CAAC,CAAC;oBACZ,CAAC,GAAG,CAAC,CAAC;oBACN,CAAC,GAAG,CAAC,CAAC;gBACR,CAAC;gBACD,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC9B,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC9B,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;oBAAE,SAAS;gBACnD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC9E,IAAI,IAAI,CAAC,EAAE,KAAK,UAAU,IAAI,IAAI,CAAC,EAAE,KAAK,UAAU;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,sEAAsE;AACtE,SAAS,eAAe,CAAC,IAAiB,EAAE,CAAS,EAAE,CAAS,EAAE,GAAsB;IACtF,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5E,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO;IAC/B,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,SAAS,IAAI,2BAA2B,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,kGAAkG;AAClG,oGAAoG;AACpG,4DAA4D;AAC5D,SAAS,aAAa,CACpB,IAAiB,EACjB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,GAAsB;IAEtB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,IAAI,CAAC,KAAK;QAAE,OAAO;IACvB,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACzB,eAAe,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACjF,IAAI,MAAM,GAAG,CAAC;QAAE,OAAO;IACvB,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,GAAG,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,GAAG,EAAE,CAAC;IAChC,IAAI,EAAE,GAAG,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChC,IAAI,EAAE,GAAG,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChC,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;QAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;SACtC,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;QAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChD,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;QAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;SACtC,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;QAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChD,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,IAAI,KAAK,GAAG,QAAQ,CAAC;IACrB,IAAI,OAAO,GAAG,QAAQ,CAAC;IACvB,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;QACrD,KAAK,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;QAC7B,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,KAAK,GAAG,QAAQ,CAAC;IACrB,IAAI,OAAO,GAAG,QAAQ,CAAC;IACvB,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;QACrD,KAAK,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;QAC7B,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACrF,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;QAC5C,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;YAAE,MAAM;QAChG,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG;gBAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,KAAK,GAAG,KAAK,EAAE,CAAC;YAClB,EAAE,IAAI,KAAK,CAAC;YACZ,KAAK,IAAI,OAAO,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,EAAE,IAAI,KAAK,CAAC;YACZ,KAAK,IAAI,OAAO,CAAC;QACnB,CAAC;IACH,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IACE,MAAM,KAAK,SAAS;YACpB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EACtF,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;IACH,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,qGAAqG;AACrG,cAAc;AACd,SAAS,gBAAgB,CAAC,IAAiB,EAAE,MAA6B,EAAE,GAAsB;IAChG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;QACnC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YAC9C,IAAI,IAAI,KAAK,SAAS;gBAAE,SAAS;YACjC,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC3B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACb,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACnC,IAAI,MAAM,KAAK,SAAS,IAAI,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACtF,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,iGAAiG;AACjG,kGAAkG;AAClG,iCAAiC;AACjC,MAAM,aAAa,GAAkB,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AACzE,MAAM,aAAa,GAAkB,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC"}
1
+ {"version":3,"file":"uniformGrid.js","sourceRoot":"","sources":["../src/uniformGrid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAW5F,mGAAmG;AACnG,mEAAmE;AACnE,EAAE;AACF,+FAA+F;AAC/F,mGAAmG;AACnG,qGAAqG;AACrG,kGAAkG;AAClG,mGAAmG;AACnG,4CAA4C;AAC5C,EAAE;AACF,sGAAsG;AACtG,oGAAoG;AACpG,sGAAsG;AACtG,qGAAqG;AACrG,kGAAkG;AAClG,MAAM,CAAC,MAAM,4BAA4B,GAAG,IAAI,CAAC;AAEjD,oGAAoG;AACpG,sGAAsG;AACtG,iGAAiG;AACjG,mGAAmG;AACnG,qGAAqG;AACrG,8FAA8F;AAC9F,uFAAuF;AACvF,EAAE;AACF,mGAAmG;AACnG,mGAAmG;AACnG,0EAA0E;AAC1E,MAAM,UAAU,+BAA+B,CAAC,QAAgB;IAC9D,MAAM,IAAI,GAAgB;QACxB,QAAQ;QACR,KAAK,EAAE,IAAI,GAAG,EAAE;QAChB,MAAM,EAAE,IAAI,GAAG,EAAE;QACjB,QAAQ,EAAE,IAAI,GAAG,EAAE;QACnB,QAAQ,EAAE,IAAI,GAAG,EAAE;QACnB,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;QACX,QAAQ,EAAE,CAAC;QACX,IAAI,EAAE,IAAI,GAAG,EAAE;KAChB,CAAC;IACF,OAAO;QACL,mBAAmB,CAAC,EAAE,EAAE,MAAM;YAC5B,OAAO,eAAe,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;QAC3C,CAAC;QACD,mBAAmB,CAAC,EAAE,EAAE,MAAM;YAC5B,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAC1B,OAAO,eAAe,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;QAC3C,CAAC;QACD,mBAAmB,CAAC,EAAE;YACpB,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5B,CAAC;QACD,iBAAiB;YACf,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACnB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACpB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACtB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;QACD,sBAAsB,CAAC,EAAE;YACvB,OAAO,oBAAoB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACxC,CAAC;QACD,iBAAiB,CAAC,GAAG;YACnB,eAAe,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAC7B,CAAC;QACD,kBAAkB,CAAC,MAAM,EAAE,GAAG;YAC5B,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC;QACD,iBAAiB,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG;YACzB,eAAe,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG;YAC/B,aAAa,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACzC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,mGAAmG;AACnG,uFAAuF;AACvF,oGAAoG;AACpG,qGAAqG;AACrG,iCAAiC;AACjC,MAAM,UAAU,uBAAuB,CAAC,KAAkC;IACxE,cAAc,GAAG,KAAK,CAAC;AACzB,CAAC;AAkCD,mGAAmG;AACnG,gGAAgG;AAChG,SAAS,UAAU,CAAC,KAAa,EAAE,QAAgB;IACjD,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC,CAAC;AACtC,CAAC;AAED,+FAA+F;AAC/F,+FAA+F;AAC/F,SAAS,QAAQ,CAAC,EAAU,EAAE,EAAU;IACtC,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC;AACvB,CAAC;AAED,+FAA+F;AAC/F,oGAAoG;AACpG,mGAAmG;AACnG,yDAAyD;AACzD,SAAS,iBAAiB,CAAC,GAAkB,EAAE,IAA2B;IACxE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IAClB,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;IAClB,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IAClC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;AACrC,CAAC;AAED,4FAA4F;AAC5F,SAAS,oBAAoB,CAAC,IAAiB,EAAE,EAAmB;IAClE,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IACxG,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACzF,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACnC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACtF,OAAO,EAAE,WAAW,EAAE,iBAAiB,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACpG,CAAC;AAED,qGAAqG;AACrG,kGAAkG;AAClG,EAAE;AACF,mGAAmG;AACnG,mGAAmG;AACnG,QAAQ;AACR,sGAAsG;AACtG,sGAAsG;AACtG,+EAA+E;AAC/E,kGAAkG;AAClG,mFAAmF;AACnF,iFAAiF;AACjF,SAAS,eAAe,CAAC,IAAiB,EAAE,EAAmB,EAAE,MAA6B;IAC5F,IACE,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;QAC7B,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;QAC7B,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC;QAC7B,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAC7B,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtB,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC5B,cAAc,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,CAAC,EAAE,CAAC,CAAC;QACnG,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,IAAI,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IAC5F,MAAM,OAAO,GAAG,iBAAiB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC5C,iGAAiG;IACjG,iGAAiG;IACjG,yCAAyC;IACzC,IAAI,CAAC,CAAC,OAAO,IAAI,4BAA4B,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACtB,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC5B,cAAc,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,CAAC,CAAC;QAC1F,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACtC,+FAA+F;IAC/F,iDAAiD;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC;IACvC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC1B,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;QACnC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC7B,IAAI,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YAC/B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACvB,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;gBAClC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC5B,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IACD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QACpB,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;IACtB,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QAC7C,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QAC7C,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;QAC7C,IAAI,GAAG,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,GAAG,GAAG,CAAC;IAC/C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+FAA+F;AAC/F,SAAS,2BAA2B,CAAC,IAA2B,EAAE,CAAS,EAAE,CAAS;IACpF,iBAAiB,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACvC,OAAO,wBAAwB,CAAC,aAAa,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,kGAAkG;AAClG,qDAAqD;AACrD,SAAS,yBAAyB,CAAC,CAAwB,EAAE,CAAwB;IACnF,iBAAiB,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACpC,iBAAiB,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC;IACpC,OAAO,mBAAmB,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;AAC3D,CAAC;AAED,qGAAqG;AACrG,mGAAmG;AACnG,wFAAwF;AACxF,SAAS,aAAa,CACpB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,IAAY,EACZ,IAAY,EACZ,IAAY,EACZ,IAAY;IAEZ,IAAI,IAAI,GAAG,CAAC,QAAQ,CAAC;IACrB,IAAI,IAAI,GAAG,QAAQ,CAAC;IACpB,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;QACnB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC;QAC3B,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC;QAC3B,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,EAAE,CAAC;YACb,EAAE,GAAG,EAAE,CAAC;YACR,EAAE,GAAG,CAAC,CAAC;QACT,CAAC;QACD,IAAI,EAAE,GAAG,IAAI;YAAE,IAAI,GAAG,EAAE,CAAC;QACzB,IAAI,EAAE,GAAG,IAAI;YAAE,IAAI,GAAG,EAAE,CAAC;IAC3B,CAAC;SAAM,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;QAClC,OAAO,CAAC,CAAC,CAAC;IACZ,CAAC;IACD,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;QACnB,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC;QAC3B,IAAI,EAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,GAAG,GAAG,CAAC;QAC3B,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC;YACZ,MAAM,CAAC,GAAG,EAAE,CAAC;YACb,EAAE,GAAG,EAAE,CAAC;YACR,EAAE,GAAG,CAAC,CAAC;QACT,CAAC;QACD,IAAI,EAAE,GAAG,IAAI;YAAE,IAAI,GAAG,EAAE,CAAC;QACzB,IAAI,EAAE,GAAG,IAAI;YAAE,IAAI,GAAG,EAAE,CAAC;IAC3B,CAAC;SAAM,IAAI,EAAE,GAAG,IAAI,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;QAClC,OAAO,CAAC,CAAC,CAAC;IACZ,CAAC;IACD,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,CAAC;IACvC,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC;AAED,oGAAoG;AACpG,kGAAkG;AAClG,oGAAoG;AACpG,qGAAqG;AACrG,8EAA8E;AAC9E,SAAS,eAAe,CAAC,IAAiB,EAAE,EAAmB;IAC7D,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACnC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO;IACjC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO;IACT,CAAC;IACD,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;QACnC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;YACnC,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,IAAI,KAAK,SAAS;gBAAE,SAAS;YACjC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACpB,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AACzB,CAAC;AAED,qGAAqG;AACrG,qGAAqG;AACrG,qGAAqG;AACrG,sGAAsG;AACtG,qGAAqG;AACrG,gGAAgG;AAChG,SAAS,uBAAuB,CAAC,IAAiB,EAAE,GAAkB;IACpE,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,KAAK,MAAM,CAAC,OAAO,EAAE,WAAW,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACjD,IAAI,OAAO,KAAK,EAAE;gBAAE,SAAS;YAC7B,8FAA8F;YAC9F,4FAA4F;YAC5F,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,EAAE;gBAAE,SAAS;YACzD,IAAI,CAAC,yBAAyB,CAAC,MAAM,EAAE,WAAW,CAAC;gBAAE,SAAS;YAC9D,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;AACH,CAAC;AAED,qGAAqG;AACrG,qGAAqG;AACrG,mGAAmG;AACnG,kGAAkG;AAClG,sGAAsG;AACtG,SAAS,eAAe,CAAC,IAAiB,EAAE,GAAkB;IAC5D,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC;QACrB,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC;YAAE,SAAS;QAC3B,MAAM,IAAI,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC;QACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACzC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBAChB,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBAChB,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACV,MAAM,CAAC,GAAG,CAAC,CAAC;oBACZ,CAAC,GAAG,CAAC,CAAC;oBACN,CAAC,GAAG,CAAC,CAAC;gBACR,CAAC;gBACD,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC9B,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAC9B,IAAI,EAAE,KAAK,SAAS,IAAI,EAAE,KAAK,SAAS;oBAAE,SAAS;gBACnD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;gBAC9E,IAAI,IAAI,CAAC,EAAE,KAAK,UAAU,IAAI,IAAI,CAAC,EAAE,KAAK,UAAU;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;IACH,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC;QAAE,uBAAuB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACnE,CAAC;AAED,kGAAkG;AAClG,sGAAsG;AACtG,iGAAiG;AACjG,UAAU;AACV,SAAS,eAAe,CAAC,IAAiB,EAAE,CAAS,EAAE,CAAS,EAAE,GAAsB;IACtF,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5E,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;YAC1B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACnC,IAAI,MAAM,KAAK,SAAS,IAAI,2BAA2B,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,SAAS,IAAI,2BAA2B,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,kGAAkG;AAClG,oGAAoG;AACpG,4DAA4D;AAC5D,SAAS,aAAa,CACpB,IAAiB,EACjB,EAAU,EACV,EAAU,EACV,EAAU,EACV,EAAU,EACV,GAAsB;IAEtB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,CAAC,KAAK,EAAE,CAAC;IACb,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC;QACzB,eAAe,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IACD,+FAA+F;IAC/F,+FAA+F;IAC/F,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IACE,MAAM,KAAK,SAAS;YACpB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EACtF,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IACD,+FAA+F;IAC/F,sGAAsG;IACtG,kGAAkG;IAClG,iGAAiG;IACjG,iGAAiG;IACjG,sFAAsF;IACtF,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO;IAClC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IACjF,IAAI,MAAM,GAAG,CAAC;QAAE,OAAO;IACvB,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,GAAG,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,GAAG,EAAE,CAAC;IAChC,IAAI,EAAE,GAAG,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChC,IAAI,EAAE,GAAG,UAAU,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChC,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;QAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;SACtC,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;QAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChD,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;QAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;SACtC,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;QAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChD,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,IAAI,KAAK,GAAG,QAAQ,CAAC;IACrB,IAAI,OAAO,GAAG,QAAQ,CAAC;IACvB,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;QACrD,KAAK,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;QAC7B,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,KAAK,GAAG,QAAQ,CAAC;IACrB,IAAI,OAAO,GAAG,QAAQ,CAAC;IACvB,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,MAAM,QAAQ,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC;QACrD,KAAK,GAAG,CAAC,QAAQ,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC;QAC7B,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACrF,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;QAC5C,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,GAAG,IAAI,CAAC,QAAQ;YAAE,MAAM;QAChG,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG;gBAAE,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,KAAK,GAAG,KAAK,EAAE,CAAC;YAClB,EAAE,IAAI,KAAK,CAAC;YACZ,KAAK,IAAI,OAAO,CAAC;QACnB,CAAC;aAAM,CAAC;YACN,EAAE,IAAI,KAAK,CAAC;YACZ,KAAK,IAAI,OAAO,CAAC;QACnB,CAAC;IACH,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IACE,MAAM,KAAK,SAAS;YACpB,aAAa,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EACtF,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;IACH,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,qGAAqG;AACrG,uEAAuE;AACvE,EAAE;AACF,sGAAsG;AACtG,oGAAoG;AACpG,sGAAsG;AACtG,iGAAiG;AACjG,yCAAyC;AACzC,SAAS,gBAAgB,CAAC,IAAiB,EAAE,MAA6B,EAAE,GAAsB;IAChG,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,IAAI,CAAC,KAAK,EAAE,CAAC;IAEb,IAAI,CAAC,CAAC,iBAAiB,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxD,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACvC,IAAI,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;QACnC,KAAK,IAAI,EAAE,GAAG,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YAC9C,IAAI,IAAI,KAAK,SAAS;gBAAE,SAAS;YACjC,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC3B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACb,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBACnC,IAAI,MAAM,KAAK,SAAS,IAAI,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACtF,CAAC;QACH,CAAC;IACH,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,MAAM,KAAK,SAAS,IAAI,yBAAyB,CAAC,MAAM,EAAE,MAAM,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtF,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,oGAAoG;AACpG,sGAAsG;AACtG,uCAAuC;AACvC,SAAS,iBAAiB,CAAC,QAAgB,EAAE,IAA2B;IACtE,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED,0FAA0F;AAC1F,qGAAqG;AACrG,iGAAiG;AACjG,oDAAoD;AACpD,IAAI,cAAc,GAAgC,IAAI,CAAC;AAEvD,iGAAiG;AACjG,kGAAkG;AAClG,iCAAiC;AACjC,MAAM,aAAa,GAAkB,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;AACzE,MAAM,aAAa,GAAkB,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flighthq/spatial",
3
- "version": "0.3.0-next.1032.e2b99fc",
3
+ "version": "0.3.0-next.1220.6982c21",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/flighthq/flight.git",
@@ -36,8 +36,8 @@
36
36
  "clean:dist": "tsx ../../scripts/clean-package-dist.ts"
37
37
  },
38
38
  "dependencies": {
39
- "@flighthq/geometry": "0.3.0-next.1032.e2b99fc",
40
- "@flighthq/types": "0.3.0-next.1032.e2b99fc"
39
+ "@flighthq/geometry": "0.3.0-next.1220.6982c21",
40
+ "@flighthq/types": "0.3.0-next.1220.6982c21"
41
41
  },
42
42
  "devDependencies": {
43
43
  "typescript": "^5.3.0"
@@ -0,0 +1,76 @@
1
+ import type { SpatialObjectId } from '@flighthq/types/contract';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { explainSpatialIndexing } from './explainSpatialIndexing';
5
+ import { createSpatialIndex, insertSpatialObject, removeSpatialObject, updateSpatialObject } from './spatialIndex';
6
+ import { MAX_INDEXED_CELLS_PER_OBJECT, createUniformGridSpatialBackend } from './uniformGrid';
7
+
8
+ describe('explainSpatialIndexing', () => {
9
+ it('reports absent for an id that was never inserted', () => {
10
+ const index = createSpatialIndex(createUniformGridSpatialBackend(10));
11
+ expect(explainSpatialIndexing(index, 42)).toEqual({ bucketCount: 0, id: 42, mode: 'absent', reason: null });
12
+ });
13
+
14
+ it('reports the bucket count of an ordinary object', () => {
15
+ const index = createSpatialIndex(createUniformGridSpatialBackend(10));
16
+ insertSpatialObject(index, 1, { minX: 0, minY: 0, maxX: 25, maxY: 25 });
17
+ // Covers cells 0..2 on each axis.
18
+ expect(explainSpatialIndexing(index, 1)).toEqual({ bucketCount: 9, id: 1, mode: 'cells', reason: null });
19
+ });
20
+
21
+ it('reports overflow with no buckets for an oversized object', () => {
22
+ const index = createSpatialIndex(createUniformGridSpatialBackend(1));
23
+ insertSpatialObject(index, 1, { minX: 0, minY: 0, maxX: 1e6, maxY: 1e6 });
24
+ expect(explainSpatialIndexing(index, 1)).toEqual({ bucketCount: 0, id: 1, mode: 'overflow', reason: null });
25
+ });
26
+
27
+ it('reports declined with the reason for non-finite bounds', () => {
28
+ const index = createSpatialIndex(createUniformGridSpatialBackend(10));
29
+ insertSpatialObject(index, 1, { minX: 0, minY: 0, maxX: Infinity, maxY: 10 });
30
+ expect(explainSpatialIndexing(index, 1)).toEqual({
31
+ bucketCount: 0,
32
+ id: 1,
33
+ mode: 'declined',
34
+ reason: 'non-finite-bounds',
35
+ });
36
+ });
37
+
38
+ it('returns to absent after a remove', () => {
39
+ const index = createSpatialIndex(createUniformGridSpatialBackend(10));
40
+ insertSpatialObject(index, 1, { minX: 0, minY: 0, maxX: 5, maxY: 5 });
41
+ removeSpatialObject(index, 1);
42
+ expect(explainSpatialIndexing(index, 1).mode).toBe('absent');
43
+ });
44
+
45
+ it('tracks the mode across updates', () => {
46
+ const index = createSpatialIndex(createUniformGridSpatialBackend(1));
47
+ insertSpatialObject(index, 1, { minX: 0, minY: 0, maxX: 2, maxY: 2 });
48
+ expect(explainSpatialIndexing(index, 1).mode).toBe('cells');
49
+ updateSpatialObject(index, 1, { minX: 0, minY: 0, maxX: 1e9, maxY: 1e9 });
50
+ expect(explainSpatialIndexing(index, 1).mode).toBe('overflow');
51
+ updateSpatialObject(index, 1, { minX: NaN, minY: 0, maxX: 2, maxY: 2 });
52
+ expect(explainSpatialIndexing(index, 1).mode).toBe('declined');
53
+ });
54
+
55
+ it('never exceeds the per-object bucket budget it exists to measure', () => {
56
+ const index = createSpatialIndex(createUniformGridSpatialBackend(1));
57
+ const ids: SpatialObjectId[] = [];
58
+ for (let i = 0; i < 8; i++) {
59
+ const extent = 10 ** i;
60
+ insertSpatialObject(index, i, { minX: 0, minY: 0, maxX: extent, maxY: extent });
61
+ ids.push(i);
62
+ }
63
+ for (const id of ids) {
64
+ expect(explainSpatialIndexing(index, id).bucketCount).toBeLessThanOrEqual(MAX_INDEXED_CELLS_PER_OBJECT);
65
+ }
66
+ });
67
+
68
+ it('mutates nothing — repeated calls report the same thing', () => {
69
+ const index = createSpatialIndex(createUniformGridSpatialBackend(10));
70
+ insertSpatialObject(index, 1, { minX: 0, minY: 0, maxX: 5, maxY: 5 });
71
+ const first = explainSpatialIndexing(index, 1);
72
+ expect(explainSpatialIndexing(index, 1)).toEqual(first);
73
+ expect(explainSpatialIndexing(index, 99).mode).toBe('absent');
74
+ expect(explainSpatialIndexing(index, 1)).toEqual(first);
75
+ });
76
+ });
@@ -0,0 +1,58 @@
1
+ import type { SpatialIndexingNotice } from '@flighthq/types/contract';
2
+ import { afterEach, describe, expect, it } from 'vitest';
3
+
4
+ import { formatSpatialIndexingNotice } from './formatSpatialIndexingNotice';
5
+ import { MAX_INDEXED_CELLS_PER_OBJECT, createUniformGridSpatialBackend, setSpatialIndexingGuard } from './uniformGrid';
6
+
7
+ afterEach(() => {
8
+ setSpatialIndexingGuard(null);
9
+ });
10
+
11
+ describe('formatSpatialIndexingNotice', () => {
12
+ it('names the sentinel to check for a decline', () => {
13
+ const message = formatSpatialIndexingNotice({
14
+ id: 3,
15
+ mode: 'declined',
16
+ reason: 'non-finite-bounds',
17
+ wouldOccupyBucketCount: 0,
18
+ });
19
+ expect(message).toContain('insertSpatialObject(3)');
20
+ expect(message).toContain('not finite');
21
+ expect(message).toContain('returns false');
22
+ });
23
+
24
+ it('names the span and the cell-size cause for an overflow, without calling it wrong', () => {
25
+ const message = formatSpatialIndexingNotice({
26
+ id: 4,
27
+ mode: 'overflow',
28
+ reason: null,
29
+ wouldOccupyBucketCount: 40000,
30
+ });
31
+ expect(message).toContain('insertSpatialObject(4)');
32
+ expect(message).toContain('40000 cells');
33
+ expect(message).toContain(`${MAX_INDEXED_CELLS_PER_OBJECT} per-object budget`);
34
+ expect(message).toContain('cellSize is too small');
35
+ // Overflow is a cost decision, not a wrong answer, and the message must not imply otherwise.
36
+ expect(message).toContain('Results are unaffected');
37
+ });
38
+
39
+ it('says plainly that a mode it does not cover carries no advice', () => {
40
+ expect(formatSpatialIndexingNotice({ id: 5, mode: 'cells', reason: null, wouldOccupyBucketCount: 4 })).toContain(
41
+ 'no caller-facing advice',
42
+ );
43
+ });
44
+
45
+ it('formats what the grid actually hands a guard', () => {
46
+ // Pins the two halves together: a record produced by a real insert, rendered by this formatter.
47
+ const notices: SpatialIndexingNotice[] = [];
48
+ setSpatialIndexingGuard((notice) => notices.push({ ...notice }));
49
+ const grid = createUniformGridSpatialBackend(1);
50
+ grid.insertSpatialObject(9, { minX: 0, minY: 0, maxX: 199, maxY: 199 });
51
+ grid.insertSpatialObject(10, { minX: NaN, minY: 0, maxX: 1, maxY: 1 });
52
+ expect(notices.map(formatSpatialIndexingNotice)).toEqual([
53
+ formatSpatialIndexingNotice({ id: 9, mode: 'overflow', reason: null, wouldOccupyBucketCount: 40000 }),
54
+ formatSpatialIndexingNotice({ id: 10, mode: 'declined', reason: 'non-finite-bounds', wouldOccupyBucketCount: 0 }),
55
+ ]);
56
+ expect(notices.map(formatSpatialIndexingNotice)[0]).toContain('40000 cells');
57
+ });
58
+ });
@@ -1,7 +1,11 @@
1
- import type { SpatialAabb, SpatialObjectId, SpatialPair } from '@flighthq/types/contract';
2
- import { describe, expect, it } from 'vitest';
1
+ import type { SpatialAabb, SpatialIndexingNotice, SpatialObjectId, SpatialPair } from '@flighthq/types/contract';
2
+ import { afterEach, describe, expect, it } from 'vitest';
3
3
 
4
- import { createUniformGridSpatialBackend } from './uniformGrid';
4
+ import { MAX_INDEXED_CELLS_PER_OBJECT, createUniformGridSpatialBackend, setSpatialIndexingGuard } from './uniformGrid';
5
+
6
+ afterEach(() => {
7
+ setSpatialIndexingGuard(null);
8
+ });
5
9
 
6
10
  // A plain AABB-overlap confirmation used to turn broadphase candidate pairs into confirmed pairs (the
7
11
  // narrow-phase stand-in): exactly the check the caller would apply downstream.
@@ -13,6 +17,137 @@ function pairKeys(pairs: readonly SpatialPair[]): string[] {
13
17
  return pairs.map((p) => `${Math.min(p.a, p.b)}-${Math.max(p.a, p.b)}`).sort();
14
18
  }
15
19
 
20
+ describe('cell range across every transition that can strand it', () => {
21
+ // Chief's class-not-instance rule applied to the ray-hang defect. The bug was never "removal after
22
+ // overflow"; it was that a separately-maintained `empty` flag could disagree with the cells it
23
+ // described, and *every* transition that empties or re-seeds the cell set is a chance for that.
24
+ // The fix derives the fact from `cells.size`, so no flag can drift — and these enumerate the
25
+ // transitions so a future re-introduction is caught wherever it is introduced.
26
+ //
27
+ // Each case is asserted two ways: the ray returns the right ids, and it returns them without
28
+ // walking a stale range. The second is the one that matters — the results were already correct
29
+ // before the fix, and the whole defect was cost. Measured on this grid: a stranded range costs
30
+ // ~70 ns per cell, so the 5,000,000-cell range used here took ~343 ms unstranded-vs-stranded
31
+ // (1e12 as in the original report is hours, i.e. an uncatchable hang). Budget 150 ms leaves the
32
+ // fixed path — an early return plus a one-object overflow scan, well under a millisecond — a
33
+ // ~1500x margin, while still failing on a stranded range by ~2.3x.
34
+ const FAR = 5_000_000;
35
+ const RAY_BUDGET_MS = 150;
36
+
37
+ // Correctness only. Used where the occupied cell range is legitimately wide, so the walk is
38
+ // genuinely proportional to it — that is the documented conservative over-walk, not the defect.
39
+ function rayIds(grid: ReturnType<typeof createUniformGridSpatialBackend>): SpatialObjectId[] {
40
+ const out: SpatialObjectId[] = [];
41
+ grid.querySpatialRay(-5, 0.5, 1, 0, out);
42
+ return out.sort((a, b) => a - b);
43
+ }
44
+
45
+ // Correctness *and* the cost bound. Used only where the transition should have left the cell set
46
+ // empty or tight, so any millions-of-cells walk means the range was stranded. Keeping these two
47
+ // helpers apart is the point: a single budgeted helper made two honest cases fail, because their
48
+ // titles claimed a seeding/removal property while the assertion silently demanded O(1).
49
+ function rayIdsFast(grid: ReturnType<typeof createUniformGridSpatialBackend>): SpatialObjectId[] {
50
+ const started = performance.now();
51
+ const ids = rayIds(grid);
52
+ expect(performance.now() - started).toBeLessThan(RAY_BUDGET_MS);
53
+ return ids;
54
+ }
55
+
56
+ // Two small objects placed FAR apart, so any stranded range spans millions of cells.
57
+ function gridWithWideRange(): ReturnType<typeof createUniformGridSpatialBackend> {
58
+ const grid = createUniformGridSpatialBackend(1);
59
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
60
+ grid.insertSpatialObject(2, { minX: FAR, minY: 0, maxX: FAR + 1, maxY: 1 });
61
+ return grid;
62
+ }
63
+
64
+ it('seeds the range on the first celled insert and widens it on the next', () => {
65
+ const grid = gridWithWideRange();
66
+ expect(rayIds(grid)).toEqual([1, 2]);
67
+ });
68
+
69
+ it('removes the last celled object while an overflowed object remains', () => {
70
+ // The reported case: overflow objects live in `bounds` but occupy no cells, so a flag derived
71
+ // from `bounds.size` stayed false here and left the range stranded.
72
+ const grid = gridWithWideRange();
73
+ grid.insertSpatialObject(3, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
74
+ grid.removeSpatialObject(1);
75
+ grid.removeSpatialObject(2);
76
+ expect(rayIdsFast(grid)).toEqual([3]);
77
+ });
78
+
79
+ it('removes the last celled object while a declined object remains', () => {
80
+ const grid = gridWithWideRange();
81
+ grid.insertSpatialObject(3, { minX: NaN, minY: 0, maxX: 1, maxY: 1 });
82
+ grid.removeSpatialObject(1);
83
+ grid.removeSpatialObject(2);
84
+ expect(rayIdsFast(grid)).toEqual([]);
85
+ });
86
+
87
+ it('removes the last celled object with nothing else in the index', () => {
88
+ const grid = gridWithWideRange();
89
+ grid.removeSpatialObject(1);
90
+ grid.removeSpatialObject(2);
91
+ expect(rayIdsFast(grid)).toEqual([]);
92
+ });
93
+
94
+ it('removes an overflowed object while celled objects remain, keeping them findable', () => {
95
+ const grid = gridWithWideRange();
96
+ grid.insertSpatialObject(3, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
97
+ grid.removeSpatialObject(3);
98
+ expect(rayIds(grid)).toEqual([1, 2]);
99
+ });
100
+
101
+ it('updates the last celled object into overflow, emptying the cells', () => {
102
+ // The overflow *transition* proper: no remove call, the object simply stops being celled.
103
+ const grid = createUniformGridSpatialBackend(1);
104
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
105
+ grid.insertSpatialObject(2, { minX: FAR, minY: 0, maxX: FAR + 1, maxY: 1 });
106
+ grid.updateSpatialObject(1, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
107
+ grid.updateSpatialObject(2, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
108
+ expect(rayIdsFast(grid)).toEqual([1, 2]);
109
+ });
110
+
111
+ it('updates an overflowed object back into cells, re-seeding rather than widening', () => {
112
+ const grid = createUniformGridSpatialBackend(1);
113
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
114
+ grid.insertSpatialObject(2, { minX: FAR, minY: 0, maxX: FAR + 1, maxY: 1 });
115
+ grid.updateSpatialObject(1, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
116
+ grid.updateSpatialObject(2, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
117
+ // Cells are empty here; coming back must seed a fresh tight range, not reuse the old wide one.
118
+ grid.updateSpatialObject(1, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
119
+ expect(rayIdsFast(grid)).toEqual([1, 2]);
120
+ });
121
+
122
+ it('clears while an overflowed object is present', () => {
123
+ const grid = gridWithWideRange();
124
+ grid.insertSpatialObject(3, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
125
+ grid.clearSpatialIndex();
126
+ expect(rayIdsFast(grid)).toEqual([]);
127
+ });
128
+
129
+ it('re-seeds a tight range after the index has been emptied and refilled', () => {
130
+ const grid = gridWithWideRange();
131
+ grid.clearSpatialIndex();
132
+ grid.insertSpatialObject(9, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
133
+ expect(rayIdsFast(grid)).toEqual([9]);
134
+ });
135
+
136
+ it('keeps region and point queries correct across the same overflow transition', () => {
137
+ // The range is the ray's concern, but the transition must not corrupt the other queries either.
138
+ const grid = gridWithWideRange();
139
+ grid.insertSpatialObject(3, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
140
+ grid.removeSpatialObject(1);
141
+ grid.removeSpatialObject(2);
142
+ const region: SpatialObjectId[] = [];
143
+ grid.querySpatialRegion({ minX: -1, minY: -1, maxX: 2, maxY: 2 }, region);
144
+ expect(region).toEqual([3]);
145
+ const point: SpatialObjectId[] = [];
146
+ grid.querySpatialPoint(0.5, 0.5, point);
147
+ expect(point).toEqual([3]);
148
+ });
149
+ });
150
+
16
151
  describe('createUniformGridSpatialBackend', () => {
17
152
  it('emits a pair spanning several shared cells exactly once', () => {
18
153
  const grid = createUniformGridSpatialBackend(10);
@@ -95,3 +230,275 @@ describe('createUniformGridSpatialBackend', () => {
95
230
  expect(coarse).toEqual(fine);
96
231
  });
97
232
  });
233
+
234
+ describe('MAX_INDEXED_CELLS_PER_OBJECT', () => {
235
+ it('is the per-object cell budget the oversized path is chosen by', () => {
236
+ const grid = createUniformGridSpatialBackend(1);
237
+ // A square block of exactly the budget stays on the ordinary path; one cell more does not. The
238
+ // budget is a count of cells, not an extent, so it is asserted through the count.
239
+ const side = Math.sqrt(MAX_INDEXED_CELLS_PER_OBJECT);
240
+ expect(Number.isInteger(side)).toBe(true);
241
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: side - 1, maxY: side - 1 });
242
+ expect(grid.explainSpatialIndexing(1)).toEqual({
243
+ bucketCount: MAX_INDEXED_CELLS_PER_OBJECT,
244
+ id: 1,
245
+ mode: 'cells',
246
+ reason: null,
247
+ });
248
+
249
+ grid.insertSpatialObject(2, { minX: 0, minY: 0, maxX: side, maxY: side - 1 });
250
+ expect(grid.explainSpatialIndexing(2).mode).toBe('overflow');
251
+ });
252
+ });
253
+
254
+ describe('non-finite bounds', () => {
255
+ it('declines rather than indexing, and answers with the false sentinel', () => {
256
+ const grid = createUniformGridSpatialBackend(10);
257
+ expect(grid.insertSpatialObject(1, { minX: NaN, minY: 0, maxX: 10, maxY: 10 })).toBe(false);
258
+ expect(grid.insertSpatialObject(2, { minX: 0, minY: 0, maxX: Infinity, maxY: 10 })).toBe(false);
259
+ expect(grid.insertSpatialObject(3, { minX: 0, minY: -Infinity, maxX: 10, maxY: 10 })).toBe(false);
260
+ expect(grid.explainSpatialIndexing(1)).toEqual({
261
+ bucketCount: 0,
262
+ id: 1,
263
+ mode: 'declined',
264
+ reason: 'non-finite-bounds',
265
+ });
266
+ });
267
+
268
+ it('returns true for finite bounds, so the sentinel distinguishes decline from success', () => {
269
+ const grid = createUniformGridSpatialBackend(10);
270
+ expect(grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 10, maxY: 10 })).toBe(true);
271
+ });
272
+
273
+ it('keeps a declined object out of every query rather than out of some', () => {
274
+ const grid = createUniformGridSpatialBackend(10);
275
+ grid.insertSpatialObject(1, { minX: NaN, minY: NaN, maxX: NaN, maxY: NaN });
276
+ grid.insertSpatialObject(2, { minX: 0, minY: 0, maxX: 10, maxY: 10 });
277
+
278
+ const region: SpatialObjectId[] = [];
279
+ grid.querySpatialRegion({ minX: -1e6, minY: -1e6, maxX: 1e6, maxY: 1e6 }, region);
280
+ expect(region).toEqual([2]);
281
+
282
+ const point: SpatialObjectId[] = [];
283
+ grid.querySpatialPoint(5, 5, point);
284
+ expect(point).toEqual([2]);
285
+
286
+ const ray: SpatialObjectId[] = [];
287
+ grid.querySpatialRay(-5, 5, 1, 0, ray);
288
+ expect(ray).toEqual([2]);
289
+
290
+ const pairs: SpatialPair[] = [];
291
+ grid.querySpatialPairs(pairs);
292
+ expect(pairs).toEqual([]);
293
+ });
294
+
295
+ it('drops an object that updates to non-finite bounds instead of stranding it at its old ones', () => {
296
+ const grid = createUniformGridSpatialBackend(10);
297
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 10, maxY: 10 });
298
+ expect(grid.updateSpatialObject(1, { minX: NaN, minY: NaN, maxX: NaN, maxY: NaN })).toBe(false);
299
+ expect(grid.explainSpatialIndexing(1).mode).toBe('declined');
300
+ const out: SpatialObjectId[] = [];
301
+ grid.querySpatialPoint(5, 5, out);
302
+ expect(out).toEqual([]);
303
+ });
304
+
305
+ it('lets a declined object recover on a later finite update', () => {
306
+ const grid = createUniformGridSpatialBackend(10);
307
+ grid.insertSpatialObject(1, { minX: NaN, minY: NaN, maxX: NaN, maxY: NaN });
308
+ expect(grid.updateSpatialObject(1, { minX: 0, minY: 0, maxX: 10, maxY: 10 })).toBe(true);
309
+ expect(grid.explainSpatialIndexing(1).mode).toBe('cells');
310
+ const out: SpatialObjectId[] = [];
311
+ grid.querySpatialPoint(5, 5, out);
312
+ expect(out).toEqual([1]);
313
+ });
314
+ });
315
+
316
+ describe('oversized region query', () => {
317
+ // The same unbounded-walk hazard from the caller's side: the region is caller-supplied, so a query
318
+ // wider than the world would walk extent-squared cells against a grid holding almost nothing.
319
+ // Measured at 69 ms for a 1000x1000-cell region over a one-object grid before the bound.
320
+ it('answers a region far wider than the grid without walking it cell by cell', () => {
321
+ const grid = createUniformGridSpatialBackend(1);
322
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
323
+ const out: SpatialObjectId[] = [];
324
+ grid.querySpatialRegion({ minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 }, out);
325
+ expect(out).toEqual([1]);
326
+ });
327
+
328
+ it('returns the same objects either way it walks', () => {
329
+ const grid = createUniformGridSpatialBackend(1);
330
+ for (let i = 0; i < 40; i++) grid.insertSpatialObject(i, { minX: i, minY: 0, maxX: i + 0.5, maxY: 1 });
331
+ const wide: SpatialObjectId[] = [];
332
+ grid.querySpatialRegion({ minX: -1e9, minY: -1e9, maxX: 1e9, maxY: 1e9 }, wide);
333
+ const narrow: SpatialObjectId[] = [];
334
+ grid.querySpatialRegion({ minX: -1, minY: -1, maxX: 41, maxY: 2 }, narrow);
335
+ expect([...wide].sort((a, b) => a - b)).toEqual([...narrow].sort((a, b) => a - b));
336
+ expect(wide.length).toBe(40);
337
+ });
338
+
339
+ it('still excludes objects the wide region misses', () => {
340
+ const grid = createUniformGridSpatialBackend(1);
341
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
342
+ grid.insertSpatialObject(2, { minX: 5e11, minY: 0, maxX: 5e11 + 1, maxY: 1 });
343
+ const out: SpatialObjectId[] = [];
344
+ grid.querySpatialRegion({ minX: -1e12, minY: -1e12, maxX: 10, maxY: 1e12 }, out);
345
+ expect(out).toEqual([1]);
346
+ });
347
+ });
348
+
349
+ describe('oversized-extent bound', () => {
350
+ // THE REGRESSION GUARD for the unbounded insert walk.
351
+ //
352
+ // The extent here is deliberately modest — 200 units at one cell per unit, so 40,401 cells, which
353
+ // an unbounded build writes in about 28 ms. That is the point: this assertion FAILS on unbounded
354
+ // code rather than HANGING it, so it stays a usable test. A realistic reproduction (an AABB 1e12
355
+ // wide, which is what a diverging rigid-body simulation actually produces) would be 1e24 cells and
356
+ // would never return, which is exactly why the bound cannot be tested at its motivating scale.
357
+ it('holds an oversized object without writing a cell per unit of its extent', () => {
358
+ const grid = createUniformGridSpatialBackend(1);
359
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 200, maxY: 200 });
360
+ const explanation = grid.explainSpatialIndexing(1);
361
+ expect(explanation.mode).toBe('overflow');
362
+ expect(explanation.bucketCount).toBe(0);
363
+ });
364
+
365
+ it('indexes an AABB far past any walkable extent in constant time', () => {
366
+ // Unreachable for an unbounded build (1e24 cells), so this one cannot be written as a
367
+ // before/after assertion — it is the proof that the motivating case is now O(1). Safe to run only
368
+ // because the assertion above fails first if the bound is ever removed.
369
+ const grid = createUniformGridSpatialBackend(1);
370
+ expect(grid.insertSpatialObject(1, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 })).toBe(true);
371
+ expect(grid.explainSpatialIndexing(1).mode).toBe('overflow');
372
+ });
373
+
374
+ it('keeps an oversized object queryable — the bound is a cost decision, not a dropped object', () => {
375
+ const grid = createUniformGridSpatialBackend(1);
376
+ grid.insertSpatialObject(1, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
377
+ grid.insertSpatialObject(2, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
378
+
379
+ const region: SpatialObjectId[] = [];
380
+ grid.querySpatialRegion({ minX: 100, minY: 100, maxX: 101, maxY: 101 }, region);
381
+ expect(region).toEqual([1]);
382
+
383
+ const point: SpatialObjectId[] = [];
384
+ grid.querySpatialPoint(500, 500, point);
385
+ expect(point).toEqual([1]);
386
+
387
+ const ray: SpatialObjectId[] = [];
388
+ grid.querySpatialRay(1e6, 1e6, 1, 0, ray);
389
+ expect(ray).toEqual([1]);
390
+
391
+ const pairs: SpatialPair[] = [];
392
+ grid.querySpatialPairs(pairs);
393
+ expect(pairKeys(pairs)).toEqual(['1-2']);
394
+ });
395
+
396
+ it('emits each overflow pair once, including overflow against overflow', () => {
397
+ const grid = createUniformGridSpatialBackend(1);
398
+ grid.insertSpatialObject(1, { minX: -1e9, minY: -1e9, maxX: 1e9, maxY: 1e9 });
399
+ grid.insertSpatialObject(2, { minX: -1e9, minY: -1e9, maxX: 1e9, maxY: 1e9 });
400
+ grid.insertSpatialObject(3, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
401
+ const pairs: SpatialPair[] = [];
402
+ grid.querySpatialPairs(pairs);
403
+ expect(pairKeys(pairs)).toEqual(['1-2', '1-3', '2-3']);
404
+ });
405
+
406
+ it('does not pair an overflow object with a disjoint object', () => {
407
+ const grid = createUniformGridSpatialBackend(1);
408
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 100000, maxY: 100000 });
409
+ grid.insertSpatialObject(2, { minX: -50, minY: -50, maxX: -40, maxY: -40 });
410
+ const pairs: SpatialPair[] = [];
411
+ grid.querySpatialPairs(pairs);
412
+ expect(pairs).toEqual([]);
413
+ });
414
+
415
+ it('removes an oversized object without walking its extent, and stops returning it', () => {
416
+ const grid = createUniformGridSpatialBackend(1);
417
+ grid.insertSpatialObject(1, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
418
+ grid.removeSpatialObject(1);
419
+ expect(grid.explainSpatialIndexing(1).mode).toBe('absent');
420
+ const out: SpatialObjectId[] = [];
421
+ grid.querySpatialPoint(0, 0, out);
422
+ expect(out).toEqual([]);
423
+ });
424
+
425
+ it('moves an object between the celled and overflow paths as it grows and shrinks', () => {
426
+ const grid = createUniformGridSpatialBackend(1);
427
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 2, maxY: 2 });
428
+ expect(grid.explainSpatialIndexing(1).mode).toBe('cells');
429
+ grid.updateSpatialObject(1, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
430
+ expect(grid.explainSpatialIndexing(1).mode).toBe('overflow');
431
+ grid.updateSpatialObject(1, { minX: 0, minY: 0, maxX: 2, maxY: 2 });
432
+ expect(grid.explainSpatialIndexing(1).mode).toBe('cells');
433
+
434
+ // Back on the ordinary path the object must be findable through the cells again, not stranded.
435
+ const out: SpatialObjectId[] = [];
436
+ grid.querySpatialPoint(1, 1, out);
437
+ expect(out).toEqual([1]);
438
+ });
439
+
440
+ it('clears overflow with the rest of the index', () => {
441
+ const grid = createUniformGridSpatialBackend(1);
442
+ grid.insertSpatialObject(1, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
443
+ grid.clearSpatialIndex();
444
+ expect(grid.explainSpatialIndexing(1).mode).toBe('absent');
445
+ const out: SpatialObjectId[] = [];
446
+ grid.querySpatialPoint(0, 0, out);
447
+ expect(out).toEqual([]);
448
+ });
449
+
450
+ it('keeps an oversized object out of the ray-traversal cell range', () => {
451
+ // Without the bound the occupied cell range stretches to the oversized object's span, and every
452
+ // subsequent ray walks it. Two small objects far apart bound the range; the oversized one must
453
+ // not widen it, which shows up as the ray still resolving correctly and promptly.
454
+ const grid = createUniformGridSpatialBackend(1);
455
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 1, maxY: 1 });
456
+ grid.insertSpatialObject(2, { minX: -1e12, minY: -1e12, maxX: 1e12, maxY: 1e12 });
457
+ const out: SpatialObjectId[] = [];
458
+ grid.querySpatialRay(-5, 0.5, 1, 0, out);
459
+ expect(out.sort()).toEqual([1, 2]);
460
+ });
461
+ });
462
+
463
+ describe('setSpatialIndexingGuard', () => {
464
+ it('reports a decline with its reason, and no span', () => {
465
+ const notices: SpatialIndexingNotice[] = [];
466
+ setSpatialIndexingGuard((notice) => notices.push({ ...notice }));
467
+ const grid = createUniformGridSpatialBackend(10);
468
+ grid.insertSpatialObject(7, { minX: NaN, minY: 0, maxX: 10, maxY: 10 });
469
+ expect(notices).toEqual([{ id: 7, mode: 'declined', reason: 'non-finite-bounds', wouldOccupyBucketCount: 0 }]);
470
+ });
471
+
472
+ it('reports an overflow with the span the bound refused to walk', () => {
473
+ const notices: SpatialIndexingNotice[] = [];
474
+ setSpatialIndexingGuard((notice) => notices.push({ ...notice }));
475
+ const grid = createUniformGridSpatialBackend(1);
476
+ grid.insertSpatialObject(7, { minX: 0, minY: 0, maxX: 199, maxY: 199 });
477
+ expect(notices).toEqual([{ id: 7, mode: 'overflow', reason: null, wouldOccupyBucketCount: 40000 }]);
478
+ });
479
+
480
+ it('stays silent on the ordinary path', () => {
481
+ const notices: SpatialIndexingNotice[] = [];
482
+ setSpatialIndexingGuard((notice) => notices.push({ ...notice }));
483
+ const grid = createUniformGridSpatialBackend(10);
484
+ grid.insertSpatialObject(1, { minX: 0, minY: 0, maxX: 10, maxY: 10 });
485
+ grid.removeSpatialObject(1);
486
+ expect(notices).toEqual([]);
487
+ });
488
+
489
+ it('null uninstalls it', () => {
490
+ const notices: SpatialIndexingNotice[] = [];
491
+ setSpatialIndexingGuard((notice) => notices.push({ ...notice }));
492
+ setSpatialIndexingGuard(null);
493
+ const grid = createUniformGridSpatialBackend(10);
494
+ grid.insertSpatialObject(1, { minX: NaN, minY: 0, maxX: 10, maxY: 10 });
495
+ expect(notices).toEqual([]);
496
+ });
497
+
498
+ it('does not change what insert returns', () => {
499
+ setSpatialIndexingGuard(() => {});
500
+ const grid = createUniformGridSpatialBackend(10);
501
+ expect(grid.insertSpatialObject(1, { minX: NaN, minY: 0, maxX: 10, maxY: 10 })).toBe(false);
502
+ expect(grid.insertSpatialObject(2, { minX: 0, minY: 0, maxX: 10, maxY: 10 })).toBe(true);
503
+ });
504
+ });