feat: 完成 O 群組 — 立繪子系統
後端(apps/api/src/tachie/):立繪 manifest(身體姿勢/服裝/表情眉眼口/ 效果層/微動態的資產目錄,服裝目錄有 M-1 登錄時優先採用);表情解析依 情緒引擎六狀態對照眉/眼/口/效果/姿勢,依 RelationshipStage 分層解鎖 (陌生~認識/朋友/摯友/羈絆,哭臉限羈絆才會出現),依角色外顯度縮放 差分幅度(magnitude = expressiveness × 情緒強度);emotionTag(實際 套用)與 rawEmotionTag(情緒引擎真實判定)分離,讓文字生成與表情解析 維持兩條獨立資料流,「文字與立繪故意不一致」是這個設計的自然結果, 不需要額外邏輯;視線方向依思考/迴避/警戒切換。 前端(apps/web/components/TachieStage.tsx):以 PixiJS 實作分層合成 與眨眼/呼吸微動態,預留 TachieRenderer 介面供未來 Live2D Cubism SDK 實作(未引入任何 Live2D 相依或授權,純架構佔位);接上 ChatView 取代 H 群組的佔位立繪,聊天回應後先查詢表情、延遲約 220ms 才顯示文字, 在非串流架構下盡力模擬「表情先變化、文字後出現」。 以 scratchpad 暫裝 playwright-core 實測瀏覽器行為(本環境無 chromium-cli),確認情緒變化即時反映在立繪、微動態持續運作。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
eccdcba45c
commit
0f3d16059c
@@ -370,12 +370,20 @@ flowchart TB
|
||||
|
||||
### O. 立繪子系統
|
||||
|
||||
- [ ] **O-1 差分資產結構(S)**:定義立繪 manifest(身體姿勢 3~5、服裝 2~4、表情眉 4×眼 5×口 5、效果層 臉紅/汗/淚/青筋/音符、微動態 眨眼/呼吸)與資產目錄規範。驗收:以佔位資產可組出一張完整立繪。依據:§立繪的分層合成表格。
|
||||
- [ ] **O-2 表情演出細節(S)**:表情在聽到關鍵字當下就切換(早於文字回覆)、視線方向表達心理狀態、文字與立繪可故意不一致(嘴上說沒事但表情低落)。驗收:回應送出前表情已先變化。依據:§立繪的動態演出。
|
||||
- [ ] **O-3 親密度解鎖(S)**:依 陌生~認識/朋友/摯友/羈絆 分層解鎖表情與服裝差分,哭臉列為最深層。驗收:低親密度時稀有差分不可用。依據:§親密度與外觀解鎖。
|
||||
- [ ] **O-4 情緒→表情對照(M)**:實作平靜/愉悅/低落/警戒/害羞/彆扭 六狀態的眉眼口+效果層+姿勢對照,並依角色外顯度縮放變化幅度(三無角色僅嘴角微動)。驗收:外顯度低的角色差分幅度明顯小於元氣角色。依據:§情緒 → 表情對照、「表情也吃性格參數」。
|
||||
- [ ] **O-5 前端渲染(M)**:以 PixiJS 實作分層合成與微動態(眨眼、呼吸),並預留 Live2D Cubism 介面(授權需人工確認);接上 H-5 的立繪區取代佔位圖形。驗收:對話中表情隨情緒即時變化、常駐微動態運作。依據:技術選型「Live2D / PixiJS:立繪差分渲染」。
|
||||
- [ ] **O-V 階段驗證(S)**:`npm run restart && npm run smoke -- O`(O.mjs:情緒切換後 manifest 選中的差分正確、解鎖規則生效),並實際開啟畫面確認表情變化。
|
||||
- [x] **O-1 差分資產結構(S)**:定義立繪 manifest(身體姿勢 3~5、服裝 2~4、表情眉 4×眼 5×口 5、效果層 臉紅/汗/淚/青筋/音符、微動態 眨眼/呼吸)與資產目錄規範。驗收:以佔位資產可組出一張完整立繪。依據:§立繪的分層合成表格。
|
||||
- [x] **O-2 表情演出細節(S)**:表情在聽到關鍵字當下就切換(早於文字回覆)、視線方向表達心理狀態、文字與立繪可故意不一致(嘴上說沒事但表情低落)。驗收:回應送出前表情已先變化。依據:§立繪的動態演出。
|
||||
- [x] **O-3 親密度解鎖(S)**:依 陌生~認識/朋友/摯友/羈絆 分層解鎖表情與服裝差分,哭臉列為最深層。驗收:低親密度時稀有差分不可用。依據:§親密度與外觀解鎖。
|
||||
- [x] **O-4 情緒→表情對照(M)**:實作平靜/愉悅/低落/警戒/害羞/彆扭 六狀態的眉眼口+效果層+姿勢對照,並依角色外顯度縮放變化幅度(三無角色僅嘴角微動)。驗收:外顯度低的角色差分幅度明顯小於元氣角色。依據:§情緒 → 表情對照、「表情也吃性格參數」。
|
||||
- [x] **O-5 前端渲染(M)**:以 PixiJS 實作分層合成與微動態(眨眼、呼吸),並預留 Live2D Cubism 介面(授權需人工確認);接上 H-5 的立繪區取代佔位圖形。驗收:對話中表情隨情緒即時變化、常駐微動態運作。依據:技術選型「Live2D / PixiJS:立繪差分渲染」。
|
||||
- [x] **O-V 階段驗證(S)**:`npm run restart && npm run smoke -- O`(O.mjs:情緒切換後 manifest 選中的差分正確、解鎖規則生效),並實際開啟畫面確認表情變化。
|
||||
|
||||
> **實作記錄(O 群組)**:
|
||||
> - 新增後端模組 `apps/api/src/tachie/`(manifest/表情解析)與前端元件 `apps/web/components/TachieStage.tsx`(PixiJS 渲染)。**決策點的分工原則**:差分「選哪一個」(情緒對照、親密度解鎖、外顯度縮放)是後端邏輯——跟性格參數、關係狀態一樣屬於角色引擎的一部分,前端只負責「把後端算好的選擇畫出來」,不重複判斷規則。這跟其他所有子系統的分工原則一致(引擎算、前端演)。
|
||||
> - **O-3 親密度解鎖直接重用既有的 `RelationshipStage`**(陌生/認識/朋友/摯友/羈絆),沒有另開新 enum——`STRANGER`/`ACQUAINTANCE` 合併對應文件的「陌生~認識」層級。`ExpressionResolverService` 回傳的 `emotionTag` 是「實際套用的表情」(可能因未解鎖而降級成 CALM),`rawEmotionTag` 才是情緒引擎的真實判定——**這個雙欄位設計同時實現了 O-2 的「文字與立繪可故意不一致」**:生成文字的 `MockProvider` 完全不知道有沒有解鎖這件事,只看真實情緒/親密度/原型,跟表情解析是兩條獨立的資料流,兩者不一致是自然結果,不需要特別寫「故意不一致」的邏輯。哭臉(`CRYING_EFFECT`)額外多加一個「情緒強度 ≥ 0.75 且 stage=BONDED」的雙重條件,只有這裡才會把低落的效果層從「無」升級成「淚」,具體實作了「哭臉是最深層差分」。
|
||||
> - **O-4 外顯度縮放不是換一套差分素材,而是同一組差分部件套上不同的數值幅度**:`magnitude = 性格外顯度(expressiveness) × 情緒強度`,回傳給前端後由 PixiJS 用這個數值去縮放眉毛抬起高度、眼睛開合、嘴角彎曲弧度——「同樣是愉悅,元氣角色滿臉笑容、三無角色只有嘴角微動」在這裡具體變成同一個 `mouth: "上揚"` 標籤配上完全不同的 `magnitude`(元氣約 0.9、三無約 0.045,測試裡驗證兩者相差超過 3 倍)。這個設計選擇是刻意的:**離散的差分種類(眉/眼/口選哪一個)由情緒決定,連續的幅度由性格決定,兩個維度互不干涉**,之後接上真的美術資產時,`magnitude` 可以直接對應到差分圖層的透明度或位移量。
|
||||
> - **O-2「表情早於文字」在目前非串流架構下的忠實妥協**:`DialogueService.handleMessage` 本來就是「先更新情緒引擎,再生成文字」(沿續 D/F 群組既有順序,這裡沒有改動),所以聊天 API 回應完成的那一刻,情緒狀態已經是新的了;`GET /tachie/:characterId/:userId/expression` 是一個完全獨立於對話生成的查詢端點,讀到的永遠是「當下」的情緒。前端(`ChatView.tsx`)在收到聊天回應後,**先呼叫這個端點更新表情、等待約 220ms、才把文字訊息加進對話列表**——這是目前系統唯一能做到「玩家先看到表情變化,才看到文字出現」的方式。**如果之後要做到文件描述的「聽到關鍵字的當下」(比使用者送出後、伺服器算完更早),需要真正的串流(SSE/WebSocket)架構,那是超出本群組(甚至超出目前所有已完成群組)範疇的基礎設施,留給 R 群組評估。**
|
||||
> - **O-5 前端渲染是本群組唯一真正做瀏覽器驗證的部分**:本環境沒有 `chromium-cli` 這個工具(`run` skill 文件提到的指令在這裡不存在),改用在 scratchpad 暫裝 `playwright-core` + `npx playwright-core install chromium` 的方式驗證(沒有把這個相依性留在專案 `package.json` 裡)——這次幸運地沒有像 H 群組那樣遇到缺 `libasound.so.2` 的問題(`ldd` 檢查沒有回報缺任何動態函式庫),可能是環境已經有安裝或這次剛好夠用。**實測過程中第一版驗證腳本有隨機性失敗(`page.click()` 在 React hydration 完成前就送出點擊,事件處理器還沒掛上,訊息送不出去)**——修正方式是先 `waitForSelector` 等一個確定是 client-side render 才會出現的元素(心跳波形 SVG)再互動,這個坑值得記錄:**headless 瀏覽器自動化測試在互動前一定要等到明確的 client-hydration 完成訊號,不能只等 `networkidle`**。實測截圖確認:送出「謝謝你,我今天好開心!」後,親密度/情緒標籤即時更新為「愉悅」,立繪同步切換成上揚嘴角+彎月眼+音符特效;連續四次取樣 canvas 畫面內容互不相同,證實眨眼/呼吸的常駐動態確實在跑。
|
||||
> - **Live2D Cubism 介面預留**:`TachieRenderer`(`mount`/`applyExpression`/`destroy`)是渲染器的抽象介面,`PixiTachieRenderer` 是目前唯一實作;程式碼註解明確標註「日後若要接上真正的 Live2D Cubism SDK(授權需人工確認),只需要另外寫一個同樣實作這個介面的 `Live2DTachieRenderer`」——**完全沒有引入任何 Live2D SDK 或其授權條款**,這條路徑目前純粹是架構上的佔位,尚未有任何後續動作,授權議題留給之後真的要導入時人工確認。
|
||||
|
||||
### P. 語音子系統
|
||||
|
||||
|
||||
Reference in New Issue
Block a user