feat: 응답 가능한 서신과 턴 시간 호환 이관

등용장과 이민족 선택 응답을 turn daemon transaction으로 연결하고 Ref의 통일 이후 상태 전이와 수신자별 메시지 저장 규칙을 보존한다.\n\n유산 턴 시간 변경을 nextTurnTimeBase 기반 결정적 계산으로 바로잡고 API, 엔진, Chromium 회귀를 추가한다.
This commit is contained in:
2026-08-19 18:03:56 +00:00
parent 9390195d5f
commit 81279e76b5
26 changed files with 1405 additions and 224 deletions
+33 -21
View File
@@ -2149,13 +2149,14 @@ export const createReservedTurnHandler = async (options: {
};
};
export type ImmediateGeneralActionKey = 'che_거병' | 'che_접경귀환';
export type ImmediateGeneralActionKey = 'che_거병' | 'che_접경귀환' | 'che_등용수락';
export type ImmediateGeneralActionExecutor = {
execute(input: {
actionKey: ImmediateGeneralActionKey;
generalId: number;
rng: RandUtil;
args?: Record<string, unknown>;
refreshKillturn?: boolean;
}): Promise<{ ok: boolean; reason?: string }>;
};
@@ -2182,7 +2183,7 @@ export const createImmediateGeneralActionExecutor = async (options: {
});
const generalModuleLoader = new GeneralTurnCommandLoader();
const contextBuilders = new Map<string, ActionContextBuilder>();
for (const actionKey of ['che_거병', 'che_접경귀환'] as const) {
for (const actionKey of ['che_거병', 'che_접경귀환', 'che_등용수락'] as const) {
if (!definitions.has(actionKey)) {
continue;
}
@@ -2202,10 +2203,7 @@ export const createImmediateGeneralActionExecutor = async (options: {
if (!definition) {
return {
ok: false,
reason:
input.actionKey === 'che_거병'
? '거병할 수 없는 모드입니다.'
: '접경귀환을 사용할 수 없는 모드입니다.',
reason: `${input.actionKey}을 실행할 수 없는 모드입니다.`,
};
}
@@ -2215,7 +2213,7 @@ export const createImmediateGeneralActionExecutor = async (options: {
}
const city = options.world.getCityById(general.cityId) ?? undefined;
const nation = general.nationId > 0 ? options.world.getNationById(general.nationId) : null;
const args = definition.parseArgs({});
const args = definition.parseArgs(input.args ?? {});
if (args === null) {
return { ok: false, reason: '인자가 올바르지 않습니다.' };
}
@@ -2246,7 +2244,7 @@ export const createImmediateGeneralActionExecutor = async (options: {
const failureText =
definition.formatConstraintFailure?.(reason, constraintCtx, args, view) ??
`${reason} ${definition.name} 실패.`;
if (input.actionKey === 'che_접경귀환') {
if (input.actionKey === 'che_접경귀환' || input.actionKey === 'che_등용수락') {
options.world.pushLog({
...createActionLog(failureText),
generalId: general.id,
@@ -2334,23 +2332,34 @@ export const createImmediateGeneralActionExecutor = async (options: {
const progressionLogs: LogEntryDraft[] = [];
let nextGeneral = resolution.general as TurnGeneral;
if (input.actionKey === 'che_거병') {
const activeActionAmount =
(
definition as GeneralActionDefinition & {
getInheritanceActiveActionAmount?: (context: ActionContextBase, args: unknown) => number;
}
).getInheritanceActiveActionAmount?.(actionContext, args) ?? 0;
const nextMeta = {
...nextGeneral.meta,
inherit_active_action:
readMetaNumber(asRecord(nextGeneral.meta), 'inherit_active_action', 0) + activeActionAmount,
...(input.refreshKillturn ? { killturn: readMetaNumber(asRecord(state.meta), 'killturn', 0) } : {}),
const activeActionAmount =
(
definition as GeneralActionDefinition & {
getInheritanceActiveActionAmount?: (context: ActionContextBase, args: unknown) => number;
}
).getInheritanceActiveActionAmount?.(actionContext, args) ?? 0;
if (
Number.isFinite(activeActionAmount) &&
activeActionAmount !== 0 &&
nextGeneral.userId &&
nextGeneral.npcState < 2
) {
nextGeneral = {
...nextGeneral,
meta: {
...nextGeneral.meta,
inherit_active_action:
readMetaNumber(asRecord(nextGeneral.meta), 'inherit_active_action', 0) + activeActionAmount,
...(input.refreshKillturn
? { killturn: readMetaNumber(asRecord(state.meta), 'killturn', 0) }
: {}),
},
};
}
if (input.actionKey === 'che_거병') {
nextGeneral = applyLegacyGeneralProgression(
{
...nextGeneral,
meta: nextMeta,
lastTurn: {
command: definition.name,
arg: extractArgsRecord(args),
@@ -2396,6 +2405,9 @@ export const createImmediateGeneralActionExecutor = async (options: {
options.world.queueMessage(effect.draft);
}
}
for (const troopId of resolution.deletedTroopIds ?? []) {
options.world.removeTroop(troopId);
}
for (const log of [...resolution.logs, ...progressionLogs]) {
options.world.pushLog(log);
}