// dbg19:修复后全流程真人验证 // 1. D1 与圆圆对话取面包 → 送大宝/咪咪/龟爷爷 → T1 完成(不经隐形柜台) // 2. D2 与咪咪对话清点货架 → T5 完成(不经隐形货架区) // 3. 睡觉竞态:低幸福+计时逼近阈值时睡觉 → 不卡死,D2 可正常交互 // 4. 庆典未被误抑制:低幸福计时到点不睡觉 → 庆典正常触发 // 5. T13 彩带 ×4 新坐标可触发 import puppeteer from 'puppeteer-core'; import { spawn } from 'node:child_process'; import net from 'node:net'; 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 talkNpc(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}]`)); let fails = 0; const check = (name, ok, extra = '') => { console.log(`${ok ? 'PASS' : 'FAIL'} ${name}${extra ? ' ' + extra : ''}`); if (!ok) fails++; }; await sleep(500); console.log('\n=== 1. D1 与圆圆对话取面包 → 送三人 ==='); // 小地图居中偏移:bakery/shop ox=40,oy=10;house ox=64,oy=26;plaza/lake 无偏移 await startScene('BakeryScene'); const t1 = await talkNpc('BakeryScene', 128, 50); // yuanyuan (88,40)+(40,10) check('对话圆圆拿到面包', await dget("taskProgress['breadTaken']") === 1, JSON.stringify(t1).slice(0, 80)); await startScene('PlazaScene'); await talkNpc('PlazaScene', 56, 136); // dabao await startScene('ShopScene'); await talkNpc('ShopScene', 80, 98); // mimi(D1:只是送面包对象,非 T5) await startScene('LakeScene'); await talkNpc('LakeScene', 200, 136); // guiyeye const done1 = await darr('tasksDone'); check('T1 送面包完成', done1.includes('T1'), done1.join(',')); check('T3 问好完成', done1.includes('T3'), done1.join(',')); console.log('\n=== 2. D2 与咪咪对话清点货架 ==='); await ev(() => window.__director.setDay(2)); await startScene('ShopScene'); await talkNpc('ShopScene', 80, 98); // mimi const done2 = await darr('tasksDone'); check('T5 清点完成', done2.includes('T5'), done2.join(',')); check('C5 线索获得', (await darr('clues')).includes('C5')); console.log('\n=== 3. 睡觉竞态:低幸福计时逼近阈值时睡觉 ==='); await ev(() => { const d = window.__director; d.forceIdle(); d.setDay(1); d.setHappiness(20); }); await startScene('HouseScene'); // lowHappyMs=119700:阈值将在 ~300ms 后(即床淡出 400ms 中途)到达,正中竞态窗口 await ev(() => { const d = window.__director; d.setHappiness(20); d.lowHappyMs = 119700; d.dayElapsedMs = 300000; }); await setPlayer('HouseScene', 88, 90); // 床 (24,56)+(64,26)=(88,82),站 y+8 await tap('KeyE'); let reachedD2 = false, stuckCeleb = false; for (let i = 0; i < 30; i++) { await sleep(500); if (await dlgOpen()) await tap('KeyE'); const day = await dget('day'); if (day === 2) { reachedD2 = true; break; } } check('睡觉正常过日到 D2(无庆典抢占)', reachedD2); stuckCeleb = await dget('celebrationActive'); check('D2 celebrationActive=false(E 可用)', !stuckCeleb); // D2 交互验证:与日历对话 await startScene('HouseScene'); const cal = await talkNpc('HouseScene', 120, 66); // calendar (56,40)+(64,26) check('D2 交互正常(日历对话)', cal.length > 0, JSON.stringify(cal).slice(0, 60)); console.log('\n=== 4. 庆典未被误抑制 ==='); await ev(() => { const d = window.__director; d.forceIdle(); d.setHappiness(20); d.lowHappyMs = 119900; }); await sleep(800); check('低幸福计时到点→庆典正常触发', await dget('celebrationActive')); await ev(() => window.__director.forceIdle()); console.log('\n=== 5. T13 彩带 ×4 新坐标 ==='); await ev(() => window.__director.setDay(6)); await startScene('PlazaScene'); const ribbons = [[184, 120], [296, 120], [184, 216], [296, 216]]; for (const [x, y] of ribbons) { await setPlayer('PlazaScene', x + 40, y + 40); await setPlayer('PlazaScene', x, y + 8); await tap('KeyE'); await sleep(300); await drain(); } check('T13 彩带完成', (await darr('tasksDone')).includes('T13'), (await darr('tasksDone')).join(',')); console.log(`\n${fails === 0 ? '全部通过 ✓' : `失败 ${fails} 项 ✗`}`); await browser.close(); process.exit(fails === 0 ? 0 : 1);