Important Fixes, New mechanics, and many more
Some checks failed
CI / validate (push) Failing after 9m21s
Some checks failed
CI / validate (push) Failing after 9m21s
This commit is contained in:
56
apps/worker/src/orchestration.test.ts
Normal file
56
apps/worker/src/orchestration.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { Character, PlayerIntent, RoundPlan } from '@dng/shared'
|
||||
import { buildActionSequence, buildRoundContext, sanitizeRoundMemory, shouldUpdateStorySummary, validateAndOrderRoundPlan } from './orchestration'
|
||||
|
||||
const character = (id: string, controller: Character['controller']): Character => ({
|
||||
id, controller, name: id, concept: 'A campaign hero', userId: controller === 'human' ? 'user' : null,
|
||||
abilities: { str: 10, dex: 10, con: 10, int: 10, wis: 10, cha: 10 },
|
||||
hp: 10, maxHp: 10, defense: 10, proficiency: 2, inventory: [], statuses: [], persona: {},
|
||||
})
|
||||
const intent = (id: string, characterId: string, createdAt: string): PlayerIntent => ({
|
||||
id, roundId: 'round', memberId: id, characterId, action: `Action ${id}`, ready: true, createdAt, updatedAt: createdAt,
|
||||
})
|
||||
|
||||
describe('round orchestration', () => {
|
||||
it('keeps a minimal context and orders humans before AI/delegated heroes', () => {
|
||||
const context = buildRoundContext({
|
||||
scene: 'Current scene', storySummary: 'Story so far',
|
||||
recentRounds: [1, 2, 3].map(number => ({ number, narration: `Round ${number}` })),
|
||||
intents: [intent('late', 'human', '2026-01-01T00:00:02.000Z'), intent('early', 'human', '2026-01-01T00:00:01.000Z')],
|
||||
characters: [character('human', 'human'), character('companion', 'ai'), character('stand-in', 'delegated')],
|
||||
memories: Array.from({ length: 10 }, (_, index) => ({ summary: `Memory ${index}`, importance: (index % 5) + 1 })),
|
||||
})
|
||||
const plan: RoundPlan = { checks: [], proposedEvents: [], relevantMemoryQueries: [], aiActions: [
|
||||
{ characterId: 'stand-in', action: 'Covers the retreat' },
|
||||
{ characterId: 'human', action: 'Illegally overrides the player' },
|
||||
{ characterId: 'companion', action: 'Scouts ahead' },
|
||||
] }
|
||||
const validated = validateAndOrderRoundPlan(plan, context)
|
||||
|
||||
expect(context.recentRounds.map(round => round.number)).toEqual([2, 3])
|
||||
expect(context.memories).toHaveLength(8)
|
||||
expect(validated.aiActions.map(action => action.characterId)).toEqual(['companion', 'stand-in'])
|
||||
expect(buildActionSequence(context, validated).map(action => action.phase)).toEqual(['human', 'human', 'ai', 'ai'])
|
||||
})
|
||||
|
||||
it('rejects mechanical references outside the active campaign', () => {
|
||||
const context = buildRoundContext({ scene: 'Scene', recentRounds: [], intents: [], characters: [character('hero', 'human')], memories: [] })
|
||||
expect(() => validateAndOrderRoundPlan({
|
||||
checks: [{ actorId: 'outsider', kind: 'ability', mode: 'normal', reason: 'Invalid' }],
|
||||
aiActions: [], proposedEvents: [], relevantMemoryQueries: [],
|
||||
}, context)).toThrow('Unknown check actor outsider')
|
||||
})
|
||||
|
||||
it('schedules durable story summaries every third completed round', () => {
|
||||
expect([1, 2, 3, 4, 5, 6].filter(shouldUpdateStorySummary)).toEqual([3, 6])
|
||||
})
|
||||
|
||||
it('drops model-authored memory labels that PostgreSQL cannot cast to uuid[]', () => {
|
||||
expect(sanitizeRoundMemory({
|
||||
summary: 'The party learned who controls the gate.',
|
||||
importance: 4,
|
||||
tags: ['gate'],
|
||||
entityIds: ['the-gatekeeper', '9ea0ef06-34bf-4cf3-8f2f-af1a65180d0e', '9ea0ef06-34bf-4cf3-8f2f-af1a65180d0e'],
|
||||
})?.entityIds).toEqual(['9ea0ef06-34bf-4cf3-8f2f-af1a65180d0e'])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user