// Verify menu-card dino and in-game dino renderer produce matching art. 'use strict'; import { createRequire } from 'node:module'; import { readdirSync, existsSync } from 'node:fs'; const require = createRequire('/root/kidcraft/node_modules/'); const { chromium } = require('playwright-core'); function findBrowserExe() { const root = '/root/.cache/ms-playwright'; for (const dir of readdirSync(root)) { if (!dir.startsWith('chromium')) continue; for (const sub of ['chrome-headless-shell-linux64/chrome-headless-shell', 'chrome-linux/headless_shell']) { const p = `${root}/${dir}/${sub}`; if (existsSync(p)) return p; } } } let failures = 0; const ok = (c, l) => { console.log(`${c ? 'PASS' : 'FAIL'} ${l}`); if (!c) failures++; }; const browser = await chromium.launch({ executablePath: findBrowserExe(), args: ['--no-sandbox'] }); const page = await browser.newPage(); await page.goto('http://127.0.0.1:8095/', { waitUntil: 'networkidle' }); await page.waitForTimeout(800); const res = await page.evaluate(() => { function avgColor(canvas) { const cx = canvas.getContext('2d'); const d = cx.getImageData(0, 0, canvas.width, canvas.height).data; let r = 0, g = 0, b = 0, n = 0; for (let i = 0; i < d.length; i += 4) { if (d[i + 3] < 40) continue; // skip transparent r += d[i]; g += d[i + 1]; b += d[i + 2]; n++; } return n ? [r / n, g / n, b / n] : null; } const out = {}; // menu card canvases are the first canvas child of each .card const cards = document.querySelectorAll('.card'); const keys = ['compy', 'raptor', 'trike', 'rex']; cards.forEach((card, idx) => { const cv = card.querySelector('canvas'); out[keys[idx]] = avgColor(cv); }); // render in-game style dino for each species via shared renderer out.game = {}; for (const k of keys) { const cv = document.createElement('canvas'); cv.width = 180; cv.height = 88; const c = cv.getContext('2d'); // identical backdrop as drawCardPreview const g = c.createLinearGradient(0, 0, 0, 88); g.addColorStop(0, 'rgba(120,190,140,0.16)'); g.addColorStop(1, 'rgba(30,60,45,0.28)'); c.fillStyle = g; c.beginPath(); c.roundRect(0, 0, 180, 88, 8); c.fill(); c.save(); const scale = k === 'rex' ? 1.5 : k === 'compy' ? 0.95 : k === 'trike' ? 1.05 : 1.18; c.translate(90 - 6 * scale, 88 * 0.60); c.scale(scale, scale); paintDino(c, { x: 0, y: 4, dir: 0, r: 17, sp: k, stage: 2, phase: 1.15, moving: false, resting: false, bite: k === 'rex' ? 0.85 : 0, eat: 0, ci: 0, preview: true }); c.restore(); out.game[k] = avgColor(cv); } return out; }); for (const k of ['compy', 'raptor', 'trike', 'rex']) { const [r1, g1, b1] = res[k], [r2, g2, b2] = res.game[k]; const d = Math.hypot(r1 - r2, g1 - g2, b1 - b2); ok(d < 12, `${k}: card vs game avg color Δ=${d.toFixed(1)} rgb(${r1.toFixed(0)},${g1.toFixed(0)},${b1.toFixed(0)}) vs (${r2.toFixed(0)},${g2.toFixed(0)},${b2.toFixed(0)})`); } // screenshots for human review await page.screenshot({ path: '/tmp/shot-menu-v2.png' }); await page.fill('#nameInput', 'RexCheck'); await page.click('.card[data-key="rex"]'); await page.click('#playBtn'); await page.waitForTimeout(2200); await page.screenshot({ path: '/tmp/shot-game-rex.png' }); await browser.close(); console.log(failures ? `\n${failures} CHECK(S) FAILED` : '\nMATCH CHECKS PASSED'); process.exit(failures ? 1 : 0);