Spaces:
Sleeping
Sleeping
| // 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); | |