diff --git a/docs/architecture/turn-state-differential-testing.md b/docs/architecture/turn-state-differential-testing.md index 70964e3..4aff3ad 100644 --- a/docs/architecture/turn-state-differential-testing.md +++ b/docs/architecture/turn-state-differential-testing.md @@ -56,9 +56,10 @@ normalized to the core `*Id` spelling before command identity comparison. canonical projection. - `trace.ts`: before/execute/after capture boundary. - `compare.ts`: exact snapshot and delta comparison. - - `turnCommandGeneralMatrix.integration.test.ts`: 29 successful general + - `turnCommandGeneralMatrix.integration.test.ts`: 37 successful general command paths, including the four-call `전투태세` completion path and - movement/return/gift/spy/sabotage commands. + movement/return/gift/spy/sabotage, recruitment, proficiency transfer, + trait reset and equipment-trade commands. - `turnCommandNationMatrix.integration.test.ts`: 8 successful nation command paths. - `turnCommandCoreReference.integration.test.ts`: declaration and live @@ -163,9 +164,9 @@ Compatibility is established per case only when: 4. live sortie also passes the battle trace comparison; 5. any ignored path is documented in the case evidence. -As of 2026-07-25, 29 general cases, 8 nation cases, declaration and live sortie +As of 2026-07-25, 37 general cases, 8 nation cases, declaration and live sortie pass this boundary. Live sortie covers battle entry, conquest, defeated-general -neutralization and last-city nation collapse. This is 39 executable comparison +neutralization and last-city nation collapse. This is 47 executable comparison cases, not a claim that all 55 general and 38 nation command classes have been dynamically compared. diff --git a/packages/logic/src/actions/turn/general/che_모병.ts b/packages/logic/src/actions/turn/general/che_모병.ts index c6d4c00..b416eb6 100644 --- a/packages/logic/src/actions/turn/general/che_모병.ts +++ b/packages/logic/src/actions/turn/general/che_모병.ts @@ -1,7 +1,10 @@ import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js'; -import { ARGS_SCHEMA, ActionDefinition as RecruitActionDefinition } from './che_징병.js'; +import { + ARGS_SCHEMA, + ActionDefinition as RecruitActionDefinition, + actionContextBuilder as recruitActionContextBuilder, +} from './che_징병.js'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; -import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; import type { GeneralActionModule } from '@sammo-ts/logic/triggers/general-action.js'; @@ -13,6 +16,7 @@ export class ActionDefinition< constructor(modules: GeneralActionModule[]) { super(modules, { + actionName: '모병', costOffset: 2, defaultTrain: 70, // GameConst::$defaultTrainHigh defaultAtmos: 70, // GameConst::$defaultAtmosHigh @@ -20,7 +24,8 @@ export class ActionDefinition< } } -export const actionContextBuilder = defaultActionContextBuilder; +// 모병도 징병과 동일하게 병종/지도/연도 컨텍스트가 필요하다. +export const actionContextBuilder = recruitActionContextBuilder; export const commandSpec: GeneralTurnCommandSpec = { key: 'che_모병', diff --git a/packages/logic/src/actions/turn/general/che_징병.ts b/packages/logic/src/actions/turn/general/che_징병.ts index 829bc51..5f1a025 100644 --- a/packages/logic/src/actions/turn/general/che_징병.ts +++ b/packages/logic/src/actions/turn/general/che_징병.ts @@ -418,7 +418,7 @@ export class ActionResolver< general.meta = addMetaNumber(general.meta, dexGain.key, dexGain.amount); } - tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME }); + tryApplyUniqueLottery(context, { acquireType: '아이템', reason: actionName }); return { effects: [] }; } diff --git a/packages/logic/src/actions/turn/general/che_탈취.ts b/packages/logic/src/actions/turn/general/che_탈취.ts index 62a7aae..3fb35cd 100644 --- a/packages/logic/src/actions/turn/general/che_탈취.ts +++ b/packages/logic/src/actions/turn/general/che_탈취.ts @@ -101,8 +101,11 @@ export class ActionResolver< const rawGold = result.agriDamage * destCity.level * yearCoef * (0.25 + commRatio / 4); const rawRice = result.commDamage * destCity.level * yearCoef * (0.25 + agriRatio / 4); - let stolenGold = Math.floor(rawGold); - let stolenRice = Math.floor(rawRice); + // 레거시는 탈취량을 부동소수점으로 유지한 채 국가/장수 DB 정수 필드에 + // 기록할 때 반올림한다. 여기서 미리 내림하면 국고와 본국 몫이 1씩 + // 달라질 수 있다. + let stolenGold = rawGold; + let stolenRice = rawRice; const isSupplied = destCity.supplyState === 1; @@ -119,9 +122,8 @@ export class ActionResolver< effects.push( createNationPatchEffect( { - ...destNation, - gold: destNation.gold - stolenGold, - rice: destNation.rice - stolenRice, + gold: Math.round(destNation.gold - stolenGold), + rice: Math.round(destNation.rice - stolenRice), }, destNation.id ) @@ -130,23 +132,23 @@ export class ActionResolver< effects.push( createCityPatchEffect( { - ...destCity, - state: 34, + // 레거시는 같은 명령 안에서 잠시 34로 쓴 뒤 최종 32로 + // 덮어쓴다. 관찰 가능한 최종 상태는 32다. + state: 32, }, args.destCityId ) ); } else { - const commDmg = Math.floor(stolenGold / 12); - const agriDmg = Math.floor(stolenRice / 12); + const commDmg = stolenGold / 12; + const agriDmg = stolenRice / 12; effects.push( createCityPatchEffect( { - ...destCity, - commerce: Math.max(0, destCity.commerce - commDmg), - agriculture: Math.max(0, destCity.agriculture - agriDmg), - state: 34, + commerce: Math.round(Math.max(0, destCity.commerce - commDmg)), + agriculture: Math.round(Math.max(0, destCity.agriculture - agriDmg)), + state: 32, }, args.destCityId ) @@ -165,7 +167,6 @@ export class ActionResolver< effects.push( createNationPatchEffect( { - ...nation, gold: nation.gold + nationShareGold, rice: nation.rice + nationShareRice, }, @@ -186,8 +187,8 @@ export class ActionResolver< }); consumeSuccessfulStrategyItem(this.pipeline, context); - general.gold += myShareGold; - general.rice += myShareRice; + general.gold = Math.round(general.gold + myShareGold); + general.rice = Math.round(general.rice + myShareRice); return { effects }; } diff --git a/tools/integration-tests/test/turnCommandGeneralMatrix.integration.test.ts b/tools/integration-tests/test/turnCommandGeneralMatrix.integration.test.ts index 64f71b5..718072b 100644 --- a/tools/integration-tests/test/turnCommandGeneralMatrix.integration.test.ts +++ b/tools/integration-tests/test/turnCommandGeneralMatrix.integration.test.ts @@ -190,6 +190,22 @@ const cases: Array<[string, Record | undefined, Record {