fix general domestic command parity
This commit is contained in:
@@ -53,6 +53,9 @@ const readTech = (nation: Nation): number => {
|
|||||||
return typeof tech === 'number' && Number.isFinite(tech) ? tech : 0;
|
return typeof tech === 'number' && Number.isFinite(tech) ? tech : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 레거시 nation.tech는 MariaDB FLOAT이며 다음 명령 재조회 시 6자리 유효숫자로 양자화된다.
|
||||||
|
const toLegacyStoredTech = (value: number): number => Number(value.toPrecision(6));
|
||||||
|
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
> implements GeneralActionDefinition<TriggerState, TechResearchArgs, TechResearchContext<TriggerState>> {
|
> implements GeneralActionDefinition<TriggerState, TechResearchArgs, TechResearchContext<TriggerState>> {
|
||||||
@@ -105,7 +108,9 @@ export class ActionDefinition<
|
|||||||
|
|
||||||
context.nation.meta = {
|
context.nation.meta = {
|
||||||
...context.nation.meta,
|
...context.nation.meta,
|
||||||
tech: currentTech + techScore / Math.max(context.nationGeneralCount, this.env.initialNationGenLimit),
|
tech: toLegacyStoredTech(
|
||||||
|
currentTech + techScore / Math.max(context.nationGeneralCount, this.env.initialNationGenLimit)
|
||||||
|
),
|
||||||
};
|
};
|
||||||
context.general.gold = Math.max(0, context.general.gold - result.costGold);
|
context.general.gold = Math.max(0, context.general.gold - result.costGold);
|
||||||
context.general.experience += result.exp;
|
context.general.experience += result.exp;
|
||||||
@@ -144,8 +149,10 @@ export const actionContextBuilder: ActionContextBuilder = (
|
|||||||
...base,
|
...base,
|
||||||
city: base.city,
|
city: base.city,
|
||||||
nation: base.nation,
|
nation: base.nation,
|
||||||
nationGeneralCount: options.worldRef.listGenerals().filter((general) => general.nationId === base.nation!.id)
|
nationGeneralCount:
|
||||||
.length,
|
typeof base.nation.meta.gennum === 'number' && Number.isFinite(base.nation.meta.gennum)
|
||||||
|
? base.nation.meta.gennum
|
||||||
|
: options.worldRef.listGenerals().filter((general) => general.nationId === base.nation!.id).length,
|
||||||
relYear:
|
relYear:
|
||||||
typeof options.scenarioMeta?.startYear === 'number'
|
typeof options.scenarioMeta?.startYear === 'number'
|
||||||
? options.world.currentYear - options.scenarioMeta.startYear
|
? options.world.currentYear - options.scenarioMeta.startYear
|
||||||
|
|||||||
@@ -907,6 +907,203 @@ integration('general movement command boundary and log parity', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
interface DomesticBoundaryCase {
|
||||||
|
name: string;
|
||||||
|
action: 'che_성벽보수' | 'che_수비강화' | 'che_치안강화' | 'che_기술연구' | 'che_정착장려' | 'che_물자조달';
|
||||||
|
actorPatch?: Record<string, unknown>;
|
||||||
|
fixturePatches?: FixturePatches;
|
||||||
|
completed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domesticBoundaryCases: DomesticBoundaryCase[] = [
|
||||||
|
{
|
||||||
|
name: 'wall repair rejects a neutral actor',
|
||||||
|
action: 'che_성벽보수',
|
||||||
|
actorPatch: { nationId: 0 },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wall repair rejects a wandering nation',
|
||||||
|
action: 'che_성벽보수',
|
||||||
|
fixturePatches: { nations: { 1: { level: 0 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wall repair rejects a foreign-occupied city',
|
||||||
|
action: 'che_성벽보수',
|
||||||
|
fixturePatches: { cities: { 3: { nationId: 2 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wall repair rejects an unsupplied city',
|
||||||
|
action: 'che_성벽보수',
|
||||||
|
fixturePatches: { cities: { 3: { supplyState: 0 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wall repair rejects insufficient gold',
|
||||||
|
action: 'che_성벽보수',
|
||||||
|
actorPatch: { gold: 0 },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wall repair rejects a wall already at capacity',
|
||||||
|
action: 'che_성벽보수',
|
||||||
|
fixturePatches: { cities: { 3: { wall: 1000, wallMax: 1000 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'wall repair preserves the early-capital front debuff',
|
||||||
|
action: 'che_성벽보수',
|
||||||
|
fixturePatches: { cities: { 3: { wall: 0, wallMax: 10_000, frontState: 1 } } },
|
||||||
|
completed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defence reinforcement rejects a defence already at capacity',
|
||||||
|
action: 'che_수비강화',
|
||||||
|
fixturePatches: { cities: { 3: { defence: 1000, defenceMax: 1000 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'defence reinforcement preserves the early-capital front debuff',
|
||||||
|
action: 'che_수비강화',
|
||||||
|
fixturePatches: { cities: { 3: { defence: 0, defenceMax: 10_000, frontState: 1 } } },
|
||||||
|
completed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'security reinforcement rejects security already at capacity',
|
||||||
|
action: 'che_치안강화',
|
||||||
|
fixturePatches: { cities: { 3: { security: 1000, securityMax: 1000 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'security reinforcement preserves the early-capital front rule',
|
||||||
|
action: 'che_치안강화',
|
||||||
|
fixturePatches: { cities: { 3: { security: 0, securityMax: 10_000, frontState: 1 } } },
|
||||||
|
completed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tech research rejects a neutral actor',
|
||||||
|
action: 'che_기술연구',
|
||||||
|
actorPatch: { nationId: 0 },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tech research rejects a wandering nation',
|
||||||
|
action: 'che_기술연구',
|
||||||
|
fixturePatches: { nations: { 1: { level: 0 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tech research rejects a foreign-occupied city',
|
||||||
|
action: 'che_기술연구',
|
||||||
|
fixturePatches: { cities: { 3: { nationId: 2 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tech research rejects an unsupplied city',
|
||||||
|
action: 'che_기술연구',
|
||||||
|
fixturePatches: { cities: { 3: { supplyState: 0 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tech research rejects insufficient gold',
|
||||||
|
action: 'che_기술연구',
|
||||||
|
actorPatch: { gold: 0 },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'tech research uses stored nation general count at the level cap',
|
||||||
|
action: 'che_기술연구',
|
||||||
|
fixturePatches: { nations: { 1: { tech: 3000, generalCount: 11 } } },
|
||||||
|
completed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'settlement encouragement rejects insufficient rice',
|
||||||
|
action: 'che_정착장려',
|
||||||
|
actorPatch: { rice: 0 },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'settlement encouragement rejects a city at population capacity',
|
||||||
|
action: 'che_정착장려',
|
||||||
|
fixturePatches: { cities: { 3: { population: 200_000, populationMax: 200_000 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'settlement encouragement clamps at population capacity',
|
||||||
|
action: 'che_정착장려',
|
||||||
|
fixturePatches: { cities: { 3: { population: 199_999, populationMax: 200_000 } } },
|
||||||
|
completed: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'procurement rejects a neutral actor',
|
||||||
|
action: 'che_물자조달',
|
||||||
|
actorPatch: { nationId: 0 },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'procurement rejects a wandering nation',
|
||||||
|
action: 'che_물자조달',
|
||||||
|
fixturePatches: { nations: { 1: { level: 0 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'procurement rejects a foreign-occupied city',
|
||||||
|
action: 'che_물자조달',
|
||||||
|
fixturePatches: { cities: { 3: { nationId: 2 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'procurement rejects an unsupplied city',
|
||||||
|
action: 'che_물자조달',
|
||||||
|
fixturePatches: { cities: { 3: { supplyState: 0 } } },
|
||||||
|
completed: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'procurement succeeds without owned gold or rice',
|
||||||
|
action: 'che_물자조달',
|
||||||
|
actorPatch: { gold: 0, rice: 0 },
|
||||||
|
completed: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
integration('general domestic command boundary, value, and log parity', () => {
|
||||||
|
it.each(domesticBoundaryCases)(
|
||||||
|
'$name',
|
||||||
|
async ({ name, action, actorPatch, fixturePatches, completed }) => {
|
||||||
|
const request = buildRequest(action, undefined, actorPatch, fixturePatches);
|
||||||
|
request.setup!.world!.hiddenSeed = `general-domestic-${name}`;
|
||||||
|
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([]);
|
||||||
|
|
||||||
|
if (completed) {
|
||||||
|
expect(semanticLogSignatures(core.after.logs)).toEqual(
|
||||||
|
semanticLogSignatures(addedReferenceLogs(reference.before, reference.after.logs))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
120_000
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
integration('명장일람 rank_data command parity', () => {
|
integration('명장일람 rank_data command parity', () => {
|
||||||
it('화계 increments firenum from the same seeded value as legacy', async () => {
|
it('화계 increments firenum from the same seeded value as legacy', async () => {
|
||||||
const request = buildRequest(
|
const request = buildRequest(
|
||||||
|
|||||||
Reference in New Issue
Block a user