Files
pbakaus_impeccable/skill/scripts/lib/hero-checks.mjs
T
Paul BakausandAbdul Wahab 77acd73cd3 Hero readings: sibling regions fold into one line; ink colour only on type at cap 16+
A build reached hero 81% and stalled on eight staff rows read one at a time (and both ways on ink colour). The session asked the user and was told to build as written; the force was legitimate and recorded.

AI-assisted (Claude Code).
2026-08-28 06:13:59 +05:00

205 lines
12 KiB
JavaScript

/**
* Hero-gate checks that name a miss as a number the model can act on.
*
* comp-diff scores regions; these read what a designer reads when the two
* frames sit side by side and says it as numbers: the headline is set at
* cap 78px where the comp's is 103; it wraps to four lines where the comp
* has three; its ink is #2a2a2a where the comp's is #a72f1b; it starts
* 60px lower in its box; the masthead is 92px tall where the comp's is 58;
* these grid cells carry ink the comp does not have (a kicker, a divider, a
* second nav row). Every one of those was a pin in the first human review
* of the third sweep, on builds the region scores had already let through.
*
* All functions are pure over decoded rasters and the spec; the gate wires
* them and decides what vetoes.
*/
import { fingerprint } from './font-fingerprint.mjs';
import { crop } from './raster.mjs';
import { dominantColors, deltaE, detailGrid } from './image-metrics.mjs';
import { inkBox } from '../comp-diff.mjs';
/** Dominant ink colour of a crop: the heaviest cluster that is not the ground. */
export function inkColor(img) {
const cols = dominantColors(img, 4);
if (!cols.length) return null;
const ground = cols[0];
const ink = cols.find((c) => c !== ground && deltaE(c.lab, ground.lab) > 20) || null;
return { ground, ink };
}
/**
* Compare one text region's build crop against the comp's measurement.
* `region` is a spec region with `type.comp` (font-match --measure) and
* `px`; `compCrop` / `buildCrop` are the region crops at comp scale.
* Returns { findings: string[], metrics }.
*/
export function textRegionCheck(region, compCrop, buildCrop, { capTol = 0.22, minCap = 10 } = {}) {
const findings = [];
// Measure the comp crop now rather than trusting spec.type.comp: the spec
// may carry an older fingerprint's reading, and this check has to agree
// with itself on both sides.
const comp = fingerprint(compCrop);
// 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 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 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) {
// nothing legible in the box: comp-diff's missing/contradicted covers it
return { findings, metrics };
}
const capDelta = (bfp.capHeightPx - comp.capHeightPx) / comp.capHeightPx;
if (Math.abs(capDelta) > capTol) {
findings.push(`text ${region.id}: cap height ${bfp.capHeightPx}px in the build, ${comp.capHeightPx}px in the comp (${capDelta > 0 ? '+' : ''}${Math.round(capDelta * 100)}%); set font-size so the cap height renders at ${comp.capHeightPx}px${region.type.chosen ? ` (font-match ranked ${region.type.chosen.family} ${region.type.chosen.weight} at ${region.type.chosen.fontSizePx}px)` : ''}`);
}
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)
if (comp.densTall != null && bfp.densTall != null && Math.abs(capDelta) <= capTol) {
const r = bfp.densTall / comp.densTall;
if (r > 1.25) findings.push(`text ${region.id}: the face renders ${Math.round((r - 1) * 100)}% heavier than the comp's (ink density ${bfp.densTall.toFixed(2)} vs ${comp.densTall.toFixed(2)}); drop a weight step or use the ranked face`);
else if (r < 0.75) findings.push(`text ${region.id}: the face renders ${Math.round((1 - r) * 100)}% lighter than the comp's (ink density ${bfp.densTall.toFixed(2)} vs ${comp.densTall.toFixed(2)}); raise a weight step or use the ranked face`);
}
// colour: dominant ink of each crop. Small type on a ruled or grainy
// ground (a track row across staff lines at cap 14) has no reliable ink
// cluster; the reading fired both ways on neighbouring rows of one list.
if (comp.capHeightPx >= 16) {
const ca = inkColor(compCrop), cb = inkColor(buildCrop);
if (ca && cb && ca.ink && cb.ink) {
const d = deltaE(ca.ink.lab, cb.ink.lab);
if (d > 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`);
}
}
// vertical placement inside the box: top of ink
const ba = inkBox(compCrop), bb = inkBox(buildCrop);
if (ba && bb) {
const dy = bb.y - ba.y;
if (Math.abs(dy) > Math.max(12, compCrop.height * 0.15)) findings.push(`text ${region.id}: its first line starts ${Math.abs(Math.round(dy))}px ${dy > 0 ? 'lower' : 'higher'} than in the comp (${bb.y}px vs ${ba.y}px into the region box); the spacing above it is ${dy > 0 ? 'too large' : 'too small'}`);
const dx = bb.x - ba.x;
if (Math.abs(dx) > Math.max(12, compCrop.width * 0.15)) findings.push(`text ${region.id}: it starts ${Math.abs(Math.round(dx))}px ${dx > 0 ? 'further right' : 'further left'} than in the comp`);
}
metrics.capDelta = +capDelta.toFixed(3);
return { findings, metrics };
}
/**
* Rows of a crop that carry a horizontal rule: a row whose gray step from
* the row above (or below) is strong across at least `span` of the width.
* Returns row indices sorted top to bottom.
*/
export function ruleRows(img, { span = 0.5, step = 28 } = {}) {
const W = img.width, H = img.height;
const gray = (x, y) => { const i = (y * W + x) * 4; return 0.299 * img.data[i] + 0.587 * img.data[i + 1] + 0.114 * img.data[i + 2]; };
const rows = [];
for (let y = 1; y < H - 1; y++) {
let strong = 0;
for (let x = 0; x < W; x++) { const d = Math.max(Math.abs(gray(x, y) - gray(x, y - 1)), Math.abs(gray(x, y) - gray(x, y + 1))); if (d > step) strong++; }
if (strong >= W * span) rows.push(y);
}
// collapse adjacent rows into one edge
const out = [];
for (const y of rows) if (!out.length || y - out[out.length - 1] > 3) out.push(y);
return out;
}
/**
* A thin chrome region (masthead, nav bar, footer strip) has a height, and
* its height is where its rule sits. Compare the first horizontal rule's row
* in comp vs build; fall back to the ink extents when neither has a rule.
*/
export function chromeStripCheck(region, compCrop, buildCrop) {
const findings = [];
const strip = compCrop.height <= compCrop.width * 0.35;
if (!strip) return { findings };
// a control that is one link or one button, not a bar across its box, has
// no strip height to compare (its underline read as a 'rule' for 27
// attempts in one session)
if (region.kind === 'control') {
const ib = inkBox(compCrop);
if (!ib || ib.w < compCrop.width * 0.6) return { findings };
}
const ra = ruleRows(compCrop), rb = ruleRows(buildCrop);
if (ra.length && rb.length) {
// the rule that closes the strip is the first one from the top (a grid
// row often carries the next element's top edge lower down)
const ya = ra[0], yb = rb[0];
const dy = yb - ya;
if (Math.abs(dy) > Math.max(5, compCrop.height * 0.06)) findings.push(`${region.kind} ${region.id}: its rule sits ${ya}px into the box in the comp and ${yb}px in the build (${dy > 0 ? '+' : ''}${dy}px), so the strip is ${dy > 0 ? 'taller' : 'shorter'} than the comp's; match the height, not only the position`);
return { findings, comp: ya, build: yb };
}
const ba = inkBox(compCrop), bb = inkBox(buildCrop);
if (!ba || !bb) return { findings };
if (ba.w >= compCrop.width * 0.6 && ba.h <= compCrop.height * 0.6) {
const dh = bb.h - ba.h;
if (Math.abs(dh) > Math.max(10, ba.h * 0.25)) findings.push(`${region.kind} ${region.id}: its ink is ${bb.h}px tall in the build and ${ba.h}px in the comp (${dh > 0 ? '+' : ''}${dh}px); match the height, not only the position`);
}
return { findings, comp: ba, build: bb };
}
/**
* Cells of the frame where the build carries ink and the comp is calm.
* Returns { cells: [{col,row,label}], fraction } on a cols x rows grid.
* `floor` is the comp energy under which a cell counts as calm; `added` is
* the build energy over which the build counts as inked.
*/
export function inventedInk(comp, build, { cols = 10, rows = 10, floor = 10, added = 12, ratio = 2.5 } = {}) {
const a = detailGrid(comp, cols, rows, 512), b = detailGrid(build, cols, rows, 512);
const cells = [];
for (let r = 0; r < rows; r++) for (let c = 0; c < cols; c++) {
const i = r * cols + c;
// calm in the comp (grain, flat ground) and inked in the build well past
// what grain would give: a kicker over paper, a divider, a nav row
if (!(a.cells[i] < floor && b.cells[i] > Math.max(added, a.cells[i] * ratio))) continue;
// the comp must be calm around the cell too: a hard edge one pixel over
// the cell boundary in the build (a bar shifted by a subpixel of the
// alignment) reads as invented otherwise
let neighbourhood = 0, n = 0;
for (let dr = -1; dr <= 1; dr++) for (let dc = -1; dc <= 1; dc++) { const rr = r + dr, cc = c + dc; if (rr < 0 || cc < 0 || rr >= rows || cc >= cols) continue; neighbourhood += a.cells[rr * cols + cc]; n++; }
if (neighbourhood / n >= floor * 2) continue;
cells.push({ col: c, row: r, label: `${String.fromCharCode(65 + c)}${r}`, comp: +a.cells[i].toFixed(1), build: +b.cells[i].toFixed(1) });
}
return { cells, fraction: cells.length / (cols * rows) };
}
/**
* A plate cropped by its box: the comp's artwork keeps a margin inside the
* region on some side and the build's ink runs flush to that edge (object-fit:
* cover on a box smaller than the artwork's aspect, or an <img> sized to the
* column). The best build of the fifth sweep passed the hero at 87% with the
* cover arch cut off at the left and bottom; the human review called it a
* bug in one word. Returns the sides clipped, or [].
*/
export function plateClipCheck(region, compCrop, buildCrop, { margin = 6 } = {}) {
const a = inkBox(compCrop), b = inkBox(buildCrop);
if (!a || !b) return { sides: [] };
const W = compCrop.width, H = compCrop.height;
const sides = [];
const flush = (v) => v <= 1;
if (a.x >= margin && flush(b.x)) sides.push('left');
if (a.y >= margin && flush(b.y)) sides.push('top');
if (W - (a.x + a.w) >= margin && flush(W - (b.x + b.w))) sides.push('right');
if (H - (a.y + a.h) >= margin && flush(H - (b.y + b.h))) sides.push('bottom');
return { sides, comp: a, build: b };
}