Fix recruitment and seizure command parity
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -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<TriggerState>[]) {
|
||||
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_모병',
|
||||
|
||||
@@ -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: [] };
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -190,6 +190,22 @@ const cases: Array<[string, Record<string, unknown> | undefined, Record<string,
|
||||
['che_첩보', { destCityID: 70 }, undefined],
|
||||
['che_화계', { destCityID: 70 }, undefined],
|
||||
['che_파괴', { destCityID: 70 }, undefined],
|
||||
['che_선동', { destCityID: 70 }, undefined],
|
||||
['che_탈취', { destCityID: 70 }, undefined],
|
||||
['che_모병', { crewType: 1100, amount: 100 }, undefined],
|
||||
['che_징병', { crewType: 1100, amount: 100 }, undefined],
|
||||
['che_숙련전환', { srcArmType: 1, destArmType: 2 }, { dex1: 100 }],
|
||||
[
|
||||
'che_내정특기초기화',
|
||||
undefined,
|
||||
{ specialDomestic: 'che_인덕', lastTurn: { command: '내정 특기 초기화', term: 1 } },
|
||||
],
|
||||
[
|
||||
'che_전투특기초기화',
|
||||
undefined,
|
||||
{ specialWar: 'che_귀병', lastTurn: { command: '전투 특기 초기화', term: 1 } },
|
||||
],
|
||||
['che_장비매매', { itemType: 'weapon', itemCode: 'che_무기_01_단도' }, undefined],
|
||||
];
|
||||
|
||||
integration('general command success matrix', () => {
|
||||
|
||||
Reference in New Issue
Block a user