Files
Genarrative/src/services/storyEngine/threadSignalRouter.test.ts
T
kdletters cbc27bad4a
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

159 lines
4.5 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { AnimationState, type GameState } from '../../types';
import { collectStorySignals, resolveSignalsToThreadUpdates } from './threadSignalRouter';
function createState(): GameState {
return {
worldType: null,
customWorldProfile: null,
playerCharacter: null,
runtimeStats: {
playTimeMs: 0,
lastPlayTickAt: null,
hostileNpcsDefeated: 0,
questsAccepted: 0,
itemsUsed: 0,
scenesTraveled: 0,
},
currentScene: 'Story',
storyHistory: [],
storyEngineMemory: {
discoveredFactIds: [],
inferredFactIds: [],
activeThreadIds: ['thread-1'],
resolvedScarIds: [],
recentCarrierIds: [],
recentSignalIds: [],
recentCompanionReactions: [],
},
characterChats: {},
animationState: AnimationState.IDLE,
currentEncounter: null,
npcInteractionActive: false,
currentScenePreset: {
id: 'scene-1',
name: '断桥旧哨',
description: '旧哨火和断桥一起守着边城北口。',
imageSrc: '',
treasureHints: [],
narrativeResidues: [],
},
sceneHostileNpcs: [],
playerX: 0,
playerOffsetY: 0,
playerFacing: 'right',
playerActionMode: 'idle',
scrollWorld: false,
inBattle: false,
playerHp: 0,
playerMaxHp: 0,
playerMana: 0,
playerMaxMana: 0,
playerSkillCooldowns: {},
activeBuildBuffs: [],
activeCombatEffects: [],
playerCurrency: 0,
playerInventory: [],
playerEquipment: {
weapon: null,
armor: null,
relic: null,
},
npcStates: {},
quests: [
{
id: 'quest-1',
issuerNpcId: 'npc-1',
issuerNpcName: '梁砺',
sceneId: 'scene-1',
threadId: 'thread-1',
contractId: 'thread-contract:thread-1',
title: '调查断桥旧案',
description: '先去看清桥口到底发生了什么。',
summary: '调查断桥旧案',
objective: {
kind: 'inspect_treasure',
targetSceneId: 'scene-1',
requiredCount: 1,
},
progress: 0,
status: 'active',
reward: {
affinityBonus: 0,
currency: 0,
items: [],
},
rewardText: '',
steps: [],
activeStepId: null,
visibleStage: 0,
hiddenFlags: [],
discoveredFactIds: [],
relatedCarrierIds: [],
},
],
roster: [],
companions: [],
currentBattleNpcId: null,
currentNpcBattleMode: null,
currentNpcBattleOutcome: null,
sparReturnEncounter: null,
sparPlayerHpBefore: null,
sparPlayerMaxHpBefore: null,
sparStoryHistoryBefore: null,
};
}
describe('threadSignalRouter', () => {
it('collects signals and advances memory/quest stage', () => {
const prevState = createState();
const nextState = {
...createState(),
playerInventory: [
{
id: 'carrier-1',
category: '专属物品',
name: '封痕信物',
quantity: 1,
rarity: 'rare',
tags: ['relic'],
runtimeMetadata: {
origin: 'ai_compiled',
generationChannel: 'quest_reward',
seedKey: 'seed',
sourceReason: '旧案把它重新推到了你眼前。',
storyFingerprint: {
visibleClue: '桥口旧铜味一直留在它的边角。',
witnessMark: '它曾被人攥着在封桥夜里来回传递。',
unresolvedQuestion: '它为什么一直没有被交回去?',
currentAppearanceReason: '封桥旧案再次被提起。',
relatedThreadIds: ['thread-1'],
relatedScarIds: ['scar-1'],
reactionHooks: ['名单'],
},
},
},
],
} satisfies GameState;
const signals = collectStorySignals({
prevState,
nextState,
actionText: '接下调查断桥旧案的委托',
lastFunctionId: 'npc_quest_accept',
rewardItems: nextState.playerInventory,
});
const resolved = resolveSignalsToThreadUpdates({
state: nextState,
signals,
contracts: [],
});
expect(signals.some((signal) => signal.signalType === 'accept_contract')).toBe(true);
expect(signals.some((signal) => signal.signalType === 'obtain_carrier')).toBe(true);
expect(resolved.storyEngineMemory?.recentSignalIds?.length).toBeGreaterThan(0);
expect(resolved.quests[0]?.visibleStage).toBeGreaterThan(0);
expect(resolved.quests[0]?.relatedCarrierIds).toContain('carrier-1');
});
});