fix domestic trait reset unique lottery
This commit is contained in:
@@ -5,6 +5,7 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition
|
|||||||
import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js';
|
import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js';
|
||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
|
import { tryApplyUniqueLottery } from '@sammo-ts/logic/rewards/uniqueLottery.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
import { setMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
import { setMetaNumber } from '@sammo-ts/logic/war/utils.js';
|
||||||
import { DOMESTIC_TRAIT_KEYS } from '@sammo-ts/logic/triggers/special/domestic/index.js';
|
import { DOMESTIC_TRAIT_KEYS } from '@sammo-ts/logic/triggers/special/domestic/index.js';
|
||||||
@@ -86,6 +87,7 @@ export class ActionDefinition<
|
|||||||
delete general.meta.specAge;
|
delete general.meta.specAge;
|
||||||
setMetaNumber(general.meta, 'specage', general.age + 1);
|
setMetaNumber(general.meta, 'specage', general.age + 1);
|
||||||
context.addLog('새로운 내정 특기를 가질 준비가 되었습니다.');
|
context.addLog('새로운 내정 특기를 가질 준비가 되었습니다.');
|
||||||
|
tryApplyUniqueLottery(context, { acquireType: '아이템', reason: ACTION_NAME });
|
||||||
return { effects: [] };
|
return { effects: [] };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { LEGACY_RANK_DATA_TYPES } from '@sammo-ts/common';
|
import { asRecord, LEGACY_RANK_DATA_TYPES } from '@sammo-ts/common';
|
||||||
|
|
||||||
import { compareTurnSnapshotDeltas } from '../src/turn-differential/compare.js';
|
import { compareTurnSnapshotDeltas } from '../src/turn-differential/compare.js';
|
||||||
import { runCoreTurnCommandTrace, type TurnCommandFixtureRequest } from '../src/turn-differential/coreCommandTrace.js';
|
import { runCoreTurnCommandTrace, type TurnCommandFixtureRequest } from '../src/turn-differential/coreCommandTrace.js';
|
||||||
@@ -3258,6 +3258,154 @@ integration('general command post-required cooldown boundary matrix', () => {
|
|||||||
}, 120_000);
|
}, 120_000);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type GeneralSpecialResetBoundaryCase = {
|
||||||
|
name: string;
|
||||||
|
action: 'che_내정특기초기화' | 'che_전투특기초기화';
|
||||||
|
actorPatch: Record<string, unknown>;
|
||||||
|
completed: boolean;
|
||||||
|
previousTypeKey: 'prev_types_special' | 'prev_types_special2';
|
||||||
|
expectedPreviousTypes?: string[];
|
||||||
|
expectedSpecAgeKey: 'specAge' | 'specAge2';
|
||||||
|
expectUniqueItem?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const generalSpecialResetBoundaryCases: GeneralSpecialResetBoundaryCase[] = [
|
||||||
|
{
|
||||||
|
name: 'domestic reset rejects an actor without a domestic trait',
|
||||||
|
action: 'che_내정특기초기화',
|
||||||
|
actorPatch: { specialDomestic: 'None' },
|
||||||
|
completed: false,
|
||||||
|
previousTypeKey: 'prev_types_special',
|
||||||
|
expectedSpecAgeKey: 'specAge',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'war reset rejects an actor without a war trait',
|
||||||
|
action: 'che_전투특기초기화',
|
||||||
|
actorPatch: { specialWar: 'None' },
|
||||||
|
completed: false,
|
||||||
|
previousTypeKey: 'prev_types_special2',
|
||||||
|
expectedSpecAgeKey: 'specAge2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'domestic reset restarts the previous-type cycle after every trait was seen',
|
||||||
|
action: 'che_내정특기초기화',
|
||||||
|
actorPatch: {
|
||||||
|
specialDomestic: 'che_인덕',
|
||||||
|
lastTurn: { command: '내정 특기 초기화', term: 1 },
|
||||||
|
meta: { prev_types_special: Array.from({ length: 7 }, (_, index) => `domestic-${index}`) },
|
||||||
|
},
|
||||||
|
completed: true,
|
||||||
|
previousTypeKey: 'prev_types_special',
|
||||||
|
expectedPreviousTypes: ['che_인덕'],
|
||||||
|
expectedSpecAgeKey: 'specAge',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'war reset restarts the previous-type cycle after every trait was seen',
|
||||||
|
action: 'che_전투특기초기화',
|
||||||
|
actorPatch: {
|
||||||
|
specialWar: 'che_귀병',
|
||||||
|
lastTurn: { command: '전투 특기 초기화', term: 1 },
|
||||||
|
meta: { prev_types_special2: Array.from({ length: 19 }, (_, index) => `war-${index}`) },
|
||||||
|
},
|
||||||
|
completed: true,
|
||||||
|
previousTypeKey: 'prev_types_special2',
|
||||||
|
expectedPreviousTypes: ['che_귀병'],
|
||||||
|
expectedSpecAgeKey: 'specAge2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'domestic reset stores the old trait and runs the forced unique lottery',
|
||||||
|
action: 'che_내정특기초기화',
|
||||||
|
actorPatch: {
|
||||||
|
specialDomestic: 'che_인덕',
|
||||||
|
lastTurn: { command: '내정 특기 초기화', term: 1 },
|
||||||
|
meta: { inheritRandomUnique: true, prev_types_special: ['che_농업'] },
|
||||||
|
},
|
||||||
|
completed: true,
|
||||||
|
previousTypeKey: 'prev_types_special',
|
||||||
|
expectedPreviousTypes: ['che_농업', 'che_인덕'],
|
||||||
|
expectedSpecAgeKey: 'specAge',
|
||||||
|
expectUniqueItem: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'war reset stores the old trait and runs the forced unique lottery',
|
||||||
|
action: 'che_전투특기초기화',
|
||||||
|
actorPatch: {
|
||||||
|
specialWar: 'che_귀병',
|
||||||
|
lastTurn: { command: '전투 특기 초기화', term: 1 },
|
||||||
|
meta: { inheritRandomUnique: true, prev_types_special2: ['che_신산'] },
|
||||||
|
},
|
||||||
|
completed: true,
|
||||||
|
previousTypeKey: 'prev_types_special2',
|
||||||
|
expectedPreviousTypes: ['che_신산', 'che_귀병'],
|
||||||
|
expectedSpecAgeKey: 'specAge2',
|
||||||
|
expectUniqueItem: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
integration('general special reset constraint, state, unique lottery, and log parity', () => {
|
||||||
|
it.each(generalSpecialResetBoundaryCases)(
|
||||||
|
'$name',
|
||||||
|
async ({
|
||||||
|
name,
|
||||||
|
action,
|
||||||
|
actorPatch,
|
||||||
|
completed,
|
||||||
|
previousTypeKey,
|
||||||
|
expectedPreviousTypes,
|
||||||
|
expectedSpecAgeKey,
|
||||||
|
expectUniqueItem,
|
||||||
|
}) => {
|
||||||
|
const request = buildRequest(action, undefined, actorPatch, {
|
||||||
|
world: { initYear: 180, initMonth: 1 },
|
||||||
|
});
|
||||||
|
request.setup!.world!.hiddenSeed = `general-special-reset-${name}`;
|
||||||
|
request.observe!.includeGlobalHistoryLogs = true;
|
||||||
|
const reference = runReferenceTurnCommandTraceRequest(
|
||||||
|
workspaceRoot!,
|
||||||
|
request as unknown as Record<string, unknown>
|
||||||
|
);
|
||||||
|
const core = await runCoreTurnCommandTrace(request, reference.before);
|
||||||
|
const referenceActor = reference.after.generals.find((entry) => entry.id === 1);
|
||||||
|
const coreActor = core.after.generals.find((entry) => entry.id === 1);
|
||||||
|
|
||||||
|
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))
|
||||||
|
);
|
||||||
|
|
||||||
|
if (completed) {
|
||||||
|
expect(asRecord(referenceActor?.meta)[previousTypeKey]).toEqual(expectedPreviousTypes);
|
||||||
|
expect(asRecord(coreActor?.meta)[previousTypeKey]).toEqual(expectedPreviousTypes);
|
||||||
|
expect(referenceActor?.[expectedSpecAgeKey]).toBe(31);
|
||||||
|
expect(coreActor?.[expectedSpecAgeKey]).toBe(31);
|
||||||
|
}
|
||||||
|
if (expectUniqueItem) {
|
||||||
|
const referenceItems = [
|
||||||
|
referenceActor?.itemHorse,
|
||||||
|
referenceActor?.itemWeapon,
|
||||||
|
referenceActor?.itemBook,
|
||||||
|
referenceActor?.itemExtra,
|
||||||
|
];
|
||||||
|
const coreItems = [coreActor?.itemHorse, coreActor?.itemWeapon, coreActor?.itemBook, coreActor?.itemExtra];
|
||||||
|
expect(referenceItems.some((item) => item && item !== 'None')).toBe(true);
|
||||||
|
expect(coreItems).toEqual(referenceItems);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
120_000
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const missingTargetCases: Array<{ name: string; action: string; args: Record<string, unknown> }> = [
|
const missingTargetCases: Array<{ name: string; action: string; args: Record<string, unknown> }> = [
|
||||||
{
|
{
|
||||||
name: 'gift to a missing general',
|
name: 'gift to a missing general',
|
||||||
|
|||||||
Reference in New Issue
Block a user