import { Injectable } from "@nestjs/common"; import { PrismaService } from "../prisma/prisma.service.js"; import { ProjectionService } from "./projection.service.js"; import { WorkRosterService } from "./work-roster.service.js"; // M-9 錨定點與知識邊界:錨定點之前是角色的親身經歷、之後不存在。場景匯入時只寫入共用場景資料庫, // 只有 storyOrder <= 錨定點的場景會被投影進角色記憶——移動錨定點(無論前進或後退)都要讓「已投影」的 // 場景集合與「storyOrder <= 錨定點」的場景集合重新同步。 @Injectable() export class AnchorService { constructor( private readonly prisma: PrismaService, private readonly projection: ProjectionService, private readonly roster: WorkRosterService, ) {} async setAnchor(workId: string, newStoryOrder: number): Promise<{ projected: number; unprojected: number }> { const work = await this.prisma.client.work.findUniqueOrThrow({ where: { id: workId } }); const toProject = await this.prisma.client.scene.findMany({ where: { workId, storyOrder: { lte: newStoryOrder }, projected: false }, include: { participants: true }, }); for (const scene of toProject) { await this.projection.projectScene(scene.id); for (const presence of scene.participants) { await this.roster.checkAndPromote(presence.characterId); } } // 錨定點後退:她「不再知道」超出新錨定點的後段劇情——移除這些場景先前投影出的所有角色資料。 const toUnproject = await this.prisma.client.scene.findMany({ where: { workId, storyOrder: { gt: newStoryOrder }, projected: true }, }); for (const scene of toUnproject) { await this.prisma.client.episodicMemory.deleteMany({ where: { sourceSceneId: scene.id } }); await this.prisma.client.semanticMemory.deleteMany({ where: { sourceSceneId: scene.id } }); await this.prisma.client.personalityTraitSnapshot.deleteMany({ where: { sourceSceneId: scene.id } }); await this.prisma.client.characterRelationshipEventLog.deleteMany({ where: { sceneId: scene.id } }); await this.prisma.client.scene.update({ where: { id: scene.id }, data: { projected: false } }); } await this.prisma.client.work.update({ where: { id: work.id }, data: { anchorStoryOrder: newStoryOrder } }); return { projected: toProject.length, unprojected: toUnproject.length }; } // M-9 依錨定點取用「當時的她」的性格參數:取 atStoryOrder <= 錨定點裡最新的一筆快照。 async effectivePersonalitySnapshot(characterId: string, atStoryOrder: number) { return this.prisma.client.personalityTraitSnapshot.findFirst({ where: { characterId, atStoryOrder: { lte: atStoryOrder } }, orderBy: { atStoryOrder: "desc" }, }); } }