forked from xiangwang25/whale-town-end-v2
83 lines
3.5 KiB
TypeScript
83 lines
3.5 KiB
TypeScript
import { WorldNpcPlanner, fallbackResearcherPlan, townDate } from './world_npc.planner';
|
|
import { WorldNpcService } from './world_npc.service';
|
|
import { WorldNpcDailyPlan } from './world_npc.types';
|
|
import { findWorldRoute } from './world_npc.world';
|
|
|
|
describe('WorldNpcService', () => {
|
|
const previousPersistence = process.env.WORLD_NPC_PERSISTENCE;
|
|
let service: WorldNpcService;
|
|
|
|
beforeEach(() => {
|
|
process.env.WORLD_NPC_PERSISTENCE = 'off';
|
|
const planner = {
|
|
createDailyPlan: async (_definition: unknown, _context: unknown, now: number) => fallbackResearcherPlan(now),
|
|
} as unknown as WorldNpcPlanner;
|
|
service = new WorldNpcService(planner);
|
|
});
|
|
|
|
afterAll(() => {
|
|
process.env.WORLD_NPC_PERSISTENCE = previousPersistence;
|
|
});
|
|
|
|
it('returns the versioned NPC snapshot only on its current map', () => {
|
|
const snapshot = service.getMapSnapshot('whale_port');
|
|
expect(snapshot.npcs[0]).toEqual(expect.objectContaining({
|
|
npcId: 'npc_whale_researcher', name: '鲸小研', dailyGoal: expect.any(String), planSource: 'fallback',
|
|
}));
|
|
expect(service.getMapSnapshot('work_zone').npcs).toEqual([]);
|
|
});
|
|
|
|
it('builds a semantic cross-map route instead of raw coordinate patrol', () => {
|
|
const route = findWorldRoute('square_dock_research', 'cafe_research_table');
|
|
expect(route[0]).toBe('square_dock_research');
|
|
expect(route[route.length - 1]).toBe('cafe_research_table');
|
|
expect(route).toEqual(expect.arrayContaining([
|
|
'square_work_gate', 'work_square_gate', 'work_cafe_gate', 'cafe_entrance',
|
|
]));
|
|
});
|
|
|
|
it('executes todays activity route continuously without teleporting to the initial point', async () => {
|
|
const now = Date.now();
|
|
const plan: WorldNpcDailyPlan = {
|
|
date: townDate(now), goal: '去咖啡馆收集研究问题', source: 'agent',
|
|
activities: [{
|
|
id: 'cafe_visit', title: '咖啡馆访谈', intention: '前往咖啡馆访谈',
|
|
locationId: 'cafe_research_table', startMinute: 0, endMinute: 1440,
|
|
activityKind: 'socialize', dialogue: '你最近在研究什么?',
|
|
}],
|
|
};
|
|
service.replacePlanForTesting(plan);
|
|
|
|
let clock = now;
|
|
const observedLocations = ['square_dock_research'];
|
|
let sawTransition = false;
|
|
for (let index = 0; index < 24; index += 1) {
|
|
const result = await service.tick(clock);
|
|
const active = service.getRuntimeForTesting().activeAction;
|
|
expect(active).toBeDefined();
|
|
if (active?.kind === 'transition') sawTransition = true;
|
|
clock = active!.completesAt + 1;
|
|
await service.tick(clock);
|
|
observedLocations.push(service.getRuntimeForTesting().locationId);
|
|
if (service.getRuntimeForTesting().locationId === 'cafe_research_table') break;
|
|
}
|
|
|
|
expect(sawTransition).toBe(true);
|
|
expect(observedLocations).toContain('work_square_gate');
|
|
expect(observedLocations[observedLocations.length - 1]).toBe('cafe_research_table');
|
|
expect(observedLocations.slice(1)).not.toContain('square_dock_research');
|
|
expect(service.getMapSnapshot('whale_cafe', clock).npcs[0]).toEqual(expect.objectContaining({
|
|
state: 'talking', publicIntention: '前往咖啡馆访谈',
|
|
}));
|
|
});
|
|
|
|
it('uses deterministic schedules that cover the full town day', () => {
|
|
const plan = fallbackResearcherPlan(Date.now());
|
|
expect(plan.activities[0].startMinute).toBe(0);
|
|
expect(plan.activities[plan.activities.length - 1].endMinute).toBe(1440);
|
|
plan.activities.slice(1).forEach((item, index) => {
|
|
expect(plan.activities[index].endMinute).toBe(item.startMinute);
|
|
});
|
|
});
|
|
});
|