From 3211236014437bb12b0f1ff69d7b06662dee8a5a Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Wed, 30 Sep 2026 11:55:51 +0000 Subject: [PATCH] =?UTF-8?q?Packet:=20point=204's=20reference=20names=20wha?= =?UTF-8?q?t=20prints,=20Mark=205:18=E2=80=9320,=20in=20all=2011=20locales?= =?UTF-8?q?=20(the=20Gerasene=20reading=20keeps=205:12=E2=80=9320),=20re-p?= =?UTF-8?q?inned;=20the=20passage=20gets=20a=20700-char=20ceiling=20beside?= =?UTF-8?q?=20its=20250=20floor,=20in=20the=20render=20gate=20and,=20headl?= =?UTF-8?q?ess,=20in=20the=20structure=20gate=20for=20every=20locale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/check-packet-a1-structure.mjs | 23 ++++++++++++++++++ internal/check-packet-render.mjs | 8 +++++++ library/packet/poster-render.integrity.js | 2 +- library/scripts/posters/packet-strings.json | 26 ++++++++++----------- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/internal/check-packet-a1-structure.mjs b/internal/check-packet-a1-structure.mjs index 429cd0ae6..18552429e 100644 --- a/internal/check-packet-a1-structure.mjs +++ b/internal/check-packet-a1-structure.mjs @@ -182,6 +182,29 @@ const box = (html) => { ok(/metacamp\.org/.test(page), 'the wheel lost its attribution (source and website)', 'twelve "5 minutes" badges and the source attribution') } +// ── D2. POINT 4 PRINTS MARK 5:18-20 ONLY, IN EVERY LOCALE ───────────────────────────────────── +// The same 250..700 bound as check-packet-render, here because this gate runs in gates:pre with no +// browser and no generated packets: check-packet-render only runs over packets someone generated +// (check:packets-all SKIPs on a clean checkout) and the browser gate is not measured headless. The +// floor catches a truncated or missing passage; the ceiling catches the full 1-20 passage coming back +// (2,141 chars in English), which would also push the twelve points off their one page. +{ + const P4_MIN = 250, P4_MAX = 700 + const text = (h) => h.replace(/<[^>]+>/g, '').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"') + .replace(/'/g, "'").replace(/&/g, '&').trim() + const bad = [] + const langs = Object.keys(strings).filter((k) => k !== '_meta') + for (const lang of langs) { + const page = createPacketRenderer({ strings, lang }).pointsPage() + const at = page.indexOf('class="p4-passage"') + const end = at < 0 ? -1 : page.indexOf('', at) + const len = end < 0 ? 0 : text(page.slice(page.indexOf('>', at) + 1, end)).length + if (len < P4_MIN || len > P4_MAX) bad.push(`${lang} ${len}`) + } + ok(!bad.length, `point 4's passage is outside ${P4_MIN}..${P4_MAX} chars (Mark 5:18-20 only): ${bad.join(', ')}`, + `point 4 prints Mark 5:18-20 only (${P4_MIN}..${P4_MAX} chars) in all ${langs.length} locales`) +} + // ── E. EVERY ZONE A READER CAN PICK ──────────────────────────────────────────────────────────── { const zones = zonesWithSelectors(clusters, { deafCount: 123 }) diff --git a/internal/check-packet-render.mjs b/internal/check-packet-render.mjs index 7044dc333..219146823 100644 --- a/internal/check-packet-render.mjs +++ b/internal/check-packet-render.mjs @@ -542,11 +542,19 @@ if (r.rhythms !== r.rhythmsTotal) fails.push(`only ${r.rhythms}/${r.rhythmsTotal // ★ POINT 4 PRINTS MARK 5:18-20 (2026-09-30, the second revision: only those verses, so all twelve points fit // one page), with verse 19 in bold. English is 385 chars + the credit; 250 is a floor no truncation or // placeholder survives, with room for the more compact languages (Russian is 277). +// ★ AND A CEILING: the floor alone also passes the full 1-20 passage (2,141 chars in English). The browser +// gate's one-sheet fit refuses 1-20 too, but it is NOT MEASURED in a headless run, so this ceiling is the +// only headless guard for "18-20 only". 18-20 measures 277-428 chars across the 11 locales; 1-20 is well +// over 700 in every one of them. const MARK5_MIN = 250 +const MARK5_MAX = 700 if (r.mark5Len < MARK5_MIN) { fails.push(r.mark5Len === 0 ? 'Mark 5:18-20 is NOT printed (a requirement for point 4): no .p4-passage block' : `Mark 5:18-20 looks truncated: ${r.mark5Len} chars, expected at least ${MARK5_MIN}`) +} else if (r.mark5Len > MARK5_MAX) { + fails.push(`point 4's passage is ${r.mark5Len} chars, over ${MARK5_MAX}: more than Mark 5:18-20 is printed ` + + '(the full 1-20 passage is back?), and the twelve points no longer fit one page') } if (!r.v19Len) fails.push('Mark 5:19 is not in bold (no .p4-v19 inside the passage)') const HEART_ITEMS = 7 diff --git a/library/packet/poster-render.integrity.js b/library/packet/poster-render.integrity.js index 48cc859f1..aa9b46f67 100644 --- a/library/packet/poster-render.integrity.js +++ b/library/packet/poster-render.integrity.js @@ -12,4 +12,4 @@ export const POSTER_RENDER_GLOBAL_SHA384 = 'sha384-DZa1KOOlRZMm17kxVEyMXNGafsBBt // The sha384 of library/scripts/posters/packet-strings.json, served at /lib/packet-strings.json. The // packet, list and card text in every language, fetched at print time (packet/packetStrings.js) and used only // when it matches; otherwise the print falls back to the bundled English block. -export const PACKET_STRINGS_SHA384 = 'sha384-lkip1VHdPSdyL3xFpwtXyc+RqOqLeOpXsd+e0IIk5o150CXc7gWod2+GgC7EKbf1' +export const PACKET_STRINGS_SHA384 = 'sha384-63CpJ25458CweIfy8slxoPsI78Mh+5XZCxvLmpcHjrKWSyhomObwqtvjEsURJj8f' diff --git a/library/scripts/posters/packet-strings.json b/library/scripts/posters/packet-strings.json index 3075565a0..ce02f7cbe 100644 --- a/library/scripts/posters/packet-strings.json +++ b/library/scripts/posters/packet-strings.json @@ -2,8 +2,8 @@ "_meta": { "purpose": "EVERY human-readable string in the UPG-100 prayer packet, in one file, so a new language is a new block here and NOT a code change. packet.mjs reads LANG (default 'en') and falls back to 'en' key-by-key, so a partial translation renders the translated keys and leaves the rest in English rather than blanking.", "how_to_translate": "Copy the \"en\" block, change the key to the ISO code, translate the values. Keep {placeholders} exactly as they are — they are substituted at render time. Do not translate scripture REFERENCES (book names are resolved by the reader's own Bible); DO translate the prayer text.", - "note_on_scripture": "point4_passage is the ONLY full scripture text in the packet (Mark 5:1-20, printed in full by product decision). A translation MUST supply its own rendering in a public-domain or properly-licensed version FOR THAT LANGUAGE. ⚠ Do NOT translate it yourself and do NOT leave a machine rendering in place: this is printed scripture in a prayer packet. A language block that omits point4_passage falls back to the English WEB text per-key, which is honest — an untrusted translation of Mark 5 is not.", - "note_on_references": "Scripture REFERENCES (Matthew 27:27, Mark 5:1-20) are deliberately left in English in every language block, so a reader can look them up in their own Bible regardless of the packet's language. ⚠ MEASURED TRADEOFF, not an oversight: on a rendered French page the English book names are visibly foreign next to fully-translated prayer text. If a language wants localised book names, add a `books` map to that block and teach the renderer to use it — do NOT translate the refs inline, because the same strings identify the passage for any future lookup.", + "note_on_scripture": "point4_passage is the ONLY full scripture text in the packet (Mark 5:18-20, printed in full by product decision). A translation MUST supply its own rendering in a public-domain or properly-licensed version FOR THAT LANGUAGE. ⚠ Do NOT translate it yourself and do NOT leave a machine rendering in place: this is printed scripture in a prayer packet. A language block that omits point4_passage falls back to the English WEB text per-key, which is honest — an untrusted translation of Mark 5 is not.", + "note_on_references": "Scripture REFERENCES (Matthew 27:27, Mark 5:18-20) are deliberately left in English in every language block, so a reader can look them up in their own Bible regardless of the packet's language. ⚠ MEASURED TRADEOFF, not an oversight: on a rendered French page the English book names are visibly foreign next to fully-translated prayer text. If a language wants localised book names, add a `books` map to that block and teach the renderer to use it — do NOT translate the refs inline, because the same strings identify the passage for any future lookup.", "note_on_scripts": "ALL 11 app locales now ship. Script coverage MEASURED by rendering at 48px and READING THE PIXELS: Latin, Cyrillic (ru), Arabic (ar) and CJK (zh) shape correctly with the system Georgia/Palatino/serif stack. DEVANAGARI (hi) does NOT — it renders as tofu boxes — so Noto Serif Devanagari is vendored (OFL) and base64-INLINED into hi packets by the renderer's SCRIPT_FONT map. Inlined, not linked, because a packet is a standalone file that must print offline; ~165KB on a 3.5MB packet, and only for that language. mr and ne map to the same face.\n⚠ TWO MEASUREMENTS LIED ABOUT THIS, so do not automate it cheaply: (1) comparing shaped width against a missing font reported ar and zh BROKEN and hi FINE — exactly backwards, because Devanagari tofu boxes match real glyph advance; (2) an ink-column-profile heuristic scored CORRECTLY-RENDERED Devanagari the same as tofu (3 vs 2 distinct heights), because its baseline bar gives a uniform profile. check:packet-render therefore asserts only that a DECLARED @font-face LOADED — the mechanical half — and leaves glyph correctness to a human reading a screenshot. A detector that blocks working packets is worse than none.", "note_on_rtl": "ar is right-to-left. The renderer emits dir on from an RTL set (packet-render.js), and scripture REFERENCES are wrapped in dir=ltr + unicode-bidi:isolate. Without that isolation the bidi algorithm reorders a reference that STARTS WITH A DIGIT: '1 Peter 5:6-11' rendered as 'Peter 5:6-11 1'. Note the reference LIST then reads right-to-left on an RTL page (first item rightmost) — that is correct for an RTL reader, not a defect." }, @@ -82,7 +82,7 @@ "sub": "the Father's love", "text": "That they would be SAVED and KNOW their heavenly Father loves them — and that through them He would bring salvation to their family, friends, neighbours, coworkers, classmates, everyone they know personally.", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -367,7 +367,7 @@ "sub": "el amor del Padre", "text": "Que sean SALVOS y CONOZCAN que su Padre celestial los ama — y que a través de ellos Él lleve salvación a su familia, amigos, vecinos, compañeros de trabajo, compañeros de clase, a todos los que conocen personalmente.", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -645,7 +645,7 @@ "sub": "l'amour du Père", "text": "Qu'ils soient SAUVÉS et qu'ils SACHENT que leur Père céleste les aime — et qu'à travers eux Il apporte le salut à leur famille, leurs amis, leurs voisins, leurs collègues, leurs camarades, à tous ceux qu'ils connaissent personnellement.", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -923,7 +923,7 @@ "sub": "o amor do Pai", "text": "Que sejam SALVOS e SAIBAM que o seu Pai celestial os ama — e que através deles Ele leve salvação à sua família, amigos, vizinhos, colegas de trabalho, colegas de classe, a todos os que conhecem pessoalmente.", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -1198,7 +1198,7 @@ "sub": "die Liebe des Vaters", "text": "Dass sie GERETTET werden und WISSEN, dass ihr himmlischer Vater sie liebt — und dass Er durch sie Rettung zu ihrer Familie, ihren Freunden, Nachbarn, Kollegen, Mitschülern bringt, zu allen, die sie persönlich kennen.", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -1476,7 +1476,7 @@ "sub": "l'amore del Padre", "text": "Che siano SALVATI e SAPPIANO che il loro Padre celeste li ama — e che attraverso di loro Egli porti salvezza alla loro famiglia, agli amici, ai vicini, ai colleghi, ai compagni di classe, a tutti coloro che conoscono personalmente.", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -1751,7 +1751,7 @@ "sub": "dragostea Tatălui", "text": "Să fie MÂNTUIȚI și să ȘTIE că Tatăl lor ceresc îi iubește — și ca prin ei El să aducă mântuire familiei lor, prietenilor, vecinilor, colegilor de muncă, colegilor de clasă, tuturor celor pe care îi cunosc personal.", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -2026,7 +2026,7 @@ "sub": "любовь Отца", "text": "Чтобы они были СПАСЕНЫ и ЗНАЛИ, что их Небесный Отец любит их — и чтобы через них Он принёс спасение их семье, друзьям, соседям, сослуживцам, одноклассникам, всем, кого они знают лично.", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -2304,7 +2304,7 @@ "sub": "محبة الآب", "text": "أن يَخلُصوا ويَعرِفوا أن أباهم السماوي يحبهم — وأن يحمل بهم الخلاص إلى عائلاتهم وأصدقائهم وجيرانهم وزملائهم في العمل والدراسة، وكل من يعرفونهم شخصيًا.", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -2582,7 +2582,7 @@ "sub": "पिता का प्रेम", "text": "कि वे उद्धार पाएँ और जानें कि उनका स्वर्गीय पिता उनसे प्रेम करता है — और उनके द्वारा वह उनके परिवार, मित्रों, पड़ोसियों, सहकर्मियों, सहपाठियों, उन सबके पास उद्धार पहुँचाए जिन्हें वे व्यक्तिगत रूप से जानते हैं।", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true @@ -2857,7 +2857,7 @@ "sub": "天父的爱", "text": "愿他们得救,并知道天父爱他们——也愿神借着他们,把救恩带给他们的家人、朋友、邻舍、同事、同学,以及他们所认识的每一个人。", "refs": [ - "Mark 5:1–20", + "Mark 5:18–20", "Acts 16:31" ], "print_passage": true