import { describe, expect, it } from 'vitest'; import { MINIMAL_MAP } from '../fixtures/minimalMap.js'; import { InMemoryWorld, TestGameRunner } from '../testEnv.js'; import type { City, General, Nation } from '../../src/domain/entities.js'; import type { WorldSnapshot } from '../../src/world/types.js'; import { commandSpec as developAgricultureSpec } from '../../src/actions/turn/general/che_농지개간.js'; import type { TurnCommandEnv } from '../../src/actions/turn/commandEnv.js'; import { loadActionModuleBundle } from '../../src/actionModules/bundle.js'; describe('Domestic Affairs Scenario', () => { it('should increase agriculture when executing "Farming" command', { timeout: 15_000 }, async () => { // 1. Setup World const mockNation: Nation = { id: 1, name: 'Test Nation', color: '#FF0000', capitalCityId: 1, chiefGeneralId: 1, gold: 10000, rice: 10000, power: 0, level: 5, typeCode: 'test', meta: {}, }; const cityDef = MINIMAL_MAP.cities[0]; if (!cityDef) throw new Error('City definition missing'); const mockCity: City = { id: 1, name: cityDef.name, nationId: 1, level: 1, state: 0, population: 10000, populationMax: 10000, agriculture: 500, agricultureMax: 1000, commerce: 500, commerceMax: 1000, security: 500, securityMax: 1000, defence: 500, defenceMax: 1000, wall: 500, wallMax: 1000, supplyState: 1, frontState: 0, meta: {}, }; const mockGeneral: General = { id: 1, name: 'Domestic Officer', nationId: 1, cityId: 1, troopId: 0, npcState: 0, experience: 100, dedication: 100, officerLevel: 5, gold: 1000, rice: 1000, crew: 0, crewTypeId: 0, train: 100, atmos: 100, injury: 0, age: 30, stats: { leadership: 50, strength: 50, intelligence: 100 }, role: { personality: null, specialDomestic: null, specialWar: null, items: { horse: null, weapon: null, book: null, item: null }, }, triggerState: { flags: {}, counters: {}, modifiers: {}, meta: {} }, meta: { killturn: 24 }, }; const snapshot: WorldSnapshot = { scenarioConfig: { environment: { mapName: 'minimal_map', unitSet: 'default' }, options: {} } as any, scenarioMeta: { title: 'Test', startYear: 200, life: 0, fiction: 0, history: [], ignoreDefaultEvents: false, }, map: MINIMAL_MAP, unitSet: { id: 'default', name: 'default' }, nations: [mockNation], cities: [mockCity], generals: [mockGeneral], troops: [], diplomacy: [], events: [], initialEvents: [], }; const moduleBaselineSnapshot = structuredClone(snapshot); const moreEffectSnapshot = structuredClone(snapshot); const world = new InMemoryWorld(snapshot); const runner = new TestGameRunner(world, 200, 1); // 2. Prepare Command const systemEnv: TurnCommandEnv = { develCost: 50, trainDelta: 5, atmosDelta: 5, maxTrainByCommand: 100, maxAtmosByCommand: 100, sabotageDefaultProb: 0.5, sabotageProbCoefByStat: 0.1, sabotageDefenceCoefByGeneralCount: 0.1, sabotageDamageMin: 10, sabotageDamageMax: 20, openingPartYear: 200, maxGeneral: 10, defaultNpcGold: 1000, defaultNpcRice: 1000, defaultCrewTypeId: 1, defaultSpecialDomestic: null, defaultSpecialWar: null, initialNationGenLimit: 10, maxTechLevel: 10, baseGold: 1000, baseRice: 1000, maxResourceActionAmount: 1000, }; const farmingDef = developAgricultureSpec.createDefinition(systemEnv); // 3. Execute await runner.runTurn([ { generalId: 1, commandKey: 'che_농지개간', resolver: farmingDef, args: {}, }, ]); // 4. Verify const updatedCity = world.getCity(1)!; // 레거시 che_농지개간: 능력치·경험등급·0.8~1.2 난수·성공 배율을 모두 반영한다. expect(updatedCity.agriculture).toBe(664); const baselineModuleWorld = new InMemoryWorld(moduleBaselineSnapshot); const baselineModuleRunner = new TestGameRunner(baselineModuleWorld, 200, 1); const baselineBundle = await loadActionModuleBundle(); await baselineModuleRunner.runTurn([ { generalId: 1, commandKey: 'che_농지개간', resolver: developAgricultureSpec.createDefinition({ ...systemEnv, generalActionModules: baselineBundle.general, }), args: {}, }, ]); const moreEffectWorld = new InMemoryWorld(moreEffectSnapshot); const moreEffectRunner = new TestGameRunner(moreEffectWorld, 200, 1); const moduleBundle = await loadActionModuleBundle(undefined, 'event_MoreEffect'); const moreEffectDefinition = developAgricultureSpec.createDefinition({ ...systemEnv, generalActionModules: moduleBundle.general, }); await moreEffectRunner.runTurn([ { generalId: 1, commandKey: 'che_농지개간', resolver: moreEffectDefinition, args: {}, }, ]); expect(baselineModuleWorld.getCity(1)?.agriculture).toBe(672); expect(moreEffectWorld.getCity(1)?.agriculture).toBe(844); }); it('should not increase agriculture when city is already maxed', async () => { // Setup with max agriculture const mockNation: Nation = { id: 1, name: 'Test Nation', color: '#FF0000', capitalCityId: 1, chiefGeneralId: 1, gold: 10000, rice: 10000, power: 0, level: 5, typeCode: 'test', meta: {}, }; const mockCity: City = { id: 1, name: 'City A', nationId: 1, level: 1, state: 0, population: 10000, populationMax: 10000, agriculture: 1000, agricultureMax: 1000, commerce: 500, commerceMax: 1000, security: 500, securityMax: 1000, defence: 500, defenceMax: 1000, wall: 500, wallMax: 1000, supplyState: 1, frontState: 0, meta: {}, }; const mockGeneral: General = { id: 1, name: 'Domestic Officer', nationId: 1, cityId: 1, troopId: 0, npcState: 0, experience: 100, dedication: 100, officerLevel: 5, gold: 1000, rice: 1000, crew: 0, crewTypeId: 0, train: 100, atmos: 100, injury: 0, age: 30, stats: { leadership: 50, strength: 50, intelligence: 100 }, role: { personality: null, specialDomestic: null, specialWar: null, items: { horse: null, weapon: null, book: null, item: null }, }, triggerState: { flags: {}, counters: {}, modifiers: {}, meta: {} }, meta: { killturn: 24 }, }; const snapshot: WorldSnapshot = { scenarioConfig: { environment: { mapName: 'minimal_map', unitSet: 'default' }, options: {} } as any, map: MINIMAL_MAP, nations: [mockNation], cities: [mockCity], generals: [mockGeneral], troops: [], diplomacy: [], events: [], initialEvents: [], }; const world = new InMemoryWorld(snapshot); const runner = new TestGameRunner(world, 200, 1); const systemEnv: TurnCommandEnv = { develCost: 50 } as any; const farmingDef = developAgricultureSpec.createDefinition(systemEnv); await runner.runTurn([ { generalId: 1, commandKey: 'che_농지개간', resolver: farmingDef, args: {}, }, ]); const updatedCity = world.getCity(1)!; expect(updatedCity.agriculture).toBe(1000); }); });