clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

main
atlas / analysis / cellsystems / plan.ts
plan.ts 4.1K
1// The plan: the one account of which cells a grid shows.2//3// `cellPlan(ground, system, cellID)` is issue #5 §5.4's contractual output,4// and its **emission order is frozen**: the plan is read positionally, cell5// for cell — "cell 12 of 32, emission order is the contract" — so a6// reordering is a failure, not a detail. The order property in7// analysis/test/properties.test.ts holds it.8//9// THE ORDER, which is the whole contract:10//11// 1. For each ancestor from the root down, that ancestor's children except12// the one on the path to the held cell, as `neighbor`s carrying their13// distance from it. The root's neighbours come first and the held cell's14// siblings come last, so contextDistance descends across the run.15// 2. Then either16// a. the held cell as a `leaf`, if it sits at the system's maxLevel —17// and then nothing follows, because a leaf has nothing to divide; or18// b. the held cell as a `scope`, followed by every one of its children as19// `child`s in the system's stable child order.20//21// The root plan — no held cell — is case 2b with no ancestors and no scope:22// the root's children, and nothing else.23//24// Session state arrives as arguments. Which system is active and which cell is25// held live in the app's session (§4.1); this function is handed both and26// keeps neither.2728import type { CellID, CellSystem, GroundedCellSystem, Pole, Ring } from "./contract.ts";29import type { Extent, Ground } from "./ground.ts";30import type { CellRole } from "./tokens.ts";3132/**33 * One cell of a plan.34 *35 * The key order below is the order the shared vectors serialize: the36 * fixtures are compared as JSON text, so it is cheaper to keep than to37 * explain. Only the array order is contractual, but a diff nobody has to38 * read is worth the discipline.39 */40export interface PlanCell {41 /** The cell's opaque id. The field keeps the name `hash` whatever system minted it. */42 readonly hash: CellID;43 readonly extent: Extent;44 readonly ring: Ring;45 readonly pole: Pole | null;46 readonly childIndex: number;47 readonly role: CellRole;48 /** How many levels above the held cell this cell's parent sits; 0 off the ancestor run. */49 readonly contextDistance: number;50}5152/**53 * The cells a grid shows, in frozen emission order.54 *55 * `cellID` is the held cell — `""` for the root. `system` must be one that56 * `appliesTo` this ground; a system that does not is a caller error, not a57 * plan.58 */59export function cellPlan(ground: Ground, system: CellSystem, cellID: CellID): PlanCell[] {60 const on = system.on(ground);61 const cells: PlanCell[] = [];62 const chain = ancestorChain(on, cellID);63 for (let depth = 0; depth < chain.length - 1; depth++) {64 const here = chain[depth];65 const selected = chain[depth + 1];66 if (here === undefined || selected === undefined) continue;67 for (const id of on.children(here)) {68 if (id === selected) continue;69 cells.push(planCell(on, id, "neighbor", chain.length - 1 - depth));70 }71 }72 if (cellID && on.level(cellID) >= system.maxLevel(ground)) {73 cells.push(planCell(on, cellID, "leaf", 0));74 return cells;75 }76 // The cell the reader is inside, outlined rather than tiled. It is the one77 // part of the grid that survives putting the subdivision away: what is on78 // screen is still a chosen place, and a boundary says so where a bare map79 // does not.80 if (cellID) {81 cells.push(planCell(on, cellID, "scope", 0));82 }83 for (const id of on.children(cellID)) {84 cells.push(planCell(on, id, "child", 0));85 }86 return cells;87}8889/** The walk from the root down to the held cell, root first. */90export function ancestorChain(on: GroundedCellSystem, id: CellID): CellID[] {91 const chain: CellID[] = [id];92 let held = id;93 while (held) {94 held = on.parent(held);95 chain.unshift(held);96 }97 return chain;98}99100function planCell(101 on: GroundedCellSystem,102 id: CellID,103 role: CellRole,104 contextDistance: number,105): PlanCell {106 return {107 hash: id,108 extent: on.bbox(id),109 ring: on.ring(id),110 pole: on.poleContained(id),111 childIndex: on.childIndex(id),112 role,113 contextDistance,114 };115}