Merge main into bundler-2/work

This commit is contained in:
doxa-maps-88655c70 2026-09-24 04:15:36 +00:00
commit 5d5f162b1c
3 changed files with 73 additions and 6 deletions

View file

@ -0,0 +1,58 @@
// check-zone-census — every zone a USER CAN SELECT must have a poster.
//
// THE BUG CLASS THIS EXISTS FOR: a short count with no error. generate.mjs used to
// abort on the first bad zone (16 of 29 written, the rest silently unbuilt), and
// separately, tooling that enumerated clusters.json rather than the LEGEND warned
// that two zones were "MISSING" when nothing was missing — they are superseded and
// unreachable. Both look like success from a distance; only a census shows them.
//
// ★ ASK THE LEGEND, NOT THE DATA FILE. clusters.json carries 30 list definitions;
// the legend exposes 28 leaves. deaf-old-world and deaf-new-world-asia-pacific are
// collapsed into the unified `deaf` row (affinity bloc A017) — verified against
// live records: 63 + 60 = 123 = A017, zero groups on either side of the
// difference. They stay in clusters.json deliberately (three isolation-proof
// scripts assert deaf_lists.length === 2), but no user can reach them, so a poster
// for them is not a gap. Counting the file instead of the legend manufactures two
// phantom failures and hides real ones behind them.
//
// Usage: node internal/check-zone-census.mjs [postersDir]
import { readFileSync, existsSync, readdirSync } from 'node:fs'
import { resolve, join } from 'node:path'
import { pathToFileURL } from 'node:url'
const REPO = process.env.DOXA_REPO
|| resolve('/home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-packet')
const DATA = join(REPO, 'plugin-designs', 'my-upg-100-list', 'data')
const POSTERS = resolve(process.argv[2]
|| join(REPO, 'library', 'scripts', 'posters', 'out', 'posters'))
const clusters = JSON.parse(readFileSync(join(DATA, 'clusters.json'), 'utf8'))
const { buildLegendNodes, leafZones } = await import(pathToFileURL(join(DATA, 'legend-tree.js')).href)
const leaves = leafZones(buildLegendNodes(clusters, { records: null }))
if (!existsSync(POSTERS)) {
console.log(`ZONE-CENSUS: SKIP — no posters at ${POSTERS} (run generate.mjs first).`)
process.exit(0)
}
const built = new Set(readdirSync(POSTERS).filter((f) => f.endsWith('.html')).map((f) => f.slice(0, -5)))
const missing = leaves.map((z) => z.id).filter((id) => !built.has(id))
// Definitions that exist in the data but are NOT reachable — reported as context,
// never as a failure, so the difference between "unbuilt" and "superseded" stays
// visible instead of being argued about every time someone counts.
const declared = new Set()
for (const k of ['lists', 'deaf_lists', 'asia_lists', 'uncovered_lists']) {
for (const z of clusters[k] || []) declared.add(z.id)
}
const unreachable = [...declared].filter((id) => !leaves.some((z) => z.id === id))
console.log(` selectable zones : ${leaves.length}`)
console.log(` posters built : ${built.size}`)
console.log(` unreachable defs : ${unreachable.length}${unreachable.length ? ` (${unreachable.join(', ')}) — superseded, not a gap` : ''}`)
if (missing.length) {
console.error(`\nZONE-CENSUS: FAIL — ${missing.length} selectable zone(s) have NO poster:`)
for (const id of missing) console.error(` - ${id}`)
process.exit(1)
}
console.log(`\nZONE-CENSUS: PASS — every one of the ${leaves.length} selectable zones has a poster.`)

View file

@ -42,7 +42,7 @@
// ONLY=laos node publish-html.mjs # one
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { dirname, join, resolve } from 'node:path';
const __dirname = dirname(fileURLToPath(import.meta.url));
@ -56,13 +56,21 @@ if (!existsSync(SRC)) {
process.exit(1);
}
// The zone ids the app can actually reach. A poster for no zone is not publishable.
// ★ THE ZONE IDS THE APP CAN ACTUALLY REACH — ask the LEGEND, not clusters.json.
// Enumerating the raw cluster keys is what this used to do, and it is wrong:
// clusters.json carries 30 list definitions but the legend exposes 28 leaves.
// deaf-old-world and deaf-new-world-asia-pacific are SUPERSEDED by the unified
// `deaf` row (affinity bloc A017) — verified against live data, 63 + 60 = 123 =
// A017 exactly, with zero groups on either side of the difference. They are kept
// in clusters.json deliberately (three isolation-proof scripts assert
// deaf_lists.length === 2), but no user can select them, so a poster for them is
// unreachable. Enumerating them made this script warn that two zones were
// "MISSING" when nothing was missing at all.
const clusters = JSON.parse(readFileSync(
join(REPO, 'plugin-designs', 'my-upg-100-list', 'data', 'clusters.json'), 'utf8'));
const zoneIds = new Set();
for (const key of ['lists', 'deaf_lists', 'asia_lists', 'uncovered_lists']) {
for (const z of clusters[key] || []) zoneIds.add(z.id);
}
const { buildLegendNodes, leafZones } = await import(
pathToFileURL(join(REPO, 'plugin-designs', 'my-upg-100-list', 'data', 'legend-tree.js')).href);
const zoneIds = new Set(leafZones(buildLegendNodes(clusters, { records: null })).map((z) => z.id));
mkdirSync(DEST, { recursive: true });

View file

@ -57,6 +57,7 @@
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
"check:card-deck": "node internal/check-card-deck.mjs",
"check:poster-raster": "node internal/check-poster-raster.mjs",
"check:zone-census": "node internal/check-zone-census.mjs",
"check:print-geometry": "node internal/check-print-geometry.mjs",
"check:packet-browser": "node internal/check-packet-browser.mjs",
"check:packet-render": "node internal/check-packet-render.mjs",