feat(遊戲化): 新增大廳、附身、時間流、後日談進度等遊戲化系統功能
新增角色大廳(lobby)邀約與通話、附身(possession)、時鐘(clock)抽象、 時間流與睡眠負債、後日談進度、行為選擇與旁觀反應等模組,並補齊對應冒煙 測試(R~V)與單元測試;同步調整 web/mobile 對應頁面與元件。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+13
-2
@@ -13,10 +13,17 @@ async function listGroupFiles() {
|
||||
return files.filter((f) => f.endsWith(".mjs") && f !== "run.mjs").sort();
|
||||
}
|
||||
|
||||
async function runOne(file) {
|
||||
// 連續跑多個群組時,偶爾會在某個群組剛結束、下一個群組第一個請求時遇到暫時性的連線錯誤
|
||||
// (observed:長時間跑(如 T 群組的重試/全域暫停測試)結束後,緊接著的下一個群組第一個 fetch
|
||||
// 偶發 "fetch failed"/連線被對方關閉——與程式邏輯無關,是連線在群組交接瞬間的暫時性狀態)。
|
||||
// 每個群組檔案的既有慣例是「一進來就先 cleanup() 清掉自己上一輪可能留下的資料」,因此整個
|
||||
// default() 重跑一次是安全、可重複的,不會因為半途失敗留下的殘留資料造成誤判。
|
||||
const TRANSIENT_ERROR_PATTERN = /fetch failed|ECONNRESET|ECONNREFUSED|socket|other side closed|network/i;
|
||||
|
||||
async function runOne(file, attempt = 1) {
|
||||
const group = file.replace(/\.mjs$/, "");
|
||||
const modUrl = pathToFileURL(path.join(SMOKE_DIR, file)).href;
|
||||
log("冒煙測試", "INF", `開始執行 ${group} 群組冒煙測試`);
|
||||
log("冒煙測試", "INF", `開始執行 ${group} 群組冒煙測試${attempt > 1 ? `(第 ${attempt} 次嘗試)` : ""}`);
|
||||
try {
|
||||
const mod = await import(modUrl);
|
||||
if (typeof mod.default !== "function") {
|
||||
@@ -27,6 +34,10 @@ async function runOne(file) {
|
||||
return true;
|
||||
} catch (err) {
|
||||
log("冒煙測試", "ERR", `${group} 群組冒煙測試失敗:${err.message}`);
|
||||
if (attempt === 1 && TRANSIENT_ERROR_PATTERN.test(err.message)) {
|
||||
log("冒煙測試", "WRN", `${group} 疑似暫時性連線錯誤,重試一次`);
|
||||
return runOne(file, attempt + 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user