新增角色大廳(lobby)邀約與通話、附身(possession)、時鐘(clock)抽象、 時間流與睡眠負債、後日談進度、行為選擇與旁觀反應等模組,並補齊對應冒煙 測試(R~V)與單元測試;同步調整 web/mobile 對應頁面與元件。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
55 lines
2.8 KiB
TypeScript
55 lines
2.8 KiB
TypeScript
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);
|
||
}
|
||
}
|
||
|
||
// 錨定點後退:她「不再知道」超出新錨定點的後段劇情——移除這些場景先前投影出的所有角色資料。
|
||
// T17/T25:清除清單與 ProjectionService.clearProjectedData() 共用同一份,避免這裡各自維護一份
|
||
// 清單、日後新增的投影資料類型忘了同步加在兩處(先前 CharacterEmotionBaseline/ProceduralRule
|
||
// 上線時就漏了這裡,直到本次才發現並補上)。
|
||
const toUnproject = await this.prisma.client.scene.findMany({
|
||
where: { workId, storyOrder: { gt: newStoryOrder }, projected: true },
|
||
});
|
||
for (const scene of toUnproject) {
|
||
await this.projection.clearProjectedData(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" },
|
||
});
|
||
}
|
||
}
|