1// The plan, and the order that is its contract.2//3// The order is compared positionally wherever a plan is checked — here, and4// in the property suite (properties.test.ts) that reconstructs it from the5// rule alone. These tests document what that order *is*, so a reader can6// learn the rule without decoding a reconstruction.78import assert from "node:assert/strict";9import test from "node:test";1011import { ancestorChain, cellPlan } from "../cellsystems/plan.ts";12import { geohashSystem } from "../cellsystems/geohash.ts";13import { s2System } from "../cellsystems/s2.ts";14import { sphere, square } from "./grounds.ts";1516const roles = (held: string) =>17 cellPlan(square, geohashSystem, held).map((cell) => `${cell.role}:${cell.hash}`);1819test("the root plan is the root's children and nothing else", () => {20 const plan = cellPlan(square, geohashSystem, "");21 assert.equal(plan.length, 32);22 assert.ok(plan.every((cell) => cell.role === "child"));23 assert.deepEqual(plan.map((cell) => cell.hash), geohashSystem.on(square).children(""));24});2526test("a held cell emits its ancestors' neighbours, then itself, then its children", () => {27 const plan = roles("m6");28 // 31 neighbours at the root, 31 more inside m, then the scope and 32 children.29 assert.equal(plan.length, 31 + 31 + 1 + 32);30 assert.equal(plan[0], "neighbor:0");31 assert.equal(plan[30], "neighbor:z", "the root's neighbours come first, in child order");32 assert.equal(plan[31], "neighbor:m0");33 assert.equal(plan[62], "scope:m6", "the held cell sits between its context and its subdivision");34 assert.equal(plan[63], "child:m60");35 assert.equal(plan[94], "child:m6z");36 assert.ok(!plan.includes("neighbor:m"), "the cell on the path is never its own neighbour");37 assert.ok(!plan.includes("neighbor:m6"));38});3940test("context distance counts levels up from the held cell", () => {41 const plan = cellPlan(square, geohashSystem, "m6");42 const distances = plan43 .filter((cell) => cell.role === "neighbor")44 .map((cell) => cell.contextDistance);45 assert.deepEqual([...new Set(distances)], [2, 1],46 "the root's neighbours are two levels out; m's siblings are one");47 assert.ok(plan.filter((cell) => cell.role !== "neighbor")48 .every((cell) => cell.contextDistance === 0));49});5051test("a cell at the floor is a leaf, and nothing follows it", () => {52 const plan = cellPlan(square, geohashSystem, "m6s");53 const last = plan[plan.length - 1];54 assert.equal(last?.role, "leaf");55 assert.equal(last?.hash, "m6s");56 assert.equal(plan.filter((cell) => cell.role === "child").length, 0,57 "a leaf has nothing to divide");58 assert.equal(plan.filter((cell) => cell.role === "scope").length, 0,59 "and it is not also a scope");60 assert.equal(plan.length, 31 * 3 + 1);61});6263test("the order is the same order for a system shaped nothing like geohash", () => {64 const plan = cellPlan(sphere, s2System, "5");65 assert.deepEqual(plan.map((cell) => `${cell.role}:${cell.hash}`), [66 "neighbor:1", "neighbor:3", "neighbor:7", "neighbor:9", "neighbor:b",67 "scope:5",68 "child:44", "child:4c", "child:54", "child:5c",69 ]);70});7172test("an S2 cell at the floor is a leaf too", () => {73 const plan = cellPlan(sphere, s2System, "47a1cb");74 const last = plan[plan.length - 1];75 assert.equal(last?.role, "leaf");76 assert.equal(last?.hash, "47a1cb");77 // Five neighbours at the root, three at each of ten levels, then the leaf.78 assert.equal(plan.length, 5 + 3 * 10 + 1);79});8081test("every plan cell carries the five things a renderer reads", () => {82 for (const cell of cellPlan(sphere, s2System, "5")) {83 assert.equal(typeof cell.hash, "string");84 assert.equal(cell.extent.length, 4);85 assert.ok(cell.ring.length >= 4);86 assert.ok(cell.pole === null || cell.pole === "north" || cell.pole === "south");87 assert.ok(Number.isInteger(cell.childIndex));88 }89 // The cells of that plan that circle a pole say so, and they are the ones a90 // renderer has to close along the picture's own top or bottom edge: the91 // opposite face over in the context, the held face, and the one child of it92 // the pole actually falls in.93 const polar = cellPlan(sphere, s2System, "5").filter((cell) => cell.pole !== null);94 assert.deepEqual(polar.map((cell) => `${cell.hash}:${cell.pole}`),95 ["b:south", "5:north", "54:north"]);96});9798test("the ancestor chain walks root first", () => {99 assert.deepEqual(ancestorChain(geohashSystem.on(square), "m6s"), ["", "m", "m6", "m6s"]);100 assert.deepEqual(ancestorChain(geohashSystem.on(square), ""), [""]);101 assert.deepEqual(ancestorChain(s2System.on(sphere), "44"), ["", "5", "44"]);102});103104test("session state is an argument, so one ground plans two ways at once", () => {105 const geohash = cellPlan(sphere, geohashSystem, "m");106 const s2 = cellPlan(sphere, s2System, "5");107 assert.notEqual(geohash.length, s2.length);108 // Neither call disturbed the other, because neither wrote anything down.109 assert.deepEqual(cellPlan(sphere, geohashSystem, "m"), geohash);110});