121 lines
7.0 KiB
JavaScript
121 lines
7.0 KiB
JavaScript
// dbg20:审计修复验证
|
||
// 1. 广场地图:邮筒瓦片(gid47)在 (11,10);4 个挂彩带点改为路灯(gid 29),无预置彩带
|
||
// 2. C3 邮筒交互可达(D2)
|
||
// 3. T13 挂彩带前无彩带图像,挂上后出现(帧 29/30/31)
|
||
// 4. 阿福指引:D6→T14_HINT,D7→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);
|