Honey Village: six model builds + hub frontend, Dockerized for Dokploy

This commit is contained in:
2026-07-15 10:50:12 -04:00
commit 67253bded5
523 changed files with 58879 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
// 机制专项:悲伤行为 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);