doxa-maps/internal/check-mercator-slice.mjs
Doxa Maps 838df76c14 Paraphrase the Driver's words, keeping the attribution and the measurement
studio strips verbatim Driver quotes from comments and messages when landing (L64), and
these were written before that. Reporting WHO reported a defect and WHEN is what makes a
comment checkable, so the attribution stays; it is only the quoted wording that goes.

Five sites: the scrollbar gate, the embed-url gate and embedBase, the poster-slicing
gate, and research-map's print rule. Every one keeps the date, the source and the
measured numbers — which were always the load-bearing part. "shows a disproportionate
amount of the far south" says the same thing as the quote it replaces, and the 37.0%
band squeezed into 25% is what actually proves it.

No behaviour touched; comments and gate headers only. All six gates still green.
2026-09-28 18:09:41 +00:00

78 lines
3.9 KiB
JavaScript

#!/usr/bin/env node
/**
* check-mercator-slice — a poster's tile rows must be equal on SCREEN, not equal in degrees.
*
* THE DEFECT: Simple Research's Export → poster showed far too much of Antarctica
* and comes out at the wrong, vertical aspect.
*
* The big-poster renderer stitches tiles, each drawn into an EQUAL-HEIGHT slot of the output. It picked
* each row's latitude band by dividing the degree span evenly — but screen height is proportional to
* ln(tan(pi/4 + lat/2)), not to latitude. So equal degree bands are unequal screen bands, and painting
* them into equal slots squeezes some and stretches others.
*
* ★ THE ASSERTION IS THE PROPERTY, AND THE CONTROL IS THE OLD CODE. Every band must occupy the same
* share of the rendered height, AND the degree-linear slicing it replaced must FAIL that same check —
* otherwise this passes on a tree where nothing was fixed.
*
* Exit 0 pass · 1 fail.
*/
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const { equalHeightLatBands, mercatorY, latAtMercatorY, MERCATOR_MAX_LAT } =
await import(resolve(ROOT, 'library/maps/mercator-slice.js'))
const fails = []
const shares = (bands, s, n) => {
const tot = mercatorY(n) - mercatorY(s)
return bands.map(([bs, bn]) => (mercatorY(bn) - mercatorY(bs)) / tot)
}
for (const [s, n, rows] of [[-60, 75, 4], [-85, 85, 6], [-10, 10, 3], [20, 60, 5], [-60, 75, 1]]) {
const bands = equalHeightLatBands(s, n, rows)
if (bands.length !== rows) { fails.push(`${rows} rows requested, ${bands.length} returned`); continue }
const sh = shares(bands, s, n)
const worst = Math.max(...sh.map((x) => Math.abs(x - 1 / rows)))
if (worst > 1e-9) {
fails.push(`bands of a ${s}..${n} view in ${rows} rows are not equal on screen: `
+ `shares ${sh.map((x) => (x * 100).toFixed(1) + '%').join(' ')}`)
}
// north-first, contiguous, and covering the whole span
if (Math.abs(bands[0][1] - Math.max(s, n)) > 1e-9) fails.push(`first band does not start at the north edge (${s}..${n})`)
if (Math.abs(bands[rows - 1][0] - Math.min(s, n)) > 1e-9) fails.push(`last band does not end at the south edge (${s}..${n})`)
for (let i = 1; i < rows; i++) {
if (Math.abs(bands[i][1] - bands[i - 1][0]) > 1e-9) fails.push(`bands ${i - 1}/${i} are not contiguous`)
}
}
// ★ THE CONTROL: the degree-linear slicing this replaced MUST fail the same property.
const linearBands = (s, n, rows) => {
const step = (n - s) / rows
return Array.from({ length: rows }, (_, r) => [n - (r + 1) * step, n - r * step])
}
const linWorst = (() => {
const sh = shares(linearBands(-60, 75, 4), -60, 75)
return Math.max(...sh.map((x) => Math.abs(x - 0.25)))
})()
if (linWorst <= 1e-9) {
fails.push('degree-linear slicing PASSES the equal-height assertion, so the assertion cannot detect '
+ 'the defect it exists for — it is measuring nothing.')
}
// The poles are undefined in Mercator; a world view must clamp rather than produce Infinity.
const polar = equalHeightLatBands(-90, 90, 4)
if (!polar.every(([a, b]) => Number.isFinite(a) && Number.isFinite(b))) {
fails.push('a -90..90 view produces non-finite bands; Mercator is undefined at the poles and must clamp')
}
if (Math.abs(polar[0][1]) > MERCATOR_MAX_LAT + 1e-6) fails.push('the north edge was not clamped to the Mercator limit')
if (Math.abs(latAtMercatorY(mercatorY(41.2)) - 41.2) > 1e-9) fails.push('mercatorY/latAtMercatorY are not inverses')
if (fails.length) {
console.error('MERCATOR-SLICE: FAIL')
for (const f of fails) console.error(` ✗ ${f}`)
process.exit(1)
}
console.log(`MERCATOR-SLICE: PASS — every tile row occupies an equal share of the RENDERED height across five `
+ `view/row combinations, degree-linear slicing fails the same check (worst band off by ${(linWorst * 100).toFixed(1)} `
+ 'points), and a polar view clamps instead of going infinite.')