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
@@ -0,0 +1,193 @@
-- CreateTable
CREATE TABLE "illustrations" (
"id" TEXT NOT NULL PRIMARY KEY,
"workId" TEXT NOT NULL,
"volume" INTEGER NOT NULL,
"page" INTEGER,
"type" TEXT NOT NULL,
"sceneId" TEXT,
"description" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "illustrations_workId_fkey" FOREIGN KEY ("workId") REFERENCES "works" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "illustrations_sceneId_fkey" FOREIGN KEY ("sceneId") REFERENCES "scenes" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "costume_catalog_entries" (
"id" TEXT NOT NULL PRIMARY KEY,
"characterId" TEXT NOT NULL,
"illustrationId" TEXT,
"label" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "costume_catalog_entries_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "costume_catalog_entries_illustrationId_fkey" FOREIGN KEY ("illustrationId") REFERENCES "illustrations" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "pose_vocabulary_entries" (
"id" TEXT NOT NULL PRIMARY KEY,
"characterId" TEXT NOT NULL,
"illustrationId" TEXT,
"label" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "pose_vocabulary_entries_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "pose_vocabulary_entries_illustrationId_fkey" FOREIGN KEY ("illustrationId") REFERENCES "illustrations" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "scenes" (
"id" TEXT NOT NULL PRIMARY KEY,
"workId" TEXT NOT NULL,
"volume" INTEGER NOT NULL,
"chapterLabel" TEXT NOT NULL,
"storyOrder" INTEGER NOT NULL,
"summary" TEXT NOT NULL,
"rawText" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "scenes_workId_fkey" FOREIGN KEY ("workId") REFERENCES "works" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "scene_presences" (
"id" TEXT NOT NULL PRIMARY KEY,
"sceneId" TEXT NOT NULL,
"characterId" TEXT NOT NULL,
CONSTRAINT "scene_presences_sceneId_fkey" FOREIGN KEY ("sceneId") REFERENCES "scenes" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "scene_presences_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "scene_lines" (
"id" TEXT NOT NULL PRIMARY KEY,
"sceneId" TEXT NOT NULL,
"lineIndex" INTEGER NOT NULL,
"lineType" TEXT NOT NULL,
"rawText" TEXT NOT NULL,
"hasExplicitMarker" BOOLEAN NOT NULL DEFAULT false,
"markerCharacterId" TEXT,
"speakerCharacterId" TEXT,
"confidence" REAL NOT NULL DEFAULT 0,
"status" TEXT NOT NULL DEFAULT 'PENDING',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "scene_lines_sceneId_fkey" FOREIGN KEY ("sceneId") REFERENCES "scenes" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "scene_lines_speakerCharacterId_fkey" FOREIGN KEY ("speakerCharacterId") REFERENCES "characters" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "scene_facts" (
"id" TEXT NOT NULL PRIMARY KEY,
"sceneId" TEXT NOT NULL,
"eventLabel" TEXT NOT NULL,
"factKey" TEXT NOT NULL,
"factValue" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "scene_facts_sceneId_fkey" FOREIGN KEY ("sceneId") REFERENCES "scenes" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "scene_contradictions" (
"id" TEXT NOT NULL PRIMARY KEY,
"workId" TEXT NOT NULL,
"eventLabel" TEXT NOT NULL,
"factKey" TEXT NOT NULL,
"description" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'PENDING_HUMAN_REVIEW',
"resolution" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "scene_contradictions_workId_fkey" FOREIGN KEY ("workId") REFERENCES "works" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "language_corpus_entries" (
"id" TEXT NOT NULL PRIMARY KEY,
"characterId" TEXT NOT NULL,
"sceneId" TEXT,
"text" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "language_corpus_entries_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "personality_trait_snapshots" (
"id" TEXT NOT NULL PRIMARY KEY,
"characterId" TEXT NOT NULL,
"atStoryOrder" INTEGER NOT NULL,
"note" TEXT NOT NULL,
"expressivenessOverride" REAL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "personality_trait_snapshots_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "character_relationship_event_logs" (
"id" TEXT NOT NULL PRIMARY KEY,
"characterId" TEXT NOT NULL,
"otherCharacterId" TEXT NOT NULL,
"sceneId" TEXT,
"delta" REAL NOT NULL,
"description" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "character_relationship_event_logs_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "character_relationship_event_logs_otherCharacterId_fkey" FOREIGN KEY ("otherCharacterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "character_relationship_event_logs_sceneId_fkey" FOREIGN KEY ("sceneId") REFERENCES "scenes" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_episodic_memories" (
"id" TEXT NOT NULL PRIMARY KEY,
"characterId" TEXT NOT NULL,
"content" TEXT NOT NULL,
"occurredAt" DATETIME NOT NULL,
"emotionTag" TEXT NOT NULL,
"emotionIntensity" REAL NOT NULL,
"retrievalCount" INTEGER NOT NULL DEFAULT 0,
"lastRetrievedAt" DATETIME,
"source" TEXT NOT NULL,
"weight" REAL NOT NULL DEFAULT 1,
"relatedUserId" TEXT,
"isPrivate" BOOLEAN NOT NULL DEFAULT false,
"sourceSceneId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "episodic_memories_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "episodic_memories_relatedUserId_fkey" FOREIGN KEY ("relatedUserId") REFERENCES "users" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "episodic_memories_sourceSceneId_fkey" FOREIGN KEY ("sourceSceneId") REFERENCES "scenes" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_episodic_memories" ("characterId", "content", "createdAt", "emotionIntensity", "emotionTag", "id", "isPrivate", "lastRetrievedAt", "occurredAt", "relatedUserId", "retrievalCount", "source", "weight") SELECT "characterId", "content", "createdAt", "emotionIntensity", "emotionTag", "id", "isPrivate", "lastRetrievedAt", "occurredAt", "relatedUserId", "retrievalCount", "source", "weight" FROM "episodic_memories";
DROP TABLE "episodic_memories";
ALTER TABLE "new_episodic_memories" RENAME TO "episodic_memories";
CREATE TABLE "new_semantic_memories" (
"id" TEXT NOT NULL PRIMARY KEY,
"characterId" TEXT NOT NULL,
"fact" TEXT NOT NULL,
"about" TEXT NOT NULL,
"sourceSceneId" TEXT,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "semantic_memories_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "semantic_memories_sourceSceneId_fkey" FOREIGN KEY ("sourceSceneId") REFERENCES "scenes" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_semantic_memories" ("about", "characterId", "createdAt", "fact", "id") SELECT "about", "characterId", "createdAt", "fact", "id" FROM "semantic_memories";
DROP TABLE "semantic_memories";
ALTER TABLE "new_semantic_memories" RENAME TO "semantic_memories";
CREATE TABLE "new_works" (
"id" TEXT NOT NULL PRIMARY KEY,
"title" TEXT NOT NULL,
"volumeProgress" TEXT,
"worldview" TEXT,
"progressAnchor" TEXT,
"anchorStoryOrder" INTEGER NOT NULL DEFAULT 0,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_works" ("createdAt", "id", "progressAnchor", "title", "updatedAt", "volumeProgress", "worldview") SELECT "createdAt", "id", "progressAnchor", "title", "updatedAt", "volumeProgress", "worldview" FROM "works";
DROP TABLE "works";
ALTER TABLE "new_works" RENAME TO "works";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
-- CreateIndex
CREATE UNIQUE INDEX "scenes_workId_storyOrder_key" ON "scenes"("workId", "storyOrder");
-- CreateIndex
CREATE UNIQUE INDEX "scene_presences_sceneId_characterId_key" ON "scene_presences"("sceneId", "characterId");
@@ -0,0 +1,35 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_personality_trait_snapshots" (
"id" TEXT NOT NULL PRIMARY KEY,
"characterId" TEXT NOT NULL,
"sourceSceneId" TEXT,
"atStoryOrder" INTEGER NOT NULL,
"note" TEXT NOT NULL,
"expressivenessOverride" REAL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "personality_trait_snapshots_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "characters" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "personality_trait_snapshots_sourceSceneId_fkey" FOREIGN KEY ("sourceSceneId") REFERENCES "scenes" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
INSERT INTO "new_personality_trait_snapshots" ("atStoryOrder", "characterId", "createdAt", "expressivenessOverride", "id", "note") SELECT "atStoryOrder", "characterId", "createdAt", "expressivenessOverride", "id", "note" FROM "personality_trait_snapshots";
DROP TABLE "personality_trait_snapshots";
ALTER TABLE "new_personality_trait_snapshots" RENAME TO "personality_trait_snapshots";
CREATE TABLE "new_scenes" (
"id" TEXT NOT NULL PRIMARY KEY,
"workId" TEXT NOT NULL,
"volume" INTEGER NOT NULL,
"chapterLabel" TEXT NOT NULL,
"storyOrder" INTEGER NOT NULL,
"summary" TEXT NOT NULL,
"rawText" TEXT NOT NULL,
"projected" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "scenes_workId_fkey" FOREIGN KEY ("workId") REFERENCES "works" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_scenes" ("chapterLabel", "createdAt", "id", "rawText", "storyOrder", "summary", "volume", "workId") SELECT "chapterLabel", "createdAt", "id", "rawText", "storyOrder", "summary", "volume", "workId" FROM "scenes";
DROP TABLE "scenes";
ALTER TABLE "new_scenes" RENAME TO "scenes";
CREATE UNIQUE INDEX "scenes_workId_storyOrder_key" ON "scenes"("workId", "storyOrder");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
@@ -0,0 +1,22 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_scenes" (
"id" TEXT NOT NULL PRIMARY KEY,
"workId" TEXT NOT NULL,
"volume" INTEGER NOT NULL,
"chapterLabel" TEXT NOT NULL,
"storyOrder" INTEGER NOT NULL,
"sourceType" TEXT NOT NULL DEFAULT 'NOVEL',
"summary" TEXT NOT NULL,
"rawText" TEXT NOT NULL,
"projected" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "scenes_workId_fkey" FOREIGN KEY ("workId") REFERENCES "works" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
INSERT INTO "new_scenes" ("chapterLabel", "createdAt", "id", "projected", "rawText", "storyOrder", "summary", "volume", "workId") SELECT "chapterLabel", "createdAt", "id", "projected", "rawText", "storyOrder", "summary", "volume", "workId" FROM "scenes";
DROP TABLE "scenes";
ALTER TABLE "new_scenes" RENAME TO "scenes";
CREATE UNIQUE INDEX "scenes_workId_storyOrder_key" ON "scenes"("workId", "storyOrder");
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;
@@ -0,0 +1,11 @@
-- CreateTable
CREATE TABLE "scene_interactions" (
"id" TEXT NOT NULL PRIMARY KEY,
"sceneId" TEXT NOT NULL,
"characterId" TEXT NOT NULL,
"otherCharacterId" TEXT NOT NULL,
"delta" REAL NOT NULL,
"description" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "scene_interactions_sceneId_fkey" FOREIGN KEY ("sceneId") REFERENCES "scenes" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
+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")
}