// smoke4.mjs: functional verification via director state probes. // Verifies: scene transitions, happiness/awareness via debug, clue discovery, ending triggers. import { chromium } from 'playwright'; import { spawn } from 'child_process'; const PORT = 4181; const server = spawn('node', ['node_modules/vite/bin/vite.js', 'preview', '--port', String(PORT), '--strictPort'], { cwd: process.cwd(), stdio: 'pipe', }); const waitServer = () => new Promise((resolve, reject) => { const t = setTimeout(() => reject(new Error('server timeout')), 30000); server.stdout.on('data', (d) => { if (d.toString().includes('Local:')) { clearTimeout(t); resolve(); } }); server.stderr.on('data', (d) => { if (d.toString().includes('Local:')) { clearTimeout(t); resolve(); } }); }); const errors = []; const checks = {}; function check(name, cond) { checks[name] = !!cond; if (!cond) errors.push('CHECK FAIL: ' + name); } try { await waitServer(); const browser = await chromium.launch(); const page = await browser.newPage({ viewport: { width: 960, height: 540 } }); page.on('pageerror', (err) => { errors.push('PAGEERROR: ' + err.message); }); await page.goto(`http://localhost:${PORT}/`, { waitUntil: 'load', timeout: 15000 }); await page.waitForTimeout(800); // start game await page.keyboard.press('Enter'); await page.waitForTimeout(1000); const st = () => page.evaluate(() => { const d = window.director; return d ? { happiness: d.happiness, awareness: d.awareness, day: d.day, stage: d.stage, cluesCount: d.clues.size, tasksCount: d.tasksDone.size, playerName: d.playerName, } : null; }); // 1. game started, default state let r = await st(); check('game_started', r !== null); check('happiness_default_30', r.happiness === 30); check('awareness_default_0', r.awareness === 0); check('day_default_1', r.day === 1); check('name_default_小满', r.playerName === '小满'); // 2. debug: set happiness to 100 -> stage S4 await page.evaluate(() => window.director.debugSetHappiness(100)); await page.waitForTimeout(300); r = await st(); check('happy100_stage_S4', r.stage === 'S4'); // 3. debug: set happiness 10 -> S0 await page.evaluate(() => window.director.debugSetHappiness(10)); await page.waitForTimeout(300); r = await st(); check('happy10_stage_S0', r.stage === 'S0'); // 4. debug: set happiness 70 -> S2 await page.evaluate(() => window.director.debugSetHappiness(70)); await page.waitForTimeout(300); r = await st(); check('happy70_stage_S2', r.stage === 'S2'); // 5. debug: set all clues -> awareness increases, clues 9 await page.evaluate(() => window.director.debugAllClues()); r = await st(); check('all_clues_9', r.cluesCount === 9); // 6. debug: set day to 5 await page.evaluate(() => window.director.debugSetDay(5)); r = await st(); check('day_set_5', r.day === 5); // 7. ending determination: awareness>=70 && clues>=6 -> E3 await page.evaluate(() => { window.director.debugSetAwareness(80); }); r = await st(); const ending = await page.evaluate(() => window.director.determineEnding()); check('ending_E3_when_aware_clues', ending === 'E3'); // 8. scene transitions: active scenes const sceneInfo = await page.evaluate(() => { const g = window.game; return g.scene.scenes.filter(s => s.scene.isActive()).map(s => s.scene.key); }); check('uiscene_active', sceneInfo.includes('UIScene')); check('mapscene_active', sceneInfo.some(k => ['HouseScene','PlazaScene','BakeryScene','ShopScene','ClinicScene','LakeScene','FieldScene','WellAreaScene','FogBoundaryScene'].includes(k))); await browser.close(); } catch (e) { errors.push('TEST_FAIL: ' + e.message); } finally { server.kill('SIGTERM'); } console.log('=== CHECKS ===', JSON.stringify(checks, null, 2)); console.log('=== ERRORS (' + errors.length + ') ==='); errors.slice(0, 20).forEach((e) => console.log(e)); process.exit(errors.length > 0 ? 1 : 0);