forked from xiangwang25/whale-town-end-v2
feat: integrate invitation access, world NPCs, and deployment
This commit is contained in:
82
src/business/world_npc/world_npc.service.spec.ts
Normal file
82
src/business/world_npc/world_npc.service.spec.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user