Files

121 lines
7.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// dbg20:审计修复验证
// 1. 广场地图:邮筒瓦片(gid47)在 (11,10)4 个挂彩带点改为路灯(gid 29),无预置彩带
// 2. C3 邮筒交互可达(D2
// 3. T13 挂彩带前无彩带图像,挂上后出现(帧 29/30/31)
// 4. 阿福指引:D6→T14_HINTD7→T15_HINT
// 5. 门淡出庆典竞态:低幸福计时逼近阈值时过门 → celebrationActive 不卡死,新场景可交互
import fs from 'node:fs';
import puppeteer from 'puppeteer-core';
import { spawn } from 'node:child_process';
import net from 'node:net';
let fails = 0;
const check = (name, ok, extra = '') => { console.log(`${ok ? 'PASS' : 'FAIL'} ${name}${extra ? ' ' + extra : ''}`); if (!ok) fails++; };
// ---- 静态地图检查 ----
const plaza = JSON.parse(fs.readFileSync('public/assets/maps/plaza.json', 'utf8'));
const ob = plaza.layers.find(l => l.name === 'obstacles').data;
const W = plaza.width;
const g = (x, y) => ob[y * W + x];
check('邮筒瓦片 gid47 @ (11,10)', g(11, 10) === 47);
check('挂彩带点全部改为路灯 gid29', g(11,7)===29 && g(18,7)===29 && g(11,13)===29 && g(18,13)===29);
check('无预置彩带 gid30/31/32', ![30,31,32].includes(g(11,7)) && ![30,31,32].includes(g(18,7)) && ![30,31,32].includes(g(11,13)) && ![30,31,32].includes(g(18,13)));
const preview = spawn('npx', ['vite','preview','--host','127.0.0.1','--port','4815','--strictPort'], { stdio:'ignore' });
process.on('exit', () => { try{preview.kill()}catch{} });
for (let i=0;i<50;i++){ const ok = await new Promise(r=>{const s=net.connect(4815,'127.0.0.1');s.once('connect',()=>{s.end();r(true)});s.once('error',()=>r(false))}); if(ok)break; await new Promise(r=>setTimeout(r,300)); }
const browser = await puppeteer.launch({ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', headless: true, args: ['--autoplay-policy=no-user-gesture-required','--mute-audio'] });
const page = await browser.newPage();
await page.setViewport({ width: 960, height: 560 });
page.on('pageerror', e => console.log('[pageerror]', (e.stack||e.message).slice(0,400)));
page.on('console', m => { if (m.type()==='error') console.log('[console.error]', m.text().slice(0,200)); });
await page.goto('http://127.0.0.1:4815/', { waitUntil: 'networkidle2' });
await page.waitForFunction('window.__game && window.__director', { timeout: 20000 });
await page.waitForFunction(() => window.__game.scene.isActive('TitleScene'), { timeout: 20000 });
await page.evaluate(() => { document.querySelector('#game input[type="text"]').closest('div').querySelector('button').click(); });
await page.waitForFunction(() => window.__game.scene.isActive('HouseScene'), { timeout: 15000 });
const sleep = (ms) => new Promise(r=>setTimeout(r,ms));
const tap = async (code, hold=90) => { await page.keyboard.down(code); await sleep(hold); await page.keyboard.up(code); await sleep(30); };
const ev = (fn, ...a) => page.evaluate(fn, ...a);
const dlgOpen = () => ev(() => { const u = window.__game.scene.getScene('UIScene'); return !!(u && u.payload); });
const dlgText = () => ev(() => { const u = window.__game.scene.getScene('UIScene'); return u && u.dlgBody ? u.dlgBody.text : ''; });
async function drain(maxIter = 40) {
const texts = [];
for (let i = 0; i < maxIter; i++) { if (!(await dlgOpen())) break; texts.push(await dlgText()); await tap('KeyE'); await sleep(160); }
return texts;
}
async function startScene(k) {
await ev((k) => { const sm = window.__game.scene;
for (const sc of sm.getScenes(true)) { const key = sc.scene.key; if (key !== 'UIScene' && key !== k) sm.stop(key); }
sm.start(k, { spawn: 'default' }); }, k);
await page.waitForFunction((kk) => window.__game.scene.isActive(kk), { timeout: 9000 }, k);
await sleep(400); await drain();
}
async function setPlayer(k, x, y) {
await ev((k,x,y) => { const s = window.__game.scene.getScene(k); s.player.setPosition(x, y); s.player.setVelocity(0,0); }, k, x, y);
await sleep(120);
}
async function interactAt(sceneKey, x, y) {
await setPlayer(sceneKey, x + 40, y + 40);
await setPlayer(sceneKey, x, y + 8);
await tap('KeyE');
await sleep(300);
return drain();
}
const dget = (e) => ev(new Function(`return window.__director.${e}`));
const darr = (e) => ev(new Function(`return [...window.__director.${e}]`));
await sleep(500);
console.log('\n=== 2. C3 邮筒交互(D2 ===');
await ev(() => window.__director.setDay(2));
await startScene('PlazaScene');
const texts2 = await interactAt('PlazaScene', 184, 176); // mailbox_c3 区
check('C3 线索获得', (await darr('clues')).includes('C3'), JSON.stringify(texts2).slice(0, 60));
console.log('\n=== 3. T13 挂彩带视觉反馈 ===');
await ev(() => window.__director.setDay(6));
await startScene('PlazaScene');
const imgBefore = await ev(() => {
const s = window.__game.scene.getScene('PlazaScene');
return s.children.list.filter(o => o.type === 'Image' && o.texture.key === 'tiles16' && [29,30,31].includes(o.frame.name)).length;
});
check('挂彩带前场景无彩带图像', imgBefore === 0, `实际 ${imgBefore}`);
await interactAt('PlazaScene', 184, 120); // ribbon_1
const imgAfter = await ev(() => {
const s = window.__game.scene.getScene('PlazaScene');
return s.children.list.filter(o => o.type === 'Image' && o.texture.key === 'tiles16' && [29,30,31].includes(o.frame.name)).map(o => ({ x: o.x, y: o.y, f: o.frame.name }));
});
check('挂上后出现彩带图像(帧29红@184,120)', imgAfter.some(o => o.x === 184 && o.y === 120 && o.f === 29), JSON.stringify(imgAfter));
console.log('\n=== 4. 阿福地点指引 ===');
const t14 = await interactAt('PlazaScene', 248, 136); // afu @D6
check('D6 阿福 T14 指引', t14.some(t => t.includes('广场中央站好')), JSON.stringify(t14).slice(0, 70));
await ev(() => window.__director.setDay(7));
await startScene('PlazaScene');
const t15 = await interactAt('PlazaScene', 248, 136); // afu @D7
check('D7 阿福 T15 指引', t15.some(t => t.includes('广场北边')), JSON.stringify(t15).slice(0, 70));
console.log('\n=== 5. 门淡出庆典竞态 ===');
await ev(() => { const d = window.__director; d.forceIdle(); d.setDay(3); d.setHappiness(20); });
await startScene('PlazaScene');
// 阈值 ~300ms 后到达:落在门淡出 250ms 窗口内
await ev(() => { const d = window.__director; d.setHappiness(20); d.lowHappyMs = 119700; });
// 广场→小屋门 (72,88):先到 30px 外武装,再踏上门
await setPlayer('PlazaScene', 72 + 30, 88);
await setPlayer('PlazaScene', 72, 88);
let arrived = false;
for (let i = 0; i < 20; i++) {
await sleep(300);
if (await dlgOpen()) await tap('KeyE');
if (await ev(() => window.__game.scene.isActive('HouseScene'))) { arrived = true; break; }
}
check('过门到达小屋', arrived);
check('celebrationActive 未卡死', !(await dget('celebrationActive')));
await drain();
// 新场景可交互:日历 (56,40)+(64,26)=(120,66)
const cal = await interactAt('HouseScene', 120, 66);
check('新场景交互正常(日历)', cal.length > 0, JSON.stringify(cal).slice(0, 50));
console.log(`\n${fails === 0 ? '全部通过 ✓' : `失败 ${fails} 项 ✗`}`);
await browser.close();
process.exit(fails === 0 ? 0 : 1);