Country Posters: the whole-India poster prints, every group named
India's card is no longer "too many to fit": it prints as the 60x96 side-lane sheet (all 855 groups named beside the map, crossing-free leaders, real photos), whatever paper the wall asks for, until the shared size picker offers 66x66 and 36x60 too. Its facts row reads "36 states and union territories" (English, from the wall head's plural key). As ruled, the whole-India card offers no prayer list, packet or cards; the state cards keep theirs. Its card preview is the real sheet. check:side-lanes (+ :self-test, gates:pre): the three side-lane formats on a seeded worst case (855 groups, stacked and clustered) through the real renderer: every name once, 0 crossings re-measured from the SVG, no overflow, photos only where real; without the lane clamp the same data crosses 36 times, and the arm fails.
This commit is contained in:
parent
1bb5b90984
commit
1c61cbb926
5 changed files with 124 additions and 5 deletions
BIN
data/posters/country-ind.webp
Normal file
BIN
data/posters/country-ind.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
|
|
@ -50,6 +50,8 @@
|
|||
"check:composites-mount": "node internal/check-composites-mount.mjs",
|
||||
"check:country-names": "node internal/check-country-names.mjs",
|
||||
"check:country-sheet-one-path": "node plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs",
|
||||
"check:side-lanes": "node plugin-designs/country-posters/scripts/check-side-lanes.mjs",
|
||||
"check:side-lanes:self-test": "node plugin-designs/country-posters/scripts/check-side-lanes.mjs --self-test",
|
||||
"check:country-sheet-one-path:self-test": "node plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs --self-test",
|
||||
"check:coverage-consistent": "node internal/check-gospel-coverage-consistent.mjs",
|
||||
"check:coverage-consistent:chained": "VERIFY_MAX_AGE_DAYS=36500 node internal/check-gospel-coverage-consistent.mjs",
|
||||
|
|
@ -348,7 +350,7 @@
|
|||
"fix:sri": "node internal/fix-sri.mjs",
|
||||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:done-evidence check:script-flags check:backlog-status check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:backlog-refs check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:wall-photo-weight check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-design-catalogue",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-new-driver-references check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:dxlife check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:wall-photo-weight check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-design-catalogue",
|
||||
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
|
||||
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
|
|
@ -455,6 +457,8 @@
|
|||
"check:composites-mount": "every capability-4/5 composite's OWN setup() and mounted hooks run without throwing, in en and ar, headless (no browser): vite loads each composite as its bundle entry defines it (configureApp, and ProfileLoader's provides where the entry mounts ProfileLoader), and @vue/runtime-core mounts it on a no-op renderer. Exists because 08f89f55 shipped two composites calling getRuntimeLocale/detectLocale/RTL_LOCALES with no import: the build was green, setup threw a ReferenceError in every locale, and Big Picture and Simple Research rendered nothing. Proves setup runs; cannot prove the children render.",
|
||||
"check:country-names": "every ISO 3166-1 alpha-3 our data uses must resolve to a localized country name. Guards the JOIN, not Intl: library/data/iso3-to-alpha2.json drifting from library/data/countries.json makes a country render as a bare code, which still looks like a value. Checks en/ar/zh (ar is our only RTL locale; zh makes an untranslated Latin code unmistakable) and asserts BOTH directions of drift. Carries two negative arms — an unassigned code and a malformed one — and FAILS if either resolves, because a coverage count cannot otherwise tell a hit from a lookup that returns its input.",
|
||||
"check:country-sheet-one-path": "a country sheet, and an Indian state or union territory sheet, each draw through ONE path (@map/packet/countrySheet.js; country-posters/src/indiaStateSheet.js), so one place cannot print two posters. Parses (comments cannot count) plugin sources: a direct generateCountrySheet FAILS anywhere, a direct generateSheet FAILS inside country-posters outside the state helper, every makeCountrySheet call must pass `countries`, and the known callers must be found. Named data answers: seriesFor over country-posters.json (India 1 of 1, each 3x3 country N of 173, an absent code no series, exactly one no-sheet country); over the served india-states.json + gb-IND-ADM1.geojson: 36 divisions, the 34 with groups numbered 1..34 each with an outline, the rest named, the shared ADM1 never mutated.",
|
||||
"check:side-lanes": "the large side-lane sheets (60x96, 66x66, 36x60) through the real poster-render.js, on a SEEDED worst case (855 groups, stacked duplicates, tight clusters, real and placeholder photos): every name drawn exactly once, leader crossings 0 (re-measured from the SVG, and equal to the renderer's own count), no overflow rows, photos only where a real one exists and the format allows them.",
|
||||
"check:side-lanes:self-test": "a poster-render.js copy WITHOUT the lane clamp must make check:side-lanes' crossing arm FAIL on the same seeded data (it does: 36 crossings), so the arm is shown able to fail.",
|
||||
"check:country-sheet-one-path:self-test": "plants each defect check:country-sheet-one-path guards (a direct generateCountrySheet call, makeCountrySheet without countries, the old guard that skipped India, a position given to an absent code) and requires each to FAIL; a comment naming generateCountrySheet must NOT.",
|
||||
"check:coverage-consistent": "the gospel-resources surfaces must agree: no resource on an ISO RESERVED code (und/mul/mis/zxx), every resourced code NAMED, table reach recounted off the pins, no invented URL, tiers reconcile. Static, sub-second. Self-test: `bun run check:coverage-consistent:poison`, all 12 clauses (A-L) must fail. Clause L is the one that is not about consistency: it fails when the link-verification record goes stale, because 'link checked' is a claim about the present tense. Written after the same drift defect was found by hand three times.",
|
||||
"check:csv-shape": "the export must OPEN as a table: header on line 0, every row matching. Run the `check:csv-shape:self-test` script for the NEGATIVE test (forces the provenance comment back to line 0 IN MEMORY and requires this gate to REJECT it) — the header used to claim 'NEGATIVE-TESTED' with no way to re-run it.",
|
||||
|
|
|
|||
BIN
plugin-designs/country-posters/assets/previews/country-ind.webp
Normal file
BIN
plugin-designs/country-posters/assets/previews/country-ind.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -37,7 +37,7 @@
|
|||
*/
|
||||
import { computed, inject, ref, watch } from 'vue'
|
||||
import PosterWall from '@map/components/poster/PosterWall.vue'
|
||||
import { useOptionalT, useOptionalI18n } from '@map/composables/useOptionalT.js'
|
||||
import { useOptionalT, useOptionalI18n, pickPluralForm, fillParams } from '@map/composables/useOptionalT.js'
|
||||
// A country's name in the reader's language (Intl, joined from our ISO-3 codes).
|
||||
import { countryName } from '@map/utils/countryName.js'
|
||||
import { createPeopleGroupRecords } from '@map/lists/peopleGroupRecords.js'
|
||||
|
|
@ -149,7 +149,10 @@ const recordsFor = createPeopleGroupRecords({
|
|||
lang: () => getRuntimeLocale() || detectLocale(),
|
||||
profileConfig,
|
||||
})
|
||||
const countryRecords = (c) => (c.indiaState
|
||||
// ★ THE WHOLE-INDIA POSTER IS THE POSTER ONLY: no prayer list, packet or cards (855 groups would make a book), so its
|
||||
// card's rows are empty. Its sheet still draws every group: makeCountrySheet fetches them itself.
|
||||
const isWholeIndia = (c) => !c.indiaState && String(c.iso3 || '').toUpperCase() === 'IND'
|
||||
const countryRecords = (c) => (isWholeIndia(c) ? Promise.resolve([]) : c.indiaState
|
||||
? (c.slugs.length ? recordsFor({ type: 'pg_slugs', values: c.slugs }) : Promise.resolve([]))
|
||||
: recordsFor({ type: 'country', values: [c.iso3] }))
|
||||
// ★ GENERATED AT THE CLICK, through the ONE shared path (@map/packet/countrySheet.js, which the Regions
|
||||
|
|
@ -165,7 +168,19 @@ const sheetForRecord = makeCountrySheet({
|
|||
assetsBase: () => assetsBase.value,
|
||||
countries: data.countries || [],
|
||||
})
|
||||
const countryOnlySheet = (card, format) => sheetForRecord(byId.get(card.id) || card, format)
|
||||
// ★ INDIA PRINTS AS ONE LARGE SIDE-LANE SHEET (60x96: every group named beside the map, one leader per cluster),
|
||||
// whatever paper the wall asks for, until the shared size picker also offers 66x66 and 36x60. Its facts row names
|
||||
// the true count of its divisions, in English like the rest of the sheet, from the same plural key as the wall head.
|
||||
const INDIA_FORMAT = '60x96'
|
||||
const indiaAdminAreas = () => {
|
||||
const n = (indiaStates.value || []).length
|
||||
return n ? fillParams(pickPluralForm(tStatic('countryPosters.indiaItemCount', 'en') || '', n, 'en'), { n }) : undefined
|
||||
}
|
||||
const countryOnlySheet = (card, format) => {
|
||||
const rec = byId.get(card.id) || card
|
||||
if (isWholeIndia(rec) && format !== 'packet') return sheetForRecord({ ...rec, adminAreas: indiaAdminAreas() }, INDIA_FORMAT)
|
||||
return sheetForRecord(rec, format)
|
||||
}
|
||||
// A state's sheet through its own ONE path (src/indiaStateSheet.js), in India's hue and the indiaState series.
|
||||
const stateSheet = makeIndiaStateSheet({
|
||||
records: recordsFor, assetsBase: () => assetsBase.value, states: () => indiaStates.value, region: INDIA_REGION,
|
||||
|
|
@ -223,7 +238,7 @@ const collapsedBy = (sec) => sec.total < 60
|
|||
// unsupportedReason is an English note (and a flag), not display text. The card already states the
|
||||
// count above it, so the sentence needs no number and no plural agreement.
|
||||
// A requested country with no poster at all says so in regions-list.vue's words (the regions catalogue).
|
||||
const unsupported = (c) => (c.noGroups ? t('countryPosters.indiaStateNoGroups') : c.noPoster ? tRegions('No poster has been generated for this country yet.') : c.unsupportedReason ? t('messages.countryPosterTooMany') : null)
|
||||
const unsupported = (c) => (c.noGroups ? t('countryPosters.indiaStateNoGroups') : c.noPoster ? tRegions('No poster has been generated for this country yet.') : c.unsupportedReason && !isWholeIndia(c) ? t('messages.countryPosterTooMany') : null)
|
||||
|
||||
// ★ THROUGH THE CATALOGUE, like the UPG-100 wall: the heading, the tag, the intro and the counts read
|
||||
// in the page's language. The intro keeps its one emphasis as three plain keys (A, the emphasis, B)
|
||||
|
|
|
|||
100
plugin-designs/country-posters/scripts/check-side-lanes.mjs
Normal file
100
plugin-designs/country-posters/scripts/check-side-lanes.mjs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#!/usr/bin/env node
|
||||
// check:side-lanes — the large side-lane sheets (60x96 / 66x66 / 36x60) name every group once, horizontally, with
|
||||
// leaders that never cross, on a DETERMINISTIC worst case: a seeded synthetic country of 855 groups with stacked
|
||||
// duplicates and tight clusters (district geocodes are like that), long and short names, real and placeholder photos.
|
||||
// Rendered through the real poster-render.js (renderPoster), so it measures what prints.
|
||||
//
|
||||
// Asserts per format: every name drawn exactly once; leader crossings 0 (the renderer's own count, re-measured here
|
||||
// from the SVG); no overflow rows; photos = the pins with a real photo (60x96/66x66) or none (36x60).
|
||||
// --self-test: a renderer copy WITHOUT the lane clamp must FAIL the crossing arm (else the arm proves nothing).
|
||||
// Exit 0 pass, 1 fail.
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
|
||||
const J = (p) => JSON.parse(readFileSync(join(ROOT, p), 'utf8'))
|
||||
const RENDER = join(ROOT, 'library/packet/poster-render.js')
|
||||
|
||||
function synthetic(n = 855) {
|
||||
let seed = 20261001
|
||||
const rnd = () => ((seed = (seed * 1103515245 + 12345) % 2147483648) / 2147483648)
|
||||
const syll = ['ka', 'ra', 'mu', 'thi', 'ban', 'dho', 'li', 'sa', 'ga', 'pa', 'rwa', 'kot', 'ni', 'al', 'u']
|
||||
const centres = Array.from({ length: 60 }, () => [68 + rnd() * 28, 8 + rnd() * 27])
|
||||
const pins = []
|
||||
for (let i = 0; i < n; i++) {
|
||||
const c = centres[Math.floor(rnd() * centres.length)]
|
||||
const stacked = rnd() < 0.15 && pins.length // an exact duplicate of an earlier position
|
||||
const lng = stacked ? pins[pins.length - 1].lng : c[0] + (rnd() - 0.5) * 1.6
|
||||
const lat = stacked ? pins[pins.length - 1].lat : c[1] + (rnd() - 0.5) * 1.6
|
||||
const words = 1 + Math.floor(rnd() * (rnd() < 0.05 ? 5 : 2))
|
||||
const name = Array.from({ length: words }, () => Array.from({ length: 2 + Math.floor(rnd() * 3) }, () => syll[Math.floor(rnd() * syll.length)]).join('')).join(' ')
|
||||
.replace(/^./, (m) => m.toUpperCase()) + ` ${i}`
|
||||
const image_url = rnd() < 0.18 ? `https://example.invalid/p/${i}.jpg` : 'https://example.invalid/no-photo-images/asia.jpg'
|
||||
pins.push({ name, lat, lng, slug: `g-${i}`, country_code: 'IND', population: 1000 + i, image_url })
|
||||
}
|
||||
return pins
|
||||
}
|
||||
|
||||
async function measure(createPosterGenerator, format, pins) {
|
||||
const adm0 = J('public/dx-maps/assets/boundaries/world-adm0.geojson')
|
||||
const series = J('library/scripts/posters/series.json')
|
||||
const zone = { id: 'country-ind', zoneKey: 'country-ind', label: 'India', count: pins.length, color: '#d4a640', adm0: ['IND'], selector: { type: 'country_codes', values: ['IND'] } }
|
||||
const zoneFC = { type: 'FeatureCollection', features: adm0.features.filter((f) => f.properties?.iso3 === 'IND') }
|
||||
const gen = createPosterGenerator({ env: { FORMAT: format, SPARSE: '1', POSTER_DATE: '2026-10-01' }, SERIES_DEF: series.country, ISO3_NAME: { IND: 'India' }, adm0, adm1Ind: null, adm1Chn: null, nePlaces: [], series: { totalGroups: pins.length, totalLists: 1, index: 1 } })
|
||||
const out = await gen.renderPoster(zone, pins, zoneFC)
|
||||
const html = out.html || ''
|
||||
const names = [...html.matchAll(/<text class="snm"[^>]*>([^<]*)<\/text>/g)].map((m) => m[1])
|
||||
const photos = (html.match(/<image href="/g) || []).length
|
||||
// re-measure crossings from the SVG itself, independent of the renderer's own count
|
||||
const polys = [...html.matchAll(/class="slead" d="M([^"]+)"/g)].map((m) => m[1].split('L').map((q) => q.trim().split(' ').map(Number)))
|
||||
const segs = polys.map((pl) => pl.slice(1).map((q, k) => [pl[k], q]))
|
||||
const sx = (p, q, r) => (r[1] - p[1]) * (q[0] - p[0]) - (q[1] - p[1]) * (r[0] - p[0])
|
||||
const X = (a, b) => (sx(a[0], a[1], b[0]) > 0) !== (sx(a[0], a[1], b[1]) > 0) && (sx(b[0], b[1], a[0]) > 0) !== (sx(b[0], b[1], a[1]) > 0)
|
||||
let crossings = 0
|
||||
for (let i = 0; i < segs.length; i++) for (let j = i + 1; j < segs.length; j++) if (segs[i].some((a) => segs[j].some((b) => X(a, b)))) crossings++
|
||||
return { stats: out.sideLanes, names, photos, crossings, leaders: polys.length }
|
||||
}
|
||||
|
||||
function judge(format, m, pins, wantPhotos) {
|
||||
const fails = []
|
||||
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
||||
const want = new Map(pins.map((p) => [esc(p.name), 0]))
|
||||
for (const n of m.names) if (want.has(n)) want.set(n, want.get(n) + 1)
|
||||
const missing = [...want].filter(([, v]) => v === 0).length, dup = [...want].filter(([, v]) => v > 1).length
|
||||
if (!m.stats) fails.push(`${format}: renderPoster returned no sideLanes stats (not a side-lane format?)`)
|
||||
if (m.names.length !== pins.length || missing || dup) fails.push(`${format}: ${m.names.length} names drawn for ${pins.length} groups (${missing} missing, ${dup} duplicated)`)
|
||||
if (m.crossings !== 0) fails.push(`${format}: ${m.crossings} leader crossings (re-measured from the SVG)`)
|
||||
if (m.stats && m.stats.crossings !== m.crossings) fails.push(`${format}: the renderer reports ${m.stats.crossings} crossings, the SVG has ${m.crossings}`)
|
||||
if (m.stats && m.stats.overflowRows) fails.push(`${format}: ${m.stats.overflowRows} rows overflow the band`)
|
||||
const real = pins.filter((p) => !/no-photo-images/.test(p.image_url)).length
|
||||
if (m.photos !== (wantPhotos ? real : 0)) fails.push(`${format}: ${m.photos} photos drawn, expected ${wantPhotos ? real : 0}`)
|
||||
return fails
|
||||
}
|
||||
|
||||
const selfTest = process.argv.includes('--self-test')
|
||||
const src = readFileSync(RENDER, 'utf8')
|
||||
const pins = synthetic()
|
||||
if (selfTest) {
|
||||
// POISON: drop the lane clamp (each lane at its own pin's height); stubs/runs/fans must then cross on this data
|
||||
const a = 'let y = Math.min(Math.max(c.y, lo + LANE_GAP), hi - LANE_GAP);'
|
||||
if (!src.includes(a)) { console.log('NOT MEASURED: the lane clamp line moved; update the poison'); process.exitCode = 75 }
|
||||
else {
|
||||
const poisoned = src.replace(a, 'let y = c.y; lo = -Infinity; hi = Infinity;')
|
||||
const { createPosterGenerator } = await import('data:text/javascript;base64,' + Buffer.from(poisoned).toString('base64'))
|
||||
const m = await measure(createPosterGenerator, '60x96', pins)
|
||||
const f = judge('60x96', m, pins, true)
|
||||
console.log(f.some((x) => /crossings/.test(x)) ? `✓ caught: no lane clamp -> ${m.crossings} crossings` : '✗ MISSED: the crossing arm stayed green without the lane clamp')
|
||||
process.exitCode = f.some((x) => /crossings/.test(x)) ? 0 : 1
|
||||
}
|
||||
} else {
|
||||
const { createPosterGenerator } = await import(RENDER)
|
||||
const fails = [], lines = []
|
||||
for (const [format, photos] of [['60x96', true], ['66x66', true], ['36x60', false]]) {
|
||||
const m = await measure(createPosterGenerator, format, pins)
|
||||
fails.push(...judge(format, m, pins, photos))
|
||||
lines.push(`${format}: ${m.names.length} names, ${m.leaders} leaders, ${m.crossings} crossings, ${m.photos} photos, ${m.stats?.namePt} pt`)
|
||||
}
|
||||
if (fails.length) { fails.forEach((f) => console.log('FAIL ' + f)); process.exitCode = 1 }
|
||||
else console.log(`PASS check:side-lanes — synthetic ${pins.length} groups (stacked + clustered); ${lines.join(' | ')}`)
|
||||
}
|
||||
Loading…
Reference in a new issue