nx_aoi_grid.nx
buildroot/runtime/nx_aoi_grid.nx
about
nx_aoi_grid.nx -- area-of-interest spatial grid for open-world culling.
Per the Agar.io/Slither.io lesson (reverse-engineered protocols at
ClitherProject + Diep.io / Krunker community wikis): the single
biggest bandwidth saver for open-world multiplayer is to ONLY stream
entities within the player's view radius. Naive "broadcast all
entities to all players" scales as O(N^2) in players-times-entities;
area-of-interest culling brings it to O(N * view_density).
This primitive provides:
- 2D cell-bucket grid over Q14-fixed-point world coordinates
- insert / remove / query_radius operations
- returns entity IDs within radius for a (player_x, player_z) query
Use case: caller maintains an "entity table" keyed by entity ID; the
grid is the spatial index that lets a per-player streamer ask
"which entities should this player see?" 60 times per second.
Bandwidth math (from genre-bench doc):
Naive: 100 entities * 60 Hz * 30 B/entity = 180 KB/s -- 25x over
the 7 KB/s dial-up ceiling.
With AOI (20 in-view): 20 * 60 * 30 = 36 KB/s. Still over.
With AOI + nx_state_delta_codec (4 B/entity typical):
20 * 60 * 4 = 4.8 KB/s. FITS UNDER 7 KB/s.
Storage layout (i64 cells in flat block + raw bucket bytes):
h[0] = cell_size_q14 (size of one cell side in Q14 metres)
h[1] = grid_dim (cells per axis; total cells = grid_dim^2)
h[2] = origin_x_q14 (world coord of cell (0,0) lower edge)
h[3] = origin_z_q14
h[4] = bucket_capacity (max entities per cell)
h[5] = bucket_storage (i64 ptr; layout below)
Per cell layout (i64 cells):
[0] = count of entities in cell
[1..1+3*cap] = (id, x_q14, z_q14) triples
Cell stride = 1 + 3 * bucket_capacity.
Source references (open):
- Slither.io protocol: ClitherProject/Slither.io-Protocol (GitHub)
dependencies 2 imports · 0 importers
imports: nx_syscalls.nxnx_tier.nx
imported by: nobody (leaf or entry point)
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 56 | const NX_MAGIC_16384: i64 = 16384 |
| 57 | const NX_MAGIC_9999: i64 = 9999 |
| 58 | const NX_MAGIC_2000: i64 = 2000 |
| 60 | const NX_AOI_HDR_CELL_SIZE: nx_int = 0 |
| 61 | const NX_AOI_HDR_GRID_DIM: nx_int = 1 |
| 62 | const NX_AOI_HDR_ORIGIN_X: nx_int = 2 |
| 63 | const NX_AOI_HDR_ORIGIN_Z: nx_int = 3 |
| 64 | const NX_AOI_HDR_BUCKET_CAP: nx_int = 4 |
| 65 | const NX_AOI_HDR_STORAGE: nx_int = 5 |
| 66 | const NX_AOI_HDR_SIZE: nx_int = 6 |
functions
| 70 | func _aoi_cell_stride(g: *i64) -> nx_int called by 1: _aoi_cell_ptr |
| 75 | func _aoi_cell_ptr(g: *i64, cx: nx_int, cz: nx_int) -> *i64 |
| 88 | func _aoi_to_cell(g: *i64, world_q14: nx_int, axis: nx_int) -> nx_int |
| 104 | func nx_aoi_grid_new( |
| 139 | func nx_aoi_grid_insert(g: *i64, id: i64, x_q14: nx_int, z_q14: nx_int) -> nx_int |
| 158 | func nx_aoi_grid_remove(g: *i64, id: i64, x_q14: nx_int, z_q14: nx_int) -> nx_int |
| 195 | func nx_aoi_grid_query_radius( |
| 260 | func main() -> i64 |