Commit graph

25 commits

Author SHA1 Message Date
Doxa Maps
042d2ec10f UPG100 packet: the set captions count every group of the list
A group with no record in a set figure's data (no near-neighbour data,
for example) was in none of that figure's numbers, so the captions
added up to less than the list: Laos printed 94 of its 95 groups. Each
caption now names the groups without data ("1 without data", in all 11
languages), so its parts add up to the list. check:packet-variants
asserts the sum both ways.
2026-10-03 00:55:43 +00:00
Doxa Maps
e3d56d2f15 UPG100 packet: full-width life charts, a shorter page-1 strategy, self-fitting points
- The life calendar and the reached chart share one grid at the page's
  full printable width: each week a 22x11 box, bold year numbers every
  5 years (larger every 10), week numbers 1/13/26/39/52, a clear rule
  every 10 years and light rules at the quarters. The reached chart has
  no week or year numbers; each 10-row block is labelled with its share
  of the list's people, 10% to 100%.
- Page 1: the UPG100 series sets its own one-paragraph Jesus column
  (series.json strategyBody) and prints the explainer's first paragraph
  only (sideBoxMore), so the three columns balance and the map grows.
  The long text is one constant shared by both header layouts; the
  Country and India-state posters print it unchanged.
- The points page takes, per language, the largest size tier that fits
  one sheet, up to the earlier two-sheet sizes. The tier comes from a
  character-count estimate; the render gate fails if the real page is
  ever taller than that estimate.
2026-10-03 00:20:58 +00:00
Doxa Maps
9910b7e410 check:evangelical-share reads the life grids as SVG: the week and year numbers are text.lc-t, and the reader's words are checked with the tags stripped (the grid's markup names data-weeks as geometry) 2026-10-02 22:28:30 +00:00
Doxa Maps
0a407907d5 Full detail pages: the centered set reads upward to the cross with a legend that says what a ring is; the life in weeks and the reached grid are one light SVG each, the same shape, x11
Centered set: the ring numbers ride the axis BELOW the cross, 9 at the bottom, so the eye climbs 9 → 1 to it;
the dots turn half a circle to keep the number lane clear. The ring legend opens with what a ring means in
plain words (sets_rings_lede x11) before listing the rings that hold someone.
Life in weeks + the reached grid: each grid is ONE inline SVG, two paths of square subpaths (empty, shaded),
hairlines between decades, small week and year numbers; measured on the two pages together: 10,553 page nodes
→ 86, load 474 → 214 ms, print 641 → 320 ms. The reached grid now has the life grid's exact shape (same
geometry, no week or year numbers, centred), shaded from the top by this list's share.
Gates: variants and render decode each grid's subpaths back to box indexes in reading order (5,200 distinct,
Jesus' 33 years = the first 1,716, the share from the top, the same viewBox for both); two-sided: shading
the LAST 33 years fails both. Gospel's three optional nits folded in (it un’altra, example_note x5, ru чёрточка).
2026-10-02 22:01:16 +00:00
Doxa Maps
7e18948698 Prayer points page: the seven prayer themes and three prayer scenes head the twelve points, x11; point 12 is the John 17 blessing ladder
Themes (bless the nations, perfect unity, protection from evil, hearing God's voice, true identity in Christ,
deep relational health, the priesthood of believers) to weave into the wheel's five-minute blocks, then the
scenes treasure, mountain and sea, each with its two references from the Wall of People Groups' poster
(Haggai 2:7 · Matthew 13:44, Isaiah 2:2 · Matthew 21:21, Habakkuk 2:14 · Matthew 8:26). Point 12: the blessing,
the great, the greater and one of the greatest blessings of John 17, unity and protection, beyond all we ask
or think (John 17:3 · John 17:11–23 · Ephesians 3:20). Words drafted by the gospel lane x11; books Haggai,
Isaiah, Habakkuk, Ephesians x11. The page still fits one sheet in every language (19px text, 6px between points).
Gates: parity fails a point whose references are not English's (ten languages kept point 12's old ones) and
needs 7 themes + 3 scenes per language; variants checks the themes block and point 12 in both packets.
2026-10-02 21:51:18 +00:00
Doxa Maps
3c1d38bdc8 Packet lists: the three questions head each page ONCE over one grid of tally cells, and both packets open their list with the explanation band, x11
Full detail: the question no longer repeats under every tally cell; the heads stand once per page over the
cells' own columns, and each cell is all tally room (96px columns, taller boxes). The worksheet's boxes
get a little more room too.
Both lists' first page: the intro and the blessing (Numbers 6:24-26, its credit, one line of references, the
wheel pointer) at full width, then ONE band like the poster's (ruled above and below, three columns between
short rules): the pace table (strongest to slower, weekly marked, each load computed from the list), what the
three columns ask, and an EXAMPLE row of tally cells under the table's own heads, exactly as wide as the
tally block and directly over it. One code path (explainTop, explainBand) for both packets.
Measured with a 100-group list in all 11 languages: the worksheet's first page holds 10 rows (the tallest,
ru, keeps 67px), Full detail's 12 under a 640px block (the tallest uses 612); the example sits over the real
cells to the pixel. Gates: packet-variants checks the band in BOTH packets (once, before the first row,
computed paces, one example row, heads once per page); packet-render reads the page sizes from the
renderer, fails a clipped first-page block, and measures text direction against the content box (a padded
cell is still pinned left). New strings x11: howto_bless_col, example_title, example_note.
2026-10-02 21:43:24 +00:00
Doxa Maps
db5cd42841 Packet list: the first page explains the list before it lists anyone, with a prayer-pace table, x11
What the three columns ask; the blessing to pray over each group, Numbers 6:24-26 in full with its credit
(the public-domain text per language, compared verse by verse with its source; ro and hi print the English,
as the packet's other scripture does); one line of further blessing references (the Synodal's own Psalm
numbers in Russian); the prayer wheel at the end; and a pace table, daily (strongest) to every three months
(slower), weekly marked as the healthiest, each pace's daily load computed from the list's size.
The first page holds 11 rows (16 before); later pages keep 18. Measured with a 100-group list in all 11
languages: the longest first pages (ru, hi) keep 77px under their last row, the references fit one line.
check:packet-variants asserts the description, once, before the first row, and the computed pace; the
render gate follows the new pagination.
2026-10-02 18:54:44 +00:00
Doxa Maps
3056db68a6 Gates that read the poster wall's markup read the lifted PosterSection too: check:packet-variants (its card-deck and packet tapes) and check:poster-fetch-guard (no direct poster fetch) 2026-10-02 07:23:19 +00:00
Doxa Maps
2c397d25ef Source comments state design history and measurements as facts, without quoting or naming anyone
Comments in library/ and internal/ that quoted a person, attributed a decision to one, used first person,
named fleet agents or carried private host names now state the same technical fact in neutral words:
poster-render (including the comments it writes into generated posters), packet-render, the poster and
research scripts, PosterWall, DataTable, the legends, the layouts and the gates. The network-policy note
states the policy itself. Behaviour is unchanged: a few printed gate messages and reason labels drop an
agent name, the poster-render integrity pin follows its comment-only edit, and series.json changes only in
its _meta and _why notes. The historical poster design snapshots (library/scripts/posters/snapshots/),
which nothing reads, are removed.
2026-10-01 00:37:17 +00:00
Doxa Maps
f963d90bc3 Packet: the lightweight worksheet shows every group's photo and short description too (a photo cell and a two-line description per row, 16 + 18 rows a page); the variant gate asserts both headless, the render gate counts the rows' photos and follows the new pagination 2026-09-30 12:04:20 +00:00
Doxa Maps
d78e485643 "Who among them knows Christ" counts people, not weeks
The page reused the life calendar's grid and counted its shaded boxes in
weeks ("0.61% · 32 weeks"), under a week axis and a year axis, with a lede
calling it "one human life in weeks". On this page a box is an equal share
of the list's PEOPLE, so the same 5,200 boxes are now drawn with no week or
year axis, and the legend states each side in people: how many follow
Jesus and how many do not yet, each with its share (the two percentages
sum to 100 as printed), plus how many people one box stands for. Numbers
are formatted in the reader's locale.

The labels become "Followers of Jesus" / "Not yet following Jesus" and the
lede drops the weeks, in all eleven languages; the served strings file is
re-pinned. check:evangelical-share asserted the weeks figure; it now
asserts the people totals, the bare percentage and the absence of weeks
and axes, with the life page (which keeps its axes) as the negative
control. check:packet-variants found the grid by an exact class string
and now matches the class prefix.
2026-09-29 20:08:55 +00:00
Doxa Maps
ae6d60780b Send the research map's live map to packet page 1 through the mapImage seam
Page 1 keeps the live map rather than a fetched sheet, and it was handed
across as a bare <img> in mapSvg — raw markup the renderer interpolates.
The page-1 redesign no longer draws that full page, so the map arrived
shrunk inside a layout built for a stored sheet. It reads as a styling
quirk rather than a wiring mistake, which is why it survived here after
country-posters was fixed. The URL now goes through mapImage, which places
it as the whole page.

This is the third defect of the same shape between these two files: a fix
lands in one twin and not the other, and nothing reports it. So the gate
asserts the seam for every host that rasterises a live map, rather than for
one file.

Two fixes to the check itself, both found by poisoning it rather than by
reading it. `[^)]*` cannot cross the arrow function's own parens, so
`useMapPoster(() => map.value).render(` never matched and the new section
skipped both files — it passed while testing nothing. And an older
assertion pinned the exact literal `{ mapSvg, variant }`, so moving to the
seam failed a check that was still describing the shape it replaced; it now
asserts that the variant travels, not the argument list it travelled in. A
gate that names one spelling of a call turns every refactor into a false
alarm, and a false alarm is how a real one gets waved through.

Both twins now use the seam. The render is skipped for a card deck, which
has no map page; this map's legend menu only asks for full or lightweight,
so that arm guards a future caller rather than a path taken today.
2026-09-29 07:31:17 +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
88f747cf5c The bounded set and the centered set each get their own page again, at print size
A second look: the bounded set on one page and the centered set on another. Each chart
keeps everything the redesign gave it — its heading, the cross at the centre, the nine numbered
target bands with dots spread through them, the full ring legend — as one large square panel (960px)
centred on its page, instead of two 660px panels sharing one. The centered set's page carries the
legend; both carry the pair's heading so they read as one spread.

Also the profile row's photo URL is escaped (esc(src)), the identical hunk regions landed in
1aeafead0, so the two merge cleanly: a quote in an API image_url closed the attribute and injected
markup into the printed document.

check:packet-a1-structure and check:packet-variants assert the two-page order and the derived
lightweight delta (four back pages dropped).
2026-09-29 05:26:27 +00:00
Doxa Maps
181f723bf8 The legend's Packet export did nothing on the research map
The legend emits 'packet:full' or 'packet:lightweight' — it widened its
existing event rather than adding a second one — and this router still
tested the whole string for 'packet'. Neither submenu item matched any
branch, so the Packet export opened nothing, logged nothing, and looked
like a menu item nobody had wired. Found by upg100, who had already hit
the same thing in its own copy.

The kind is split before it is compared, the way upg-100-list does it, and
a bare 'packet' still means the full packet so an older emitter keeps
working. The variant then has to survive the call as well: openPacket
ignored it and printPacket defaults to full, so lightweight would have
printed the full packet even once the menu fired.

The gate asserts the seam rather than one file — it reads the kinds the
legend actually emits and requires every consumer to split before
comparing, because this bug cannot be seen by reading either side alone.
It currently fails on country-posters/countries-map.vue, which has the
identical dead menu and is not mine to change.

Two fixes to the check itself while writing it: a fixed-size window read
only part of a long router and reported correct code as broken, so the
function's extent is found by brace depth; and a trailing \b after a
quoted literal never matches, which made the assertion fire on every
consumer including the working ones.
2026-09-29 05:26:26 +00:00
Doxa Maps
46a53f0b4e The carried commits keep the reason and drop the speaker
Thirteen lines arriving in this landing named or quoted the Driver: the
packet renderer and its two gates, and the desktop build. check:no-new-driver-references fails on each of
them, so they are reworded here the same way as the sweep: the date and
the requirement stay, the speaker and the quote go.
2026-09-29 05:26:26 +00:00
Doxa Maps
4e2685cdad The packet follows the printed Module 1 sheets: page 1 is the map alone, the calendar holds Jesus' 33 years, the sets share one page
A review of the full-detail packet, page by page:
- Page 1: a poster carries its own title, so the packet's title above it printed two headers and
  shrank the map to fit beneath them. A poster sheet now fills the page inside the white margin by
  itself; in another language its kicker and title become the packet's own, fitted to the English
  width. A map-only sheet keeps the packet's title in the band it leaves empty.
- The life calendar is laid out like the Module 1 "Life Calendar in Weeks": square boxes, week
  numbers across the top, the year down the side, a rule between decades, framed and centred. The
  first 33 years are shaded as the life of Jesus (1,716 weeks).
- The %-evangelical grid fills from the top in reading order instead of scattering.
- The flow pages had no page padding at all, so their headings sat on the sheet's edge; they keep
  the same margin as every other page now.
- The bounded and centered sets are one page, two square panels side by side as on the Module 1
  list of 100, each headed by name with a cross at its centre. The centered set is a target of nine
  numbered bands, its dots spread through their bands by the shared ringBands geometry (no overlaps,
  none on the numbers), and the legend explains every ring with its count.
- The lightweight packet lists every group as a worksheet: a short note, one column header per page,
  numbered rows sized for paper with room to tick, no photos or descriptions.

Seven new strings in all eleven languages. check:packet-a1-structure and check:packet-variants
assert the new contract two-sided (a poster page 1 has one header, a map-only page 1 still has the
packet's; every ring is named, a populated one with its count and an empty one with a dash).
2026-09-29 05:26:23 +00:00
Doxa Maps
2367c2ef64 Build the prayer packet page by page to its specified structure
FULL DETAIL is four front pages, the profiles, four back pages:
  map, triangle, life calendar, % evangelical, profiles,
  bounded set, centered set, prayer wheel, 12 prayer points.
LIGHTWEIGHT is two front, the profiles, two back:
  map, triangle, profiles, prayer wheel, 12 prayer points.
Both are generated from data, in every packet language, from the
browser and from the offline generator.

PAGE 1 - the map, on a vertical page. One lift for every caller
(packet-render.js liftMap): the browser composable, the UPG 100
profile and the offline generator each carried an identical copy of
the old wrapper (250px down, scale 0.62), and three copies of a layout
decision is how page 1 drifts between print paths. The lift prefers
the map-only vertical sheet (mapSheetFiles: <zone>.packet.html, then
the poster) and places it so its names land on the same 60px margin
every other page keeps; a poster falls back fitted under the title at
0.81 rather than 0.62. The title block is drawn after the map, since a
map-only sheet begins with a full-page background. Anything that is not
a map sheet - an icon-sized <svg> from a host that answers a missing
file with a 200 page - is refused, and page 1 says the map is missing.
Page 1 now states which set it belongs to ("One of 28 UPG 100 prayer
lists"), only when the caller supplies the count.

PAGE 2 - one inverted triangle in the Vision Card's look. The card's
steps are the top (every people group, the unreached, the frontier,
the network's list), narrowing to the UPG 100 packets and, at the tip,
this packet. The card's greens, darkening as it narrows, walked
through its own colour stops; every numeral picks white or ink by the
WCAG ratio (all at least 3.4:1, large bold type). Tier widths are
geometric, not proportional - 2,107, 28 and ~100 against 12,379 would
be hairlines - so where two sets truly nest the caption states the
step removed, and the network's list, which does not sit inside
frontier, gets the card's "N of them are frontier" note instead of a
false subtraction. The packets tier and its count appear only when the
caller supplies one.

BACK PAGES - the bounded and centered sets get a page each at twice the
width, under the one page key, so lightweight drops the pair together.

THE WHEEL - the Metacamp Module 1 prayer wheel: its twelve colours,
sampled from the fill operators in the source PDF's content stream; its
orientation, the opening PRAISE at nine o'clock and READ THE WORD at
twelve; white labels with a shadow and a faint outline, so a print
path that drops SVG filters still leaves them legible on the palest
segments; "5 minutes" badges; the attribution with the website. The
labels stay the packet's own rhythm names, translated in every
language and matching the 12 points page.

The offline generator makes both packets (PACKET_VARIANT=lightweight;
the full packet keeps its names, lightweight is <zone>.lightweight and
stamped with its variant).

STRINGS: four new keys, in all eleven blocks - each block's own words
for "packet" and "prayer list", the frontier note aligned to the word
the frontier tier uses on the same page.

GATES: check-packet-a1-structure (registered in gates:pre) reads every
sheet's CONTENT for the order of both variants, page 1's lift in all
four cases, the triangle's tiers and steps, the wheel's colours and
orientation, and all 28 zones. Mutation-tested eleven ways, each caught
by its own message. check-packet-variants now asserts both sets pages
and a four-sheet difference. The browser gate check-packet-render
counted funnel rows and bars by inline-style substrings, which an SVG
triangle has none of - both would have read zero and passed - so it
now counts .funnel-n against .funnel-tier, its sheet arithmetic counts
the sets pages, and its Latin allowlist carries the renamed product
name. check-packet-browser used a copy of the old wrapper; it now uses
the product's lift.
2026-09-29 02:16:28 +00:00
Doxa Maps
9609cd84ba Say "provisional, pending a product decision" instead of naming a row
Follow-up to 50ebdf89, which kept an internal tracking id on the grounds
that a reader needs to know where the answer comes from. The ruling is
that in shipped code the id should go too, and it is right: a row number
means nothing to someone reading packet-render.js, and it points at a
place they cannot open.

What the comment has to carry is that the value MAY CHANGE, so a later
reader does not treat it as settled and hardcode it somewhere. "Provisional,
pending a product decision" says that without pointing anywhere.

Three sites, all mine. CenteredSetTarget.vue carries the same reference
and is not mine; it belongs to the carded fleet sweep.
2026-09-28 17:53:38 +00:00
Doxa Maps
35dfd82b83 Drop quotes and transcript references from the packet comments and gates
Per the 17:25Z rule: no quotes, no citations and no transcript references
in strings, commits or code. Studio had to strip these from two of my
commit messages during L64, which is a sign I should be doing it at the
source rather than leaving it to whoever lands the work.

Nine sites across five files, all of them lines I added. The engineering
content is unchanged -- what goes is the attribution, not the reasoning.
"A disabled button is visually indistinguishable from an unresponsive one"
is the durable fact; who observed it is provenance a later reader cannot
verify and that ties shipped code to a private transcript.

Where a decision is genuinely still open I keep the tracking reference
("still open, tracked as row 89") but not the speaker, because a reader
hitting that comment needs to know the value may change and where the
answer will come from.

Scope is deliberately my own unlanded additions. packet-render.js,
PosterWall.vue and upg-100-list.vue carry many more such comments from
before the rule; those are not mine and a fleet-wide sweep is a separate
decision. Flagged rather than silently rewritten.

All seven gates green after the edit, including the two whose own header
text changed.
2026-09-28 17:53:38 +00:00
Doxa Maps
1272aa6dc3 Record why the ring legend needs no overflow assertion
gospel landed 21ebb4f2 -- "Stop the ring legend pushing the centered-set
chart off the page" -- which is the same figure and the same legend I had
just added to the packet, so it was worth checking whether the print side
has the bug too.

It does not, and the reason is worth writing down rather than leaving as
arithmetic I happened to do once: a printed sheet is much taller than
their constrained screen pane. With ALL TEN rings named the column needs
~924px of a ~1715px page.

I wrote the assertion anyway and then deleted it, because mutation-testing
showed it only begins to fail at 51 legend rows -- and there are only ten
rings, so it could never fail for any input this code can produce. That is
a tautology dressed as a safety check, the same class as the "rows close
on the bottom margin" assertion already removed from check-profile-table:
a value compared against a bound it cannot reach reads as coverage and is
not.

What actually protects the layout is the empty-ring filter asserted just
above (ten rows is the hard ceiling) plus rega's rendered check. The
comment now says so, including the detail that gospel's fix leans on
overflow:auto and print has no scroll -- so if this page ever does get
tight, their remedy is not available here.
2026-09-28 17:53:38 +00:00
Doxa Maps
4b45285b7d Hold the packet's ring names to the screen's, and adopt its exact words
The agreed ring vocabulary now lives in two catalogues in eleven locales
each -- gospelInternet.json for the screen legend, packet-strings.json for
the printed sheet -- and nothing held them together. They legitimately
want different SHAPES (the screen renders the axes as standalone labels,
the packet composes them into one string per ring), so neither can import
the other; but the WORDS must match.

The failure that makes this worth a gate is quiet: one cell is provisional
on the Driver's row 89, and it appears in rings 1, 4 and 7. Answer it in
one file and not the other and the sheet and the screen state different
things about the same ring, each correct in isolation, with no error
anywhere. Only a reader holding both would notice.

Measuring first showed 36 of 77 cells already differed, in two classes.
Most were grammar -- the packet's culture terms follow a comma
mid-sentence, so they are lowercase -- but some were genuinely different
words: "Same family" against "Same language family", German "verwandte"
against "Nahe", Hindi "वही जन" against "वही जनसमूह". Those are drift, and
the screen's wording wins because it is what readers already see. The
packet now adopts every term verbatim, lowercasing only the first
character of the culture half, which is a no-op in zh/ar/hi and leaves
German common nouns capitalised.

That adoption caught a bug in my own assertion: I had hardcoded ring 7-9
as "Same family" from CenteredSetTarget.vue's header, while the shipping
string is "Same language family". A summary in a comment is a description
of the data, never a second source of it.

The gate also refuses to pass when it compared nothing -- rename a key on
either side and every lookup returns undefined, every pair is skipped, and
a silent check reports success. Mutation-tested both ways: answering row
89 on the screen only, and renaming a packet key.
2026-09-28 17:53:37 +00:00
Doxa Maps
fccf6ee2fe Name the rings on the packet's centered set
The figure printed rings without saying what they mean -- honest but thin.
It now names each one with the vocabulary gospel and exp settled, the same
words the screen legend renders, so a printed sheet cannot disagree with
the screen.

COMPOSED FROM band x culture, NOT A FLAT 1..10 TABLE, and that is the
whole design decision. "Same people" in column 1 is provisional on the
Driver's row 89 -- and it appears in rings 1, 4 AND 7. A ten-string table
would need three edits kept in step, which is exactly how two of them end
up saying different things. Composed, it is one string
(ring_culture_same), and if he picks "culture" the ring numbering, the
columns and the colours are all untouched.

Ring 10 stands alone ("Nearby only") because it is not a band-and-culture
reading at all -- it is what is left when no linguistic relation was
found. The gate asserts that separately, and a mutant that composes it
like the other nine fails.

ONLY THE RINGS THAT HOLD SOMEONE. Ten rows of vocabulary beside a figure
is a glossary, not a legend, and a named ring with no dot on it invites
the reader to hunt for something that is not there. ringTally's per-ring
counts drive the list, so it states what this zone actually contains.

Mutation-tested: a wrong band order, listing all ten rings regardless of
count, and composing ring 10 like the others each fail with their own
message. Eight new strings x eleven locales; packet-strings-parity green.
2026-09-28 17:53:37 +00:00
Doxa Maps
3c1d513cc3 The packet gets its bounded and centered sets page
Brief A1 back matter: after the profiles, before the wheel, full detail
only. The lightweight packet is map, triangle, profiles, wheel, points --
these two figures are the part a reader sits with rather than carries.

The layouts are regions' library/components/charts modules, imported
rather than injected: unlike describeGroup's country resolver they have
zero imports and run under plain node, and packet-render already imports
scatterCells from that directory. Verified by importing and CALLING them,
not by reading them. They return data, so the SVG is ours.

Gospel's non-negotiable is carried through and checked rather than
trusted: the bounded set keeps a deliberate gap either side of the
boundary because a dot ON the line is the one thing a bounded set cannot
express, and print has no hover to disambiguate it. Measured on the real
module with 40 groups -- inside max 85.3 of 92 allowed, outside min 119.8
of 112 required. The figure scales through the viewBox, so the gap lives
in coordinates the scaling cannot touch; shrink the figure, never the gap.

UNKNOWN POINTS ARE STATED, NOT PLOTTED. layoutBoundedSet gives them no
x/y, which is the module being right: `reached` undefined means we do not
know which side they belong on, so there is no honest place to draw them.
My first version mapped them through the same dot() helper and crashed on
p.x.toFixed -- a better outcome than silently drawing them at (0,0), which
would have put "we don't know" inside the circle. They go in the tally,
because on a printed page a category the reader cannot see reads as zero.

check-packet-variants' sheet delta goes 2 -> 3, updated in this commit
rather than relaxed into a range. It also now SUPPLIES the sets fixture:
setsPage returns '' without data, so a build() that omitted `sets`
exercised the filter for every page except the newest one, and the gate
sat green through the whole rewrite while asserting a delta that had
become wrong. A fixture missing an input silently narrows what the whole
file covers.

Seven strings x eleven locales; packet-strings-parity green. Gospel's own
ring names live in gospelInternet.json, a plugin catalogue, so there is no
collision with these.
2026-09-28 17:53:37 +00:00
Doxa Maps
642fc15310 Prayer List gets the cards' caret, and starts printing the packet
The Driver asked for the SAME caret the cards already have on the Prayer
List, with two entries -- Full detail and Lightweight -- and said plainly
not to destroy the print options that exist. So nothing is merged, nothing
is removed, and the Poster is untouched.

There was no dropdown COMPONENT to reuse: the cards' caret is an inline
disclosure, one ref plus a sibling tape of plain buttons, deliberately not
a <select> because a native dropdown collides with other dropdowns on the
host page. So the PATTERN is generalized rather than copied. deckOpenFor
now holds a compound `${id}:${which}` key, which keeps "opening one closes
the others" free -- copy/paste would have given two open-states, and two
tapes could then sit open at once and overlap in the grid gap.

While wiring it, a real defect surfaced: THE WALL'S PRAYER LIST BUTTON HAS
NEVER PRINTED A PACKET. downloadPacket calls printList, a columnar table of
name/country/population/religion. The packet -- map, life calendar,
%-evangelical grid, triangle, profiles, wheel, points -- is printPacket,
which every other surface calls and this wall never did. That is a direct
cause of the packet "not following the design that was asked for" here: no
change to the renderer could reach a button that was not calling it.
printList stays exported for any surface that still wants the list.

The two packets are a FILTER over the existing page array, not a second
code path, so they cannot drift apart in layout, i18n, RTL or print scale.
The filter is keyed and never indexed -- an index filter reads identically
today and drops the wrong page the first time the order changes, and that
order is the Driver's. listPages is deliberately outside the filter: a
variant may drop a FIXED page, never one whose count depends on the data,
because that drops people silently. An unknown variant falls back to full.

internal/check-packet-variants.mjs pins all of it, and was mutation-tested
rather than trusted: dropping list pages, reverting to printList, adding a
second open-state ref, and typoing a variant id each fail with their own
message.
2026-09-28 16:49:01 +00:00