Skip to main content

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.

RankApp/docs labelZoom rangeTypical scale
5Continent1–2Entire continents
4Country3–5Nations
3Region6–8States or provinces
2City9–11Metropolitan areas
1District12–14Neighborhoods
0Block15–22Individual 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_id
  • bucket_id
  • origin_level_rank
  • ancestor_distance
  • accrual_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:

  1. look up bucket keys already attached to the child parcel
  2. keep only keys whose origin_level_rank + ancestor_distance matches the parent level rank
  3. verify geometric containment with index::outer_contains_inner(parent_index, parent_id, child_index, child_id)
  4. 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.

note

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.