fix: 기본 등용과 대상 임관 명령을 복구

Ref 기본 일반장 명령 46개 exact-set 검사를 추가하고 등용 및 장수대상임관을 사용자 명령표에 복원한다. 실제 DB와 API, 데몬, Chromium을 연결해 등용 서신 수신과 수락 임관까지 검증하며 내부 등용수락 명령의 profile 로딩도 바로잡는다.
This commit is contained in:
2026-08-21 01:33:02 +00:00
parent 7b14585f0d
commit 6db5192a4d
8 changed files with 573 additions and 3 deletions
@@ -2204,9 +2204,18 @@ export const createImmediateGeneralActionExecutor = async (options: {
}): Promise<ImmediateGeneralActionExecutor> => {
const env = buildCommandEnv(options.world.getScenarioConfig(), options.world.getUnitSet());
const commandProfile = options.commandProfile ?? DEFAULT_TURN_COMMAND_PROFILE;
// 등용수락은 예약 화면에 노출되는 명령이 아니라 등용 서신의 응답이
// 직접 실행하는 내부 명령이다. 선택 가능 명령 프로필에 없더라도 등용
// 서신을 수락할 수 있도록 즉시 행동 정의에는 항상 포함한다.
const immediateCommandProfile: TurnCommandProfile = commandProfile.general.includes('che_등용수락')
? commandProfile
: {
...commandProfile,
general: [...commandProfile.general, 'che_등용수락'],
};
const { general: definitions } = await buildReservedTurnDefinitions({
env,
commandProfile,
commandProfile: immediateCommandProfile,
defaultActionKey: DEFAULT_ACTION,
});
const generalModuleLoader = new GeneralTurnCommandLoader();
@@ -133,6 +133,7 @@ const buildWorld = (
const buildImmediateActionWorld = (options: {
general: TurnGeneral;
additionalGenerals?: TurnGeneral[];
cities: TurnWorldSnapshot['cities'];
nations: TurnWorldSnapshot['nations'];
map: MapDefinition;
@@ -162,7 +163,7 @@ const buildImmediateActionWorld = (options: {
ignoreDefaultEvents: false,
};
const snapshot: TurnWorldSnapshot = {
generals: [options.general],
generals: [options.general, ...(options.additionalGenerals ?? [])],
cities: options.cities,
nations: options.nations,
troops: [],
@@ -480,6 +481,81 @@ describe('my information world commands', () => {
expect(nextIntInclusive).not.toHaveBeenCalled();
});
it('loads the internal recruitment acceptance action outside the selectable command profile', async () => {
const recipient = buildGeneral({
id: 8,
userId: 'user-8',
name: '재야장수',
nationId: 0,
cityId: 1,
officerLevel: 0,
});
const recruiter = buildGeneral({
id: 9,
userId: 'user-9',
name: '등용장수',
nationId: 2,
cityId: 2,
});
const map = {
id: 'test',
name: 'test',
cities: [buildMapCity(1, [2]), buildMapCity(2, [1])],
};
const fixture = buildImmediateActionWorld({
general: recipient,
additionalGenerals: [recruiter],
cities: [
{ id: 1, name: '낙양', nationId: 0, supplyState: 1, meta: {} },
{ id: 2, name: '장안', nationId: 2, supplyState: 1, meta: {} },
] as TurnWorldSnapshot['cities'],
nations: [
{
id: 2,
name: '등용국',
color: '#222222',
typeCode: 'che_중립',
level: 1,
capitalCityId: 2,
chiefGeneralId: recruiter.id,
gold: 0,
rice: 0,
power: 0,
meta: { gennum: 1 },
},
] as TurnWorldSnapshot['nations'],
map,
});
const executor = await createImmediateGeneralActionExecutor({
world: fixture.world,
reservedTurns: fixture.reservedTurns,
scenarioMeta: fixture.scenarioMeta,
map,
commandProfile: {
general: ['che_등용'],
nation: [],
},
});
await expect(
executor.execute({
actionKey: 'che_등용수락',
generalId: recipient.id,
rng: new RandUtil(new LiteHashDRBG('accept-recruitment-letter')),
args: { destNationId: 2, destGeneralId: recruiter.id },
})
).resolves.toEqual({ ok: true });
expect(fixture.world.getGeneralById(recipient.id)).toMatchObject({
nationId: 2,
cityId: 2,
officerLevel: 1,
});
expect(fixture.world.getGeneralById(recruiter.id)).toMatchObject({
experience: recruiter.experience + 100,
dedication: recruiter.dedication + 100,
});
});
it('preserves the Ref uprising precheck order and messages after the game starts', async () => {
const general = buildGeneral({ nationId: 1, cityId: 1 });
const fixture = buildImmediateActionWorld({