1
This commit is contained in:
@@ -89,6 +89,51 @@ describe('echoMemory', () => {
|
||||
expect(synced.seenBackstoryChapterIds).toContain('scar');
|
||||
});
|
||||
|
||||
it('accepts projected story engine memory snapshots with missing arrays', () => {
|
||||
const npcState: NpcPersistentState = {
|
||||
affinity: 18,
|
||||
helpUsed: false,
|
||||
chattedCount: 0,
|
||||
giftsGiven: 0,
|
||||
inventory: [],
|
||||
recruited: false,
|
||||
revealedFacts: [],
|
||||
knownAttributeRumors: [],
|
||||
firstMeaningfulContactResolved: false,
|
||||
seenBackstoryChapterIds: [],
|
||||
stanceProfile: {
|
||||
trust: 50,
|
||||
warmth: 46,
|
||||
ideologicalFit: 50,
|
||||
fearOrGuard: 38,
|
||||
loyalty: 28,
|
||||
currentConflictTag: null,
|
||||
recentApprovals: [],
|
||||
recentDisapprovals: [],
|
||||
},
|
||||
};
|
||||
|
||||
const synced = syncNpcNarrativeState({
|
||||
encounter: createEncounter(),
|
||||
npcState,
|
||||
storyEngineMemory: {
|
||||
currentChapter: {
|
||||
id: 'chapter-1',
|
||||
title: '断桥再燃',
|
||||
summary: '桥口旧案重新压到眼前。',
|
||||
stage: 'rising',
|
||||
relatedQuestIds: [],
|
||||
relatedSceneIds: [],
|
||||
relatedThreadIds: ['thread-1'],
|
||||
pressureTags: [],
|
||||
},
|
||||
} as never,
|
||||
});
|
||||
|
||||
expect(synced.revealedFacts).toContain('publicMask');
|
||||
expect(synced.revealedFacts).toContain('thread:thread-1');
|
||||
});
|
||||
|
||||
it('writes recent carriers and scar echoes into story engine memory', () => {
|
||||
const item: InventoryItem = {
|
||||
id: 'runtime:quest:evidence',
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import { buildThemePackFromWorldProfile } from './themePack';
|
||||
import {
|
||||
buildEncounterVisibilitySlice,
|
||||
createEmptyStoryEngineMemoryState,
|
||||
normalizeStoryEngineMemoryState,
|
||||
} from './visibilityEngine';
|
||||
import { buildFallbackWorldStoryGraph } from './worldStoryGraph';
|
||||
|
||||
@@ -81,8 +81,7 @@ export function syncNpcNarrativeState(params: {
|
||||
return npcState;
|
||||
}
|
||||
|
||||
const storyEngineMemory =
|
||||
params.storyEngineMemory ?? createEmptyStoryEngineMemoryState();
|
||||
const storyEngineMemory = normalizeStoryEngineMemoryState(params.storyEngineMemory);
|
||||
const activeThreadIds =
|
||||
storyEngineMemory.activeThreadIds.length > 0
|
||||
? storyEngineMemory.activeThreadIds
|
||||
@@ -122,8 +121,7 @@ export function appendStoryEngineCarrierMemory(
|
||||
state: GameState,
|
||||
items: InventoryItem[],
|
||||
) {
|
||||
const storyEngineMemory =
|
||||
state.storyEngineMemory ?? createEmptyStoryEngineMemoryState();
|
||||
const storyEngineMemory = normalizeStoryEngineMemoryState(state.storyEngineMemory);
|
||||
const carriers = items.filter((item) => item.runtimeMetadata?.storyFingerprint);
|
||||
if (carriers.length <= 0) {
|
||||
return {
|
||||
|
||||
@@ -71,6 +71,67 @@ export function createEmptyStoryEngineMemoryState(): StoryEngineMemoryState {
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeStoryEngineMemoryState(
|
||||
memory?: Partial<StoryEngineMemoryState> | null,
|
||||
): StoryEngineMemoryState {
|
||||
const empty = createEmptyStoryEngineMemoryState();
|
||||
if (!memory) return empty;
|
||||
|
||||
// 后端投影或旧存档可能只带增量字段,前端消费前统一补齐数组字段。
|
||||
return {
|
||||
...empty,
|
||||
...memory,
|
||||
discoveredFactIds: Array.isArray(memory.discoveredFactIds)
|
||||
? memory.discoveredFactIds
|
||||
: empty.discoveredFactIds,
|
||||
inferredFactIds: Array.isArray(memory.inferredFactIds)
|
||||
? memory.inferredFactIds
|
||||
: empty.inferredFactIds,
|
||||
activeThreadIds: Array.isArray(memory.activeThreadIds)
|
||||
? memory.activeThreadIds
|
||||
: empty.activeThreadIds,
|
||||
resolvedScarIds: Array.isArray(memory.resolvedScarIds)
|
||||
? memory.resolvedScarIds
|
||||
: empty.resolvedScarIds,
|
||||
recentCarrierIds: Array.isArray(memory.recentCarrierIds)
|
||||
? memory.recentCarrierIds
|
||||
: empty.recentCarrierIds,
|
||||
openedSceneChapterIds: Array.isArray(memory.openedSceneChapterIds)
|
||||
? memory.openedSceneChapterIds
|
||||
: empty.openedSceneChapterIds,
|
||||
recentSignalIds: Array.isArray(memory.recentSignalIds)
|
||||
? memory.recentSignalIds
|
||||
: empty.recentSignalIds,
|
||||
recentCompanionReactions: Array.isArray(memory.recentCompanionReactions)
|
||||
? memory.recentCompanionReactions
|
||||
: empty.recentCompanionReactions,
|
||||
companionArcStates: Array.isArray(memory.companionArcStates)
|
||||
? memory.companionArcStates
|
||||
: empty.companionArcStates,
|
||||
worldMutations: Array.isArray(memory.worldMutations)
|
||||
? memory.worldMutations
|
||||
: empty.worldMutations,
|
||||
chronicle: Array.isArray(memory.chronicle)
|
||||
? memory.chronicle
|
||||
: empty.chronicle,
|
||||
factionTensionStates: Array.isArray(memory.factionTensionStates)
|
||||
? memory.factionTensionStates
|
||||
: empty.factionTensionStates,
|
||||
consequenceLedger: Array.isArray(memory.consequenceLedger)
|
||||
? memory.consequenceLedger
|
||||
: empty.consequenceLedger,
|
||||
companionResolutions: Array.isArray(memory.companionResolutions)
|
||||
? memory.companionResolutions
|
||||
: empty.companionResolutions,
|
||||
narrativeCodex: Array.isArray(memory.narrativeCodex)
|
||||
? memory.narrativeCodex
|
||||
: empty.narrativeCodex,
|
||||
simulationRunResults: Array.isArray(memory.simulationRunResults)
|
||||
? memory.simulationRunResults
|
||||
: empty.simulationRunResults,
|
||||
};
|
||||
}
|
||||
|
||||
function buildBaseFactIds(
|
||||
narrativeProfile?: ActorNarrativeProfile | null,
|
||||
backstoryReveal?: CharacterBackstoryRevealConfig | null,
|
||||
@@ -113,7 +174,7 @@ function resolveUnlockedChapterIds(
|
||||
export function buildEncounterVisibilitySlice(
|
||||
params: EncounterVisibilityParams,
|
||||
) {
|
||||
const memory = params.storyEngineMemory ?? createEmptyStoryEngineMemoryState();
|
||||
const memory = normalizeStoryEngineMemoryState(params.storyEngineMemory);
|
||||
const factIds = buildBaseFactIds(params.narrativeProfile, params.backstoryReveal);
|
||||
const unlockedChapterIds = resolveUnlockedChapterIds(
|
||||
params.backstoryReveal,
|
||||
@@ -193,7 +254,7 @@ export function buildEncounterVisibilitySlice(
|
||||
export function buildQuestVisibilitySlice(
|
||||
params: QuestVisibilityParams,
|
||||
) {
|
||||
const memory = params.storyEngineMemory ?? createEmptyStoryEngineMemoryState();
|
||||
const memory = normalizeStoryEngineMemoryState(params.storyEngineMemory);
|
||||
const narrativeProfile = params.issuerNarrativeProfile;
|
||||
const factIds = dedupeStrings([
|
||||
narrativeProfile ? 'publicMask' : null,
|
||||
|
||||
Reference in New Issue
Block a user