diff --git a/skill/scripts/comp-diff.mjs b/skill/scripts/comp-diff.mjs index 0d9b640d9..082dfe172 100644 --- a/skill/scripts/comp-diff.mjs +++ b/skill/scripts/comp-diff.mjs @@ -279,6 +279,11 @@ export function compare({ comp, build, spec = null, align = 'top', label = '', k // contradicted while the whole-image search forgives it. Find the best // global translation once and shift the aligned build by it before // cropping regions; the whole score above stays as measured. + // The side-by-side and heatmap show the build as captured; only the + // region crops read the shifted copy. (The shifted copy used to be what the + // side-by-side drew, and its padding read as a white "letterbox" on the + // build in every human review.) + const asCaptured = aligned; const shift = bestShift(comp, aligned); if (shift.dx || shift.dy) { const shifted = createImage(aligned.width, aligned.height, [255, 255, 255, 255]); @@ -291,7 +296,7 @@ export function compare({ comp, build, spec = null, align = 'top', label = '', k return { ...r, score: strip(s), verdict: verdictFor(s, r.kind), inkBox: { comp: inkBox(a), build: inkBox(b) }, _a: a, _b: b }; }); const compPalette = dominantColors(comp), buildPalette = dominantColors(aligned); - return { label, align, whole: strip(whole), regions, aligned, compPalette, buildPalette, _whole: whole }; + return { label, align, whole: strip(whole), regions, aligned: asCaptured, alignedShifted: aligned, shift, compPalette, buildPalette, _whole: whole }; } function strip(s) { diff --git a/skill/scripts/lib/hero-checks.mjs b/skill/scripts/lib/hero-checks.mjs index ec0c9f132..a37d09a06 100644 --- a/skill/scripts/lib/hero-checks.mjs +++ b/skill/scripts/lib/hero-checks.mjs @@ -39,12 +39,20 @@ export function textRegionCheck(region, compCrop, buildCrop, { capTol = 0.22, mi // may carry an older fingerprint's reading, and this check has to agree // with itself on both sides. const comp = fingerprint(compCrop); - if (!comp || !comp.capHeightPx || comp.capHeightPx < minCap || comp.glyphs < 6) return { findings, metrics: null }; + // colour reads on any text region, measured or not: a spine set vertical + // (unmeasurable) came back white on red where the comp had black on red + // in five builds + const colourOnly = () => { + const ca = inkColor(compCrop), cb = inkColor(buildCrop); + if (ca && cb && ca.ink && cb.ink && deltaE(ca.ink.lab, cb.ink.lab) > 22) findings.push(`text ${region.id}: ink is ${cb.ink.hex} in the build, ${ca.ink.hex} in the comp; use the comp's colour`); + return { findings, metrics: null }; + }; + if (!comp || !comp.capHeightPx || comp.capHeightPx < minCap || comp.glyphs < 6) return colourOnly(); // rotated type (a spine set vertical) reads as many short 'lines' of one // or two glyphs; the fingerprint has nothing to say about it - if (comp.lines >= 5 && comp.glyphs / comp.lines < 3) return { findings, metrics: null }; + if (comp.lines >= 5 && comp.glyphs / comp.lines < 3) return colourOnly(); // a cap taller than half the box is a drawing read as a glyph, not type - if (comp.capHeightPx > compCrop.height * 0.6) return { findings, metrics: null }; + if (comp.capHeightPx > compCrop.height * 0.6) return colourOnly(); const bfp = fingerprint(buildCrop); const metrics = { comp: { cap: comp.capHeightPx, lines: comp.lines, glyphs: comp.glyphs }, build: bfp ? { cap: bfp.capHeightPx, lines: bfp.lines, glyphs: bfp.glyphs } : null }; if (!bfp || bfp.glyphs < 4) { @@ -57,6 +65,14 @@ export function textRegionCheck(region, compCrop, buildCrop, { capTol = 0.22, mi } if (comp.lines >= 2 && bfp.lines !== comp.lines && Math.abs(bfp.lines - comp.lines) >= 1) { findings.push(`text ${region.id}: ${bfp.lines} line${bfp.lines === 1 ? '' : 's'} in the build, ${comp.lines} in the comp; the measure (max-width, font-size, letter-spacing) wraps it differently, so the block is a different shape`); + } else if (comp.lines >= 3 && bfp.lines === comp.lines && Math.abs(capDelta) <= capTol) { + // same lines at the same size: the leading is the remaining shape + const ba0 = inkBox(compCrop), bb0 = inkBox(buildCrop); + if (ba0 && bb0) { + const pa = ba0.h / comp.lines, pb = bb0.h / bfp.lines; + const dp = (pb - pa) / pa; + if (Math.abs(dp) > 0.2) findings.push(`text ${region.id}: line pitch ${Math.round(pb)}px in the build, ${Math.round(pa)}px in the comp (${dp > 0 ? '+' : ''}${Math.round(dp * 100)}%); set line-height so ${comp.lines} lines stand ${Math.round(ba0.h)}px tall`); + } } // weight: compare ink density of tall glyphs when both sides have it and // the sizes agree (density at a different cap is a different reading)