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 / render / test / pyramid.test.ts
pyramid.test.ts 5.9K
1// What a lens holds, judged against the corpus tile inventories — and2// against a split sheet small enough to inventory by hand.3//4// The corpus inventories are the record of every tile in every corpus5// pyramid, taken from the archives themselves. A reader that agrees with6// them agrees about three separate things at once: the bounds a lens fills,7// the coverage bitsets past `fullZoom` (and, as Mars shows, at every level a8// lens chooses to declare one), and the per-level format table. Disagree9// about any of them and this test names the tiles you invented and the ones10// you lost.11//12// Several lenses may name one pyramid — a split sheet keeps one archive and13// windows it three ways — so the derivation is compared as the union over14// the lenses that name it, which is what a pyramid on disk actually is. No15// corpus volume is split any more, so the invented sheet below carries that16// case, with every expected tile written out longhand.1718import test from "node:test";19import { strict as assert } from "node:assert";20import { Coverage, inventoryNames, tileWindowAt } from "../data/pyramid.ts";21import { inventories, payloads, tileGrid, volumes } from "./fixtures.ts";22import { splitSheet } from "./models.ts";2324test("every corpus pyramid is exactly the tiles the seam would ask for", () => {25 let pyramids = 0;26 for (const slug of volumes()) {27 const grid = tileGrid(slug);28 const list = [...payloads(slug).values()].flatMap((payload) => payload.lenses);29 const byPyramid = new Map<string, (typeof list)[number][]>();30 for (const lens of list) {31 byPyramid.set(lens.tiles, [...(byPyramid.get(lens.tiles) ?? []), lens]);32 }33 for (const inventory of inventories(slug)) {34 const derived = new Set<string>();35 for (const lens of byPyramid.get(inventory.pyramid) ?? []) {36 for (const name of inventoryNames(lens, grid)) derived.add(name);37 }38 const recorded = new Set(inventory.tiles.map((tile) => tile.name));39 const missing = [...recorded].filter((name) => !derived.has(name));40 const invented = [...derived].filter((name) => !recorded.has(name));41 assert.deepEqual(42 { missing: missing.slice(0, 5), invented: invented.slice(0, 5) },43 { missing: [], invented: [] },44 `${slug}/${inventory.pyramid}`);45 assert.equal(derived.size, inventory.count, `${slug}/${inventory.pyramid}: count`);46 pyramids++;47 }48 }49 assert.equal(pyramids, 3, "bend-or's basemap and both mars captures were checked");50});5152test("a split sheet's lenses share one pyramid, and the pyramid is their union", () => {53 // The whole inventory, worked out by hand from the three bands. Level 0 is54 // the square in one tile and every band touches it. At level 1 the 409655 // tiles split Sky and Land across row 0 and Depths onto row 1; at level 256 // each band owns its own row of four, and the square's bottom row belongs57 // to nobody. Level 3 is Depths alone, past its `fullZoom`, and its bitset58 // admits exactly three of the sixteen tiles its window touches.59 const expected = [60 "0/0/0.webp",61 "1/0/0.webp", "1/0/1.webp", "1/1/0.webp", "1/1/1.webp",62 "2/0/0.webp", "2/0/1.webp", "2/0/2.webp",63 "2/1/0.webp", "2/1/1.webp", "2/1/2.webp",64 "2/2/0.webp", "2/2/1.webp", "2/2/2.webp",65 "2/3/0.webp", "2/3/1.webp", "2/3/2.webp",66 "3/0/4.webp", "3/4/4.webp", "3/7/5.webp",67 ];68 const sheet = splitSheet();69 const lenses = sheet.worlds.get("aloft")?.lenses ?? [];70 assert.equal(new Set(lenses.map((lens) => lens.tiles)).size, 1, "one archive, three windows");7172 const union = new Set<string>();73 for (const lens of lenses) {74 for (const name of inventoryNames(lens, sheet.tileGrid)) union.add(name);75 }76 assert.deepEqual([...union].sort(), expected, "the pyramid on disk, tile for tile");7778 // And each band asks only for its own window: the level-0 tile is the one79 // tile every lens shares, so the union is smaller than the sum.80 const counts = lenses.map((lens) => inventoryNames(lens, sheet.tileGrid).length);81 assert.deepEqual(counts, [7, 7, 10], "Sky, Land, and Depths with its three deep tiles");82 assert.ok(counts.reduce((held, count) => held + count, 0) > union.size,83 "the shared root tile is derived by every lens and stored once");84});8586test("a coverage bitset is read least significant bit first, row major", () => {87 // Three tiles of a 4×2 window anchored at (1, 0): the second and fourth of88 // the top row and the third of the bottom one, which are indices 1, 3 and 689 // row-major. Low bit first inside the byte makes that 0b01001010 — the90 // whole window fits in one byte, and a reader that walked the bits the91 // other way round would find the mirror of this set.92 const coverage = new Coverage({93 x: 1, y: 0, w: 4, h: 2, bits: Buffer.from([0b01001010]).toString("base64"),94 });95 assert.equal(coverage.has(2, 0), true);96 assert.equal(coverage.has(4, 0), true);97 assert.equal(coverage.has(1, 0), false);98 assert.equal(coverage.has(3, 1), true);99 assert.equal(coverage.has(1, 1), false);100 // Outside the declared window is absent, not an exception.101 assert.equal(coverage.has(9, 9), false);102 assert.equal(coverage.has(-4, 0), false);103});104105test("a lens fills its own window and no more", () => {106 const grid = { sourceZoom: 13, firstTile: 4064, tileSize: 256, size: 8192 };107 const whole = {108 name: "w", tiles: "w", minZoom: 0, maxZoom: 6, fullZoom: 6, sourceZoom: 13,109 formats: ["jpg", "jpg", "jpg", "jpg", "jpg", "jpg", "jpg"], interpolate: true,110 };111 // A world square in one tile at level 0, sixty-four across at level 6.112 assert.deepEqual(tileWindowAt(whole, grid, 0), { x0: 0, x1: 1, y0: 0, y1: 1 });113 assert.deepEqual(tileWindowAt(whole, grid, 6), { x0: 0, x1: 64, y0: 0, y1: 64 });114 // Half the square in height is half the rows: the Mars case.115 const half = { ...whole, bounds: { x: 0, y: 0, width: 8192, height: 4096 } };116 assert.deepEqual(tileWindowAt(half, grid, 6), { x0: 0, x1: 64, y0: 0, y1: 32 });117});