import { describe, expect, it } from 'vitest'; import type { BattleSimJobPayload } from '../src/battleSim/types.js'; import { processBattleSimJob } from '../src/battleSim/processor.js'; const buildPayload = (action: BattleSimJobPayload['action']): BattleSimJobPayload => ({ action, repeatCnt: 1, year: 200, month: 1, seed: 'test-seed', attackerGeneral: { no: 1, name: 'Attacker', nation: 1, turntime: '2026-01-01 00:00:00', personal: null, special2: null, crew: 1000, crewtype: 100, atmos: 100, train: 100, intel: 70, intel_exp: 0, book: null, strength: 70, strength_exp: 0, weapon: null, injury: 0, leadership: 70, leadership_exp: 0, horse: null, item: null, explevel: 0, experience: 100, dedication: 100, officer_level: 3, officer_city: 1, gold: 1000, rice: 1000, dex1: 0, dex2: 0, dex3: 0, dex4: 0, dex5: 0, defence_train: 0, recent_war: null, warnum: 0, killnum: 0, killcrew: 0, }, attackerCity: { city: 1, nation: 1, supply: 1, name: 'AttackerCity', pop: 10000, agri: 1000, comm: 1000, secu: 1000, def: 100, wall: 100, trust: 100, level: 2, pop_max: 10000, agri_max: 1000, comm_max: 1000, secu_max: 1000, def_max: 200, wall_max: 200, dead: 0, state: 0, conflict: '{}', }, attackerNation: { type: 'test', tech: 1000, level: 1, capital: 1, nation: 1, name: 'AttackerNation', gold: 1000, rice: 1000, gennum: 1, }, defenderGenerals: [ { no: 2, name: 'Defender', nation: 2, turntime: '2026-01-01 00:00:00', personal: null, special2: null, crew: 1000, crewtype: 100, atmos: 100, train: 100, intel: 60, intel_exp: 0, book: null, strength: 60, strength_exp: 0, weapon: null, injury: 0, leadership: 60, leadership_exp: 0, horse: null, item: null, explevel: 0, experience: 100, dedication: 100, officer_level: 3, officer_city: 2, gold: 1000, rice: 1000, dex1: 0, dex2: 0, dex3: 0, dex4: 0, dex5: 0, defence_train: 0, recent_war: null, warnum: 0, killnum: 0, killcrew: 0, }, ], defenderCity: { city: 2, nation: 2, supply: 1, name: 'DefenderCity', pop: 10000, agri: 1000, comm: 1000, secu: 1000, def: 100, wall: 100, trust: 100, level: 2, pop_max: 10000, agri_max: 1000, comm_max: 1000, secu_max: 1000, def_max: 200, wall_max: 200, dead: 0, state: 0, conflict: '{}', }, defenderNation: { type: 'test', tech: 1000, level: 1, capital: 2, nation: 2, name: 'DefenderNation', gold: 1000, rice: 1000, gennum: 1, }, unitSet: { id: 'test', name: 'test', crewTypes: [ { id: 100, armType: 1, name: '보병', attack: 100, defence: 100, speed: 7, avoid: 10, magicCoef: 0, cost: 9, rice: 9, requirements: [], attackCoef: {}, defenceCoef: {}, info: [], initSkillTrigger: null, phaseSkillTrigger: null, iActionList: null, }, { id: 999, armType: 9, name: '성벽', attack: 0, defence: 0, speed: 1, avoid: 0, magicCoef: 0, cost: 0, rice: 9, requirements: [], attackCoef: {}, defenceCoef: {}, info: [], initSkillTrigger: null, phaseSkillTrigger: null, iActionList: null, }, ], }, config: { armPerPhase: 500, maxTrainByCommand: 100, maxAtmosByCommand: 100, maxTrainByWar: 110, maxAtmosByWar: 150, castleCrewTypeId: 999, armTypes: { footman: 1, wizard: 4, siege: 5, misc: 6, castle: 9, }, }, time: { year: 200, month: 1, startYear: 180, }, }); describe('battle sim processor', () => { it('returns the fixed-seed battle summary instead of only a successful shape', () => { const payload = buildPayload('battle'); const result = processBattleSimJob(payload); expect(result).toMatchObject({ result: true, reason: 'success', datetime: '2026-01-01 00:00:00', avgWar: 1, phase: 2, killed: 625, maxKilled: 625, minKilled: 625, dead: 1000, maxDead: 1000, minDead: 1000, attackerRice: 65, defenderRice: 83, attackerSkills: { 부상: 1 }, defendersSkills: [{ 회피시도: 1, 회피: 1 }], }); expect(result.lastWarLog?.generalActionLog).toContain('퇴각했습니다.'); }); it('returns the fixed defender ID order for reorder action', () => { const payload = buildPayload('reorder'); const result = processBattleSimJob(payload); expect(result.result).toBe(true); expect(result.order).toEqual([2]); }); it('executes crew trigger handlers in simulator battles', () => { const payload = buildPayload('battle'); payload.unitSet.crewTypes![0]!.phaseSkillTrigger = ['che_선제사격시도', 'che_선제사격발동']; payload.unitSet.crewTypes!.splice(1, 0, { ...payload.unitSet.crewTypes![0]!, id: 200, name: '수비 보병', phaseSkillTrigger: null, }); payload.defenderGenerals[0]!.crewtype = 200; const result = processBattleSimJob(payload); expect(result.result).toBe(true); expect(result.attackerSkills?.['선제']).toBe(1); }); it('fails fast when a simulator unit set references an unknown crew handler', () => { const payload = buildPayload('battle'); payload.unitSet.crewTypes![0]!.iActionList = ['missing_action']; expect(() => processBattleSimJob(payload)).toThrow('Unknown crew type action'); }); });