Hierarchy
The protocol uses a quadtree to address space inside each Index. That quadtree decides where a parcel is stored, what its natural depth is, and which cells broadphase search probes.
Hierarchy payouts use that spatial structure, but they do not walk the quadtree cell-by-cell at collection time. The tax system opens buckets on child parcels, then enclosing parcels collect and forward those buckets across deployed Index levels.
See also: How It Works → The Shared Index, Key Concepts → Spatial Hierarchy, Tax → Where the 8% Goes, Cash Flows → Why Hierarchy Position Matters, Exploring the Map → Hierarchy Levels.
Quadtree Cells
Each Index divides space into recursively nested cells. The root cell covers the full coordinate range. Each deeper level splits one cell into four children.
An Index stores each parcel in exactly one cell: the cell at that parcel's natural depth. The cells table maps depth-prefixed Morton keys to parcel IDs.
The protocol supports quadtree depth up to 31. Each Index chooses its own cell_size and max_depth when it is created.
Natural Depth
Natural depth is the shallowest cell that fully contains the parcel's AABB.
The AABB is the parcel's axis-aligned bounding box: min_x, min_y, max_x, max_y. natural_depth(...) starts at the deepest configured level and moves upward until both AABB corners land in the same cell on both axes.
If the parcel straddles a cell boundary at depth d, the protocol retries one level shallower. Depth 0 always works because the root cell contains everything.
This is geometry-driven, not owner-driven. Small parcels usually land deeper. Large parcels usually land shallower.
Morton Codes and Depth-Prefixed Keys
The protocol uses Morton codes to turn 2D cell coordinates into one sortable integer.
- X bits and Y bits are interleaved
- nearby cells stay near each other numerically
- range queries stay cheap because spatial locality survives the encoding
A raw Morton code is not enough because the same code can appear at multiple depths. The protocol fixes that with a depth prefix:
key = depth_prefix(morton_code, depth)
That key uniquely identifies one quadtree cell at one depth. The Index.cells table stores parcel IDs under that key.
parent_key: The Enclosing Cell One Level Up
parent_key(key) returns the depth-prefixed key for the enclosing cell one level shallower.
Conceptually, that is the quadtree parent relation: remove one pair of Morton bits and decrement depth by one. It is still the right way to think about how cells nest.
What changed is the payout story. The current hierarchy tax flow does not climb a parent_key chain during collection. It uses tax buckets plus geometric containment checks.
Deployed Levels Are Separate Index Objects
The app and docs talk about six levels: Block, District, City, Region, Country, and Continent. Those names are deployment conventions, not protocol-level enum fields.
On-chain, the protocol works with separate shared Index objects plus numeric level ranks supplied by the market.
| Rank | App/docs label | Zoom range | Typical scale |
|---|---|---|---|
| 5 | Continent | 1–2 | Entire continents |
| 4 | Country | 3–5 | Nations |
| 3 | Region | 6–8 | States or provinces |
| 2 | City | 9–11 | Metropolitan areas |
| 1 | District | 12–14 | Neighborhoods |
| 0 | Block | 15–22 | Individual blocks |
Each deployed level is its own Index with its own geometry density and storage tables. A parcel lives in one Index, not in a global cross-level tree object.
cell_size and max_depth
cell_size sets the finest grid resolution for an Index. max_depth sets how many quadtree levels that Index supports.
Both values are fixed when the Index is created with with_config(...) or share_with_config(...). Admin updates can replace Config, but max_depth is not part of that mutable config. The code calls this out directly: max_depth is fixed at Index creation time.
That immutability matters because natural depth, cell addressing, and broadphase shifts all depend on the original depth budget.
Tax Cascade Is Bucket-Based
Hierarchy tax moves through buckets attached to parcels. It is not a quadtree walk from parent cell to parent cell.
When a taxable action accrues upstream tax, tax_accrue(...) puts that value into tax_buckets under the paying parcel's ID. The key records:
polygon_idbucket_idorigin_level_rankancestor_distanceaccrual_epoch
The store also tracks polygon_buckets, which is the reverse index from parcel ID to bucket keys.
Collection happens when someone calls market::collect_tax(...) for a candidate parent parcel. The market resolves the parent's level rank, then tax::collect_tax(...) and collect_batch_impl(...) do four checks:
- look up bucket keys already attached to the child parcel
- keep only keys whose
origin_level_rank + ancestor_distancematches the parent level rank - verify geometric containment with
index::outer_contains_inner(parent_index, parent_id, child_index, child_id) - reject retroactive claims if the parent parcel was created after the bucket accrued
If the checks pass, the protocol removes the matched bucket from the child parcel, pays the current parent parcel its share, and writes the remainder as a new bucket on the parent parcel.
So the cascade is cross-index in the sense that one level's parcel can collect from a parcel in another deployed Index. But each hop is bucket-to-bucket, not cell-to-cell.
The quadtree still matters here. It determines parcel placement and supports the geometry lookups used to prove containment. The payout itself lives in the tax store.
candidates() Searches One Index
candidates(index, query_id) is a single-Index broadphase search.
It reads the query parcel from that Index, computes its AABB, and calls broadphase_from_aabb(...). That helper scans every occupied depth from 0 to index.max_depth, derives the covered cells at each depth, and collects parcel IDs from index.cells.
This means candidates() can find overlaps between parcels stored at different natural depths inside the same Index. It does not search other deployed levels. Cross-level containment checks use functions such as outer_contains_inner(...) with two explicit Index arguments.
After broadphase, the protocol runs exact geometry checks. overlapping(index, query_id) combines candidates() with SAT-based overlap verification.
Why This Structure Matters
The protocol separates two jobs that often get conflated:
- the quadtree solves spatial indexing inside one
Index - tax buckets solve deferred hierarchy payouts across deployed levels
That split keeps storage simple. Each parcel is stored once, at its natural depth, inside one Index. Broadphase remains local to that Index. Hierarchy revenue still flows upward, but through explicit bucket state and explicit containment checks.
If you keep those two layers separate, the code reads cleanly: quadtree for addressability, buckets for cash flow.