코드 수정

This commit is contained in:
2026-01-11 11:48:12 +00:00
parent 511aade81c
commit cc41d1538e
8 changed files with 87 additions and 122 deletions
+9 -3
View File
@@ -18,6 +18,8 @@ import {
type UnitSetDefinition, type UnitSetDefinition,
type WarBattleOutcome, type WarBattleOutcome,
type WarActionModule, type WarActionModule,
type WarUnitReport,
type CrewTypeDefinition,
} from '@sammo-ts/logic'; } from '@sammo-ts/logic';
import { type BattleSimJobPayload, type BattleSimLogBuckets, type BattleSimResultPayload } from './types.js'; import { type BattleSimJobPayload, type BattleSimLogBuckets, type BattleSimResultPayload } from './types.js';
@@ -217,7 +219,7 @@ const resolveCityRiceConsumption = (options: {
year: number; year: number;
startYear: number; startYear: number;
}): number => { }): number => {
const cityReport = options.battle.reports.find((report: any) => report.type === 'city'); const cityReport = options.battle.reports.find((report: WarUnitReport) => report.type === 'city');
if (!cityReport) { if (!cityReport) {
return 0; return 0;
} }
@@ -225,7 +227,9 @@ const resolveCityRiceConsumption = (options: {
return 0; return 0;
} }
const crewType = options.unitSet.crewTypes?.find((item: any) => item.id === options.castleCrewTypeId); const crewType = options.unitSet.crewTypes?.find(
(item: CrewTypeDefinition) => item.id === options.castleCrewTypeId
);
const riceCoef = crewType?.rice ?? 1; const riceCoef = crewType?.rice ?? 1;
const tech = Number(options.defenderNation.meta.tech ?? 0); const tech = Number(options.defenderNation.meta.tech ?? 0);
const trainAtmos = resolveCityTrainAtmos(options.year, options.startYear); const trainAtmos = resolveCityTrainAtmos(options.year, options.startYear);
@@ -333,7 +337,9 @@ export const processBattleSimJob = (payload: BattleSimJobPayload): BattleSimResu
}); });
lastBattle = outcome; lastBattle = outcome;
const attackerReport = outcome.reports.find((report: any) => report.type === 'general' && report.isAttacker); const attackerReport = outcome.reports.find(
(report: WarUnitReport) => report.type === 'general' && report.isAttacker
);
const killed = attackerReport?.killed ?? 0; const killed = attackerReport?.killed ?? 0;
const dead = attackerReport?.dead ?? 0; const dead = attackerReport?.dead ?? 0;
+1 -1
View File
@@ -328,7 +328,7 @@ const evaluateAvailability = (
reason: result.reason, reason: result.reason,
}; };
} }
const missingKinds = new Set(result.missing.map((req: any) => req.kind)); const missingKinds = new Set(result.missing.map((req: RequirementKey) => req.kind));
const inputOnlyMissing = const inputOnlyMissing =
missingKinds.size === 0 ? reqArg : Array.from(missingKinds).every((kind) => INPUT_REQUIREMENT_KINDS.has(kind)); missingKinds.size === 0 ? reqArg : Array.from(missingKinds).every((kind) => INPUT_REQUIREMENT_KINDS.has(kind));
if (inputOnlyMissing) { if (inputOnlyMissing) {
@@ -5,7 +5,6 @@ import {
nearCity, nearCity,
reqGeneralGold, reqGeneralGold,
reqGeneralRice, reqGeneralRice,
existsDestCity, existsDestCity,
} from '@sammo-ts/logic/constraints/presets.js'; } from '@sammo-ts/logic/constraints/presets.js';
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
@@ -22,7 +22,7 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
import type { GeneralTurnCommandSpec } from './index.js'; import type { GeneralTurnCommandSpec } from './index.js';
export interface ReturnArgs { } export interface ReturnArgs {}
const ACTION_NAME = '귀환'; const ACTION_NAME = '귀환';
const ACTION_KEY = 'che_귀환'; const ACTION_KEY = 'che_귀환';
@@ -1,14 +1,12 @@
import type { General, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js'; import type { General, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js';
import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js'; import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js';
import { import {
allow,
existsDestNation, existsDestNation,
existsDestGeneral, existsDestGeneral,
notSameDestNation, notSameDestNation,
destGeneralInDestNation, destGeneralInDestNation,
notLord, notLord,
readMetaNumberFromUnknown, readMetaNumberFromUnknown,
unknownOrDeny,
} from '@sammo-ts/logic/constraints/presets.js'; } from '@sammo-ts/logic/constraints/presets.js';
import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js';
import type { import type {
@@ -44,6 +42,8 @@ export class ActionResolver<
> implements GeneralActionResolver<TriggerState, AcceptScoutArgs> { > implements GeneralActionResolver<TriggerState, AcceptScoutArgs> {
readonly key = ACTION_KEY; readonly key = ACTION_KEY;
constructor(private readonly env: TurnCommandEnv) {}
resolve( resolve(
context: AcceptScoutResolveContext<TriggerState>, context: AcceptScoutResolveContext<TriggerState>,
_args: AcceptScoutArgs _args: AcceptScoutArgs
@@ -59,7 +59,6 @@ export class ActionResolver<
// 1. Logs // 1. Logs
const destNationName = destNation.name; const destNationName = destNation.name;
const recruiterName = destGeneral.name;
const generalName = general.name; const generalName = general.name;
const josaRo = JosaUtil.pick(destNationName, '로'); const josaRo = JosaUtil.pick(destNationName, '로');
@@ -67,28 +66,14 @@ export class ActionResolver<
// Self Log // Self Log
context.addLog(`<D>${destNationName}</>${josaRo} 망명하여 수도로 이동합니다.`, { context.addLog(`<D>${destNationName}</>${josaRo} 망명하여 수도로 이동합니다.`, {
// Text says "Move to Capital", but logic might move to recruiter city.
// Legacy log says "수도로 이동합니다", but implementation moves to destGeneral city if present!
// We should match implementation or text? Text is just flavor.
category: LogCategory.ACTION, category: LogCategory.ACTION,
format: LogFormat.InGame, // Using InGame or specific format? format: LogFormat.PLAIN,
}); });
// Recruiter Log
// We need to add log to recruiter? `context.addSideEffectLog`?
// Current system mostly logs for the actor.
// If we want to log for recruiter, we might need a way to push logs to others in `effects` or strictly via `addLog` with target?
// `GeneralActionResolveContext` usually implies logs are for the actor.
// But `GeneralTurnOutcome` doesn't explicitly return logs for others.
// We can create a patch for recruiter that appends to their log?
// Or usage of `addLog` might support target? No, `addLog` in context usually targets actor.
// We will skip Recruiter Log for now or rely on Global Log.
// Global Log // Global Log
context.addLog(`<Y>${generalName}</>${josaYi} <D><b>${destNationName}</b></>${josaRo} <S>망명</>하였습니다.`, { context.addLog(`<Y>${generalName}</>${josaYi} <D><b>${destNationName}</b></>${josaRo} <S>망명</>하였습니다.`, {
category: LogCategory.ACTION, // Global category? category: LogCategory.ACTION,
format: LogFormat.InGame, // Global logs are handled by system? format: LogFormat.PLAIN,
// In new system, we might need to specify it.
}); });
// 2. Recruiter Rewards // 2. Recruiter Rewards
@@ -103,12 +88,9 @@ export class ActionResolver<
); );
// 3. Betrayal Logic // 3. Betrayal Logic
// If currentNation exists (and > 0), handle betrayal return logic.
const defaultGold = 1000; // From env? context.env.defaultNpcGold? Or GameConst?
// Using context.env values if available. SystemEnv has `baseGold`?
// Legacy: GameConst::$defaultGold (usually 1000/2000). // Legacy: GameConst::$defaultGold (usually 1000/2000).
const safeGold = context.env.baseGold || 1000; const safeGold = this.env.baseGold || 1000;
const safeRice = context.env.baseRice || 1000; const safeRice = this.env.baseRice || 1000;
let newGold = general.gold; let newGold = general.gold;
let newRice = general.rice; let newRice = general.rice;
@@ -147,10 +129,6 @@ export class ActionResolver<
// Penalty // Penalty
// 10% * betray count deduction // 10% * betray count deduction
const penaltyFactor = 1 - 0.1 * betrayCount; const penaltyFactor = 1 - 0.1 * betrayCount;
if (penaltyFactor < 0) {
// Should not be less than 0? capped at ?
// Legacy: (1 - 0.1 * betray).
}
// Apply penalty // Apply penalty
newExp = Math.floor(newExp * Math.max(0, penaltyFactor)); newExp = Math.floor(newExp * Math.max(0, penaltyFactor));
newDed = Math.floor(newDed * Math.max(0, penaltyFactor)); newDed = Math.floor(newDed * Math.max(0, penaltyFactor));
@@ -162,9 +140,8 @@ export class ActionResolver<
} }
// 4. Update General (Self) // 4. Update General (Self)
let targetCityId = destGeneral.cityId; // Join recruiter const targetCityId = destNation.capitalCityId;
// If recruiter is not valid city? if (!targetCityId) throw new Error('Capital city not found.');
if (!targetCityId) targetCityId = destNation.capitalCityId!;
effects.push( effects.push(
createGeneralPatchEffect( createGeneralPatchEffect(
@@ -176,30 +153,17 @@ export class ActionResolver<
gold: newGold, gold: newGold,
rice: newRice, rice: newRice,
officerLevel: 1, // Reset rank officerLevel: 1, // Reset rank
// officer_city: 0 via meta
crew: general.crew, // Keep crew? Legacy implies checking troop leader.
// If troop leader, disband troop.
// TS entity `troopId`.
troopId: 0, // Quit troop troopId: 0, // Quit troop
meta: { meta: {
...general.meta, ...general.meta,
officer_city: 0, officer_city: 0,
betray: newBetray, betray: newBetray,
// killturn logic?
}, },
}, },
general.id general.id
) )
); );
// 5. Update Nations Gen Count (Visual only? or real count)
// Legacy updates `gennum`.
// We can create patches for nations if `gennum` is part of Nation entity?
// Nation entity usually doesn't store computed `gennum` in TS domain?
// If it's real column, we can update.
// Checking entity: `Nation` interface does NOT have `gennum`.
// So we skip updating gennum on Nation entity.
return { effects }; return { effects };
} }
} }
@@ -211,26 +175,18 @@ export class ActionDefinition<
public readonly name = ACTION_NAME; public readonly name = ACTION_NAME;
private readonly resolver: ActionResolver<TriggerState>; private readonly resolver: ActionResolver<TriggerState>;
constructor() { constructor(env: TurnCommandEnv) {
this.resolver = new ActionResolver(); this.resolver = new ActionResolver(env);
} }
parseArgs(raw: unknown): AcceptScoutArgs | null { parseArgs(raw: unknown): AcceptScoutArgs | null {
// Validate args
const args = raw as Partial<AcceptScoutArgs>; const args = raw as Partial<AcceptScoutArgs>;
if (typeof args.destNationId !== 'number' || typeof args.destGeneralId !== 'number') return null; if (typeof args.destNationId !== 'number' || typeof args.destGeneralId !== 'number') return null;
return { destNationId: args.destNationId, destGeneralId: args.destGeneralId }; return { destNationId: args.destNationId, destGeneralId: args.destGeneralId };
} }
buildConstraints(_ctx: ConstraintContext, _args: AcceptScoutArgs): Constraint[] { buildConstraints(_ctx: ConstraintContext, _args: AcceptScoutArgs): Constraint[] {
return [ return [existsDestNation(), existsDestGeneral(), notSameDestNation(), destGeneralInDestNation(), notLord()];
// notBeNeutral(), // Ignored to allow betrayal
existsDestNation(),
existsDestGeneral(), // Need to check if destGeneral exists
notSameDestNation(),
destGeneralInDestNation(),
notLord(),
];
} }
resolve( resolve(
@@ -242,16 +198,15 @@ export class ActionDefinition<
} }
export const actionContextBuilder: ActionContextBuilder = (base, options) => { export const actionContextBuilder: ActionContextBuilder = (base, options) => {
// Populate destNation and destGeneral const args = options.actionArgs as Partial<AcceptScoutArgs>;
const args = base.args as Partial<AcceptScoutArgs>;
let destNation: Nation | undefined; let destNation: Nation | undefined;
let destGeneral: General | undefined; let destGeneral: General | undefined;
if (args.destNationId) { if (args.destNationId) {
destNation = options.worldRef?.getNation(args.destNationId); destNation = options.worldRef?.getNationById(args.destNationId) ?? undefined;
} }
if (args.destGeneralId) { if (args.destGeneralId) {
destGeneral = options.worldRef?.getGeneral(args.destGeneralId); destGeneral = options.worldRef?.getGeneralById(args.destGeneralId) ?? undefined;
} }
return { return {
@@ -263,14 +218,11 @@ export const actionContextBuilder: ActionContextBuilder = (base, options) => {
export const commandSpec: GeneralTurnCommandSpec = { export const commandSpec: GeneralTurnCommandSpec = {
key: 'che_등용수락', key: 'che_등용수락',
category: '계략', // Strategy? or '인사'(Personnel)? Legacy not checked for category. "군사" in task.md? category: '인사',
// che_등용수락 is usually separate.
// che_등용 is 인사(Personnel).
// Let's use '인사'.
reqArg: true, reqArg: true,
args: { args: {
destNationId: 'number', destNationId: 'number',
destGeneralId: 'number', destGeneralId: 'number',
}, },
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(), createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
}; };
@@ -111,7 +111,7 @@ export class GeneralTurnCommandLoader {
constructor( constructor(
private readonly importers: Record<GeneralTurnCommandKey, GeneralTurnCommandImporter> = defaultImporters private readonly importers: Record<GeneralTurnCommandKey, GeneralTurnCommandImporter> = defaultImporters
) { } ) {}
async load(key: GeneralTurnCommandKey): Promise<GeneralTurnCommandModule> { async load(key: GeneralTurnCommandKey): Promise<GeneralTurnCommandModule> {
const cached = this.cache.get(key); const cached = this.cache.get(key);
+15 -15
View File
@@ -114,11 +114,11 @@ export const suppliedDestCity = (): Constraint => ({
requires: (ctx) => requires: (ctx) =>
resolveDestCityId(ctx) !== undefined resolveDestCityId(ctx) !== undefined
? [ ? [
{ {
kind: 'destCity', kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0, id: resolveDestCityId(ctx) ?? 0,
}, },
] ]
: [], : [],
test: (ctx, view) => { test: (ctx, view) => {
const destCity = readDestCity(ctx, view); const destCity = readDestCity(ctx, view);
@@ -250,11 +250,11 @@ export const existsDestCity = (): Constraint => ({
requires: (ctx) => requires: (ctx) =>
resolveDestCityId(ctx) !== undefined resolveDestCityId(ctx) !== undefined
? [ ? [
{ {
kind: 'destCity', kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0, id: resolveDestCityId(ctx) ?? 0,
}, },
] ]
: [], : [],
test: (ctx, view) => { test: (ctx, view) => {
const destCityId = resolveDestCityId(ctx); const destCityId = resolveDestCityId(ctx);
@@ -316,11 +316,11 @@ export const notNeutralDestCity = (): Constraint => ({
requires: (ctx) => requires: (ctx) =>
resolveDestCityId(ctx) !== undefined resolveDestCityId(ctx) !== undefined
? [ ? [
{ {
kind: 'destCity', kind: 'destCity',
id: resolveDestCityId(ctx) ?? 0, id: resolveDestCityId(ctx) ?? 0,
}, },
] ]
: [], : [],
test: (ctx, view) => { test: (ctx, view) => {
const destCity = readDestCity(ctx, view); const destCity = readDestCity(ctx, view);
@@ -5,7 +5,6 @@ import { InMemoryWorld, TestGameRunner } from '../../testEnv.js';
import { evaluateActionConstraints } from '../../../src/constraints/evaluate.js'; import { evaluateActionConstraints } from '../../../src/constraints/evaluate.js';
import type { TurnCommandEnv } from '../../../src/actions/turn/commandEnv.js'; import type { TurnCommandEnv } from '../../../src/actions/turn/commandEnv.js';
import type { ConstraintContext, RequirementKey, StateView } from '../../../src/constraints/types.js'; import type { ConstraintContext, RequirementKey, StateView } from '../../../src/constraints/types.js';
import { readGeneral } from '../../../src/constraints/helpers.js';
const MOCK_SCENARIO_BASE = { const MOCK_SCENARIO_BASE = {
title: 'Test', title: 'Test',
@@ -84,15 +83,15 @@ const systemEnv: TurnCommandEnv = {
maxResourceActionAmount: 1000, maxResourceActionAmount: 1000,
}; };
function createConstraintContext(actor: General, year: number = 200, args: any = {}): ConstraintContext { function createConstraintContext(actorId: number, cityId: number, nationId: number, args: any = {}): ConstraintContext {
return { return {
actorId: actor.id, actorId,
cityId: actor.cityId, cityId,
nationId: actor.nationId || 0, nationId,
args, args,
env: { env: {
...systemEnv, ...systemEnv,
world: { currentYear: year }, world: { currentYear: 200 },
openingPartYear: systemEnv.openingPartYear, openingPartYear: systemEnv.openingPartYear,
map: MINIMAL_MAP, map: MINIMAL_MAP,
cities: MINIMAL_MAP.cities, cities: MINIMAL_MAP.cities,
@@ -186,7 +185,12 @@ describe('che_등용수락', () => {
experience: 0, experience: 0,
dedication: 0, dedication: 0,
officerLevel: 1, officerLevel: 1,
role: null as any, role: {
personality: null,
specialDomestic: null,
specialWar: null,
items: { horse: null, weapon: null, book: null, item: null },
},
injury: 0, injury: 0,
gold: 1000, gold: 1000,
rice: 1000, rice: 1000,
@@ -218,18 +222,17 @@ describe('che_등용수락', () => {
world.snapshot.generals.push(recruiterGen); world.snapshot.generals.push(recruiterGen);
world.snapshot.nations.push(nation2); world.snapshot.nations.push(nation2);
const { commandSpec } = await import('../../../src/actions/turn/general/che_등용수락.js');
await runner.runTurn([ await runner.runTurn([
{ {
generalId: neutralGen.id, generalId: neutralGen.id,
commandKey: 'che_등용수락', commandKey: 'che_등용수락',
resolver: ( resolver: commandSpec.createDefinition(systemEnv),
await import('../../../src/actions/turn/general/che_등용수락.js')
).commandSpec.createDefinition({} as any),
args: { destNationId: 2, destGeneralId: 2 }, args: { destNationId: 2, destGeneralId: 2 },
context: { context: {
destNation: nation2, destNation: nation2,
destGeneral: recruiterGen, destGeneral: recruiterGen,
env: systemEnv,
}, },
}, },
]); ]);
@@ -238,7 +241,7 @@ describe('che_등용수락', () => {
const updatedRecruiter = world.getGeneral(recruiterGen.id); const updatedRecruiter = world.getGeneral(recruiterGen.id);
expect(updatedSelf?.nationId).toBe(2); expect(updatedSelf?.nationId).toBe(2);
expect(updatedSelf?.cityId).toBe(102); expect(updatedSelf?.cityId).toBe(102); // Capital of Nation2
expect(updatedSelf?.experience).toBe(100); expect(updatedSelf?.experience).toBe(100);
expect(updatedSelf?.dedication).toBe(100); expect(updatedSelf?.dedication).toBe(100);
@@ -265,7 +268,12 @@ describe('che_등용수락', () => {
experience: 1000, experience: 1000,
dedication: 1000, dedication: 1000,
officerLevel: 1, officerLevel: 1,
role: null as any, role: {
personality: null,
specialDomestic: null,
specialWar: null,
items: { horse: null, weapon: null, book: null, item: null },
},
injury: 0, injury: 0,
gold: 2000, gold: 2000,
rice: 2000, rice: 2000,
@@ -302,7 +310,12 @@ describe('che_등용수락', () => {
experience: 0, experience: 0,
dedication: 0, dedication: 0,
officerLevel: 1, officerLevel: 1,
role: null as any, role: {
personality: null,
specialDomestic: null,
specialWar: null,
items: { horse: null, weapon: null, book: null, item: null },
},
injury: 0, injury: 0,
gold: 1000, gold: 1000,
rice: 1000, rice: 1000,
@@ -334,18 +347,17 @@ describe('che_등용수락', () => {
world.snapshot.nations.push(nation1); world.snapshot.nations.push(nation1);
world.snapshot.nations.push(nation2); world.snapshot.nations.push(nation2);
const { commandSpec } = await import('../../../src/actions/turn/general/che_등용수락.js');
await runner.runTurn([ await runner.runTurn([
{ {
generalId: betrayer.id, generalId: betrayer.id,
commandKey: 'che_등용수락', commandKey: 'che_등용수락',
resolver: ( resolver: commandSpec.createDefinition(systemEnv),
await import('../../../src/actions/turn/general/che_등용수락.js')
).commandSpec.createDefinition({} as any),
args: { destNationId: 2, destGeneralId: 2 }, args: { destNationId: 2, destGeneralId: 2 },
context: { context: {
destNation: nation2, destNation: nation2,
destGeneral: recruiterGen, destGeneral: recruiterGen,
env: systemEnv,
}, },
}, },
]); ]);
@@ -453,22 +465,16 @@ describe('che_등용수락', () => {
world.snapshot.nations.push(nation1); world.snapshot.nations.push(nation1);
world.snapshot.nations.push(nation2); world.snapshot.nations.push(nation2);
// Manually check constraints for denial const { commandSpec } = await import('../../../src/actions/turn/general/che_등용수락.js');
const def = (await import('../../../src/actions/turn/general/che_등용수락.js')).commandSpec.createDefinition( const def = commandSpec.createDefinition(systemEnv);
{} as any
);
const args = { destNationId: 2, destGeneralId: 2 }; const args = { destNationId: 2, destGeneralId: 2 };
const ctx = createConstraintContext(monarch, 200, args); const ctx = createConstraintContext(monarch.id, monarch.cityId, monarch.nationId!, args);
const view = createViewState(world, 200); const view = createViewState(world, 200);
const result = evaluateActionConstraints(def, ctx, view, args); const result = evaluateActionConstraints(def, ctx, view, args);
expect(result.kind).toBe('deny'); expect(result.kind).toBe('deny');
if (result.kind === 'deny') { if (result.kind === 'deny') {
const reason = result.constraintName || result.reason;
// notLord constraint failure.
// In TS presets, notLord(monarch) returns deny.
// ConstraintName should be 'notLord' or 'NotLord'.
expect(result.constraintName).toMatch(/NotLord/i); expect(result.constraintName).toMatch(/NotLord/i);
} }
}); });
@@ -490,7 +496,12 @@ describe('che_등용수락', () => {
experience: 0, experience: 0,
dedication: 0, dedication: 0,
officerLevel: 1, officerLevel: 1,
role: null as any, role: {
personality: null,
specialDomestic: null,
specialWar: null,
items: { horse: null, weapon: null, book: null, item: null },
},
injury: 0, injury: 0,
gold: 1000, gold: 1000,
rice: 1000, rice: 1000,
@@ -516,8 +527,6 @@ describe('che_등용수락', () => {
typeCode: 'che_def', typeCode: 'che_def',
meta: {}, meta: {},
}; };
// Recruiter also in same nation? Or different?
// Arg destNationId is key.
const recruiterGen: General = { const recruiterGen: General = {
id: 2, id: 2,
name: 'Recruiter', name: 'Recruiter',
@@ -546,12 +555,11 @@ describe('che_등용수락', () => {
world.snapshot.generals.push(recruiterGen); world.snapshot.generals.push(recruiterGen);
world.snapshot.nations.push(nation1); world.snapshot.nations.push(nation1);
const def = (await import('../../../src/actions/turn/general/che_등용수락.js')).commandSpec.createDefinition( const { commandSpec } = await import('../../../src/actions/turn/general/che_등용수락.js');
{} as any const def = commandSpec.createDefinition(systemEnv);
); const args = { destNationId: 1, destGeneralId: 2 };
const args = { destNationId: 1, destGeneralId: 2 }; // Target same nation 1
const ctx = createConstraintContext(general, 200, args); const ctx = createConstraintContext(general.id, general.cityId, general.nationId!, args);
const view = createViewState(world, 200); const view = createViewState(world, 200);
const result = evaluateActionConstraints(def, ctx, view, args); const result = evaluateActionConstraints(def, ctx, view, args);