clone

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

feat/atlas-next-horizon
atlas / analysis / cellsystems / contract.ts
contract.ts 6.4K
1// The cell-system contract: eighteen methods, and the rules they keep.2//3// A cell system divides a ground into an exact hierarchy a reader can4// telescope through. Geohash was the first and grew into the old app's bones;5// this contract is what pulled it back out, so a second system is one more6// file and a third is a registry entry (issue #5 §5.4).7//8// The eighteen split cleanly in two, and the split is the whole of the9// de-globalization:10//11// THREE methods answer about a ground before any cell is named — whether12// this system will divide it, how deep the telescope goes, how many13// characters the navigator's field accepts.14//15// FIFTEEN answer about a cell on a ground. They are reached through16// `system.on(ground)`, which binds the ground once and hands back an object17// whose methods take only their own arguments.18//19// THE RULES, which the conformance suite (analysis/test/conformance.ts)20// checks as properties rather than examples:21//22// Opaque ids. A cell id is an opaque string and "" is the root.23// Nothing outside a system may take it apart —24// geohash refines by appending a character, S2 does25// not (the children of 47a1cb are 47a1ca4 and its26// siblings), and a third system may do neither.27// Stable child order. `children(id)` answers in one order, forever. The28// plan emits in it, the palette indexes into it, and29// the vectors gate compares positionally.30// Inclusive containment. `contains` is true on the boundary. A point on a31// shared edge is in both cells; nothing here owns a32// tie-break, because the caller asking is drawing,33// not partitioning.34// Descent agrees. `descendTarget(id, at)` names a child that35// `contains` the point, or "" at the floor.36// Whole-or-nothing. `parseInput` answers a canonical id or null. A37// draft is not a place; the field holds it and the38// session stays put.39// Closed rings. `ring(id)` returns a loop whose last point equals40// its first.41// Continuity. A ring never jumps the seam: consecutive points42// stay within half a world of each other in x, even43// where that runs the loop off the surface. The44// adapters wrap or split it; the system does not.45// Poles. `poleContained(id)` says which pole a cell circles,46// because a loop around a pole closes along the47// picture's own top or bottom edge and only the48// system knows it needs to.49//50// Coordinates are OL world coordinates throughout: x east, y **negative-down**51// — the space a pin's coordinate lives in.5253import type { Coordinate, Extent, Ground } from "./ground.ts";5455/** An opaque cell address. `""` is the root of every system. */56export type CellID = string;5758/** A closed boundary loop, pre-tessellated, in OL world coordinates. */59export type Ring = readonly Coordinate[];6061/** Which pole a cell circles, when it circles one. */62export type Pole = "north" | "south";6364/** The chip's two cuts: a faint context and a bright principal. */65export interface CellLabel {66 readonly context: string;67 readonly principal: string;68}6970/** One named row for the pin card: this system's own address for a point. */71export interface LocatedCell {72 readonly label: string;73 readonly value: string;74}7576/**77 * The fifteen methods that answer about a cell on a ground already bound.78 *79 * Every one of these is a pure function of the ground it was bound to and its80 * own arguments. Nothing here reads a session, a camera, or a page.81 */82export interface GroundedCellSystem {83 /** Depth of a cell. The root `""` is level 0. */84 level(id: CellID): number;8586 /** One level up; `""` from a level-1 cell, and `""` from the root. */87 parent(id: CellID): CellID;8889 /** The next level down, in a stable order. `children("")` are the roots. */90 children(id: CellID): CellID[];9192 /** Ordinal among siblings — the priority order and the palette index. */93 childIndex(id: CellID): number;9495 /** Whether a point is inside. Boundaries are inclusive. */96 contains(id: CellID, at: Coordinate): boolean;9798 /** The child of `id` under the point, or `""` at the telescope's floor. */99 descendTarget(id: CellID, at: Coordinate): CellID;100101 /** `[minX, minY, maxX, maxY]`, OL world coordinates. */102 bbox(id: CellID): Extent;103104 /** A point inside the cell, OL world coordinates. */105 center(id: CellID): Coordinate;106107 /** The closed, continuous boundary loop. See the continuity rule above. */108 ring(id: CellID): Ring;109110 /** Which pole this cell circles, for ring closure; null for most cells. */111 poleContained(id: CellID): Pole | null;112113 /** The chip's two cuts. */114 label(id: CellID): CellLabel;115116 /** Palette index for the cell's accent; siblings must differ. */117 colorKey(id: CellID): number;118119 /** What the navigator's field keeps of a keystroke. */120 normalizeInput(text: string): string;121122 /** A canonical id, or null while the text is not yet a place. */123 parseInput(text: string): CellID | null;124125 /** This system's own address for a point, for the pin card, or null. */126 locate(at: Coordinate): LocatedCell | null;127}128129/**130 * A registered cell system: its identity, the three questions it answers131 * about a ground, and the binding that produces the other fifteen.132 *133 * Adding a system is implementing this and putting it in a registry. There is134 * no other seam — see `docs/analysis.md` for the walkthrough.135 */136export interface CellSystem {137 /** Stable machine name. Sessions persist it; it never changes. */138 readonly slug: string;139140 /** How the navigator names it. */141 readonly name: string;142143 /** The mark its cycle button wears. */144 readonly short: string;145146 /** Whether this system can divide this ground at all. */147 appliesTo(ground: Ground): boolean;148149 /** How deep the telescope goes on this ground. */150 maxLevel(ground: Ground): number;151152 /** How many characters the navigator's field accepts on this ground. */153 inputLength(ground: Ground): number;154155 /** Bind the ground once; the fifteen follow. */156 on(ground: Ground): GroundedCellSystem;157}