feat: 完成 M 群組 — 既有作品考據與輕小說章節管線

共用場景資料庫(Scene/ScenePresence/SceneLine/SceneFact/SceneInteraction,
角色中立客觀記錄)與投影(ProjectionService 寫入 EpisodicMemory/
SemanticMemory/PersonalityTraitSnapshot/CharacterRelationshipEventLog,
逐一可追溯到來源場景)完全分層。歸屬信心分數(明示標記決定性、語言指紋
從已歸屬語料統計、場景名冊硬約束、對話輪替)與隔離區/指紋冷啟動(低信心
或樣本不足一律隔離,指紋精煉後可重新嘗試釋放)。多角色投影與修正(改一處
場景資料庫、reprojectScene 同步更新所有受影響角色,使用者互動記憶因為
從不帶 sourceSceneId 而結構性地不受影響,發現得晚的歸屬錯誤額外記一筆
CORRECTION 事件)。作品名冊建置門檻(達標自動建置,門檻檢查排在投影完成
之後才做,避免用到投影前的舊統計)。錨定點知識邊界(雙向:前進載入、
後退遺忘)與矛盾偵測(同優先序來源待人工確認,跨優先序可依小說原文>
官方設定集>動畫改編自動裁決)。插圖索引與衍生服裝目錄/姿勢語彙表。

刻意簡化:章節→場景切分與互動正負權重皆採結構化輸入(不做自動 NLP
場景邊界偵測與情感分析),比照 F-7 MockProvider 的做法——機制先做對,
之後有真實 NLP/LLM 能力再替換輸入來源。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-08-13 16:58:04 +08:00
co-authored by Claude Sonnet 5
parent cd948f0026
commit c66799ee7c
21 changed files with 1747 additions and 20 deletions
+245 -8
View File
@@ -24,6 +24,7 @@ enum MemorySource {
INTERACTION // 互動
OFFLINE_GENERATED // 離線生成
SOURCE_EXTRACTION // 原作萃取
CORRECTION // M-10 回溯修正:發現記錯了,以「想起來其實不是這樣」的新事件消化,不竄改既有的 INTERACTION 記憶
}
// 情緒標籤類型:對應情緒子系統六種狀態(見 D 群組)
@@ -124,15 +125,19 @@ model User {
}
model Work {
id String @id @default(cuid())
title String
volumeProgress String? // 卷數進度
worldview String? // 世界觀
progressAnchor String? // 作品進度錨定點(M-9/N 群組使用;描述作品實際描寫過的時期)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id String @id @default(cuid())
title String
volumeProgress String? // 卷數進度
worldview String? // 世界觀
progressAnchor String? // 作品進度錨定點(M-9/N 群組使用;描述作品實際描寫過的時期,人類可讀)
anchorStoryOrder Int @default(0) // M-9 錨定點的數值刀口:對應 Scene.storyOrder,之前是親身經歷,之後不存在
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
characters Character[]
characters Character[]
scenes Scene[]
illustrations Illustration[]
contradictions SceneContradiction[]
@@map("works")
}
@@ -174,6 +179,14 @@ model Character {
ageEvidence CharacterAgeEvidence[]
romanceEventLogs RomanceEventLog[]
adultContentAttempts AdultContentAttemptLog[]
scenePresences ScenePresence[]
spokenLines SceneLine[]
costumeCatalogEntries CostumeCatalogEntry[]
poseVocabularyEntries PoseVocabularyEntry[]
languageCorpusEntries LanguageCorpusEntry[]
personalityTraitSnapshots PersonalityTraitSnapshot[]
relationshipEventLogsFrom CharacterRelationshipEventLog[] @relation("EventLogFrom")
relationshipEventLogsTo CharacterRelationshipEventLog[] @relation("EventLogTo")
@@map("characters")
}
@@ -325,6 +338,10 @@ model EpisodicMemory {
relatedUserId String? // 這段記憶與哪位使用者相關(E-5 關係加權檢索用)
relatedUser User? @relation(fields: [relatedUserId], references: [id], onDelete: SetNull)
isPrivate Boolean @default(false) // K-3 隱私邊界:一對一聊過的私密內容,群聊中不主動洩漏(口風不緊原型除外)
// M-7/M-10 可追溯性:這筆記憶是否由某個場景投影而來。只有這裡非 null 的記錄會被「重新投影」刪除重建;
// 使用者互動記憶(source=INTERACTION)永遠不會設這個欄位,因此重新投影永遠不會動到使用者互動記錄。
sourceSceneId String?
sourceScene Scene? @relation(fields: [sourceSceneId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
@@map("episodic_memories")
@@ -336,6 +353,8 @@ model SemanticMemory {
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
fact String // 去情境化事實
about String // 對象(關於誰/什麼)
sourceSceneId String? // M-4/M-7 可追溯性:由哪個場景萃取而來(新登場設定)
sourceScene Scene? @relation(fields: [sourceSceneId], references: [id], onDelete: SetNull)
createdAt DateTime @default(now())
@@map("semantic_memories")
@@ -457,3 +476,221 @@ model ProactiveCareLog {
@@map("proactive_care_logs")
}
// M-1 插圖類型:對立繪的用途不同——封面定基礎氣質、彩頁給服裝差分、黑白插圖給表情/姿勢差分。
enum IllustrationType {
COVER
COLOR_PAGE
BW_ILLUSTRATION
}
// M-1 插圖索引:不儲存受版權保護的原圖本身,只登錄「畫面描述」供服裝目錄/姿勢語彙/表情差分參考使用。
model Illustration {
id String @id @default(cuid())
workId String
work Work @relation(fields: [workId], references: [id], onDelete: Cascade)
volume Int
page Int?
type IllustrationType
sceneId String? // 對應場景(插圖與文本的交叉索引)
scene Scene? @relation(fields: [sceneId], references: [id], onDelete: SetNull)
description String // 畫面描述:表情/姿勢/服裝等文字記錄
createdAt DateTime @default(now())
costumeCatalogEntries CostumeCatalogEntry[]
poseVocabularyEntries PoseVocabularyEntry[]
@@map("illustrations")
}
// M-1 服裝目錄:逐卷登錄角色出現過的服裝,立繪服裝層只能從這個目錄取用,不可自創。
model CostumeCatalogEntry {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
illustrationId String?
illustration Illustration? @relation(fields: [illustrationId], references: [id], onDelete: SetNull)
label String // 例如「制服」「便服」「禮服」
createdAt DateTime @default(now())
@@map("costume_catalog_entries")
}
// M-1 姿勢語彙:繪師畫該角色時慣用的身體語言(總是抱著書、習慣性歪頭),納入立繪姿勢差分的官方認證來源。
model PoseVocabularyEntry {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
illustrationId String?
illustration Illustration? @relation(fields: [illustrationId], references: [id], onDelete: SetNull)
label String // 例如「總是抱著書」「習慣性歪頭」
createdAt DateTime @default(now())
@@map("pose_vocabulary_entries")
}
// M-4 場景內逐行的類型:對應五類萃取產物裡「事件」以外,需要逐行分類才能各自導向正確目的地的部分。
enum SceneLineType {
DIALOGUE // 台詞 → 語言風格語料
ACTION // 動作描寫 → 事件的一部分
MONOLOGUE // 內心獨白/心理描寫 → 性格參數證據
NARRATION // 敘述/新登場設定 → 語意記憶
}
enum SceneLineStatus {
PENDING // 尚待歸屬判定
CONFIRMED // 信心分數達門檻且指紋一致,已寫入場景資料庫
QUARANTINED // 低信心或指紋矛盾,隔離、不寫入任何角色
}
// M-3 共用場景資料庫:章節文本以場景為單位切分後的客觀記錄單位,角色中立,不含主觀詮釋。
// M-8/M-9 的共用年表以 storyOrder 排序——同作品所有場景共用一條數線,錨定點即在此數線上切一刀。
model Scene {
id String @id @default(cuid())
workId String
work Work @relation(fields: [workId], references: [id], onDelete: Cascade)
volume Int
chapterLabel String
storyOrder Int // 全作品共用年表排序鍵
sourceType String @default("NOVEL") // M-8 矛盾裁決優先序用:NOVEL/OFFICIAL_SETTEI/ANIME_ADAPTATION
summary String // 場景摘要(客觀記錄:在場者、誰說了哪句、誰做了什麼)
rawText String // 匯入的原始場景文本(僅供測試/自製文本使用,不得為受版權保護原文)
projected Boolean @default(false) // M-9:是否已投影進角色記憶(只有 storyOrder <= 錨定點的場景會被投影)
createdAt DateTime @default(now())
participants ScenePresence[]
lines SceneLine[]
illustrations Illustration[]
facts SceneFact[]
episodicMemories EpisodicMemory[]
semanticMemories SemanticMemory[]
relationshipEventLogs CharacterRelationshipEventLog[]
personalityTraitSnapshots PersonalityTraitSnapshot[]
interactions SceneInteraction[]
@@unique([workId, storyOrder])
@@map("scenes")
}
// M-3/M-5 場景名冊(硬約束):不在場的角色不可能說話,歸屬候選只限在場者。
model ScenePresence {
id String @id @default(cuid())
sceneId String
scene Scene @relation(fields: [sceneId], references: [id], onDelete: Cascade)
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
@@unique([sceneId, characterId])
@@map("scene_presences")
}
// M-4/M-5/M-6 場景內逐行記錄:每一行都有獨立的歸屬信心分數與狀態,是隔離區機制運作的最小單位。
model SceneLine {
id String @id @default(cuid())
sceneId String
scene Scene @relation(fields: [sceneId], references: [id], onDelete: Cascade)
lineIndex Int
lineType SceneLineType
rawText String
hasExplicitMarker Boolean @default(false) // 是否有「○○說道」等明示標記(決定性證據)
markerCharacterId String? // 明示標記指向的角色(若有)
speakerCharacterId String? // 目前歸屬(可能是明示、也可能是推論後確認的結果)
speakerCharacter Character? @relation(fields: [speakerCharacterId], references: [id], onDelete: SetNull)
confidence Float @default(0)
status SceneLineStatus @default(PENDING)
createdAt DateTime @default(now())
@@map("scene_lines")
}
// M-8 場景事實:客觀、可檢查的事實標記(例如天氣),跨場景比對同一事件標籤下的事實是否矛盾。
model SceneFact {
id String @id @default(cuid())
sceneId String
scene Scene @relation(fields: [sceneId], references: [id], onDelete: Cascade)
eventLabel String // 事件標籤:同一事件標籤下的事實理應一致(例如「秋季運動會」)
factKey String // 例如「天氣」
factValue String // 例如「雨天」
createdAt DateTime @default(now())
@@map("scene_facts")
}
// M-4 互動的中立記錄:場景資料庫階段只記錄「發生了什麼互動」,尚未套用到任何角色的關係帳本——
// 套用(寫入 CharacterRelationshipEventLog)是投影階段的職責,一樣受 M-9 錨定點知識邊界節制。
model SceneInteraction {
id String @id @default(cuid())
sceneId String
scene Scene @relation(fields: [sceneId], references: [id], onDelete: Cascade)
characterId String
otherCharacterId String
delta Float
description String
createdAt DateTime @default(now())
@@map("scene_interactions")
}
enum ContradictionStatus {
PENDING_HUMAN_REVIEW
RESOLVED
}
// M-8 矛盾偵測:不同場景描寫衝突時標記待人工確認;裁決優先序見 ContradictionDetectionService。
model SceneContradiction {
id String @id @default(cuid())
workId String
work Work @relation(fields: [workId], references: [id], onDelete: Cascade)
eventLabel String
factKey String
description String
status ContradictionStatus @default(PENDING_HUMAN_REVIEW)
resolution String?
createdAt DateTime @default(now())
@@map("scene_contradictions")
}
// M-4/M-6 語言風格語料:已確認歸屬的台詞樣本,供語言指紋統計(人稱/語尾/口癖頻率)使用。
model LanguageCorpusEntry {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
sceneId String?
text String
createdAt DateTime @default(now())
@@map("language_corpus_entries")
}
// M-8 性格演變追蹤:參數不是單一值而是帶時間戳的序列,依錨定點取用當時的性格參數。
model PersonalityTraitSnapshot {
id String @id @default(cuid())
characterId String
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
sourceSceneId String? // M-4/M-7 可追溯性:由哪個場景的獨白/心理描寫萃取而來
sourceScene Scene? @relation(fields: [sourceSceneId], references: [id], onDelete: SetNull)
atStoryOrder Int // 對應的年表位置
note String // 描述性文字:這個時間點角色參數上的變化說明
expressivenessOverride Float? // 可選:覆寫 G-1 外顯度參數,模擬成長軌跡
createdAt DateTime @default(now())
@@map("personality_trait_snapshots")
}
// M-4 互動→關係帳本(角色對角色版):每次原作互動評正負向權重,累積出她對每個角色的信任/親密度,
// 可追溯到來源場景;同一事件寫入雙方視角,權重可以不對稱。
model CharacterRelationshipEventLog {
id String @id @default(cuid())
characterId String
character Character @relation("EventLogFrom", fields: [characterId], references: [id], onDelete: Cascade)
otherCharacterId String
otherCharacter Character @relation("EventLogTo", fields: [otherCharacterId], references: [id], onDelete: Cascade)
sceneId String?
scene Scene? @relation(fields: [sceneId], references: [id], onDelete: SetNull)
delta Float
description String
createdAt DateTime @default(now())
@@map("character_relationship_event_logs")
}