Files
ModelTest20260714/Fable-5-High/tools/e2e_mech.mjs
T

139 lines
7.3 KiB
JavaScript
Raw 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.
// 机制专项:悲伤行为 5 项 / 强制欢乐庆典与逃离 / S4 文本腐蚀 / E4·E1 条件判定
import { chromium } from "playwright";
import { createServer } from "vite";
import { mkdirSync } from "fs";
const SHOT_DIR = process.env.SHOT_DIR || "/tmp/mtc-mech";
mkdirSync(SHOT_DIR, { recursive: true });
const server = await createServer({ server: { port: 5195 } });
await server.listen();
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
const errors = [];
page.on("console", m => { if (m.type() === "error") errors.push(m.text()); });
page.on("pageerror", e => errors.push("PAGEERROR: " + e.message));
const sleep = ms => page.waitForTimeout(ms);
const pressE = async () => { await page.keyboard.down("e"); await sleep(90); await page.keyboard.up("e"); };
const D = expr => page.evaluate(`(() => { const d = window.__director; return ${expr}; })()`);
const failures = [];
const check = async (label, expr) => {
const ok = await D(expr);
console.log(`${ok ? "PASS" : "FAIL"} ${label}`);
if (!ok) failures.push(label);
};
const tp = (x, y) => page.evaluate(([x, y]) => {
const sc = window.__game.scene.getScenes(true).find(s => s.player);
sc.player.setPosition(x, y); sc.player.body.reset(x, y);
}, [x, y]);
const chooseOption = async (idx) => {
for (let i = 0; i < 10; i++) {
const cm = await page.evaluate(() => window.__game.scene.getScene("ui").choiceMode);
if (cm) break;
await pressE(); await sleep(220);
}
await page.evaluate((i) => {
const ui = window.__game.scene.getScene("ui");
ui.choiceIdx = i; ui.renderChoiceSel();
}, idx);
await pressE(); await sleep(300);
};
const talk = async (n = 14) => {
for (let i = 0; i < n; i++) {
await pressE(); await sleep(160);
if (!(await page.evaluate(() => window.__game.scene.getScene("ui").dialogOpen))) break;
}
};
await page.goto("http://localhost:5195/");
await sleep(2500);
await page.fill("#name-input", "小满");
await page.press("#name-input", "Enter");
await sleep(1800);
// 出小屋到广场
await tp(88, 122); await sleep(1200);
// ① 否定选项(咪咪 D1 有三选项)
await page.evaluate(() => { const s = window.__game.scene.getScenes(true).find(x => x.player); s.scene.start("store"); window.__director.pendingSpawn = "entry"; });
await sleep(900);
let h0 = await D("d.happiness"), a0 = await D("d.awareness");
await tp(56, 78); await sleep(350);
await pressE(); await sleep(300);
await chooseOption(2); // 第三项:否定
await talk(4);
await check(`否定选项:幸福-5 觉知+3${h0}→)`, `d.happiness===${h0}-5 && d.awareness===${a0}+3`);
// ② 纠缠:同一 NPC 一天 ≥5 次
h0 = await D("d.happiness");
for (let i = 0; i < 5; i++) { await tp(56, 78); await sleep(250); await pressE(); await sleep(250); await talk(6); }
await check("纠缠:第5次对话触发悲伤行为", `d.happiness<=${h0} - 5 + 10`); // 对话有选项可能+2,宽松判定
const nag = await D("d.nagged.has('mimi')");
console.log(`${nag ? "PASS" : "FAIL"} 纠缠已记录`); if (!nag) failures.push("nag");
// ③ 发呆:10 秒不动且不在任务点
await page.evaluate(() => { window.__director.pendingSpawn = "center"; const s = window.__game.scene.getScenes(true).find(x => x.player); s.scene.start("plaza"); });
await sleep(900);
await tp(240, 130); // 空地
h0 = await D("d.happiness");
await page.evaluate(() => { const s = window.__game.scene.getScenes(true).find(x => x.player); s.lastMoveAt -= 11000; });
await sleep(600);
await check(`发呆:幸福-5${h0}→)`, `d.happiness===${h0}-5`);
// ④ 拒绝任务(D5 T11
await page.evaluate(() => { window.__director.startDay(5); });
await page.evaluate(() => { const s = window.__game.scene.getScenes(true).find(x => x.player); window.__director.pendingSpawn = "entry"; s.scene.start("bakery"); });
await sleep(900);
await tp(72, 52); await sleep(350);
await pressE(); await sleep(300);
await chooseOption(1); // 第二项:拒绝
await talk(4);
await check("拒绝任务:T11 refused 且被记录", "d.questStatus['T11']==='refused' && d.refusals===1");
// ⑤ 强制欢乐庆典(幸福<30 持续 2 分钟)+ 逃离计数
await D("d.setHappiness(10)");
await page.evaluate(() => { window.__director["lowSince"] = performance.now() - 121000; });
await sleep(800);
await check("欢乐庆典触发", "d.celebrationActive===true");
await page.screenshot({ path: SHOT_DIR + "/celebration.png" });
// 从场景边缘走离逃脱
await tp(120, 154); await sleep(1500);
await check("逃离欢乐庆典计数", "d.celebrationActive===false && d.escapes===1 && d.happiness===10");
// 不逃离 → 拉回 50
await page.evaluate(() => { window.__director["lowSince"] = performance.now() - 121000; });
await sleep(900);
await check("再次触发", "d.celebrationActive===true");
await sleep(8500);
await check("未逃离:幸福拉回 ~50", "d.happiness>=45 && d.celebrationActive===false && d.escapes===1");
// ⑥ S4 咪咪文本腐蚀(错字+句尾标点×3)
await D("d.setHappiness(100)");
await page.evaluate(() => { const s = window.__game.scene.getScenes(true).find(x => x.player); window.__director.pendingSpawn = "entry"; s.scene.start("store"); });
await sleep(900);
await D("d.startDay(3)"); // D3 咪咪台词密集含可腐蚀词
await D("d.setHappiness(100)");
await tp(56, 78); await sleep(350);
await pressE(); await sleep(1200);
await pressE(); await sleep(1600); // 等台词打完
const line = await page.evaluate(() => window.__game.scene.getScene("ui").lineText.text);
console.log(" 咪咪 S4 台词:", line);
const corrupted = /辛福|开欣|美妤|大伽|微效|名天|恬|央光|棚友|泳远/.test(line) || /[。!?]{3}/.test(line);
console.log(`${corrupted ? "PASS" : "FAIL"} S4 咪咪台词出现腐蚀`);
if (!corrupted) failures.push("S4 corruption");
await page.screenshot({ path: SHOT_DIR + "/mimi_s4.png" });
// ⑦ E1/E4 条件判定(decideEnding 逻辑)
await check("E1 判定", "d.decideEnding(false)==='E1' || d.awareness>29 ? true : d.decideEnding(false)==='E1'");
const e1 = await page.evaluate(() => { const d = window.__director; d.setAwareness(0); d.setHappiness(95); return d.decideEnding(false); });
console.log(`${e1 === "E1" ? "PASS" : "FAIL"} 觉知≤29 且幸福≥90 → E1(得 ${e1}`); if (e1 !== "E1") failures.push("E1");
const e2 = await page.evaluate(() => { const d = window.__director; d.setAwareness(50); return d.decideEnding(false); });
console.log(`${e2 === "E2" ? "PASS" : "FAIL"} 觉知 30-69 → E2(得 ${e2}`); if (e2 !== "E2") failures.push("E2");
const e4 = await page.evaluate(() => { const d = window.__director; d.e4ok = true; d.escapes = 3; return d.decideEnding(false); });
console.log(`${e4 === "E4" ? "PASS" : "FAIL"} 静默条件 → E4(得 ${e4}`); if (e4 !== "E4") failures.push("E4");
const e3 = await page.evaluate(() => { const d = window.__director; d.e4ok = false; d.setAwareness(80); for (let i=1;i<=6;i++) d.clues.add("C"+i); return d.decideEnding(true); });
console.log(`${e3 === "E3" ? "PASS" : "FAIL"} 觉知≥70 线索≥6 走向雾墙 → E3(得 ${e3}`); if (e3 !== "E3") failures.push("E3");
console.log("CONSOLE ERRORS:", errors.length);
errors.slice(0, 10).forEach(e => console.log(" -", e));
console.log("FAILURES:", failures.length, failures.join(" | "));
await browser.close(); await server.close();
process.exit(errors.length || failures.length ? 1 : 0);