// Visual audit: screenshot every asset class up close so we can judge what // needs enhancement. Writes to ../playtest/audit/. import puppeteer from 'puppeteer-core'; import { spawn } from 'child_process'; import http from 'http'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const APP_DIR = path.join(__dirname, '..', 'app'); const OUT = path.join(__dirname, '..', 'playtest', 'audit'); fs.mkdirSync(OUT, { recursive: true }); const port = 3960 + Math.floor(Math.random() * 30); const srv = spawn('node', ['server/index.js'], { cwd: APP_DIR, env: { ...process.env, PORT: String(port) }, stdio: 'pipe' }); await new Promise((res, rej) => { const t0 = Date.now(); (function poll() { http.get(`http://127.0.0.1:${port}/`, () => res()).on('error', () => Date.now() - t0 > 8000 ? rej(new Error('no server')) : setTimeout(poll, 150)); })(); }); const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome-stable', headless: 'new', args: ['--no-sandbox', '--disable-dev-shm-usage', '--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'], defaultViewport: { width: 1280, height: 720 }, }); const page = await browser.newPage(); page.on('pageerror', e => console.log('ERR', e.message)); await page.goto(`http://127.0.0.1:${port}/`, { waitUntil: 'networkidle0' }); await page.waitForFunction('window.GAME && window.GAME.ready'); const sleep = ms => new Promise(r => setTimeout(r, ms)); const shot = async name => { await page.screenshot({ path: path.join(OUT, name + '.png') }); console.log('shot', name); }; // crank quality for the audit await page.evaluate(() => { GAME.test.setSetting('graphics.preset', 'ultra'); GAME.test.setSetting('graphics.resolutionScale', 1.0); }); // 1. every weapon viewmodel await page.evaluate(() => { GAME.test.start('vector'); GAME.test.skipBuy(); }); await sleep(400); const weapons = ['knife', 'pistol', 'deagle', 'smg', 'shotgun', 'rifle', 'sniper']; await page.evaluate(ws => { for (const w of ws) GAME.game.player.weapons[w] = GAME.game.player.weapons[w] || { ammo: 30, reserve: 90 }; GAME.test.pose(0, 0, 8, 0, -0.06); }, weapons); for (const w of weapons) { await page.evaluate(w => { GAME.game.player.current = w; }, w); await sleep(350); await shot(`weapon-${w}`); } // 2. character: front 3/4, side, back, crouched, group await page.evaluate(() => { const g = GAME.game; g.bots.forEach((b, i) => { b.pos = [100 + i * 10, 0, 100]; }); // park others away const b = g.bots[0]; b.pos = [0, 0, 16]; b.yaw = Math.PI * 0.85; GAME.test.pose(-1.5, 0, 19, 0.35, 0.0); }); await sleep(300); await shot('char-front34'); await page.evaluate(() => { GAME.game.bots[0].yaw = Math.PI / 2; }); await sleep(250); await shot('char-side'); await page.evaluate(() => { const g = GAME.game; for (let i = 0; i < Math.min(4, g.bots.length); i++) { g.bots[i].pos = [-3 + i * 2, 0, 15 - (i % 2)]; g.bots[i].yaw = Math.PI + (i - 1.5) * 0.3; } GAME.test.pose(0, 0, 21, 0, 0.02); }); await sleep(300); await shot('char-group'); // 3. map signature views, ultra for (const [m, x, y, z, yaw, pitch] of [ ['vector-asite', 20, 0, -12, 1.2, -0.05], ['foundry-hall', 0, 0, 20, 0, 0.0], ['foundry-platform', 12, 2.2, 10, -2.4, -0.12], ['canopy-bridge', 0, 0, 18, 0, -0.04], ['canopy-street', 8, -2, 2, -1.4, 0.1], ]) { const mapId = m.split('-')[0]; await page.evaluate(id => { GAME.test.start(id); GAME.test.skipBuy(); }, mapId); await sleep(350); await page.evaluate((x, y, z, yaw, pitch) => GAME.test.pose(x, y, z, yaw, pitch), x, y, z, yaw, pitch); await sleep(300); await shot(`map-${m}`); } // 4. reflective floor + neon close (the RT showcase angle) await page.evaluate(() => { GAME.test.start('vector'); GAME.test.skipBuy(); GAME.test.pose(-5.5, 0, 10, -0.5, -0.35); }); await sleep(300); await shot('rt-neon-floor'); // 5. muzzle flash mid-fire await page.evaluate(() => { GAME.test.pose(0, 0, 8, 0, 0); GAME.game.player.current = 'rifle'; GAME.game.player.weapons.rifle = { ammo: 30, reserve: 90 }; GAME.test.fire(); }); await sleep(30); await shot('fx-muzzle'); // 6. buy menu (asset cards) await page.evaluate(() => { GAME.game.state = 'buy'; GAME.game.phaseT = 6; }); await page.evaluate(() => { document.getElementById('buy-menu').style.display = 'block'; }); await sleep(200); await shot('ui-buymenu'); await browser.close(); srv.kill(); console.log('audit complete:', OUT);