packet gate: a prayer wheel with no words on it is not a prayer wheel
Some checks are pending
build-gate / build (push) Waiting to run

Blanking every wheel segment label and regenerating left check:packet-render
reporting "12 segments, 11/11 rhythms, content intact — PASS" on a wheel that
was a twelve-colour pie chart.

Both printed numbers stayed true, which is why nothing caught it. The twelve
segments are <path> elements and survive their labels being removed. And the
rhythm assertion searched document.body.innerText, where the SAME twelve rhythm
words are rendered TWICE in every packet — once per wheel segment, once as a
prayer-point heading. So "every rhythm word appears in the document" was
satisfied by the points page alone and said nothing about the wheel.

That is the self-consistency-vs-completeness family again, with a new twist:
when two surfaces share one vocabulary, a document-wide string search cannot
attribute a hit to the surface being asserted. The fix is scoping the query to
the surface, not a better word list.

wheelLabels counts the non-numeric <text> runs inside the .wheel-title sheet's
own svg, excluding the twelve "5" pips and the "5 minutes x 12" footer. It
counts ELEMENTS, NOT DISTINCT WORDS, deliberately: `praise` and `praise_end`
collapse to one word in English — which is exactly why the existing line reads
11/11 and not 12/12 — and that collision is locale-dependent, so a distinct-word
count would be a different number per language.

The Driver's spec names the wheel as one of the packet's four sections, so 12
labels is a contract. Mutated packet reports 0/12 and fails; restored reports
12/12.

i18n verified at the rendered layer with a partial locale block (4 of 12 rhythms
translated): the wheel painted four XX- labels with the other eight falling back
to English and zero blank — and the split pair is visible, segment 1 English
PRAISE beside segment 12 XX-PRAISE_END, which is what per-key resolution looks
like. All 11 locales regenerated with 95 photos each: check:packets-all 11/11,
every one 12/12 labelled, including RTL ar and Devanagari hi. Wave 34/34.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 13:23:57 +00:00
parent a177f98aff
commit 53fea6ee38

View file

@ -186,6 +186,32 @@ const r = await page.evaluate(() => {
rhythms: RHYTHMS.filter((w) => body.includes(w)).length,
rhythmsTotal: RHYTHMS.length,
wheelSegs: document.querySelectorAll('svg path[fill^="hsl"]').length,
// ⚠ COUNT THE WHEEL'S OWN LABELS, SCOPED TO THE WHEEL SHEET. `rhythms` below
// searches document.body.innerText, and the SAME rhythm vocabulary is rendered
// TWICE — once per wheel segment, and once per prayer point (each point prints
// tr(p.rhythm) as its heading). So "every rhythm word appears in the document" is
// satisfied by the POINTS page alone and says nothing about the wheel.
// Measured 2026-09-24: blanking every wheel segment label left the gate reporting
// "12 segments, 11/11 rhythms, content intact — PASS" on a prayer wheel that was a
// twelve-colour pie chart with no words on it. Both numbers stayed true: the
// segments are <path> elements and survive, and the words were still on the points
// page. The Driver's spec names the wheel as one of the packet's four sections.
//
// COUNT ELEMENTS, NOT DISTINCT WORDS. `order` renders 12 segments from 12 keys, but
// `praise` and `praise_end` collapse to the same word in English (which is why the
// line below prints 11/11, not 12/12) — and that collision is LOCALE-DEPENDENT. A
// distinct-word count would be a different number per language.
wheelLabels: (() => {
const title = document.querySelector('.wheel-title')
const sheet = title ? title.closest('.sheet') : null
const svg = sheet ? sheet.querySelector('svg') : null
if (!svg) return -1
// The segment labels are the non-numeric <text> runs: the pips are all "5" and
// the footer reads "5 <minutes> x 12".
return [...svg.querySelectorAll('text')]
.map((t) => (t.textContent || '').trim())
.filter((s) => s && !/^\d+$/.test(s) && !/^\d[\s\S]*×\s*12$/.test(s)).length
})(),
// ⚠ NOT body.includes('Gadarenes'). That was an ENGLISH proper noun, so this
// assertion would go RED on a correctly-translated packet and stays green today
// only because every locale falls back to English for this key. The check and the
@ -268,6 +294,17 @@ if (r.cards && expectPhotos && r.imgs === 0) {
+ 'PACKET_EXPECT_PHOTOS=0 to declare this a deliberately photo-less packet.')
}
if (r.wheelSegs !== 12) fails.push(`prayer wheel has ${r.wheelSegs} segments, expected 12`)
// A SEGMENT WITHOUT ITS WORD IS A COLOUR. 12 labels, one per segment, on the wheel
// sheet itself — not merely somewhere in the document.
const WHEEL_LABELS = 12
if (r.wheelLabels !== WHEEL_LABELS) {
fails.push(r.wheelLabels === -1
? 'no prayer-wheel svg found (no .wheel-title sheet) — the wheel page did not render'
: `the prayer wheel paints ${r.wheelLabels} segment label(s), expected ${WHEEL_LABELS} — `
+ 'the rhythm words are rendered TWICE in this packet (wheel segments and prayer-point '
+ 'headings), so the document-wide rhythm check below passes on the points page alone '
+ 'and cannot see an unlabelled wheel')
}
if (r.rhythms !== r.rhythmsTotal) fails.push(`only ${r.rhythms}/${r.rhythmsTotal} prayer rhythms present`)
// Mark 5:1-20 in full is a Driver requirement for point 4: "it actually lists out the
// full scripture, because prayer point number four is actually read the word." The
@ -437,7 +474,7 @@ if (r.ranges.length) {
console.log(` sheets/PDF pages : ${r.sheets} / ${pages}`)
console.log(` people cards : ${r.cards} (${r.imgs} photos, ${r.nophoto} typographic)`)
console.log(` prayer points : ${r.points} / ${POINTS_EXPECT}`)
console.log(` wheel segments : ${r.wheelSegs}`)
console.log(` wheel segments : ${r.wheelSegs} (${r.wheelLabels}/${WHEEL_LABELS} labelled)`)
console.log(` Mark 5 in full : ${r.mark5Len} chars`
+ `${scriptureIsEnglish ? ' ⚠ UNTRANSLATED (English fallback)' : ''}`)
console.log(` metacamp.org : ${r.metacamp}`)