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 / render / test / projection.test.ts
projection.test.ts 5.6K
1// `ATLAS:PIXELS`, judged against numbers worked out by hand.2//3// The projection is five facts: the banner, the window the camera fits, the4// resolution that fit implies, and the two depths — the lens's own and the5// one the view allows over it. Each one is derivable on paper from the6// documented arithmetic (`chart/projection.ts`'s own header), so each is7// derived on paper here and inlined, with the mars corpus volume as the one8// real-data anchor and invented lenses for the shapes the corpus does not9// carry.10//11// The window every derivation assumes: 1280 × 720 with the legend beside the12// map, which leaves the map 909 pixels wide — stated here rather than fitted13// backwards, so every expectation below is checkable on paper.1415import test from "node:test";16import { strict as assert } from "node:assert";17import {18 COORDINATE_SYSTEM, OVERZOOM_LEVELS, fitResolution, lensExtent, levelResolution, viewMaxZoom,19} from "../chart/projection.ts";20import { payloads, tileGrid } from "./fixtures.ts";21import { SQUARE, gamePlane, splitSheet } from "./models.ts";2223const MAP_WIDTH = 909;24const MAP_HEIGHT = 659;2526test("the banner is the constant every diagnostics snapshot carries", () => {27 // The sentence is the contract: every diagnostics consumer compares it28 // verbatim, and it may never be reworded.29 assert.equal(COORDINATE_SYSTEM,30 "ATLAS:PIXELS; origin top-left; x increases right; y decreases downward");31});3233test("a level's resolution is the world square over its tiles", () => {34 const grid = tileGrid("mars");35 assert.equal(levelResolution(grid, 0), 32, "the square in one tile");36 assert.equal(levelResolution(grid, 6), 0.5, "sixty-four tiles across");37 assert.equal(levelResolution(grid, 6), grid.size / 2 ** 6 / grid.tileSize);38});3940test("Mars opens at the fit its own bounds imply", () => {41 const grid = tileGrid("mars");42 const payload = payloads("mars").get("global");43 assert.ok(payload, "the corpus carries the world");44 const lens = payload.lenses[0];45 assert.ok(lens, "the world has a lens");4647 // The lens declares an 8192 × 4096 window anchored at the origin, and the48 // sign flips exactly once on the way into OL coordinates.49 const extent = lensExtent(lens, grid);50 assert.deepEqual(JSON.parse(JSON.stringify(extent)), [0, -4096, 8192, 0]);5152 // The fit is whichever axis is tighter: 8192/909 ≈ 9.012 beats 4096/659 ≈53 // 6.215, so the width decides and the resolution is exactly 8192/909.54 const resolution = fitResolution(extent, MAP_WIDTH, MAP_HEIGHT);55 assert.equal(resolution, 8192 / 909, "the resolution the whole world fits at");5657 // The view's zoom is the log of the fit against the level-zero resolution,58 // which is what makes zoom 0 the square in one tile on every volume:59 // log2(32 / (8192/909)) = log2(909/256) ≈ 1.8281364841941070.60 const zoom = Math.log2(levelResolution(grid, 0) / resolution);61 assert.ok(Math.abs(zoom - Math.log2(909 / 256)) < 1e-12, `${zoom} is the derived fit`);62 assert.ok(Math.abs(zoom - 1.828136484194107) < 1e-12, "and the paper number agrees");6364 assert.equal(lens.maxZoom, 6, "the lens's own depth, as the corpus declares it");65 assert.equal(viewMaxZoom(lens), 8, "the depth the view allows over it");66 assert.equal(viewMaxZoom(lens) - lens.maxZoom, OVERZOOM_LEVELS, "two levels of overzoom");67 assert.equal(lens.interpolate, true, "a photograph is resampled smoothly");68});6970test("the camera fits the window a lens drew, not the ground inside it", () => {71 // The split sheet's three lenses each window one band of the square, and72 // one of them declares a smaller surface too — the title in the margin.73 // The camera fits **bounds** every time: a reader opening a piece of a74 // split sheet is shown everything the lens drew, title included.75 const sheet = splitSheet();76 const lenses = sheet.worlds.get("aloft")?.lenses ?? [];77 assert.equal(lenses.length, 3, "the invented sheet is really split");78 for (const lens of lenses) {79 const rect = lens.bounds;80 assert.ok(rect, `${lens.name} declares a window`);81 assert.deepEqual(lensExtent(lens, sheet.tileGrid),82 [rect.x, -(rect.y + rect.height), rect.x + rect.width, -rect.y]);83 }84 // The bands, worked out by hand: y grows downward in a payload and the85 // flip pins each band between its negated bottom and top edges. Compared86 // through the serializer, because the top band's top edge is the seam's87 // one honest `-0` — a lens anchored at y = 0, negated.88 const bands = lenses.map((lens) => lensExtent(lens, sheet.tileGrid));89 assert.deepEqual(JSON.parse(JSON.stringify(bands)), [90 [0, -2048, 8192, 0],91 [0, -4096, 8192, -2048],92 [0, -6144, 8192, -4096],93 ]);94});9596test("a lens with a surface and no bounds opens on the whole world square", () => {97 // The invented hand-drawn lens is the one that tells the two rectangles98 // apart: it declares a 5,066 × 4,191 surface and no bounds, and the camera99 // must fit the 8192 square — the surface belongs to whatever divides the100 // world, not to the fit.101 const city = gamePlane();102 const lens = city.worlds.get("old-quarter")?.lenses.find((held) => held.name === "Hand-Drawn");103 assert.ok(lens?.surface, "the lens declares a surface");104 assert.equal(lens.bounds, undefined, "and no bounds");105 assert.deepEqual(lensExtent(lens, SQUARE), [0, -8192, 8192, 0]);106107 // In a 648-pixel-high map the square's height decides: 8192/648 ≈ 12.642108 // beats 8192/909, and the fit zoom is log2(32/(8192/648)) = log2(648/256).109 const resolution = fitResolution(lensExtent(lens, SQUARE), MAP_WIDTH, 648);110 assert.equal(resolution, 8192 / 648);111 assert.ok(Math.abs(112 Math.log2(levelResolution(SQUARE, 0) / resolution) - Math.log2(648 / 256)) < 1e-12);113});