fix resource transfer failure log parity
This commit is contained in:
@@ -907,6 +907,7 @@ export const createReservedTurnHandler = async (options: {
|
||||
const constraints = definition.buildConstraints(constraintCtx, actionArgs);
|
||||
const result = evaluateConstraints(constraints, constraintCtx, view);
|
||||
if (result.kind !== 'allow') {
|
||||
const failedCommandName = definition.name;
|
||||
definition = fallbackDefinition;
|
||||
actionArgs = definition.parseArgs({}) ?? {};
|
||||
actionKey = definition.key;
|
||||
@@ -914,7 +915,7 @@ export const createReservedTurnHandler = async (options: {
|
||||
const reason = result.kind === 'deny' ? result.reason : '조건을 확인할 수 없습니다.';
|
||||
blockedReason = reason;
|
||||
const meta = result.kind === 'deny' ? { constraintName: result.constraintName } : undefined;
|
||||
logs.push(createActionLog(reason, meta));
|
||||
logs.push(createActionLog(`${reason} ${failedCommandName} 실패.`, meta));
|
||||
}
|
||||
if (!usedFallback && (kind === 'general' || currentNation)) {
|
||||
const currentYearMonth = joinYearMonth(context.world.currentYear, context.world.currentMonth);
|
||||
|
||||
@@ -618,6 +618,7 @@ describe('Reserved Turn Execution Integration', () => {
|
||||
const dirty = world.consumeDirtyState();
|
||||
expect(world.getGeneralById(1)!.cityId).toBe(1);
|
||||
const denyLog = dirty.logs.find((log) => log.text.includes('같은 도시입니다.'));
|
||||
expect(denyLog?.text).toContain('이동 실패.');
|
||||
expect(denyLog?.meta?.constraintName).toBe('notSameDestCity');
|
||||
expect(dirty.logs.some((log) => log.text.includes('아무것도 실행하지 않았습니다.'))).toBe(true);
|
||||
});
|
||||
|
||||
@@ -583,7 +583,7 @@ export const friendlyDestGeneral = (): Constraint => ({
|
||||
if (destGeneral.nationId === general.nationId) {
|
||||
return allow();
|
||||
}
|
||||
return { kind: 'deny', reason: '아군이 아닙니다.' };
|
||||
return { kind: 'deny', reason: '아국 장수가 아닙니다.' };
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -3497,6 +3497,108 @@ integration('general command gift resource and target boundaries', () => {
|
||||
}, 120_000);
|
||||
});
|
||||
|
||||
interface ResourceTransferBoundaryCase {
|
||||
name: string;
|
||||
action: 'che_증여' | 'che_헌납' | 'che_군량매매';
|
||||
args: Record<string, unknown>;
|
||||
actorPatch?: Record<string, unknown>;
|
||||
fixturePatches?: FixturePatches;
|
||||
completed: boolean;
|
||||
}
|
||||
|
||||
const resourceTransferBoundaryCases: ResourceTransferBoundaryCase[] = [
|
||||
{
|
||||
name: 'gift completes with a zero transferable gold amount at the exact minimum',
|
||||
action: 'che_증여',
|
||||
args: { isGold: true, amount: 100, destGeneralID: 3 },
|
||||
actorPatch: { gold: 0 },
|
||||
completed: true,
|
||||
},
|
||||
{
|
||||
name: 'gift rejects a general from another nation',
|
||||
action: 'che_증여',
|
||||
args: { isGold: true, amount: 100, destGeneralID: 2 },
|
||||
completed: false,
|
||||
},
|
||||
{
|
||||
name: 'donation at the exact rice minimum may spend below that minimum',
|
||||
action: 'che_헌납',
|
||||
args: { isGold: false, amount: 100 },
|
||||
actorPatch: { rice: 500, crew: 0 },
|
||||
completed: true,
|
||||
},
|
||||
{
|
||||
name: 'donation completes with zero gold at the zero minimum',
|
||||
action: 'che_헌납',
|
||||
args: { isGold: true, amount: 100 },
|
||||
actorPatch: { gold: 0 },
|
||||
completed: true,
|
||||
},
|
||||
{
|
||||
name: 'buying rice with one gold preserves the fee and integer persistence boundary',
|
||||
action: 'che_군량매매',
|
||||
args: { buyRice: true, amount: 100 },
|
||||
actorPatch: { gold: 1, rice: 0 },
|
||||
completed: true,
|
||||
},
|
||||
{
|
||||
name: 'selling one rice preserves the fee and integer persistence boundary',
|
||||
action: 'che_군량매매',
|
||||
args: { buyRice: false, amount: 100 },
|
||||
actorPatch: { gold: 0, rice: 1, crew: 0 },
|
||||
completed: true,
|
||||
},
|
||||
{
|
||||
name: 'trade rate 137 preserves resource, tax, stat experience, and logs',
|
||||
action: 'che_군량매매',
|
||||
args: { buyRice: false, amount: 1_000 },
|
||||
fixturePatches: { cities: { 3: { trade: 137 } } },
|
||||
completed: true,
|
||||
},
|
||||
{
|
||||
name: 'a neutral general can trade in a neutral trader city without nation tax',
|
||||
action: 'che_군량매매',
|
||||
args: { buyRice: false, amount: 1_000 },
|
||||
actorPatch: { nationId: 0, officerLevel: 0 },
|
||||
fixturePatches: { cities: { 3: { nationId: 0 } } },
|
||||
completed: true,
|
||||
},
|
||||
];
|
||||
|
||||
integration('general resource transfer target, fee, state, and log parity', () => {
|
||||
it.each(resourceTransferBoundaryCases)(
|
||||
'$name',
|
||||
async ({ action, args, actorPatch, fixturePatches, completed }) => {
|
||||
const request = buildRequest(action, args, actorPatch, fixturePatches);
|
||||
request.setup!.world!.hiddenSeed = `general-resource-transfer-${action}-${String(args.isGold ?? args.buyRice)}`;
|
||||
request.observe!.includeGlobalHistoryLogs = true;
|
||||
const reference = runReferenceTurnCommandTraceRequest(
|
||||
workspaceRoot!,
|
||||
request as unknown as Record<string, unknown>
|
||||
);
|
||||
const core = await runCoreTurnCommandTrace(request, reference.before);
|
||||
|
||||
expect(reference.execution.outcome).toMatchObject({ completed });
|
||||
expect(core.execution.outcome).toMatchObject({
|
||||
requestedAction: action,
|
||||
actionKey: completed ? action : '휴식',
|
||||
usedFallback: !completed,
|
||||
});
|
||||
expect(core.rng).toEqual(reference.rng);
|
||||
expect(
|
||||
compareTurnSnapshotDeltas(reference.before, reference.after, core.before, core.after, {
|
||||
ignoredPathPatterns: ignoredLifecyclePaths,
|
||||
})
|
||||
).toEqual([]);
|
||||
|
||||
expect(semanticLogSignatures(core.after.logs)).toEqual(
|
||||
semanticLogSignatures(addedReferenceLogs(reference.before, reference.after.logs))
|
||||
);
|
||||
},
|
||||
120_000
|
||||
);
|
||||
});
|
||||
|
||||
type GeneralConstraintCase = {
|
||||
name: string;
|
||||
action: string;
|
||||
|
||||
Reference in New Issue
Block a user