Commit graph

6 commits

Author SHA1 Message Date
Doxa Maps
75e581019e UPG-100 packet page 1 is the full UPG-100 poster: the letter sheet with its title, scripture banner, three-column header and footer, generated at the click
generateSheet gains `sparse` (default: every format but 'packet', so every existing caller is unchanged);
the UPG-100 zone sheet answers the packet's 'packet' request with the full letter poster, the same
1500x1941 page. check:poster-browser-equiv compares that sheet with Node's letter format without SPARSE.
2026-10-02 19:44:34 +00:00
Doxa Maps
16dca4d266 Posters drawn at the click use the SPARSE layout, like their stored thumbnails
The bold header on a poster -- the UPG count and population set large, a
bigger map, and no "Jesus' Strategy to Feed the 5000" / "What is a Prayer List"
columns -- is the SPARSE layout, the "thumbnail-worthy" design. The `posters`
script generates with SPARSE=1, so every stored thumbnail has it. But the env
handed to the generator in the browser carried only FORMAT and POSTER_DATE, so
EVERY sheet drawn at the click -- UPG-100 and country alike -- fell back to the
dense layout.

MEASURED on FINAL (two UPG-100 zones, Northwest and South-Central India): the
thumbnail shows the SPARSE header; the print the reader actually gets shows the
small facts table, both vision columns and a smaller map. That is the reported
"the country poster looks worse than the UPG-100 poster" -- the comparison was a
SPARSE thumbnail against a dense sheet.

(a) posterSheets.js passes SPARSE for every POSTER format and NOT for 'packet'.
    That is exactly the split the two build scripts make: `posters` sets
    SPARSE=1, `packets` does not. The renderer's formats are 3x4, 3x3, letter and
    packet, so 'packet' alone keeps the full layout its pages were built around.

(b) check:poster-browser-equiv's poster case now runs the Node side with
    SPARSE=1 as well. It used to compare dense with dense, so it passed while no
    reader ever saw that layout on a card. The packet case stays dense on both
    sides. PASS: country-npl 909026 bytes on each; asia-india-gujarat (packet)
    835047 on each.

The stored UPG-100 thumbnails are untouched by this. What changes is the PRINT,
which now matches them.
2026-09-30 02:53:15 +00:00
Doxa Maps
6a0aade5c1 The browser/Node sheet equivalence check can measure on a fresh checkout
check:poster-browser-equiv is chained in gates:pre and exited 75 NOT MEASURED
every run. Its input, out/_records.json, is gitignored and written only by
generate.mjs against a live API, so on CI and on any box without credentials the
gate never looked at anything. The runner reported that honestly rather than as
a pass, but an identical NOT MEASURED every run is indistinguishable from an
unwired gate -- and this is the gate that proves a sheet generated in the browser
is byte-identical to the one Node writes.

An equivalence check needs the SAME input on both sides, not fresh input, so a
committed fixture loses nothing it was measuring. The fixture is the 124 public
records the two arms actually consume: Nepal's 34 for the country arm, and the
90 slugs the Gujarat zone selects for the zone arm. Same fields the served maps
show; no other data.

Verified equivalent to the full 2,106-record cache: identical zone pins in the
same order, identical Nepal set, count 90, pop 3,164,770, series {2209, 28, 1}.

Record ORDER is load-bearing -- it drives draw and label order -- so the fixture
keeps the cache's order. A first cut sorted it: every aggregate matched and the
pin SET matched, while the order differed. Sorting a fixture would have quietly
changed the sheet it produces.

generate.mjs gains RECORDS_FILE so a caller can name the offline input instead of
only the API-written cache. It still fails closed without an API when no offline
file is given. The gate passes it the very file the browser arm read, so both
sides are fed the same records by construction.

The gate now says which input it measured, because the full cache widens coverage
to all 175 countries where it exists and the fixture is the floor everywhere else.

Two-sided, on this tree: with the fixture it PASSES byte-identical on both arms
(834,174 and 921,522 bytes). Dropping the province boundaries at
posterSheets.js:206 -- the defect this gate's header records, where a country
beside India or China printed without them -- makes the country arm 251,576
against Node's 921,522 and the gate exits 1. Restored, it passes again.
2026-09-29 12:44:25 +00:00
Doxa Maps
7987f100c1 The map-sheet generator is served and imported at print time, so country-posters is back under the 1.5 MB ceiling
development was RED on gates:post (verify bundles-shipped): country-posters.js had grown to 1.66 MB, over
the 1.5 MB ceiling, when the in-browser sheet generator landed. Measured by building without it: the
generator alone was 64.9 KB of the bundle (single-file builds inline any import), and Natural Earth's
places another ~170 KB (moved to a fetch in 8167415c5).

library/packet/poster-render.js is a pure module with no imports, so the tree now serves it verbatim
at assets/lib/poster-render.js (one generate-tree line) and posterSheets imports it BY URL at the first
print — resolved against the page, not the bundle, and kept out of the build with @vite-ignore. Every map
that can print a sheet sheds it; it is downloaded once, by a reader who prints, and cached across maps.

Measured on real builds: country-posters 1,662,962 -> 1,472,015 bytes (ceiling 1,500,000);
my-upg-100-list 1,231,789; verify-bundles-shipped passes; the bundle holds the runtime import and no
generator code. check:poster-browser-equiv now also asserts the served copy is byte-identical to its
source, and still passes byte-identical through makeZoneSheet and generateCountrySheet, which import the
served copy. gates:pre 73/73.
2026-09-29 06:23:20 +00:00
Doxa Maps
7067c0ae96 Fix what an adversarial review of the print standard confirmed: 21 defects, each re-checked against the code
Ten agents read today's branch (five finders, one skeptic per area who had to confirm each finding
against the current code); 21 distinct defects survived. All are fixed here.

Generator and its proof
- generateCountrySheet fed only ADM0, so a country beside India or China printed without their
  provinces (Nepal: 251 KB against Node's 921 KB). It loads the full boundary set, as the driver does.
- check:poster-browser-equiv rebuilt its own feed, so it could not see that. It now calls what the
  browser calls (makeZoneSheet, generateCountrySheet — Nepal), deletes the target before the Node run,
  runs Node with a clean environment, and FAILS (not "not measured") when the Node generator breaks.
  Proven two-sided: it fails with the ADM1 fix reverted.
- The facts table's country list sorted in the reader's browser locale; pinned to en-US (Node's
  default, so Node's output still hashes to the baseline).
- zone-sheet-print.js imports the library relatively, so bun, Node and the bundler resolve it alike.

Print seams and wiring
- The wall's Poster Map button used its own stored-file fetch and never the generated sheet, so every
  country poster 404'd on the published tree; it now prints the generated sheet first.
- Country Posters wall: card stocks passed (its Card Deck could never print), iso3-names passed (zh/ar
  page 1 kept English names), the stored fallback reads previews/ (where the tree serves them).
- Card-stock labels render at render time, so a locale change relabels them.
- The records provider caches by language AND list, and does not keep a failed membership load.
- The UPG-100 legend's Card Deck returns quietly on a parent row instead of an evaluator error; the
  selection fetch uses PRINT_FIELDS.

Packet
- The centered-set caption counts what is DRAWN (a ring too full to separate reports its remainder).
- Each ring's number lane is sized to its own radius (ring 1 put a dot under its badge).
- Per-box line keeps a decimal below 10 people ("≈ 0" for small lists); worksheet rows name the country;
  the localized poster kicker is fitted against the poster's real 19px, not 22.

Plurals and gates
- regions.countryCount / regionCount were authored in English zero|one|other order in de, es, fr, hi, it
  and pt, so CLDR picked "27 Land", "1 países". Rewritten in each locale's own order; check:plural-forms
  now fails a string in English order (proven on the old German string).
- check:optional-t-equivalence (gates:post) crashed on the new plural fallback's imports; it injects the
  real cldrChoiceIndex and asserts the plural path (ru 21/3/5, en 0/1/7).
- check:packets-all and check-packet-render counted people as cards only, so every lightweight packet
  read as "a packet of nobody"; worksheet rows count, and the browser gate checks the grid's top fill
  (the renamed .lc-frame) instead of the retired scatter. check:packet-variants now reads the grid itself
  — 5,200 boxes, Jesus' 1,716 from the top, the % shading from the top — proven: it fails on a renderer
  that shades the last boxes.
gates:pre 68/68, check:optional-t-equivalence PASS, both bundles build.
2026-09-29 06:23:19 +00:00
Doxa Maps
b95c40e0f9 Map sheets are generated in the browser at the click, by the same generator the Node script runs
The Driver's print standard: every export is generated when it is asked for, and only previews are
stored. The zone and country map sheets — the poster, and the map-only sheet on a packet's page 1 —
existed only if library/scripts/posters/generate.mjs (Node) had written them ahead of time; no country
had a print-resolution sheet at all.

- library/packet/poster-render.js is generate.mjs's drawing layer (2,645 lines) as a pure factory,
  createPosterGenerator({ env, SERIES_DEF, ISO3_NAME, adm0, adm1Ind, adm1Chn, nePlaces, series }). Moved
  VERBATIM (template literals carry their whitespace into the output), `process.env` read through a
  local shadow; its inputs are its free variables, found by a scope-aware parse, not a grep (a grep
  missed adm0, and the proof caught it). generate.mjs is now its Node driver.
- PROVEN BYTE-IDENTICAL: all 118 sheets of a pinned offline matrix — the 28 zones in 3x4, 3x3, letter
  and packet, and a country sample at both sheet sizes — hash the same before and after.
- data/zone-sheet.js is which group pins in which zone (first claim wins), now ONE function both the
  Node driver and the browser call, so a group cannot pin in one zone on paper and another on screen.
- library/packet/posterSheets.js feeds the factory in the browser: the served full-resolution
  boundaries, the shared series and places tables, a generator per call (caching by format pinned the
  first caller's data on every later sheet). generateCountrySheet does a country end to end;
  data/zone-sheet-print.js a UPG-100 zone. ISO3_NAME moves to library/data (territory-counts re-exports).
- usePosterPrints gains opts.sheet: the poster print is the generated letter sheet as a VECTOR page,
  and the packet's page 1 the generated map-only sheet (item.mapId honoured); stored images remain the
  fallback when generation fails. PosterWall forwards it; the Country Posters wall, the UPG-100 wall and
  the UPG-100 map pass it. The UPG-100 wall and map drop their own record fetches for the shared
  provider, and the map's legend and selection HUD offer the card deck (production).
- check:poster-browser-equiv: the browser feed's sheet is byte-identical to the Node generator's for an
  India zone (packet) and a country — proven two-sided (it fails when the feed drops the city dots).
  check:selection-kinds reads the handler's branches instead of a fixed poster/packet pair, anchored on
  `if (` (proven: it fails with the cards branch deleted). library/packet/README.md: the standard.
gates:pre 68/68; both bundles build.
2026-09-29 05:26:27 +00:00