feat(遊戲化): 新增大廳、附身、時間流、後日談進度等遊戲化系統功能

新增角色大廳(lobby)邀約與通話、附身(possession)、時鐘(clock)抽象、
時間流與睡眠負債、後日談進度、行為選擇與旁觀反應等模組,並補齊對應冒煙
測試(R~V)與單元測試;同步調整 web/mobile 對應頁面與元件。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 09:10:26 +08:00
co-authored by Claude Sonnet 5
parent 127a59dafd
commit 1bea06e3cd
195 changed files with 17219 additions and 738 deletions
+10 -9
View File
@@ -26,7 +26,9 @@ async function get(path) {
}
export default async function smokeH() {
const sessionId = "smoke-h-" + Date.now();
// T38:1:1 聊天併入 SOLO 房,roomId 取代舊版由呼叫端自行產生的 sessionId。
const room = await post("/room/solo", { userId: USER_ID, characterId: CHARACTER_ID });
const sessionId = room.id; // 沿用既有 /memory/:characterId/sessions/:sessionId/messages 端點(sessionId 本就是不透明鍵)
await prisma.emotionState.update({
where: { characterId: CHARACTER_ID },
@@ -36,9 +38,8 @@ export default async function smokeH() {
const intimacyBefore = before.relationship.intimacy;
// H-1 對話 API:一輪完整對話回傳回應、情緒狀態與親密度變化
const result = await post(`/chat/${CHARACTER_ID}`, {
const result = await post(`/room/${room.id}/message`, {
userId: USER_ID,
sessionId,
text: "謝謝你,我今天好開心!",
});
if (!result.text || typeof result.text !== "string") {
@@ -51,21 +52,21 @@ export default async function smokeH() {
throw new Error("對話後親密度應有變化(累積日常互動)");
}
// H-1 session 生命週期:POST /session/:id/end 觸發睡眠固化,工作記憶應被清空
// H-1 房間生命週期:POST /room/:roomId/end 觸發睡眠固化,工作記憶應被清空
const contextBefore = await get(`/memory/${CHARACTER_ID}/sessions/${sessionId}/messages`);
if (contextBefore.context.length === 0) {
throw new Error("對話後工作記憶應保留本輪內容,session 結束前不應為空");
throw new Error("對話後工作記憶應保留本輪內容,房間結束前不應為空");
}
await post(`/session/${sessionId}/end`, { characterId: CHARACTER_ID });
await post(`/room/${room.id}/end`, {});
const contextAfter = await get(`/memory/${CHARACTER_ID}/sessions/${sessionId}/messages`);
if (contextAfter.context.length !== 0) {
throw new Error("session 結束後應觸發睡眠固化並清空工作記憶");
throw new Error("房間結束後應觸發睡眠固化並清空工作記憶");
}
// H-2~H-5:對話頁面應包含情緒晶片、親密度顯示、心跳波形、立繪佔位區塊的結構標記
const pageRes = await fetch(`http://localhost:${WEB_PORT}/chat`);
const pageRes = await fetch(`http://localhost:${WEB_PORT}/room/solo`);
if (!pageRes.ok) {
throw new Error(`GET /chat 回傳 ${pageRes.status}`);
throw new Error(`GET /room/solo 回傳 ${pageRes.status}`);
}
const html = await pageRes.text();
if (!html.includes("親密度")) {