From b0fc2e8801eb6d70401eb86e0077b17e1cf513c2 Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Sat, 15 Aug 2026 19:19:55 -0700 Subject: [PATCH] Add comp-diff, comp-spec, and build-phase: measured comp fidelity for the build phase Dependency-free PNG codec, perceptual metrics (structure / color / detail / bands), side-by-side + heatmap + per-region crops, a measured spec from the approved comp (grid overlay, sampled palette, plate list), and a phase state machine whose spec / plates / hero gates run the diff instead of asking the model to remember the image. AI-assisted (Claude). Co-Authored-By: Claude --- scripts/test-suites.mjs | 4 +- skill/scripts/build-phase.mjs | 295 +++++++++++++++++++++++ skill/scripts/comp-diff.mjs | 290 +++++++++++++++++++++++ skill/scripts/comp-spec.mjs | 252 ++++++++++++++++++++ skill/scripts/lib/image-metrics.mjs | 302 ++++++++++++++++++++++++ skill/scripts/lib/png.mjs | 250 ++++++++++++++++++++ skill/scripts/lib/raster.mjs | 194 +++++++++++++++ tests/build-phase.test.mjs | 168 +++++++++++++ tests/comp-diff.test.mjs | 225 ++++++++++++++++++ tests/fixtures/comp-fidelity/sample.png | Bin 0 -> 153599 bytes 10 files changed, 1979 insertions(+), 1 deletion(-) create mode 100644 skill/scripts/build-phase.mjs create mode 100644 skill/scripts/comp-diff.mjs create mode 100644 skill/scripts/comp-spec.mjs create mode 100644 skill/scripts/lib/image-metrics.mjs create mode 100644 skill/scripts/lib/png.mjs create mode 100644 skill/scripts/lib/raster.mjs create mode 100644 tests/build-phase.test.mjs create mode 100644 tests/comp-diff.test.mjs create mode 100644 tests/fixtures/comp-fidelity/sample.png diff --git a/scripts/test-suites.mjs b/scripts/test-suites.mjs index 1c1f64c58..52f3b1c2e 100644 --- a/scripts/test-suites.mjs +++ b/scripts/test-suites.mjs @@ -26,7 +26,7 @@ export const SUITES = { triggers: [ ...COMMON_INFRA_PATTERNS, /^scripts\/(?!benchmark-detector|build-browser-detector|build-extension)/, - /^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|concept-seed|context|context-signals|critique-storage|design-parser|doctor|hook|impeccable-paths|is-generated|lib\/(artifact-schema|composition-catalog|concept-catalog|provider|staleness|staleness-deep|staleness-notice|surface-briefs|target-slug|template-extensions)|pin|surface-brief))/, + /^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|comp-diff|comp-spec|build-phase|concept-seed|context|context-signals|critique-storage|design-parser|doctor|hook|impeccable-paths|is-generated|lib\/(artifact-schema|png|raster|image-metrics|composition-catalog|concept-catalog|provider|staleness|staleness-deep|staleness-notice|surface-briefs|target-slug|template-extensions)|pin|surface-brief))/, /^README(\.npm)?\.md$/, /^cli\/bin\//, ], @@ -54,6 +54,8 @@ export const SUITES = { 'tests/ci-test-plan.test.mjs', 'tests/cli-args.test.mjs', 'tests/concept-seed.test.mjs', + 'tests/comp-diff.test.mjs', + 'tests/build-phase.test.mjs', 'tests/serve-question.test.mjs', 'tests/context.test.mjs', 'tests/context-signals.test.mjs', diff --git a/skill/scripts/build-phase.mjs b/skill/scripts/build-phase.mjs new file mode 100644 index 000000000..8344047bc --- /dev/null +++ b/skill/scripts/build-phase.mjs @@ -0,0 +1,295 @@ +#!/usr/bin/env node +/** + * build-phase: the comp-led build as a state machine on disk, so the phases + * new-work.md names are gated by scripts instead of remembered by the model. + * + * State lives at .impeccable/build/state.json. Phases, in order: + * + * spec the approved comp is measured (comp-spec.mjs wrote spec.json) + * plates every raster region in the spec has its plate on disk + * hero the first viewport is reproduced: comp-diff of hero-repro.png + * against the comp clears the gate + * sections the rest of the surface is built inside the spec's system + * motion interaction, reveals, motion + * responsive the other viewports + * review the finish reviewer ran; disposition recorded + * + * node build-phase.mjs start --comp [--breakpoint 1440x900] + * node build-phase.mjs status # human-readable, plus NEXT line + * node build-phase.mjs status --json + * node build-phase.mjs advance # try to close the current phase; runs its gate + * node build-phase.mjs advance --force --reason "" # skip a gate; recorded, never silent + * node build-phase.mjs record hero --build .impeccable/review/hero-repro.png # run the hero gate explicitly + * node build-phase.mjs note "" # append a note to the current phase + * node build-phase.mjs finish --disposition ship|fix|rebuild|recapture + * + * Gates: + * spec -> spec.json exists and has >= 1 region + * plates -> every region with medium raster has its plate file, decodable, + * at least 2x the comp region's pixel size in width, and the + * plate scores >= PLATE_MIN against the comp crop (comp-diff, + * detail-weighted). A missing or thin plate names itself. + * hero -> .impeccable/review/hero-repro.png exists and comp-diff overall + * >= HERO_MIN (default 0.72) with no region `missing`. The + * score, the report path, and the attempt count are recorded. + * sections / motion / responsive -> no mechanical gate; advancing records + * the moment, and the finish reviewer reads the timeline. + * + * Exit codes: 0 ok / advanced, 2 gate failed (state unchanged, reasons + * printed), 1 usage. + * + * Nothing here needs a browser. Screenshots come from the harness; this + * script only measures them. + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import { decodePng } from './lib/png.mjs'; +import { crop } from './lib/raster.mjs'; +import { compare, verdictFor } from './comp-diff.mjs'; +import { SPEC_PATH, BUILD_DIR, loadSpec } from './comp-spec.mjs'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +export const STATE_PATH = path.join(BUILD_DIR, 'state.json'); +export const PHASES = ['spec', 'plates', 'hero', 'sections', 'motion', 'responsive', 'review']; +export const HERO_MIN = 0.72; +export const PLATE_MIN = 0.6; +export const HERO_REPRO = path.join('.impeccable', 'review', 'hero-repro.png'); + +function arg(name, fallback = null) { + const i = process.argv.indexOf(`--${name}`); + if (i === -1) return fallback; + const v = process.argv[i + 1]; + return v && !v.startsWith('--') ? v : fallback; +} +const flag = (name) => process.argv.includes(`--${name}`); +const now = () => new Date().toISOString(); + +export function loadState(statePath = STATE_PATH) { + if (!fs.existsSync(statePath)) return null; + return JSON.parse(fs.readFileSync(statePath, 'utf8')); +} + +export function saveState(state, statePath = STATE_PATH) { + fs.mkdirSync(path.dirname(statePath), { recursive: true }); + fs.writeFileSync(statePath, JSON.stringify(state, null, 2)); +} + +export function newState({ comp, breakpoint = null }) { + return { + tool: 'build-phase', + version: 1, + startedAt: now(), + comp, + breakpoint, + phase: 'spec', + phases: Object.fromEntries(PHASES.map((p) => [p, { status: p === 'spec' ? 'open' : 'pending', openedAt: p === 'spec' ? now() : null, closedAt: null, attempts: 0, notes: [], gate: null, forced: null }])), + finish: null, + }; +} + +// ---- gates ----------------------------------------------------------------- + +export function gateSpec(state, { specPath = SPEC_PATH } = {}) { + const spec = loadSpec(specPath); + if (!spec) return { ok: false, reasons: [`no spec at ${specPath}: run comp-spec.mjs --comp ${state.comp} --grid, name the regions, then --regions regions.json`] }; + if (!spec.regions || spec.regions.length < 1) return { ok: false, reasons: ['spec has no regions'] }; + if (spec.comp && state.comp && path.resolve(spec.comp) !== path.resolve(state.comp)) { + return { ok: false, reasons: [`spec measures ${spec.comp}, but this build started on ${state.comp}; re-run comp-spec on the approved comp`] }; + } + const plates = spec.regions.filter((r) => r.medium === 'raster').length; + return { ok: true, reasons: [], summary: `${spec.regions.length} regions, ${plates} plates` }; +} + +export function gatePlates(state, { specPath = SPEC_PATH } = {}) { + const spec = loadSpec(specPath); + if (!spec) return { ok: false, reasons: ['no spec'] }; + const rasterRegions = spec.regions.filter((r) => r.medium === 'raster'); + if (!rasterRegions.length) return { ok: true, reasons: [], summary: 'no plates owed', plates: [] }; + let comp = null; + try { comp = decodePng(fs.readFileSync(spec.comp)); } catch { /* scored without the comp crop below */ } + const reasons = [], plates = []; + for (const r of rasterRegions) { + const file = r.plate; + if (!file || !fs.existsSync(file)) { reasons.push(`plate missing for ${r.id}: expected ${file || '(no path)'}; produce it from comp-spec.mjs --crop ${r.id} with generate-image.mjs --plate`); plates.push({ id: r.id, file, status: 'missing' }); continue; } + let img; + try { img = decodePng(fs.readFileSync(file)); } catch (e) { reasons.push(`plate ${file} is not a decodable PNG: ${e.message}`); plates.push({ id: r.id, file, status: 'unreadable' }); continue; } + const minW = r.px.w * 1.5; + if (img.width < minW) reasons.push(`plate ${file} is ${img.width}px wide; the comp region is ${r.px.w}px and a shipping plate needs at least 1.5x (${Math.round(minW)}px). Regenerate at asset size, do not crop the comp.`); + let score = null; + if (comp) { + const ref = crop(comp, r.px.x, r.px.y, r.px.w, r.px.h); + const res = compare({ comp: ref, build: img, align: 'stretch', spec: null }); + score = res.whole; + if (score.overall < PLATE_MIN) reasons.push(`plate ${file} scores ${(score.overall * 100).toFixed(0)}% against the comp region ${r.id} (structure ${(score.structure * 100).toFixed(0)}%, color ${(score.color * 100).toFixed(0)}%, detail ${(score.detail * 100).toFixed(0)}%); it does not read as the same region. Regenerate with the crop as --ref and the comp-spec plate prompt.`); + } + plates.push({ id: r.id, file, status: 'ok', size: `${img.width}x${img.height}`, score: score ? score.overall : null }); + } + return { ok: reasons.length === 0, reasons, summary: `${plates.filter((p) => p.status === 'ok').length}/${rasterRegions.length} plates`, plates }; +} + +export function gateHero(state, { buildPath = HERO_REPRO, specPath = SPEC_PATH, min = HERO_MIN, outDir = path.join('.impeccable', 'review', 'diff', 'hero') } = {}) { + if (!fs.existsSync(buildPath)) return { ok: false, reasons: [`no hero capture at ${buildPath}: screenshot the first viewport at the comp's own dimensions (${state.breakpoint || 'comp size'}) into that path`] }; + const script = path.join(HERE, 'comp-diff.mjs'); + const args = [script, '--comp', state.comp, '--build', buildPath, '--out-dir', outDir, '--label', 'hero', '--json']; + const spec = loadSpec(specPath); + if (spec) args.push('--spec', specPath); + const res = spawnSync(process.execPath, args, { encoding: 'utf8' }); + if (res.status !== 0 && res.status !== 3) return { ok: false, reasons: [`comp-diff failed: ${res.stderr || res.stdout}`] }; + let report; + try { report = JSON.parse(res.stdout); } catch { return { ok: false, reasons: ['comp-diff produced no report'] }; } + const reasons = []; + if (report.overall < min) reasons.push(`hero overall ${(report.overall * 100).toFixed(0)}% < ${(min * 100).toFixed(0)}% (structure ${(report.scores.structure * 100).toFixed(0)}%, color ${(report.scores.color * 100).toFixed(0)}%, detail ${(report.scores.detail * 100).toFixed(0)}%)`); + const missing = report.regions.filter((r) => r.verdict === 'missing'); + for (const r of missing) reasons.push(`region ${r.id} is missing (detail ${(r.score.detail * 100).toFixed(0)}%, structure ${(r.score.structure * 100).toFixed(0)}%): the comp shows material the build does not`); + const contradicted = report.regions.filter((r) => r.verdict === 'contradicted'); + if (contradicted.length > Math.max(1, Math.floor(report.regions.length / 3))) reasons.push(`${contradicted.length} of ${report.regions.length} regions contradicted: ${contradicted.map((r) => r.id).join(', ')}`); + return { + ok: reasons.length === 0, + reasons, + summary: `hero ${(report.overall * 100).toFixed(0)}% (${report.verdict})`, + score: report.overall, + verdict: report.verdict, + report: path.join(outDir, 'report.json'), + sideBySide: report.files ? report.files.sideBySide : null, + worst: [...report.regions].sort((a, b) => a.score.overall - b.score.overall).slice(0, 3).map((r) => `${r.id} ${r.verdict} ${(r.score.overall * 100).toFixed(0)}%`), + }; +} + +const GATES = { spec: gateSpec, plates: gatePlates, hero: gateHero }; + +// ---- transitions ----------------------------------------------------------- + +export function runGate(state, phase, opts = {}) { + const gate = GATES[phase]; + if (!gate) return { ok: true, reasons: [], summary: 'no mechanical gate' }; + return gate(state, opts); +} + +export function advance(state, { force = false, reason = null, gateOpts = {} } = {}) { + const phase = state.phase; + const idx = PHASES.indexOf(phase); + if (idx === -1 || phase === 'review') return { ok: false, reasons: [`phase ${phase} cannot advance; use finish`] }; + const p = state.phases[phase]; + p.attempts += 1; + const gate = runGate(state, phase, gateOpts); + const { plates: _p, ...gateRecord } = gate; + p.gate = { ...gateRecord, at: now() }; + if (!gate.ok && !force) { p.status = 'open'; return { ok: false, phase, reasons: gate.reasons, gate }; } + if (!gate.ok && force) p.forced = { at: now(), reason: reason || '(no reason given)', reasons: gate.reasons }; + p.status = 'closed'; p.closedAt = now(); + const next = PHASES[idx + 1]; + state.phase = next; + state.phases[next].status = 'open'; state.phases[next].openedAt = now(); + return { ok: true, phase, next, gate, forced: !!p.forced }; +} + +export function nextInstruction(state) { + switch (state.phase) { + case 'spec': return `Measure the comp: node comp-spec.mjs --comp ${state.comp} --grid, open ${path.join(BUILD_DIR, 'comp-grid.png')}, write regions.json (every illustration, photo, texture as its own plate region), run comp-spec.mjs --comp ${state.comp} --regions regions.json, then build-phase.mjs advance.`; + case 'plates': return 'Produce every plate in the spec (comp-spec.mjs --print lists them): comp-spec.mjs --crop , then generate-image.mjs --plate (or the harness image tool with the crop as reference and the comp-spec plate prompt). Then build-phase.mjs advance. Write no page code before this passes.'; + case 'hero': return `Build only the first viewport at ${state.breakpoint || 'the comp size'} using the plates and the spec's boxes and palette; capture it into ${HERO_REPRO}; run build-phase.mjs advance. Fix the worst regions it names and re-run; do not build past the hero until it passes.`; + case 'sections': return 'Build the remaining sections inside the spec system (same corner language, rules, and palette; nothing the comp does not show). Then build-phase.mjs advance.'; + case 'motion': return 'Add the signature interaction, reveals, and motion. Then build-phase.mjs advance.'; + case 'responsive': return 'Build the other viewports (mobile first if the surface is mobile). Capture desktop.png and mobile.png into .impeccable/review/. Then build-phase.mjs advance.'; + case 'review': return 'Spawn the finish reviewer with the state file, the hero diff report, and the captures; record its disposition with build-phase.mjs finish --disposition .'; + default: return ''; + } +} + +export function renderStatus(state) { + const lines = [`BUILD-PHASE ${state.phase.toUpperCase()} comp ${state.comp}${state.breakpoint ? ` breakpoint ${state.breakpoint}` : ''}`]; + for (const p of PHASES) { + const s = state.phases[p]; + let line = ` ${p.padEnd(11)} ${s.status.padEnd(8)}`; + if (s.gate && s.gate.summary) line += ` ${s.gate.summary}`; + if (s.attempts > 1) line += ` (${s.attempts} attempts)`; + if (s.forced) line += ` FORCED: ${s.forced.reason}`; + lines.push(line); + } + if (state.finish) lines.push(` finish ${state.finish.disposition} at ${state.finish.at}`); + lines.push(`NEXT ${nextInstruction(state)}`); + return lines.join('\n'); +} + +async function main() { + const cmd = process.argv[2]; + if (!cmd || flag('help')) { + console.error('usage: build-phase.mjs start --comp [--breakpoint WxH] | status [--json] | advance [--force --reason "..."] | record hero --build | note "" | finish --disposition '); + process.exit(1); + } + if (cmd === 'start') { + const comp = arg('comp'); + if (!comp || !fs.existsSync(comp)) { console.error('build-phase: --comp is required and must exist'); process.exit(1); } + let breakpoint = arg('breakpoint'); + if (!breakpoint) { try { const i = decodePng(fs.readFileSync(comp)); breakpoint = `${i.width}x${i.height}`; } catch { /* leave null */ } } + const existing = loadState(); + if (existing && !flag('reset')) { + console.log(`build-phase: state exists (phase ${existing.phase}); pass --reset to start over`); + console.log(renderStatus(existing)); + return; + } + const state = newState({ comp, breakpoint }); + saveState(state); + console.log(renderStatus(state)); + return; + } + const state = loadState(); + if (!state) { console.error(`build-phase: no state at ${STATE_PATH}; run build-phase.mjs start --comp `); process.exit(1); } + if (cmd === 'status') { + if (flag('json')) console.log(JSON.stringify(state, null, 2)); else console.log(renderStatus(state)); + return; + } + if (cmd === 'note') { + const text = process.argv.slice(3).filter((a) => !a.startsWith('--')).join(' '); + state.phases[state.phase].notes.push({ at: now(), text }); + saveState(state); + console.log(`noted on ${state.phase}`); + return; + } + if (cmd === 'record') { + const which = process.argv[3]; + if (which !== 'hero') { console.error('build-phase: record hero --build '); process.exit(1); } + const gate = gateHero(state, { buildPath: arg('build', HERO_REPRO), min: arg('min') ? parseFloat(arg('min')) : HERO_MIN }); + state.phases.hero.attempts += 1; + state.phases.hero.gate = { ...gate, at: now() }; + saveState(state); + console.log(`${gate.ok ? 'PASS' : 'FAIL'} ${gate.summary || ''}`); + for (const r of gate.reasons) console.log(` - ${r}`); + if (gate.worst) console.log(` worst: ${gate.worst.join('; ')}`); + if (gate.sideBySide) console.log(` open ${gate.sideBySide}`); + process.exit(gate.ok ? 0 : 2); + } + if (cmd === 'advance') { + const gateOpts = {}; + if (arg('build')) gateOpts.buildPath = arg('build'); + if (arg('min')) gateOpts.min = parseFloat(arg('min')); + const res = advance(state, { force: flag('force'), reason: arg('reason'), gateOpts }); + saveState(state); + if (!res.ok) { + console.log(`GATE ${res.phase ? res.phase.toUpperCase() : ''} FAILED (state unchanged)`); + for (const r of res.reasons) console.log(` - ${r}`); + if (res.gate && res.gate.worst) console.log(` worst: ${res.gate.worst.join('; ')}`); + if (res.gate && res.gate.sideBySide) console.log(` open ${res.gate.sideBySide} and the worst region pairs before editing`); + process.exit(2); + } + console.log(`ADVANCED ${res.phase} -> ${res.next}${res.forced ? ' (FORCED; recorded)' : ''}${res.gate.summary ? ` ${res.gate.summary}` : ''}`); + console.log(`NEXT ${nextInstruction(state)}`); + return; + } + if (cmd === 'finish') { + const disposition = arg('disposition'); + if (!['ship', 'fix', 'rebuild', 'recapture'].includes(disposition)) { console.error('build-phase: finish --disposition ship|fix|rebuild|recapture'); process.exit(1); } + state.finish = { disposition, at: now(), phaseAtFinish: state.phase }; + if (state.phase === 'review') { state.phases.review.status = 'closed'; state.phases.review.closedAt = now(); } + saveState(state); + console.log(renderStatus(state)); + return; + } + console.error(`build-phase: unknown command ${cmd}`); + process.exit(1); +} + +const isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname); +if (isMain) main(); diff --git a/skill/scripts/comp-diff.mjs b/skill/scripts/comp-diff.mjs new file mode 100644 index 000000000..ed0e7cfa1 --- /dev/null +++ b/skill/scripts/comp-diff.mjs @@ -0,0 +1,290 @@ +#!/usr/bin/env node +/** + * comp-diff: measure a build screenshot against its approved comp and produce + * the evidence a reviewer (human or model) needs to judge fidelity without + * trusting anyone's memory of the image. + * + * node comp-diff.mjs --comp .impeccable/mocks/approved.png --build .impeccable/review/hero-repro.png + * node comp-diff.mjs --comp comp.png --build desktop.png --spec .impeccable/build/spec.json --out-dir .impeccable/review/diff + * node comp-diff.mjs ... --json # machine-readable report on stdout + * node comp-diff.mjs ... --threshold 0.75 # exit 3 when the overall score is below + * + * Inputs: two PNGs. The build capture may be taller than the comp (a full-page + * screenshot); it is scaled to the comp's width and the top comp-height rows + * are compared, because the comp is the first viewport. `--align stretch` + * squashes the whole build onto the comp instead, for a comp that covers a + * whole page. + * + * Outputs (in --out-dir, default .impeccable/review/diff): + * side-by-side.png comp | build, same size, labeled, with the score + * heatmap.png build with the difference painted over it (red = wrong) + * regions/.png paired crops per region at legible scale, scored + * report.json every number below, plus per-region rows + * + * Scores (0..1): structure (blurred SSIM: is the composition the same?), + * color (histogram + dominant palette: is it the same palette at the same + * coverage?), detail (high-frequency energy ratio: did the material survive, + * or did an illustration become a gradient?), bands (do the horizontal + * sections line up?). `overall` weights them 0.35 / 0.25 / 0.25 / 0.15. + * + * Regions come from --spec (comp-spec.mjs output: normalized boxes) or, with + * none, from the comp's own horizontal bands, so the per-region crops exist + * either way. Every region row carries the same four scores plus `verdict`: + * match (>= 0.8), drift (>= 0.6), missing (detail ratio < 0.35 with structure + * < 0.6), or contradicted (everything else). The words are the finish + * reviewer's fidelity vocabulary on purpose. + * + * Exit codes: 0 measured (and above threshold when one is given), 1 usage or + * unreadable input, 3 below threshold. + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { decodePng, encodePng } from './lib/png.mjs'; +import { crop, resize, fit, blit, createImage, fillRect, strokeRect, drawLabel } from './lib/raster.mjs'; +import { structureScore, colorScore, detailScore, diffMap, horizontalBands, bandScore, dominantColors } from './lib/image-metrics.mjs'; + +function arg(name, fallback = null) { + const i = process.argv.indexOf(`--${name}`); + if (i === -1) return fallback; + const v = process.argv[i + 1]; + return v && !v.startsWith('--') ? v : fallback; +} +const flag = (name) => process.argv.includes(`--${name}`); + +export function readPng(file) { + return decodePng(fs.readFileSync(file)); +} + +/** Scale the build to the comp's width; take the top comp-height rows (align=top) or squash (align=stretch). */ +export function alignBuild(comp, build, align = 'top') { + if (align === 'stretch') return resize(build, comp.width, comp.height); + const scaled = build.width === comp.width ? build : resize(build, comp.width, Math.round((build.height / build.width) * comp.width)); + if (scaled.height === comp.height) return scaled; + if (scaled.height > comp.height) return crop(scaled, 0, 0, comp.width, comp.height); + // shorter than the comp: pad with white so a short page reads as missing content, not as a resize + const out = createImage(comp.width, comp.height, [255, 255, 255, 255]); + blit(out, scaled, 0, 0); + return out; +} + +/** Weights per region kind: what a region is made of decides what losing it looks like. */ +const WEIGHTS = { + default: { structure: 0.35, color: 0.25, detail: 0.25, bands: 0.15 }, + plate: { structure: 0.25, color: 0.2, detail: 0.5, bands: 0.05 }, + image: { structure: 0.25, color: 0.2, detail: 0.5, bands: 0.05 }, + texture: { structure: 0.15, color: 0.35, detail: 0.5, bands: 0 }, + text: { structure: 0.5, color: 0.25, detail: 0.15, bands: 0.1 }, + control: { structure: 0.45, color: 0.35, detail: 0.2, bands: 0 }, +}; + +export function scorePair(a, b, kind = null) { + const structure = structureScore(a, b); + const color = colorScore(a, b); + const detail = detailScore(a, b); + const bandsA = horizontalBands(a), bandsB = horizontalBands(b); + const bands = bandScore(bandsA, bandsB); + const w = WEIGHTS[kind] || WEIGHTS.default; + const overall = w.structure * structure + w.color * color.score + w.detail * detail.score + w.bands * bands; + return { + overall: r4(overall), + structure: r4(structure), + color: r4(color.score), + colorIntersection: r4(color.intersection), + paletteMatch: r4(color.paletteMatch), + detail: r4(detail.score), + detailAdded: r4(detail.addedFraction), + bands: r4(bands), + _detail: detail, + _bands: { comp: bandsA, build: bandsB }, + }; +} + +export function verdictFor(s, kind = null) { + const painted = kind === 'plate' || kind === 'image' || kind === 'texture'; + if (painted && s.detail < 0.5) return 'missing'; + if (s.detail < 0.35 && s.structure < 0.6) return 'missing'; + if (s.overall >= 0.8) return 'match'; + if (s.overall >= 0.6) return 'drift'; + return 'contradicted'; +} + +const r4 = (v) => Math.round(v * 10000) / 10000; + +/** Regions from a spec (normalized boxes) or derived from the comp's bands. */ +export function resolveRegions(comp, spec) { + const regions = []; + if (spec && Array.isArray(spec.regions) && spec.regions.length) { + for (const r of spec.regions) { + const box = r.box || r; + if ([box.x, box.y, box.w, box.h].some((v) => typeof v !== 'number')) continue; + regions.push({ id: r.id || `region-${regions.length + 1}`, x: box.x, y: box.y, w: box.w, h: box.h, kind: r.kind || null }); + } + if (regions.length) return regions; + } + const bands = horizontalBands(comp).filter((b) => b.strength > 0.2); + const cuts = [0, ...bands.map((b) => b.y), 1].filter((v, i, arr) => i === 0 || v - arr[i - 1] > 0.06); + if (cuts[cuts.length - 1] !== 1) cuts.push(1); + for (let i = 0; i + 1 < cuts.length; i++) { + regions.push({ id: `band-${i + 1}`, x: 0, y: cuts[i], w: 1, h: cuts[i + 1] - cuts[i], kind: 'band' }); + } + if (regions.length < 2) { + return [ + { id: 'top', x: 0, y: 0, w: 1, h: 0.5, kind: 'band' }, + { id: 'bottom', x: 0, y: 0.5, w: 1, h: 0.5, kind: 'band' }, + ]; + } + return regions; +} + +/** Crop a normalized region; regions thinner than 48px in either axis are grown to that so tiny strips do not swing on subpixel noise. */ +function regionCrop(img, r) { + const minPx = 48; + let x = r.x * img.width, y = r.y * img.height, w = r.w * img.width, h = r.h * img.height; + if (h < minPx) { y -= (minPx - h) / 2; h = minPx; } + if (w < minPx) { x -= (minPx - w) / 2; w = minPx; } + return crop(img, x, y, w, h); +} + +const HEAT_LABEL = { match: [40, 160, 80, 255], drift: [220, 160, 30, 255], missing: [200, 40, 40, 255], contradicted: [200, 40, 40, 255] }; + +export function renderSideBySide(comp, build, label, score) { + const gap = 24, pad = 48; + const targetW = Math.min(comp.width, 1400); + const a = fit(comp, targetW, 100000), b = resize(build, a.width, a.height); + const out = createImage(a.width * 2 + gap + pad * 2, a.height + pad * 2 + 24, [24, 24, 28, 255]); + blit(out, a, pad, pad + 24); + blit(out, b, pad + a.width + gap, pad + 24); + drawLabel(out, 'COMP', pad, pad - 4, { scale: 2 }); + drawLabel(out, `BUILD ${label ? label.toUpperCase() : ''}`.trim(), pad + a.width + gap, pad - 4, { scale: 2 }); + const s = `OVERALL ${(score.overall * 100).toFixed(0)}% STRUCT ${(score.structure * 100).toFixed(0)}% COLOR ${(score.color * 100).toFixed(0)}% DETAIL ${(score.detail * 100).toFixed(0)}% BANDS ${(score.bands * 100).toFixed(0)}%`; + drawLabel(out, s, pad, out.height - pad + 8, { scale: 2, bg: HEAT_LABEL[verdictFor(score)] }); + return out; +} + +export function renderHeatmap(comp, build) { + const map = diffMap(comp, build); + const base = resize(build, map.width, map.height); + const out = { width: base.width, height: base.height, data: new Uint8Array(base.data) }; + for (let i = 0, p = 0; i < map.data.length; i++, p += 4) { + const d = map.data[i]; + if (d < 0.12) { // dim what matches so wrong stands out + out.data[p] = out.data[p] * 0.55 + 255 * 0.45 * 0.2; out.data[p + 1] = out.data[p + 1] * 0.55; out.data[p + 2] = out.data[p + 2] * 0.55; continue; + } + const a = Math.min(1, (d - 0.12) / 0.5); + out.data[p] = out.data[p] * (1 - a) + 235 * a; out.data[p + 1] = out.data[p + 1] * (1 - a) + 40 * a; out.data[p + 2] = out.data[p + 2] * (1 - a) + 40 * a; + } + const scaled = resize(out, comp.width, comp.height); + drawLabel(scaled, 'DIFF: RED = DIFFERS FROM COMP', 12, 12, { scale: 2 }); + return scaled; +} + +export function renderRegionPair(compCrop, buildCrop, id, score) { + const gap = 16, pad = 12; + const maxW = 700; + const a = fit(compCrop, maxW, 700, true), b = resize(buildCrop, a.width, a.height); + const out = createImage(a.width * 2 + gap + pad * 2, a.height + pad * 2 + 30, [24, 24, 28, 255]); + blit(out, a, pad, pad + 30); + blit(out, b, pad + a.width + gap, pad + 30); + const v = verdictFor(score); + drawLabel(out, `${id.toUpperCase()} COMP`, pad, pad, { scale: 2 }); + drawLabel(out, `BUILD ${v.toUpperCase()} ${(score.overall * 100).toFixed(0)}%`, pad + a.width + gap, pad, { scale: 2, bg: HEAT_LABEL[v] }); + return out; +} + +export function compare({ comp, build, spec = null, align = 'top', label = '' }) { + const aligned = alignBuild(comp, build, align); + const whole = scorePair(comp, aligned); + const regions = resolveRegions(comp, spec).map((r) => { + const a = regionCrop(comp, r), b = regionCrop(aligned, r); + const s = scorePair(a, b, r.kind); + return { ...r, score: strip(s), verdict: verdictFor(s, r.kind), _a: a, _b: b }; + }); + const compPalette = dominantColors(comp), buildPalette = dominantColors(aligned); + return { label, align, whole: strip(whole), regions, aligned, compPalette, buildPalette, _whole: whole }; +} + +function strip(s) { + const { _detail, _bands, ...rest } = s; + return rest; +} + +export function writeArtifacts(result, comp, outDir) { + fs.mkdirSync(path.join(outDir, 'regions'), { recursive: true }); + const side = renderSideBySide(comp, result.aligned, result.label, result.whole); + fs.writeFileSync(path.join(outDir, 'side-by-side.png'), encodePng(side)); + fs.writeFileSync(path.join(outDir, 'heatmap.png'), encodePng(renderHeatmap(comp, result.aligned))); + const regionFiles = []; + for (const r of result.regions) { + const file = path.join(outDir, 'regions', `${r.id}.png`); + fs.writeFileSync(file, encodePng(renderRegionPair(r._a, r._b, r.id, r.score))); + regionFiles.push(file); + } + return { sideBySide: path.join(outDir, 'side-by-side.png'), heatmap: path.join(outDir, 'heatmap.png'), regionFiles }; +} + +export function buildReport(result, files, meta) { + return { + tool: 'comp-diff', + version: 1, + createdAt: new Date().toISOString(), + ...meta, + align: result.align, + overall: result.whole.overall, + verdict: verdictFor(result.whole), + scores: result.whole, + palette: { comp: result.compPalette.map(({ hex, coverage }) => ({ hex, coverage })), build: result.buildPalette.map(({ hex, coverage }) => ({ hex, coverage })) }, + regions: result.regions.map(({ _a, _b, ...r }) => r), + files, + }; +} + +function summarize(report) { + const lines = []; + lines.push(`COMP-DIFF ${report.label ? `[${report.label}] ` : ''}overall ${(report.overall * 100).toFixed(0)}% (${report.verdict}) structure ${(report.scores.structure * 100).toFixed(0)}% color ${(report.scores.color * 100).toFixed(0)}% detail ${(report.scores.detail * 100).toFixed(0)}% bands ${(report.scores.bands * 100).toFixed(0)}%`); + lines.push(`PALETTE comp ${report.palette.comp.slice(0, 5).map((c) => `${c.hex}(${Math.round(c.coverage * 100)}%)`).join(' ')}`); + lines.push(`PALETTE build ${report.palette.build.slice(0, 5).map((c) => `${c.hex}(${Math.round(c.coverage * 100)}%)`).join(' ')}`); + for (const r of report.regions) { + lines.push(`REGION ${r.id.padEnd(18)} ${r.verdict.padEnd(12)} ${(r.score.overall * 100).toFixed(0).padStart(3)}% structure ${(r.score.structure * 100).toFixed(0).padStart(3)}% color ${(r.score.color * 100).toFixed(0).padStart(3)}% detail ${(r.score.detail * 100).toFixed(0).padStart(3)}%${r.score.detailAdded > 0.25 ? ' +invented detail' : ''}`); + } + if (report.files) { + lines.push(`FILES side-by-side ${report.files.sideBySide}`); + lines.push(`FILES heatmap ${report.files.heatmap}`); + lines.push(`FILES regions ${report.files.regionFiles.length} under ${path.dirname(report.files.regionFiles[0] || report.files.heatmap)}`); + } + const worst = [...report.regions].sort((a, b) => a.score.overall - b.score.overall).slice(0, 3); + if (worst.length) lines.push(`WORST ${worst.map((r) => `${r.id} (${r.verdict}, ${(r.score.overall * 100).toFixed(0)}%)`).join('; ')}`); + lines.push('OPEN the side-by-side and the worst region pairs before deciding anything; the numbers rank, the crops decide.'); + return lines.join('\n'); +} + +async function main() { + const compPath = arg('comp'), buildPath = arg('build'); + if (!compPath || !buildPath) { + console.error('usage: comp-diff.mjs --comp --build [--spec spec.json] [--out-dir dir] [--align top|stretch] [--label name] [--threshold 0.75] [--json]'); + process.exit(1); + } + let comp, build; + try { comp = readPng(compPath); } catch (e) { console.error(`comp-diff: cannot read comp ${compPath}: ${e.message}`); process.exit(1); } + try { build = readPng(buildPath); } catch (e) { console.error(`comp-diff: cannot read build ${buildPath}: ${e.message}`); process.exit(1); } + let spec = null; + const specPath = arg('spec'); + if (specPath) { + try { spec = JSON.parse(fs.readFileSync(specPath, 'utf8')); } catch (e) { console.error(`comp-diff: cannot read spec ${specPath}: ${e.message}`); process.exit(1); } + } + const outDir = arg('out-dir', path.join(path.dirname(buildPath), 'diff')); + const label = arg('label', path.basename(buildPath, '.png')); + const result = compare({ comp, build, spec, align: arg('align', 'top'), label }); + const files = flag('no-files') ? null : writeArtifacts(result, comp, outDir); + const report = buildReport(result, files, { label, comp: compPath, build: buildPath, spec: specPath || null, compSize: `${comp.width}x${comp.height}`, buildSize: `${build.width}x${build.height}` }); + if (files) fs.writeFileSync(path.join(outDir, 'report.json'), JSON.stringify(report, null, 2)); + if (flag('json')) console.log(JSON.stringify(report, null, 2)); + else console.log(summarize(report)); + const threshold = arg('threshold') ? parseFloat(arg('threshold')) : null; + if (threshold != null && report.overall < threshold) { + if (!flag('json')) console.log(`BELOW THRESHOLD ${(threshold * 100).toFixed(0)}%: the reproduction is not done. Fix the worst regions and re-run; do not build past the hero.`); + process.exit(3); + } +} + +const isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname); +if (isMain) main(); diff --git a/skill/scripts/comp-spec.mjs b/skill/scripts/comp-spec.mjs new file mode 100644 index 000000000..6d874c914 --- /dev/null +++ b/skill/scripts/comp-spec.mjs @@ -0,0 +1,252 @@ +#!/usr/bin/env node +/** + * comp-spec: turn an approved comp into a measured build spec, so the build + * codes against numbers and crops instead of a memory of the image. + * + * Step 1, look at the comp with a coordinate grid on it: + * node comp-spec.mjs --comp .impeccable/mocks/approved.png --grid + * writes .impeccable/build/comp-grid.png (10x10 labeled grid, A-J / 0-9) + * and prints the measured palette and horizontal bands. Open the grid + * image and name every salient region by its grid span. + * + * Step 2, write the regions file (JSON) and measure it: + * node comp-spec.mjs --comp --regions regions.json + * regions.json: { "regions": [ { "id": "exploded-plate", "kind": "plate", + * "grid": "E0:J4", "note": "exploded carburetor line drawing" }, ... ] } + * `grid` is ":" inclusive (A0 top-left cell to J9 bottom + * right); `box` { x, y, w, h } normalized 0..1 is accepted instead. `kind` + * is one of plate | image | texture | text | control | chrome | band. + * Writes .impeccable/build/spec.json: every region with its normalized + * box, pixel box, sampled palette, detail energy, and its medium: raster + * for plate / image / texture (produced as a plate, never CSS), semantic + * for text / control / chrome. `--auto` proposes band regions from the + * comp itself when you have no regions file yet. + * + * Step 3, use it: + * node comp-spec.mjs --print # compact spec for the build thread + * node comp-spec.mjs --crop exploded-plate --out tmp/plate-src.png [--scale 2] + * crops the region from the comp (reference for a plate regeneration; a + * crop is never a shipping asset, its resolution is comp grade) + * node comp-spec.mjs --plate-prompt exploded-plate # the regeneration prompt for that region + * + * comp-diff.mjs reads the same spec (`--spec`) so its region rows and this + * file's rows are the same rows. + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { decodePng, encodePng } from './lib/png.mjs'; +import { crop, resize, fillRect, strokeRect, drawLabel, drawText } from './lib/raster.mjs'; +import { dominantColors, horizontalBands, detailGrid } from './lib/image-metrics.mjs'; + +function arg(name, fallback = null) { + const i = process.argv.indexOf(`--${name}`); + if (i === -1) return fallback; + const v = process.argv[i + 1]; + return v && !v.startsWith('--') ? v : fallback; +} +const flag = (name) => process.argv.includes(`--${name}`); + +export const BUILD_DIR = path.join('.impeccable', 'build'); +export const SPEC_PATH = path.join(BUILD_DIR, 'spec.json'); +export const GRID_PATH = path.join(BUILD_DIR, 'comp-grid.png'); +export const PLATES_DIR = path.join('assets', 'plates'); + +export const RASTER_KINDS = new Set(['plate', 'image', 'texture']); +export const KINDS = new Set(['plate', 'image', 'texture', 'text', 'control', 'chrome', 'band']); +const COLS = 'ABCDEFGHIJ'; + +/** "E0:J4" -> normalized box (inclusive cell span on a 10x10 grid). */ +export function gridToBox(span) { + const m = /^([A-J])(\d):([A-J])(\d)$/i.exec(String(span).trim()); + if (!m) throw new Error(`grid span "${span}" is not :, e.g. E0:J4`); + const c0 = COLS.indexOf(m[1].toUpperCase()), r0 = +m[2], c1 = COLS.indexOf(m[3].toUpperCase()), r1 = +m[4]; + const x0 = Math.min(c0, c1), x1 = Math.max(c0, c1), y0 = Math.min(r0, r1), y1 = Math.max(r0, r1); + return { x: x0 / 10, y: y0 / 10, w: (x1 - x0 + 1) / 10, h: (y1 - y0 + 1) / 10 }; +} + +export function renderGrid(comp) { + const targetW = Math.min(1536, comp.width); + const img = resize(comp, targetW, Math.round((comp.height / comp.width) * targetW)); + const cw = img.width / 10, ch = img.height / 10; + const line = [255, 40, 40, 200]; + for (let i = 1; i < 10; i++) { + fillRect(img, Math.round(i * cw), 0, 1, img.height, line); + fillRect(img, 0, Math.round(i * ch), img.width, 1, line); + } + for (let r = 0; r < 10; r++) for (let c = 0; c < 10; c++) { + drawLabel(img, `${COLS[c]}${r}`, Math.round(c * cw) + 3, Math.round(r * ch) + 3, { scale: 2, bg: [0, 0, 0, 170], fg: [255, 230, 120, 255] }); + } + return img; +} + +function paletteOf(img) { + return dominantColors(img, 5).map(({ hex, coverage }) => ({ hex, coverage })); +} + +function energyOf(img) { + const g = detailGrid(img, 4, 4, 256); + let s = 0; for (const v of g.cells) s += v; + return s / g.cells.length; +} + + +export function measureRegions(comp, regionsInput, compPath) { + const regions = []; + const seen = new Set(); + for (const raw of regionsInput.regions || []) { + if (!raw.id) throw new Error('every region needs an id'); + if (seen.has(raw.id)) throw new Error(`duplicate region id ${raw.id}`); + seen.add(raw.id); + const kind = raw.kind && KINDS.has(raw.kind) ? raw.kind : 'band'; + const box = raw.box && typeof raw.box.x === 'number' ? raw.box : gridToBox(raw.grid); + const px = { x: Math.round(box.x * comp.width), y: Math.round(box.y * comp.height), w: Math.round(box.w * comp.width), h: Math.round(box.h * comp.height) }; + const c = crop(comp, px.x, px.y, px.w, px.h); + const energy = energyOf(c); + const raster = RASTER_KINDS.has(kind); + regions.push({ + id: raw.id, + kind, + note: raw.note || null, + box: { x: r4(box.x), y: r4(box.y), w: r4(box.w), h: r4(box.h) }, + px, + aspect: r4(px.w / px.h), + palette: paletteOf(c), + detail: { energy: r4(energy) }, + medium: raw.medium || (raster ? 'raster' : 'semantic'), + plate: raster ? (raw.plate || path.join(PLATES_DIR, `${raw.id}.png`)) : null, + text: raw.text || null, + }); + } + return { + tool: 'comp-spec', + version: 1, + createdAt: new Date().toISOString(), + comp: compPath, + compSize: { width: comp.width, height: comp.height }, + aspect: r4(comp.width / comp.height), + orientation: comp.width >= comp.height ? 'landscape' : 'portrait', + palette: paletteOf(comp), + bands: horizontalBands(comp).filter((b) => b.strength > 0.2).map((b) => ({ y: r4(b.y), strength: r4(b.strength) })), + regions, + }; +} + +/** Propose regions from the comp's bands when no regions file exists yet. */ +export function autoRegions(comp) { + const bands = horizontalBands(comp).filter((b) => b.strength > 0.2); + const cuts = [0, ...bands.map((b) => b.y), 1].filter((v, i, arr) => i === 0 || v - arr[i - 1] > 0.06); + if (cuts[cuts.length - 1] !== 1) cuts.push(1); + const regions = []; + for (let i = 0; i + 1 < cuts.length; i++) regions.push({ id: `band-${i + 1}`, kind: 'band', box: { x: 0, y: cuts[i], w: 1, h: cuts[i + 1] - cuts[i] } }); + return { regions }; +} + +const r4 = (v) => Math.round(v * 10000) / 10000; + +export function platePrompt(spec, region) { + const world = spec.palette.slice(0, 3).map((c) => c.hex).join(', '); + const kindLine = region.kind === 'texture' + ? 'This is a seamless surface texture. Output a tileable texture plate with no objects, no text, no vignette.' + : region.kind === 'image' + ? 'This is a photographic or illustrated image region. Output the same subject, same framing, same lighting.' + : 'This is a designed illustration plate. Output the same drawing, same style, same line weight and shading.'; + return [ + 'Use the provided crop as the approved visual reference and recreate it as a clean production asset at the target aspect ratio.', + kindLine, + `Preserve silhouette, composition, perspective, palette (${world}), lighting, material, and texture exactly.`, + 'Remove every piece of UI text, label, caption, button, and interface chrome that is not part of the artwork itself.', + 'Remove letterboxing, borders, card corners, drop shadows, and any layout background that the page will draw in code.', + 'Do not add objects. Do not change the concept. Do not restyle. Fill the whole frame; no margins.', + region.note ? `Region: ${region.note}.` : '', + ].filter(Boolean).join(' '); +} + +export function printSpec(spec) { + const lines = []; + lines.push(`SPEC comp ${spec.comp} ${spec.compSize.width}x${spec.compSize.height} ${spec.orientation}`); + lines.push(`PALETTE ${spec.palette.map((c) => `${c.hex}(${Math.round(c.coverage * 100)}%)`).join(' ')}`); + lines.push(`BANDS ${spec.bands.map((b) => `${Math.round(b.y * 100)}%`).join(' ') || 'none'}`); + for (const r of spec.regions) { + const b = r.box; + lines.push(`REGION ${r.id.padEnd(18)} ${r.kind.padEnd(8)} ${r.medium.padEnd(8)} box x${Math.round(b.x * 100)}% y${Math.round(b.y * 100)}% w${Math.round(b.w * 100)}% h${Math.round(b.h * 100)}% (${r.px.w}x${r.px.h}px, ${r.aspect}:1) palette ${r.palette.slice(0, 3).map((c) => c.hex).join(' ')}${r.plate ? ` plate ${r.plate}` : ''}${r.note ? ` # ${r.note}` : ''}`); + } + const plates = spec.regions.filter((r) => r.medium === 'raster'); + lines.push(`PLATES ${plates.length} to produce: ${plates.map((r) => r.id).join(', ') || 'none'}`); + lines.push('RULE anything not in this list does not exist on the page: no borders, rules, chrome, or containers the comp does not show. Every raster region ships as its plate, never as CSS.'); + return lines.join('\n'); +} + +export function loadSpec(specPath = SPEC_PATH) { + if (!fs.existsSync(specPath)) return null; + return JSON.parse(fs.readFileSync(specPath, 'utf8')); +} + +async function main() { + const specPath = arg('spec', SPEC_PATH); + if (flag('print')) { + const spec = loadSpec(specPath); + if (!spec) { console.error(`comp-spec: no spec at ${specPath}; run with --comp --regions first`); process.exit(1); } + console.log(printSpec(spec)); + return; + } + if (arg('plate-prompt')) { + const spec = loadSpec(specPath); + if (!spec) { console.error(`comp-spec: no spec at ${specPath}`); process.exit(1); } + const region = spec.regions.find((r) => r.id === arg('plate-prompt')); + if (!region) { console.error(`comp-spec: no region ${arg('plate-prompt')}`); process.exit(1); } + console.log(platePrompt(spec, region)); + return; + } + if (arg('crop')) { + const spec = loadSpec(specPath); + if (!spec) { console.error(`comp-spec: no spec at ${specPath}`); process.exit(1); } + const region = spec.regions.find((r) => r.id === arg('crop')); + if (!region) { console.error(`comp-spec: no region ${arg('crop')}; ids: ${spec.regions.map((r) => r.id).join(', ')}`); process.exit(1); } + const comp = decodePng(fs.readFileSync(spec.comp)); + let c = crop(comp, region.px.x, region.px.y, region.px.w, region.px.h); + const scale = parseFloat(arg('scale', '1')); + if (scale > 1) c = resize(c, c.width * scale, c.height * scale); + const out = arg('out', path.join(BUILD_DIR, 'crops', `${region.id}.png`)); + fs.mkdirSync(path.dirname(out), { recursive: true }); + fs.writeFileSync(out, encodePng(c, { text: { 'impeccable:crop-of': `${spec.comp}#${region.id}` } })); + console.log(`CROP ${out} (${c.width}x${c.height}) region ${region.id} of ${spec.comp}. Reference only: regenerate the plate from it, never ship it.`); + return; + } + + const compPath = arg('comp'); + if (!compPath) { + console.error('usage: comp-spec.mjs --comp (--grid | --regions | --auto) [--spec out.json]\n comp-spec.mjs --print | --crop [--out file] [--scale n] | --plate-prompt '); + process.exit(1); + } + let comp; + try { comp = decodePng(fs.readFileSync(compPath)); } catch (e) { console.error(`comp-spec: cannot read ${compPath}: ${e.message}`); process.exit(1); } + + if (flag('grid')) { + fs.mkdirSync(path.dirname(GRID_PATH), { recursive: true }); + fs.writeFileSync(GRID_PATH, encodePng(renderGrid(comp))); + console.log(`GRID ${GRID_PATH} (${comp.width}x${comp.height} comp; cells A0 top-left to J9 bottom-right)`); + console.log(`PALETTE ${paletteOf(comp).map((c) => `${c.hex}(${Math.round(c.coverage * 100)}%)`).join(' ')}`); + console.log(`BANDS ${horizontalBands(comp).filter((b) => b.strength > 0.2).map((b) => `${Math.round(b.y * 100)}%`).join(' ') || 'none'}`); + console.log('NEXT open the grid image, then write regions.json naming every salient region by grid span (e.g. "E0:J4") with kind plate|image|texture|text|control|chrome and a one-line note, and run --regions regions.json. Name every illustration, photo, and texture as its own region: those become plates.'); + return; + } + + let regionsInput; + if (arg('regions')) { + try { regionsInput = JSON.parse(fs.readFileSync(arg('regions'), 'utf8')); } catch (e) { console.error(`comp-spec: cannot read regions ${arg('regions')}: ${e.message}`); process.exit(1); } + } else if (flag('auto')) { + regionsInput = autoRegions(comp); + } else { + console.error('comp-spec: pass --grid to get the coordinate grid, then --regions (or --auto for band regions)'); + process.exit(1); + } + let spec; + try { spec = measureRegions(comp, regionsInput, compPath); } catch (e) { console.error(`comp-spec: ${e.message}`); process.exit(1); } + fs.mkdirSync(path.dirname(specPath), { recursive: true }); + fs.writeFileSync(specPath, JSON.stringify(spec, null, 2)); + console.log(`WROTE ${specPath}`); + console.log(printSpec(spec)); +} + +const isMain = process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname); +if (isMain) main(); diff --git a/skill/scripts/lib/image-metrics.mjs b/skill/scripts/lib/image-metrics.mjs new file mode 100644 index 000000000..2100d7da4 --- /dev/null +++ b/skill/scripts/lib/image-metrics.mjs @@ -0,0 +1,302 @@ +/** + * Perceptual measures for comparing a comp with a build screenshot. Pure + * functions over `{ width, height, data }` RGBA images; no I/O. + * + * Three families, because a build fails a comp in three separable ways: + * + * - structure: is the composition the same? Measured as SSIM over a blurred + * grayscale downsample, which forgives font hinting and a few pixels of + * drift and punishes a moved, missing, or invented region. + * - color: is the palette and its distribution the same? Histogram + * intersection in a coarse quantized space plus a dominant-color extraction, + * so a navy page built from a bone comp fails even if the shapes match. + * - detail: is the material there? Local high-frequency energy per cell. A + * comp with an illustration, texture, or photograph carries energy a flat + * CSS stand-in does not; the ratio build/comp per region is the most direct + * measure of "the plate got replaced by a gradient". + */ +import { resize } from './raster.mjs'; + +export function toGray(img) { + const g = new Float32Array(img.width * img.height); + for (let i = 0, p = 0; i < g.length; i++, p += 4) { + const a = img.data[p + 3] / 255; + // composite over white so transparent regions read as the page ground + const r = img.data[p] * a + 255 * (1 - a), gg = img.data[p + 1] * a + 255 * (1 - a), b = img.data[p + 2] * a + 255 * (1 - a); + g[i] = 0.2126 * r + 0.7152 * gg + 0.0722 * b; + } + return { width: img.width, height: img.height, data: g }; +} + +/** Separable box blur on a float gray image, radius r. */ +export function blurGray(gray, r) { + if (r <= 0) return gray; + const { width, height, data } = gray; + const tmp = new Float32Array(data.length), out = new Float32Array(data.length); + const win = 2 * r + 1; + for (let y = 0; y < height; y++) { + let acc = 0; + for (let x = -r; x <= r; x++) acc += data[y * width + Math.min(width - 1, Math.max(0, x))]; + for (let x = 0; x < width; x++) { + tmp[y * width + x] = acc / win; + const outX = x - r, inX = x + r + 1; + acc += data[y * width + Math.min(width - 1, inX)] - data[y * width + Math.max(0, outX)]; + } + } + for (let x = 0; x < width; x++) { + let acc = 0; + for (let y = -r; y <= r; y++) acc += tmp[Math.min(height - 1, Math.max(0, y)) * width + x]; + for (let y = 0; y < height; y++) { + out[y * width + x] = acc / win; + const outY = y - r, inY = y + r + 1; + acc += tmp[Math.min(height - 1, inY) * width + x] - tmp[Math.max(0, outY) * width + x]; + } + } + return { width, height, data: out }; +} + +/** Global SSIM between two same-size gray images using an 8x8 window grid. */ +export function ssim(a, b, win = 8) { + if (a.width !== b.width || a.height !== b.height) throw new Error('ssim: size mismatch'); + const C1 = (0.01 * 255) ** 2, C2 = (0.03 * 255) ** 2; + let total = 0, n = 0; + for (let y = 0; y + win <= a.height; y += win) { + for (let x = 0; x + win <= a.width; x += win) { + let ma = 0, mb = 0; + for (let yy = 0; yy < win; yy++) for (let xx = 0; xx < win; xx++) { const i = (y + yy) * a.width + x + xx; ma += a.data[i]; mb += b.data[i]; } + ma /= win * win; mb /= win * win; + let va = 0, vb = 0, cov = 0; + for (let yy = 0; yy < win; yy++) for (let xx = 0; xx < win; xx++) { const i = (y + yy) * a.width + x + xx; const da = a.data[i] - ma, db = b.data[i] - mb; va += da * da; vb += db * db; cov += da * db; } + va /= win * win - 1; vb /= win * win - 1; cov /= win * win - 1; + total += ((2 * ma * mb + C1) * (2 * cov + C2)) / ((ma * ma + mb * mb + C1) * (va + vb + C2)); + n++; + } + } + return n ? total / n : 1; +} + +/** SSIM of `a` against `b` shifted by (dx, dy); the overlap is compared, edges dropped. */ +function ssimShifted(a, b, dx, dy, win) { + const w = a.width - Math.abs(dx), h = a.height - Math.abs(dy); + if (w < win || h < win) return 0; + const sa = { width: w, height: h, data: new Float32Array(w * h) }; + const sb = { width: w, height: h, data: new Float32Array(w * h) }; + const ax = Math.max(0, -dx), ay = Math.max(0, -dy), bx = Math.max(0, dx), by = Math.max(0, dy); + for (let y = 0; y < h; y++) { + sa.data.set(a.data.subarray((y + ay) * a.width + ax, (y + ay) * a.width + ax + w), y * w); + sb.data.set(b.data.subarray((y + by) * b.width + bx, (y + by) * b.width + bx + w), y * w); + } + return ssim(sa, sb, win); +} + +/** + * Structure score 0..1: SSIM over blurred grayscale at a fixed working width, + * taking the best of a small translation search so a composition that sits a + * few pixels off (a different masthead height, a scrollbar) is not read as a + * different composition. Shifts up to ~4% of the width are forgiven; a moved + * region is not. + */ +export function structureScore(imgA, imgB, workWidth = 256) { + const h = Math.max(8, Math.round((imgA.height / imgA.width) * workWidth)); + const a = blurGray(toGray(resize(imgA, workWidth, h)), 2); + const b = blurGray(toGray(resize(imgB, workWidth, h)), 2); + const win = Math.min(8, Math.max(2, Math.floor(Math.min(workWidth, h) / 8))); + let best = ssim(a, b, win); + const maxShift = Math.max(2, Math.round(workWidth * 0.04)); + for (const dy of [-maxShift, -maxShift / 2, 0, maxShift / 2, maxShift]) { + for (const dx of [-maxShift, -maxShift / 2, 0, maxShift / 2, maxShift]) { + if (!dx && !dy) continue; + best = Math.max(best, ssimShifted(a, b, Math.round(dx), Math.round(dy), win)); + } + } + return Math.max(0, Math.min(1, best)); +} + +// ---- color ----------------------------------------------------------------- + +function rgbToLab(r, g, b) { + const lin = (c) => { c /= 255; return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4; }; + const R = lin(r), G = lin(g), B = lin(b); + const X = (R * 0.4124 + G * 0.3576 + B * 0.1805) / 0.95047; + const Y = (R * 0.2126 + G * 0.7152 + B * 0.0722) / 1.0; + const Z = (R * 0.0193 + G * 0.1192 + B * 0.9505) / 1.08883; + const f = (t) => (t > 0.008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116); + const fx = f(X), fy = f(Y), fz = f(Z); + return [116 * fy - 16, 500 * (fx - fy), 200 * (fy - fz)]; +} + +export function deltaE(lab1, lab2) { + return Math.hypot(lab1[0] - lab2[0], lab1[1] - lab2[1], lab1[2] - lab2[2]); +} + +/** Quantized color histogram (4 bits per channel = 4096 bins), normalized. */ +export function colorHistogram(img, sampleStep = 2) { + const bins = new Float32Array(4096); + let n = 0; + for (let y = 0; y < img.height; y += sampleStep) { + for (let x = 0; x < img.width; x += sampleStep) { + const p = (y * img.width + x) * 4; + if (img.data[p + 3] < 16) continue; + const key = ((img.data[p] >> 4) << 8) | ((img.data[p + 1] >> 4) << 4) | (img.data[p + 2] >> 4); + bins[key]++; n++; + } + } + if (n) for (let i = 0; i < bins.length; i++) bins[i] /= n; + return bins; +} + +export function histogramIntersection(h1, h2) { + let s = 0; + for (let i = 0; i < h1.length; i++) s += Math.min(h1[i], h2[i]); + return s; +} + +/** + * Dominant colors: merge histogram bins greedily by Lab distance into up to + * `k` clusters and return them sorted by coverage. + */ +export function dominantColors(img, k = 6, sampleStep = 3) { + const hist = colorHistogram(img, sampleStep); + const entries = []; + for (let i = 0; i < hist.length; i++) if (hist[i] > 0.0005) entries.push({ key: i, w: hist[i] }); + entries.sort((a, b) => b.w - a.w); + const clusters = []; + for (const e of entries) { + const r = ((e.key >> 8) & 15) * 16 + 8, g = ((e.key >> 4) & 15) * 16 + 8, b = (e.key & 15) * 16 + 8; + const lab = rgbToLab(r, g, b); + let best = null, bestD = Infinity; + for (const c of clusters) { const d = deltaE(c.lab, lab); if (d < bestD) { bestD = d; best = c; } } + if (best && bestD < 14) { + const tw = best.w + e.w; + best.rgb = [(best.rgb[0] * best.w + r * e.w) / tw, (best.rgb[1] * best.w + g * e.w) / tw, (best.rgb[2] * best.w + b * e.w) / tw]; + best.lab = rgbToLab(...best.rgb); best.w = tw; + } else clusters.push({ rgb: [r, g, b], lab, w: e.w }); + } + clusters.sort((a, b) => b.w - a.w); + const top = clusters.slice(0, k); + const covered = top.reduce((s, c) => s + c.w, 0) || 1; + return top.map((c) => ({ hex: toHex(c.rgb), coverage: +(c.w / covered).toFixed(4), lab: c.lab })); +} + +export function toHex(rgb) { + return '#' + rgb.map((v) => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join(''); +} + +/** + * Palette match 0..1: for each dominant comp color, coverage-weighted best + * Lab match in the build's dominant set (dE 0 -> 1, dE >= 40 -> 0). + */ +export function paletteMatch(compColors, buildColors) { + if (!compColors.length) return 1; + let s = 0, wsum = 0; + for (const c of compColors) { + let best = Infinity; + for (const b of buildColors) best = Math.min(best, deltaE(c.lab, b.lab)); + s += c.coverage * Math.max(0, 1 - best / 40); wsum += c.coverage; + } + return wsum ? s / wsum : 1; +} + +/** Color score 0..1: blend of histogram intersection and dominant-palette match. */ +export function colorScore(imgA, imgB) { + const inter = histogramIntersection(colorHistogram(imgA), colorHistogram(imgB)); + const pm = paletteMatch(dominantColors(imgA), dominantColors(imgB)); + return { score: 0.35 * inter + 0.65 * pm, intersection: inter, paletteMatch: pm }; +} + +// ---- detail ---------------------------------------------------------------- + +/** Mean absolute gradient (Sobel-lite) per cell over a cols x rows grid. */ +export function detailGrid(img, cols = 12, rows = 8, workWidth = 512) { + const h = Math.max(rows, Math.round((img.height / img.width) * workWidth)); + const g = toGray(resize(img, workWidth, h)); + const grid = new Float32Array(cols * rows); + const counts = new Float32Array(cols * rows); + for (let y = 1; y < g.height - 1; y++) { + const cy = Math.min(rows - 1, Math.floor((y / g.height) * rows)); + for (let x = 1; x < g.width - 1; x++) { + const cx = Math.min(cols - 1, Math.floor((x / g.width) * cols)); + const i = y * g.width + x; + const gx = Math.abs(g.data[i + 1] - g.data[i - 1]); + const gy = Math.abs(g.data[i + g.width] - g.data[i - g.width]); + grid[cy * cols + cx] += gx + gy; counts[cy * cols + cx]++; + } + } + for (let i = 0; i < grid.length; i++) grid[i] = counts[i] ? grid[i] / counts[i] : 0; + return { cols, rows, cells: grid }; +} + +/** + * Detail score 0..1 and per-cell ratio. Cells where the comp is nearly flat + * are ignored (nothing to lose); the score is the coverage-weighted mean of + * min(1, build/comp) over cells with comp energy, so extra detail in the + * build (invented chrome) is reported separately as `added`. + */ +export function detailScore(imgA, imgB, cols = 12, rows = 8) { + const a = detailGrid(imgA, cols, rows), b = detailGrid(imgB, cols, rows); + const floor = 1.5; // energy below this is a flat field at the 512px working width + let s = 0, w = 0, added = 0, addedW = 0; + const ratios = new Float32Array(cols * rows); + for (let i = 0; i < a.cells.length; i++) { + const ca = a.cells[i], cb = b.cells[i]; + ratios[i] = ca > floor ? cb / ca : (cb > floor ? Infinity : 1); + if (ca > floor) { s += Math.min(1, cb / ca) * ca; w += ca; } + if (cb > ca * 1.8 && cb > floor * 2) { added += 1; } + addedW += 1; + } + return { score: w ? s / w : 1, addedFraction: addedW ? added / addedW : 0, comp: a, build: b, ratios }; +} + +// ---- pixel diff ----------------------------------------------------------- + +/** Per-pixel Lab-ish difference map (0..1) at a working width; blurred a little. */ +export function diffMap(imgA, imgB, workWidth = 384) { + const h = Math.max(8, Math.round((imgA.height / imgA.width) * workWidth)); + const a = resize(imgA, workWidth, h), b = resize(imgB, workWidth, h); + const out = new Float32Array(workWidth * h); + for (let i = 0, p = 0; i < out.length; i++, p += 4) { + const dr = a.data[p] - b.data[p], dg = a.data[p + 1] - b.data[p + 1], db = a.data[p + 2] - b.data[p + 2]; + out[i] = Math.min(1, Math.sqrt(dr * dr + dg * dg + db * db) / 200); + } + return blurGray({ width: workWidth, height: h, data: out }, 1); +} + +// ---- bands (horizontal layout structure) --------------------------------- + +/** + * Detect horizontal band boundaries: rows where the mean color changes + * sharply. Returns normalized y positions (0..1) with strengths. This is the + * "layout grid" read of a page: header / hero / index / footer as bands. + */ +export function horizontalBands(img, workWidth = 128, minGap = 0.02) { + const h = Math.max(16, Math.round((img.height / img.width) * workWidth)); + const s = resize(img, workWidth, h); + const rowMean = new Float32Array(h * 3); + for (let y = 0; y < h; y++) { + let r = 0, g = 0, b = 0; + for (let x = 0; x < workWidth; x++) { const p = (y * workWidth + x) * 4; r += s.data[p]; g += s.data[p + 1]; b += s.data[p + 2]; } + rowMean[y * 3] = r / workWidth; rowMean[y * 3 + 1] = g / workWidth; rowMean[y * 3 + 2] = b / workWidth; + } + const edges = []; + for (let y = 1; y < h; y++) { + const d = Math.hypot(rowMean[y * 3] - rowMean[(y - 1) * 3], rowMean[y * 3 + 1] - rowMean[(y - 1) * 3 + 1], rowMean[y * 3 + 2] - rowMean[(y - 1) * 3 + 2]); + if (d > 18) edges.push({ y: y / h, strength: Math.min(1, d / 120) }); + } + // merge close edges + const merged = []; + for (const e of edges) { + const last = merged[merged.length - 1]; + if (last && e.y - last.y < minGap) { if (e.strength > last.strength) { last.y = e.y; last.strength = e.strength; } } + else merged.push({ ...e }); + } + return merged; +} + +/** Band agreement 0..1: fraction of comp bands with a build band within tolerance, and vice versa. */ +export function bandScore(bandsA, bandsB, tol = 0.04) { + if (!bandsA.length && !bandsB.length) return 1; + const match = (from, to) => from.filter((a) => to.some((b) => Math.abs(a.y - b.y) <= tol)).length; + const recall = bandsA.length ? match(bandsA, bandsB) / bandsA.length : 1; + const precision = bandsB.length ? match(bandsB, bandsA) / bandsB.length : 1; + return 0.6 * recall + 0.4 * precision; +} diff --git a/skill/scripts/lib/png.mjs b/skill/scripts/lib/png.mjs new file mode 100644 index 000000000..faa8d45a9 --- /dev/null +++ b/skill/scripts/lib/png.mjs @@ -0,0 +1,250 @@ +/** + * Dependency-free PNG decode/encode for the skill scripts. + * + * decodePng(buffer) -> { width, height, data } where data is RGBA8 (Uint8Array, + * width*height*4). Handles every color type (0, 2, 3, 4, 6), bit depths 1-16 + * (16-bit is reduced to 8), all five filters, and Adam7 interlacing. + * + * encodePng({ width, height, data }) -> Buffer, RGBA8 in, 8-bit RGBA PNG out. + * + * Kept small on purpose: the skill scripts ship without npm dependencies, and + * comps (gpt-image PNGs) and screenshots (Playwright / harness PNGs) are the + * only formats the comp-fidelity tooling has to read. + */ +import zlib from 'node:zlib'; + +const SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + +const crcTable = (() => { + const t = new Uint32Array(256); + for (let n = 0; n < 256; n++) { + let c = n; + for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; + t[n] = c >>> 0; + } + return t; +})(); + +function crc32(data) { + let c = 0xffffffff; + for (let i = 0; i < data.length; i++) c = crcTable[(c ^ data[i]) & 0xff] ^ (c >>> 8); + return (c ^ 0xffffffff) >>> 0; +} + +export function isPng(buf) { + return buf && buf.length > 8 && buf.subarray(0, 8).equals(SIGNATURE); +} + +function readChunks(buf) { + const chunks = []; + let pos = 8; + while (pos + 8 <= buf.length) { + const length = buf.readUInt32BE(pos); + const type = buf.toString('latin1', pos + 4, pos + 8); + const data = buf.subarray(pos + 8, pos + 8 + length); + chunks.push({ type, data }); + pos += 12 + length; + if (type === 'IEND') break; + } + return chunks; +} + +const CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 }; + +function paeth(a, b, c) { + const p = a + b - c; + const pa = Math.abs(p - a), pb = Math.abs(p - b), pc = Math.abs(p - c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +/** Unfilter one pass of scanlines in place; returns the raw (unfiltered) bytes. */ +function unfilter(raw, width, height, bpp, bitDepth, channels) { + const stride = Math.ceil((width * channels * bitDepth) / 8); + const out = new Uint8Array(stride * height); + let inPos = 0; + let prev = null; + for (let y = 0; y < height; y++) { + const filter = raw[inPos++]; + const line = out.subarray(y * stride, (y + 1) * stride); + line.set(raw.subarray(inPos, inPos + stride)); + inPos += stride; + switch (filter) { + case 0: break; + case 1: for (let i = bpp; i < stride; i++) line[i] = (line[i] + line[i - bpp]) & 0xff; break; + case 2: if (prev) for (let i = 0; i < stride; i++) line[i] = (line[i] + prev[i]) & 0xff; break; + case 3: + for (let i = 0; i < stride; i++) { + const left = i >= bpp ? line[i - bpp] : 0; + const up = prev ? prev[i] : 0; + line[i] = (line[i] + ((left + up) >> 1)) & 0xff; + } + break; + case 4: + for (let i = 0; i < stride; i++) { + const left = i >= bpp ? line[i - bpp] : 0; + const up = prev ? prev[i] : 0; + const ul = prev && i >= bpp ? prev[i - bpp] : 0; + line[i] = (line[i] + paeth(left, up, ul)) & 0xff; + } + break; + default: throw new Error(`png: unknown filter ${filter} on row ${y}`); + } + prev = line; + } + return { bytes: out, stride, consumed: inPos }; +} + +/** Read sample `index` (0-based across the row) from a packed scanline. */ +function sampleReader(bitDepth) { + if (bitDepth === 8) return (line, i) => line[i]; + if (bitDepth === 16) return (line, i) => line[i * 2]; // high byte + const perByte = 8 / bitDepth; + const mask = (1 << bitDepth) - 1; + const scale = 255 / mask; + return (line, i) => { + const byte = line[(i / perByte) | 0]; + const shift = 8 - bitDepth * ((i % perByte) + 1); + return Math.round(((byte >> shift) & mask) * scale); + }; +} + +function writePixels(dst, dstWidth, bytes, stride, passWidth, passHeight, colorType, bitDepth, palette, trns, mapX, mapY) { + const channels = CHANNELS[colorType]; + const read = sampleReader(bitDepth); + const rawIndex = bitDepth < 8 ? (line, i) => { + const perByte = 8 / bitDepth; + const mask = (1 << bitDepth) - 1; + const byte = line[(i / perByte) | 0]; + const shift = 8 - bitDepth * ((i % perByte) + 1); + return (byte >> shift) & mask; + } : read; + for (let y = 0; y < passHeight; y++) { + const line = bytes.subarray(y * stride, (y + 1) * stride); + const dy = mapY(y); + for (let x = 0; x < passWidth; x++) { + const dx = mapX(x); + const o = (dy * dstWidth + dx) * 4; + let r, g, b, a = 255; + switch (colorType) { + case 0: { + r = g = b = read(line, x); + if (trns && trns.gray === rawIndex(line, x)) a = 0; + break; + } + case 2: { + r = read(line, x * 3); g = read(line, x * 3 + 1); b = read(line, x * 3 + 2); + break; + } + case 3: { + const idx = rawIndex(line, x); + r = palette[idx * 3]; g = palette[idx * 3 + 1]; b = palette[idx * 3 + 2]; + if (trns && trns.alpha && idx < trns.alpha.length) a = trns.alpha[idx]; + break; + } + case 4: { + r = g = b = read(line, x * 2); a = read(line, x * 2 + 1); + break; + } + case 6: { + r = read(line, x * 4); g = read(line, x * 4 + 1); b = read(line, x * 4 + 2); a = read(line, x * 4 + 3); + break; + } + default: throw new Error(`png: unsupported color type ${colorType}`); + } + dst[o] = r; dst[o + 1] = g; dst[o + 2] = b; dst[o + 3] = a; + } + } + return channels; +} + +export function decodePng(buf) { + if (!isPng(buf)) throw new Error('png: not a PNG (bad signature)'); + const chunks = readChunks(buf); + const ihdr = chunks.find((c) => c.type === 'IHDR'); + if (!ihdr) throw new Error('png: missing IHDR'); + const width = ihdr.data.readUInt32BE(0); + const height = ihdr.data.readUInt32BE(4); + const bitDepth = ihdr.data[8]; + const colorType = ihdr.data[9]; + const interlace = ihdr.data[12]; + const channels = CHANNELS[colorType]; + if (!channels) throw new Error(`png: unsupported color type ${colorType}`); + const palChunk = chunks.find((c) => c.type === 'PLTE'); + const palette = palChunk ? palChunk.data : null; + const trnsChunk = chunks.find((c) => c.type === 'tRNS'); + let trns = null; + if (trnsChunk) { + if (colorType === 3) trns = { alpha: trnsChunk.data }; + else if (colorType === 0) trns = { gray: trnsChunk.data.readUInt16BE(0) >> (bitDepth === 16 ? 8 : 0) }; + } + const idat = Buffer.concat(chunks.filter((c) => c.type === 'IDAT').map((c) => c.data)); + const raw = zlib.inflateSync(idat); + const bpp = Math.max(1, Math.ceil((channels * bitDepth) / 8)); + const data = new Uint8Array(width * height * 4); + const text = {}; + for (const c of chunks) { + if (c.type === 'tEXt') { + const z = c.data.indexOf(0); + if (z > 0) text[c.data.toString('latin1', 0, z)] = c.data.toString('utf8', z + 1); + } + } + + if (interlace === 0) { + const { bytes, stride } = unfilter(raw, width, height, bpp, bitDepth, channels); + writePixels(data, width, bytes, stride, width, height, colorType, bitDepth, palette, trns, (x) => x, (y) => y); + } else { + // Adam7 + const passes = [ + [0, 0, 8, 8], [4, 0, 8, 8], [0, 4, 4, 8], [2, 0, 4, 4], [0, 2, 2, 4], [1, 0, 2, 2], [0, 1, 1, 2], + ]; + let offset = 0; + for (const [sx, sy, dx, dy] of passes) { + const pw = Math.ceil((width - sx) / dx); + const ph = Math.ceil((height - sy) / dy); + if (pw <= 0 || ph <= 0) continue; + const { bytes, stride, consumed } = unfilter(raw.subarray(offset), pw, ph, bpp, bitDepth, channels); + offset += consumed; + writePixels(data, width, bytes, stride, pw, ph, colorType, bitDepth, palette, trns, (x) => sx + x * dx, (y) => sy + y * dy); + } + } + return { width, height, data, text }; +} + +function chunk(type, data) { + const len = Buffer.alloc(4); + len.writeUInt32BE(data.length, 0); + const typeBuf = Buffer.from(type, 'latin1'); + const crc = Buffer.alloc(4); + crc.writeUInt32BE(crc32(Buffer.concat([typeBuf, data])), 0); + return Buffer.concat([len, typeBuf, data, crc]); +} + +/** + * Encode RGBA8 to PNG. `text` (optional) is a map of tEXt keyword -> value. + * Uses filter type 0 on every row: comps and screenshots compress fine and the + * encoder stays trivial. + */ +export function encodePng({ width, height, data }, { text = null, level = 6 } = {}) { + if (data.length !== width * height * 4) throw new Error(`png: data length ${data.length} != ${width}x${height}x4`); + const stride = width * 4; + const raw = Buffer.alloc((stride + 1) * height); + for (let y = 0; y < height; y++) { + raw[y * (stride + 1)] = 0; + raw.set(data.subarray(y * stride, (y + 1) * stride), y * (stride + 1) + 1); + } + const ihdr = Buffer.alloc(13); + ihdr.writeUInt32BE(width, 0); + ihdr.writeUInt32BE(height, 4); + ihdr[8] = 8; ihdr[9] = 6; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0; + const parts = [SIGNATURE, chunk('IHDR', ihdr)]; + if (text) { + for (const [k, v] of Object.entries(text)) { + parts.push(chunk('tEXt', Buffer.concat([Buffer.from(k, 'latin1'), Buffer.from([0]), Buffer.from(String(v), 'utf8')]))); + } + } + parts.push(chunk('IDAT', zlib.deflateSync(raw, { level }))); + parts.push(chunk('IEND', Buffer.alloc(0))); + return Buffer.concat(parts); +} diff --git a/skill/scripts/lib/raster.mjs b/skill/scripts/lib/raster.mjs new file mode 100644 index 000000000..2d3e0ae3a --- /dev/null +++ b/skill/scripts/lib/raster.mjs @@ -0,0 +1,194 @@ +/** + * Small RGBA raster toolkit shared by the comp-fidelity scripts: crop, resize + * (area-averaging down, bilinear up), composite, fills, rectangles, and a + * bitmap-font label so composites can be captioned without a font stack. + * + * An image is `{ width, height, data }` with RGBA8 data (Uint8Array). + */ + +export function createImage(width, height, fill = [0, 0, 0, 0]) { + const data = new Uint8Array(width * height * 4); + if (fill[0] || fill[1] || fill[2] || fill[3]) { + for (let i = 0; i < data.length; i += 4) { data[i] = fill[0]; data[i + 1] = fill[1]; data[i + 2] = fill[2]; data[i + 3] = fill[3]; } + } + return { width, height, data }; +} + +export function clampRect(img, x, y, w, h) { + const x0 = Math.max(0, Math.min(img.width, Math.round(x))); + const y0 = Math.max(0, Math.min(img.height, Math.round(y))); + const x1 = Math.max(x0, Math.min(img.width, Math.round(x + w))); + const y1 = Math.max(y0, Math.min(img.height, Math.round(y + h))); + return { x: x0, y: y0, w: x1 - x0, h: y1 - y0 }; +} + +export function crop(img, x, y, w, h) { + const r = clampRect(img, x, y, w, h); + const out = createImage(Math.max(1, r.w), Math.max(1, r.h)); + for (let yy = 0; yy < r.h; yy++) { + const src = ((r.y + yy) * img.width + r.x) * 4; + out.data.set(img.data.subarray(src, src + r.w * 4), yy * out.width * 4); + } + return out; +} + +/** Resize with area averaging when shrinking and bilinear when growing. */ +export function resize(img, width, height) { + width = Math.max(1, Math.round(width)); + height = Math.max(1, Math.round(height)); + if (width === img.width && height === img.height) return { width, height, data: new Uint8Array(img.data) }; + const out = createImage(width, height); + const sx = img.width / width, sy = img.height / height; + if (sx >= 1 && sy >= 1) { + for (let y = 0; y < height; y++) { + const y0 = Math.floor(y * sy), y1 = Math.min(img.height, Math.max(y0 + 1, Math.floor((y + 1) * sy))); + for (let x = 0; x < width; x++) { + const x0 = Math.floor(x * sx), x1 = Math.min(img.width, Math.max(x0 + 1, Math.floor((x + 1) * sx))); + let r = 0, g = 0, b = 0, a = 0, n = 0; + for (let yy = y0; yy < y1; yy++) { + let p = (yy * img.width + x0) * 4; + for (let xx = x0; xx < x1; xx++, p += 4) { r += img.data[p]; g += img.data[p + 1]; b += img.data[p + 2]; a += img.data[p + 3]; n++; } + } + const o = (y * width + x) * 4; + out.data[o] = r / n; out.data[o + 1] = g / n; out.data[o + 2] = b / n; out.data[o + 3] = a / n; + } + } + return out; + } + for (let y = 0; y < height; y++) { + const fy = Math.min(img.height - 1, (y + 0.5) * sy - 0.5); + const y0 = Math.max(0, Math.floor(fy)), y1 = Math.min(img.height - 1, y0 + 1), wy = fy - y0; + for (let x = 0; x < width; x++) { + const fx = Math.min(img.width - 1, (x + 0.5) * sx - 0.5); + const x0 = Math.max(0, Math.floor(fx)), x1 = Math.min(img.width - 1, x0 + 1), wx = fx - x0; + const o = (y * width + x) * 4; + for (let c = 0; c < 4; c++) { + const p00 = img.data[(y0 * img.width + x0) * 4 + c], p10 = img.data[(y0 * img.width + x1) * 4 + c]; + const p01 = img.data[(y1 * img.width + x0) * 4 + c], p11 = img.data[(y1 * img.width + x1) * 4 + c]; + out.data[o + c] = (p00 * (1 - wx) + p10 * wx) * (1 - wy) + (p01 * (1 - wx) + p11 * wx) * wy; + } + } + } + return out; +} + +/** Scale to fit inside (maxW x maxH) preserving aspect; never upscale unless `allowUpscale`. */ +export function fit(img, maxW, maxH, allowUpscale = false) { + const s = Math.min(maxW / img.width, maxH / img.height); + if (s >= 1 && !allowUpscale) return img; + return resize(img, img.width * s, img.height * s); +} + +/** Alpha-composite `src` onto `dst` at (x, y). */ +export function blit(dst, src, x, y) { + x = Math.round(x); y = Math.round(y); + for (let yy = 0; yy < src.height; yy++) { + const dy = y + yy; if (dy < 0 || dy >= dst.height) continue; + for (let xx = 0; xx < src.width; xx++) { + const dx = x + xx; if (dx < 0 || dx >= dst.width) continue; + const s = (yy * src.width + xx) * 4, d = (dy * dst.width + dx) * 4; + const a = src.data[s + 3] / 255; + if (a >= 1) { dst.data[d] = src.data[s]; dst.data[d + 1] = src.data[s + 1]; dst.data[d + 2] = src.data[s + 2]; dst.data[d + 3] = 255; continue; } + if (a <= 0) continue; + const da = dst.data[d + 3] / 255, oa = a + da * (1 - a); + for (let c = 0; c < 3; c++) dst.data[d + c] = (src.data[s + c] * a + dst.data[d + c] * da * (1 - a)) / (oa || 1); + dst.data[d + 3] = oa * 255; + } + } +} + +export function fillRect(img, x, y, w, h, rgba) { + const r = clampRect(img, x, y, w, h); + const a = (rgba[3] ?? 255) / 255; + for (let yy = r.y; yy < r.y + r.h; yy++) { + for (let xx = r.x; xx < r.x + r.w; xx++) { + const o = (yy * img.width + xx) * 4; + if (a >= 1) { img.data[o] = rgba[0]; img.data[o + 1] = rgba[1]; img.data[o + 2] = rgba[2]; img.data[o + 3] = 255; } + else { for (let c = 0; c < 3; c++) img.data[o + c] = rgba[c] * a + img.data[o + c] * (1 - a); img.data[o + 3] = Math.max(img.data[o + 3], a * 255); } + } + } +} + +export function strokeRect(img, x, y, w, h, rgba, thickness = 2) { + fillRect(img, x, y, w, thickness, rgba); + fillRect(img, x, y + h - thickness, w, thickness, rgba); + fillRect(img, x, y, thickness, h, rgba); + fillRect(img, x + w - thickness, y, thickness, h, rgba); +} + +// 5x7 bitmap font, uppercase + digits + a little punctuation. Enough for labels. +const GLYPHS = { + A: ['01110', '10001', '10001', '11111', '10001', '10001', '10001'], + B: ['11110', '10001', '10001', '11110', '10001', '10001', '11110'], + C: ['01110', '10001', '10000', '10000', '10000', '10001', '01110'], + D: ['11110', '10001', '10001', '10001', '10001', '10001', '11110'], + E: ['11111', '10000', '10000', '11110', '10000', '10000', '11111'], + F: ['11111', '10000', '10000', '11110', '10000', '10000', '10000'], + G: ['01110', '10001', '10000', '10111', '10001', '10001', '01111'], + H: ['10001', '10001', '10001', '11111', '10001', '10001', '10001'], + I: ['11111', '00100', '00100', '00100', '00100', '00100', '11111'], + J: ['00111', '00010', '00010', '00010', '00010', '10010', '01100'], + K: ['10001', '10010', '10100', '11000', '10100', '10010', '10001'], + L: ['10000', '10000', '10000', '10000', '10000', '10000', '11111'], + M: ['10001', '11011', '10101', '10101', '10001', '10001', '10001'], + N: ['10001', '10001', '11001', '10101', '10011', '10001', '10001'], + O: ['01110', '10001', '10001', '10001', '10001', '10001', '01110'], + P: ['11110', '10001', '10001', '11110', '10000', '10000', '10000'], + Q: ['01110', '10001', '10001', '10001', '10101', '10010', '01101'], + R: ['11110', '10001', '10001', '11110', '10100', '10010', '10001'], + S: ['01111', '10000', '10000', '01110', '00001', '00001', '11110'], + T: ['11111', '00100', '00100', '00100', '00100', '00100', '00100'], + U: ['10001', '10001', '10001', '10001', '10001', '10001', '01110'], + V: ['10001', '10001', '10001', '10001', '10001', '01010', '00100'], + W: ['10001', '10001', '10001', '10101', '10101', '10101', '01010'], + X: ['10001', '10001', '01010', '00100', '01010', '10001', '10001'], + Y: ['10001', '10001', '01010', '00100', '00100', '00100', '00100'], + Z: ['11111', '00001', '00010', '00100', '01000', '10000', '11111'], + 0: ['01110', '10001', '10011', '10101', '11001', '10001', '01110'], + 1: ['00100', '01100', '00100', '00100', '00100', '00100', '01110'], + 2: ['01110', '10001', '00001', '00010', '00100', '01000', '11111'], + 3: ['11110', '00001', '00001', '01110', '00001', '00001', '11110'], + 4: ['00010', '00110', '01010', '10010', '11111', '00010', '00010'], + 5: ['11111', '10000', '11110', '00001', '00001', '10001', '01110'], + 6: ['00110', '01000', '10000', '11110', '10001', '10001', '01110'], + 7: ['11111', '00001', '00010', '00100', '01000', '01000', '01000'], + 8: ['01110', '10001', '10001', '01110', '10001', '10001', '01110'], + 9: ['01110', '10001', '10001', '01111', '00001', '00010', '01100'], + ' ': ['00000', '00000', '00000', '00000', '00000', '00000', '00000'], + '.': ['00000', '00000', '00000', '00000', '00000', '01100', '01100'], + ':': ['00000', '01100', '01100', '00000', '01100', '01100', '00000'], + '-': ['00000', '00000', '00000', '11111', '00000', '00000', '00000'], + '/': ['00001', '00010', '00010', '00100', '01000', '01000', '10000'], + '%': ['11001', '11010', '00010', '00100', '01000', '01011', '10011'], + '(': ['00010', '00100', '01000', '01000', '01000', '00100', '00010'], + ')': ['01000', '00100', '00010', '00010', '00010', '00100', '01000'], + '#': ['01010', '01010', '11111', '01010', '11111', '01010', '01010'], + '_': ['00000', '00000', '00000', '00000', '00000', '00000', '11111'], + '?': ['01110', '10001', '00001', '00010', '00100', '00000', '00100'], + '=': ['00000', '00000', '11111', '00000', '11111', '00000', '00000'], + '+': ['00000', '00100', '00100', '11111', '00100', '00100', '00000'], + ',': ['00000', '00000', '00000', '00000', '01100', '00100', '01000'], +}; + +export function textWidth(text, scale = 2) { + return text.length * 6 * scale; +} + +/** Draw uppercase bitmap text. Returns width drawn. */ +export function drawText(img, text, x, y, rgba, scale = 2) { + let cx = Math.round(x); + for (const chRaw of String(text).toUpperCase()) { + const g = GLYPHS[chRaw] || GLYPHS['?']; + for (let r = 0; r < 7; r++) for (let c = 0; c < 5; c++) if (g[r][c] === '1') fillRect(img, cx + c * scale, y + r * scale, scale, scale, rgba); + cx += 6 * scale; + } + return cx - x; +} + +/** Draw a label with a background pill. */ +export function drawLabel(img, text, x, y, { fg = [255, 255, 255, 255], bg = [0, 0, 0, 220], scale = 2, pad = 4 } = {}) { + const w = textWidth(text, scale) + pad * 2, h = 7 * scale + pad * 2; + fillRect(img, x, y, w, h, bg); + drawText(img, text, x + pad, y + pad, fg, scale); + return { w, h }; +} diff --git a/tests/build-phase.test.mjs b/tests/build-phase.test.mjs new file mode 100644 index 000000000..b175979c7 --- /dev/null +++ b/tests/build-phase.test.mjs @@ -0,0 +1,168 @@ +import { describe, it, before, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { encodePng } from '../skill/scripts/lib/png.mjs'; +import { createImage, fillRect, blit, resize } from '../skill/scripts/lib/raster.mjs'; +import { gridToBox, measureRegions, platePrompt } from '../skill/scripts/comp-spec.mjs'; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const SPEC_SCRIPT = path.join(ROOT, 'skill', 'scripts', 'comp-spec.mjs'); +const PHASE_SCRIPT = path.join(ROOT, 'skill', 'scripts', 'build-phase.mjs'); + +function lcg(seed) { let s = seed >>> 0; return () => ((s = (s * 1664525 + 1013904223) >>> 0) / 0xffffffff); } + +function makeComp(w = 640, h = 400) { + const img = createImage(w, h, [240, 237, 226, 255]); + fillRect(img, 0, 0, w, 40, [19, 33, 48, 255]); + fillRect(img, 20, 60, 220, 20, [19, 33, 48, 255]); + fillRect(img, 20, 90, 180, 20, [19, 33, 48, 255]); + const rnd = lcg(3); + for (let y = 40; y < 200; y++) for (let x = 320; x < 640; x++) { + const v = 100 + Math.floor(rnd() * 140); + const p = (y * w + x) * 4; img.data[p] = v; img.data[p + 1] = v; img.data[p + 2] = v; + } + for (let i = 0; i < 3; i++) { fillRect(img, 0, 220 + i * 50, w, 1, [19, 33, 48, 255]); fillRect(img, 20, 232 + i * 50, 300, 12, [19, 33, 48, 255]); } + return img; +} + +function run(script, args, cwd) { + return spawnSync(process.execPath, [script, ...args], { cwd, encoding: 'utf8' }); +} + +describe('comp-spec', () => { + it('parses grid spans into normalized boxes', () => { + assert.deepEqual(gridToBox('A0:A0'), { x: 0, y: 0, w: 0.1, h: 0.1 }); + assert.deepEqual(gridToBox('E0:J4'), { x: 0.4, y: 0, w: 0.6, h: 0.5 }); + assert.deepEqual(gridToBox('j4:e0'), { x: 0.4, y: 0, w: 0.6, h: 0.5 }); + assert.throws(() => gridToBox('K0:A1')); + }); + + it('measures regions with palette, pixel box, medium, and plate path', () => { + const comp = makeComp(); + const spec = measureRegions(comp, { regions: [ + { id: 'masthead', kind: 'chrome', grid: 'A0:J0' }, + { id: 'art', kind: 'plate', grid: 'F1:J4', note: 'noise plate' }, + ] }, 'comp.png'); + assert.equal(spec.regions.length, 2); + const art = spec.regions.find((r) => r.id === 'art'); + assert.equal(art.medium, 'raster'); + assert.equal(art.plate, path.join('assets', 'plates', 'art.png')); + assert.equal(art.px.x, 320); + assert.ok(art.palette.length > 0); + assert.equal(spec.regions[0].medium, 'semantic'); + assert.equal(spec.orientation, 'landscape'); + assert.match(platePrompt(spec, art), /noise plate/); + }); + + it('rejects duplicate ids and missing ids', () => { + const comp = makeComp(); + assert.throws(() => measureRegions(comp, { regions: [{ id: 'a', grid: 'A0:A0' }, { id: 'a', grid: 'B0:B0' }] }, 'c.png'), /duplicate/); + assert.throws(() => measureRegions(comp, { regions: [{ grid: 'A0:A0' }] }, 'c.png'), /id/); + }); +}); + +describe('build-phase state machine (CLI)', () => { + let dir; + before(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'build-phase-')); + fs.writeFileSync(path.join(dir, 'comp.png'), encodePng(makeComp())); + fs.writeFileSync(path.join(dir, 'regions.json'), JSON.stringify({ regions: [ + { id: 'masthead', kind: 'chrome', grid: 'A0:J0' }, + { id: 'headline', kind: 'text', grid: 'A1:D2' }, + { id: 'art', kind: 'plate', grid: 'F1:J4', note: 'noise plate' }, + { id: 'list', kind: 'control', grid: 'A5:J9' }, + ] })); + }); + after(() => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} }); + + it('start writes state at spec and prints NEXT', () => { + const res = run(PHASE_SCRIPT, ['start', '--comp', 'comp.png'], dir); + assert.equal(res.status, 0, res.stderr); + assert.match(res.stdout, /BUILD-PHASE SPEC/); + assert.match(res.stdout, /NEXT Measure the comp/); + assert.ok(fs.existsSync(path.join(dir, '.impeccable', 'build', 'state.json'))); + }); + + it('spec gate fails without a spec, passes once comp-spec wrote one', () => { + let res = run(PHASE_SCRIPT, ['advance'], dir); + assert.equal(res.status, 2); + assert.match(res.stdout, /GATE SPEC FAILED/); + res = run(SPEC_SCRIPT, ['--comp', 'comp.png', '--grid'], dir); + assert.equal(res.status, 0, res.stderr); + assert.ok(fs.existsSync(path.join(dir, '.impeccable', 'build', 'comp-grid.png'))); + res = run(SPEC_SCRIPT, ['--comp', 'comp.png', '--regions', 'regions.json'], dir); + assert.equal(res.status, 0, res.stderr); + assert.match(res.stdout, /PLATES 1 to produce: art/); + res = run(PHASE_SCRIPT, ['advance'], dir); + assert.equal(res.status, 0, res.stdout + res.stderr); + assert.match(res.stdout, /ADVANCED spec -> plates/); + }); + + it('plates gate names the missing plate, rejects a comp-size crop, accepts a 2x plate', () => { + let res = run(PHASE_SCRIPT, ['advance'], dir); + assert.equal(res.status, 2); + assert.match(res.stdout, /plate missing for art/); + // comp-size crop: too small + res = run(SPEC_SCRIPT, ['--crop', 'art', '--out', 'assets/plates/art.png'], dir); + assert.equal(res.status, 0, res.stderr); + res = run(PHASE_SCRIPT, ['advance'], dir); + assert.equal(res.status, 2); + assert.match(res.stdout, /needs at least 1.5x/); + // 2x crop passes size and similarity + res = run(SPEC_SCRIPT, ['--crop', 'art', '--scale', '2', '--out', 'assets/plates/art.png'], dir); + assert.equal(res.status, 0, res.stderr); + res = run(PHASE_SCRIPT, ['advance'], dir); + assert.equal(res.status, 0, res.stdout); + assert.match(res.stdout, /ADVANCED plates -> hero/); + }); + + it('hero gate fails on a flat build and passes on a faithful one, recording attempts', () => { + const comp = makeComp(); + const flat = createImage(comp.width, comp.height, [240, 237, 226, 255]); + fillRect(flat, 0, 0, comp.width, 40, [19, 33, 48, 255]); + fs.mkdirSync(path.join(dir, '.impeccable', 'review'), { recursive: true }); + fs.writeFileSync(path.join(dir, '.impeccable', 'review', 'hero-repro.png'), encodePng(flat)); + let res = run(PHASE_SCRIPT, ['advance'], dir); + assert.equal(res.status, 2, res.stdout); + assert.match(res.stdout, /GATE HERO FAILED/); + assert.match(res.stdout, /region art is missing/); + assert.ok(fs.existsSync(path.join(dir, '.impeccable', 'review', 'diff', 'hero', 'side-by-side.png'))); + // faithful: the comp shifted by a few px, captured at 1.5x width + const shifted = createImage(comp.width, comp.height, [240, 237, 226, 255]); + blit(shifted, comp, 3, 2); + fs.writeFileSync(path.join(dir, '.impeccable', 'review', 'hero-repro.png'), encodePng(resize(shifted, comp.width * 1.5, comp.height * 1.5))); + res = run(PHASE_SCRIPT, ['advance'], dir); + assert.equal(res.status, 0, res.stdout); + assert.match(res.stdout, /ADVANCED hero -> sections/); + const state = JSON.parse(fs.readFileSync(path.join(dir, '.impeccable', 'build', 'state.json'), 'utf8')); + assert.equal(state.phases.hero.attempts, 2); + assert.ok(state.phases.hero.gate.score >= 0.72); + }); + + it('later phases advance without a gate; force is recorded; finish records the disposition', () => { + for (const from of ['sections', 'motion']) { + const res = run(PHASE_SCRIPT, ['advance'], dir); + assert.equal(res.status, 0, res.stdout); + assert.match(res.stdout, new RegExp(`ADVANCED ${from}`)); + } + let res = run(PHASE_SCRIPT, ['advance', '--force', '--reason', 'test'], dir); + assert.equal(res.status, 0); + res = run(PHASE_SCRIPT, ['finish', '--disposition', 'fix'], dir); + assert.equal(res.status, 0); + assert.match(res.stdout, /finish fix/); + res = run(PHASE_SCRIPT, ['status', '--json'], dir); + const state = JSON.parse(res.stdout); + assert.equal(state.phase, 'review'); + assert.equal(state.finish.disposition, 'fix'); + }); + + it('refuses a bad disposition and an unknown command', () => { + assert.equal(run(PHASE_SCRIPT, ['finish', '--disposition', 'great'], dir).status, 1); + assert.equal(run(PHASE_SCRIPT, ['dance'], dir).status, 1); + }); +}); diff --git a/tests/comp-diff.test.mjs b/tests/comp-diff.test.mjs new file mode 100644 index 000000000..1fdce1921 --- /dev/null +++ b/tests/comp-diff.test.mjs @@ -0,0 +1,225 @@ +import { describe, it, before } from 'node:test'; +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { decodePng, encodePng } from '../skill/scripts/lib/png.mjs'; +import { createImage, fillRect, blit, crop, resize, drawText } from '../skill/scripts/lib/raster.mjs'; +import { compare, verdictFor, alignBuild } from '../skill/scripts/comp-diff.mjs'; +import { dominantColors, structureScore, detailScore } from '../skill/scripts/lib/image-metrics.mjs'; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const SCRIPT = path.join(ROOT, 'skill', 'scripts', 'comp-diff.mjs'); + +// A synthetic "comp": bone ground, navy masthead, a red-gutter table, and a +// noisy "illustration" region on the right of the fold. Deterministic noise. +function lcg(seed) { let s = seed >>> 0; return () => ((s = (s * 1664525 + 1013904223) >>> 0) / 0xffffffff); } + +function makeComp(w = 768, h = 512) { + const img = createImage(w, h, [240, 237, 226, 255]); + fillRect(img, 0, 0, w, 32, [19, 33, 48, 255]); // masthead + drawText(img, 'CARBURETOR CLUB', 12, 8, [240, 237, 226, 255], 2); + // headline block + fillRect(img, 24, 70, 280, 22, [19, 33, 48, 255]); + fillRect(img, 24, 100, 240, 22, [19, 33, 48, 255]); + fillRect(img, 24, 136, 90, 4, [176, 40, 32, 255]); + // illustration: high-frequency noise plate + const rnd = lcg(7); + for (let y = 48; y < 240; y++) for (let x = 340; x < 740; x++) { + const v = 120 + Math.floor(rnd() * 120); + const p = (y * w + x) * 4; img.data[p] = v; img.data[p + 1] = v; img.data[p + 2] = v + 10; + } + // table with red gutter + fillRect(img, 0, 260, w, 2, [19, 33, 48, 255]); + for (let i = 0; i < 3; i++) { + const y = 270 + i * 60; + fillRect(img, 0, y, 10, 50, i === 0 ? [176, 40, 32, 255] : [19, 33, 48, 255]); + fillRect(img, 30, y + 10, 300, 14, [19, 33, 48, 255]); + fillRect(img, 30, y + 30, 200, 8, [120, 120, 120, 255]); + fillRect(img, 0, y + 56, w, 1, [19, 33, 48, 255]); + } + // CTA + fillRect(img, 24, 460, 160, 36, [19, 33, 48, 255]); + return img; +} + +function flattenIllustration(comp) { + const img = { ...comp, data: new Uint8Array(comp.data) }; + fillRect(img, 340, 48, 400, 192, [200, 200, 205, 255]); // a flat gray box where the plate was + return img; +} + +function recolor(comp) { + const img = { ...comp, data: new Uint8Array(comp.data) }; + for (let i = 0; i < img.data.length; i += 4) { + if (img.data[i] > 220 && img.data[i + 1] > 210) { img.data[i] = 20; img.data[i + 1] = 40; img.data[i + 2] = 60; } + } + return img; +} + +function shifted(comp, dx, dy) { + const img = createImage(comp.width, comp.height, [240, 237, 226, 255]); + blit(img, comp, dx, dy); + return img; +} + +function tallPage(comp) { + // a full-page screenshot: comp on top, then more page below + const img = createImage(comp.width, comp.height * 3, [240, 237, 226, 255]); + blit(img, comp, 0, 0); + fillRect(img, 0, comp.height + 40, comp.width, 200, [19, 33, 48, 255]); + return img; +} + +const SPEC = { + regions: [ + { id: 'masthead', kind: 'control', box: { x: 0, y: 0, w: 1, h: 32 / 512 } }, + { id: 'headline', kind: 'text', box: { x: 0.02, y: 60 / 512, w: 0.4, h: 100 / 512 } }, + { id: 'plate', kind: 'plate', box: { x: 340 / 768, y: 48 / 512, w: 400 / 768, h: 192 / 512 } }, + { id: 'table', kind: 'control', box: { x: 0, y: 260 / 512, w: 1, h: 190 / 512 } }, + ], +}; + +describe('png codec', () => { + it('round-trips RGBA through encode/decode', () => { + const img = createImage(20, 10, [10, 20, 30, 255]); + img.data.set([200, 100, 50, 128], (2 * 20 + 2) * 4); + const back = decodePng(encodePng(img, { text: { 'impeccable:prompt': 'hello' } })); + assert.equal(back.width, 20); assert.equal(back.height, 10); + assert.deepEqual([...back.data.subarray(0, 4)], [10, 20, 30, 255]); + assert.deepEqual([...back.data.subarray((2 * 20 + 2) * 4, (2 * 20 + 2) * 4 + 4)], [200, 100, 50, 128]); + assert.equal(back.text['impeccable:prompt'], 'hello'); + }); + + it('decodes a real gpt-image / Playwright style PNG when one is on disk (skips otherwise)', () => { + const sample = path.join(ROOT, 'tests', 'fixtures', 'comp-fidelity', 'sample.png'); + if (!fs.existsSync(sample)) return; + const img = decodePng(fs.readFileSync(sample)); + assert.ok(img.width > 0 && img.height > 0); + }); +}); + +describe('image metrics', () => { + const comp = makeComp(); + it('identity scores 1', () => { + assert.ok(structureScore(comp, comp) > 0.999); + assert.ok(detailScore(comp, comp).score > 0.999); + }); + it('dominant colors find the ground and the ink', () => { + const cols = dominantColors(comp).map((c) => c.hex); + assert.ok(cols.some((h) => h.startsWith('#f') || h.startsWith('#e')), `ground missing in ${cols}`); + assert.ok(cols.some((h) => h.startsWith('#1') || h.startsWith('#0') || h.startsWith('#2')), `ink missing in ${cols}`); + }); + it('a flattened plate loses detail', () => { + const d = detailScore(comp, flattenIllustration(comp)); + assert.ok(d.score < 0.85, `expected detail loss, got ${d.score}`); + }); +}); + +describe('comp-diff compare', () => { + const comp = makeComp(); + + it('scores the comp against itself as a match everywhere', () => { + const r = compare({ comp, build: comp, spec: SPEC }); + assert.equal(r.whole.overall, 1); + for (const region of r.regions) assert.equal(region.verdict, 'match', region.id); + }); + + it('forgives a small translation', () => { + const r = compare({ comp, build: shifted(comp, 6, 4), spec: SPEC }); + assert.ok(r.whole.overall >= 0.8, `overall ${r.whole.overall}`); + assert.equal(verdictFor(r.whole), 'match'); + }); + + it('reads the top of a full-page screenshot as the first viewport', () => { + const r = compare({ comp, build: tallPage(comp), spec: SPEC }); + assert.equal(r.aligned.height, comp.height); + assert.ok(r.whole.overall > 0.95, `overall ${r.whole.overall}`); + }); + + it('flags a flattened plate region as missing while the rest matches', () => { + const r = compare({ comp, build: flattenIllustration(comp), spec: SPEC }); + const plate = r.regions.find((x) => x.id === 'plate'); + assert.equal(plate.verdict, 'missing'); + assert.equal(r.regions.find((x) => x.id === 'table').verdict, 'match'); + assert.equal(r.regions.find((x) => x.id === 'masthead').verdict, 'match'); + }); + + it('fails a recolored page on color and structure', () => { + const r = compare({ comp, build: recolor(comp), spec: SPEC }); + assert.ok(r.whole.color < 0.7, `color ${r.whole.color}`); + assert.ok(r.whole.overall < 0.6, `overall ${r.whole.overall}`); + assert.notEqual(verdictFor(r.whole), 'match'); + }); + + it('derives band regions when no spec is given', () => { + const r = compare({ comp, build: comp }); + assert.ok(r.regions.length >= 2); + assert.ok(r.regions.every((x) => x.kind === 'band')); + }); + + it('pads a shorter build with white so a truncated page reads as missing content', () => { + const half = crop(comp, 0, 0, comp.width, comp.height / 2); + const aligned = alignBuild(comp, half); + assert.equal(aligned.height, comp.height); + const r = compare({ comp, build: half, spec: SPEC }); + assert.notEqual(r.regions.find((x) => x.id === 'table').verdict, 'match'); + }); + + it('scales a build captured at a different width onto the comp', () => { + const wide = resize(comp, comp.width * 1.5, comp.height * 1.5); + const r = compare({ comp, build: wide, spec: SPEC }); + assert.ok(r.whole.overall > 0.9, `overall ${r.whole.overall}`); + }); +}); + +describe('comp-diff CLI', () => { + let dir; + before(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), 'comp-diff-')); + const comp = makeComp(); + fs.writeFileSync(path.join(dir, 'comp.png'), encodePng(comp)); + fs.writeFileSync(path.join(dir, 'flat.png'), encodePng(flattenIllustration(comp))); + fs.writeFileSync(path.join(dir, 'spec.json'), JSON.stringify(SPEC)); + }); + + it('writes side-by-side, heatmap, region pairs, and report.json', () => { + const out = path.join(dir, 'diff'); + const res = spawnSync(process.execPath, [SCRIPT, '--comp', path.join(dir, 'comp.png'), '--build', path.join(dir, 'flat.png'), '--spec', path.join(dir, 'spec.json'), '--out-dir', out], { encoding: 'utf8' }); + assert.equal(res.status, 0, res.stderr + res.stdout); + assert.match(res.stdout, /^COMP-DIFF/m); + assert.match(res.stdout, /REGION plate\s+missing/); + for (const f of ['side-by-side.png', 'heatmap.png', 'report.json', path.join('regions', 'plate.png')]) { + assert.ok(fs.existsSync(path.join(out, f)), `${f} missing`); + } + const report = JSON.parse(fs.readFileSync(path.join(out, 'report.json'), 'utf8')); + assert.equal(report.tool, 'comp-diff'); + assert.equal(report.regions.length, 4); + assert.ok(report.palette.comp.length > 0); + // artifacts decode + const side = decodePng(fs.readFileSync(path.join(out, 'side-by-side.png'))); + assert.ok(side.width > 768); + }); + + it('exits 3 below --threshold and prints the instruction', () => { + const res = spawnSync(process.execPath, [SCRIPT, '--comp', path.join(dir, 'comp.png'), '--build', path.join(dir, 'flat.png'), '--no-files', '--threshold', '0.99'], { encoding: 'utf8' }); + assert.equal(res.status, 3); + assert.match(res.stdout, /BELOW THRESHOLD/); + }); + + it('--json prints the report', () => { + const res = spawnSync(process.execPath, [SCRIPT, '--comp', path.join(dir, 'comp.png'), '--build', path.join(dir, 'comp.png'), '--no-files', '--json'], { encoding: 'utf8' }); + assert.equal(res.status, 0); + const report = JSON.parse(res.stdout); + assert.equal(report.verdict, 'match'); + }); + + it('exits 1 with usage on missing args', () => { + const res = spawnSync(process.execPath, [SCRIPT], { encoding: 'utf8' }); + assert.equal(res.status, 1); + assert.match(res.stderr, /usage/); + }); +}); diff --git a/tests/fixtures/comp-fidelity/sample.png b/tests/fixtures/comp-fidelity/sample.png new file mode 100644 index 0000000000000000000000000000000000000000..1c95e0065a16d163b223d4e2e90c285d552dc843 GIT binary patch literal 153599 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