import puppeteer from 'puppeteer'; import { PNG } from 'pngjs'; import fs from 'fs'; const URL = 'http://127.0.0.1:4939'; const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox', '--disable-setuid-sandbox', '--enable-unsafe-swiftshader', '--use-gl=angle', '--use-angle=swiftshader'], }); const page = await browser.newPage(); await page.setViewport({ width: 1024, height: 600 }); const errors = []; const logs = []; page.on('console', (msg) => { const t = `[${msg.type()}] ${msg.text()}`; logs.push(t); if (msg.type() === 'error') errors.push(t); }); page.on('pageerror', (err) => errors.push('[pageerror] ' + err.message)); await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 45000 }); await new Promise(r => setTimeout(r, 4000)); // sanity: dom state const state = await page.evaluate(() => ({ title: document.title, hasMenu: !!document.getElementById('menu'), hasNewBtn: !!document.getElementById('m-new'), uiHidden: document.getElementById('ui')?.classList.contains('hidden'), canvases: [...document.querySelectorAll('canvas')].map(c => ({ w: c.width, h: c.height, id: c.id || '(main)' })), webgl: (() => { try { const c = document.createElement('canvas'); return !!c.getContext('webgl2'); } catch { return false; } })(), })); console.log('STATE:', JSON.stringify(state, null, 1)); async function analyze(file, label) { const buf = fs.readFileSync(file); const png = PNG.sync.read(buf); const { width: w, height: h, data } = png; const region = (x0, y0, x1, y1) => { let r = 0, g = 0, b = 0, n = 0; for (let y = y0; y < y1; y += 4) for (let x = x0; x < x1; x += 4) { const i = (y * w + x) * 4; r += data[i]; g += data[i + 1]; b += data[i + 2]; n++; } return [Math.round(r / n), Math.round(g / n), Math.round(b / n)]; }; const px = (x, y) => { const i = (y * w + x) * 4; return [data[i], data[i + 1], data[i + 2]]; }; // variance sample across whole frame let sum = 0, sum2 = 0, n = 0; for (let y = 0; y < h; y += 6) for (let x = 0; x < w; x += 6) { const [r, g, b] = px(x, y); const l = (r + g + b) / 3; sum += l; sum2 += l * l; n++; } const mean = sum / n; const std = Math.sqrt(sum2 / n - mean * mean); console.log(`${label}: mean=${mean.toFixed(1)} std=${std.toFixed(1)} top=${region(0, 0, w, 60)} center=${region(w / 3, h / 3, 2 * w / 3, 2 * h / 3)} bottom=${region(0, h - 80, w, h)}`); } await page.screenshot({ path: '/tmp/shot_menu.png' }); await analyze('/tmp/shot_menu.png', 'MENU '); // proceed into game try { await page.evaluate(() => document.getElementById('m-new').click()); await new Promise(r => setTimeout(r, 500)); await page.evaluate(() => { const inp = document.querySelector('#wiz-name'); if (inp) inp.value = 'The Gilded Carrot'; document.querySelector('#wiz-start')?.click(); }); await new Promise(r => setTimeout(r, 2500)); await page.screenshot({ path: '/tmp/shot_game.png' }); await analyze('/tmp/shot_game.png', 'GAME '); await page.evaluate(() => window.__TS.G.timeScale = 3); await new Promise(r => setTimeout(r, 6000)); await page.screenshot({ path: '/tmp/shot_busy.png' }); await analyze('/tmp/shot_busy.png', 'BUSY '); const gameInfo = await page.evaluate(() => ({ day: window.__TS.G.shop.day, clock: Math.round(window.__TS.G.shop.minutes), customers: window.__TS.G.customers.length, gold: Math.round(window.__TS.G.gold), phase: window.__TS.G.shop.phase, isOpen: window.__TS.G.shop.isOpen, })); console.log('SIM:', JSON.stringify(gameInfo)); } catch (e) { console.log('FLOW ERROR:', e.message); } console.log('ERRORS(' + errors.length + '):'); errors.slice(0, 12).forEach(e => console.log(' ' + e.slice(0, 300))); await browser.close();