Files
core2026/app/game-api/test/battleSimProcessor.test.ts
T

285 lines
7.2 KiB
TypeScript

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');
});
});