fix: NPC 전쟁 포상 병력 단위 계산 복원

Ref costWithTech의 병력 100명 단위를 기본 NPC 및 사용자 전쟁 포상 기준에 반영한다. 어려움 이민족 초기 자원이 포상 필요 상태로 판정되는 회귀 테스트를 추가한다.
This commit is contained in:
2026-08-26 15:00:29 +00:00
parent 76d1942cce
commit 1ccda1f0fb
2 changed files with 36 additions and 6 deletions
+6 -4
View File
@@ -314,8 +314,10 @@ export class AutorunNationPolicy {
const stat = scenarioConfig.stat; const stat = scenarioConfig.stat;
if (this.reqNpcWarGold === 0 || this.reqNpcWarRice === 0) { if (this.reqNpcWarGold === 0 || this.reqNpcWarRice === 0) {
const crewType = findCrewTypeById(unitSet, env.defaultCrewTypeId); const crewType = findCrewTypeById(unitSet, env.defaultCrewTypeId);
const baseGold = crewType ? crewType.cost * getTechCost(tech, env.maxTechLevel) * stat.npcMax : 0; // Ref costWithTech() receives a troop count, not a leadership value.
const baseRice = crewType ? crewType.rice * getTechCost(tech, env.maxTechLevel) * stat.npcMax : 0; // The policy funds four complete recruitments at the NPC stat cap.
const baseGold = crewType ? crewType.cost * getTechCost(tech, env.maxTechLevel) * stat.npcMax * 100 : 0;
const baseRice = crewType ? crewType.rice * getTechCost(tech, env.maxTechLevel) * stat.npcMax * 100 : 0;
if (this.reqNpcWarGold === 0) { if (this.reqNpcWarGold === 0) {
this.reqNpcWarGold = roundTo(baseGold * 4, -2); this.reqNpcWarGold = roundTo(baseGold * 4, -2);
} }
@@ -326,8 +328,8 @@ export class AutorunNationPolicy {
if (this.reqHumanWarUrgentGold === 0 || this.reqHumanWarUrgentRice === 0) { if (this.reqHumanWarUrgentGold === 0 || this.reqHumanWarUrgentRice === 0) {
const crewType = findCrewTypeById(unitSet, env.defaultCrewTypeId); const crewType = findCrewTypeById(unitSet, env.defaultCrewTypeId);
const baseGold = crewType ? crewType.cost * getTechCost(tech, env.maxTechLevel) * stat.max : 0; const baseGold = crewType ? crewType.cost * getTechCost(tech, env.maxTechLevel) * stat.max * 100 : 0;
const baseRice = crewType ? crewType.rice * getTechCost(tech, env.maxTechLevel) * stat.max : 0; const baseRice = crewType ? crewType.rice * getTechCost(tech, env.maxTechLevel) * stat.max * 100 : 0;
if (this.reqHumanWarUrgentGold === 0) { if (this.reqHumanWarUrgentGold === 0) {
this.reqHumanWarUrgentGold = roundTo(baseGold * 6, -2); this.reqHumanWarUrgentGold = roundTo(baseGold * 6, -2);
} }
@@ -176,6 +176,32 @@ const unitSet: UnitSetDefinition = {
}; };
describe('NPC policy lifecycle', () => { describe('NPC policy lifecycle', () => {
it('keeps hard-invader starting resources below the Ref-compatible NPC war funding target', () => {
const hardInvaderNation = {
...snapshot.nations[0]!,
name: 'ⓞ강족',
meta: { ...snapshot.nations[0]!.meta, tech: 15_000 },
};
const hardInvaderScenario = structuredClone(snapshot.scenarioConfig);
hardInvaderScenario.stat.npcMax = 80;
const hardInvaderGeneral = { ...general, npcState: 9, gold: 99_999, rice: 99_999 };
const policy = new AutorunNationPolicy({
general: hardInvaderGeneral,
aiOptions: null,
nationPolicy: null,
serverPolicy: null,
nation: hardInvaderNation,
env: { ...commandEnv, maxTechLevel: 12 },
scenarioConfig: hardInvaderScenario,
unitSet,
});
expect(policy.reqNpcWarGold).toBe(806_400);
expect(policy.reqNpcWarRice).toBe(806_400);
expect(hardInvaderGeneral.gold).toBeLessThan(policy.reqNpcWarGold);
expect(hardInvaderGeneral.rice).toBeLessThan(policy.reqNpcWarRice);
});
it('applies CAS-protected semantic policy changes and the next AI instance consumes them without scheduler changes', () => { it('applies CAS-protected semantic policy changes and the next AI instance consumes them without scheduler changes', () => {
const world = new InMemoryTurnWorld(state, snapshot, { schedule }); const world = new InMemoryTurnWorld(state, snapshot, { schedule });
const first = applyNpcPolicyMutation({ const first = applyNpcPolicyMutation({
@@ -254,8 +280,10 @@ describe('NPC policy lifecycle', () => {
expect(policy.reqNationGold).toBe(4_321); expect(policy.reqNationGold).toBe(4_321);
expect(policy.priority).toEqual(['천도']); expect(policy.priority).toEqual(['천도']);
expect(policy.reqNpcDevelGold).toBe(540); expect(policy.reqNpcDevelGold).toBe(540);
expect(policy.reqNpcWarGold).toBe(3_900); expect(policy.reqNpcWarGold).toBe(391_500);
expect(policy.reqNpcWarRice).toBe(3_900); expect(policy.reqNpcWarRice).toBe(391_500);
expect(policy.reqHumanWarUrgentGold).toBe(626_400);
expect(policy.reqHumanWarUrgentRice).toBe(626_400);
const beforeConflict = structuredClone(world.getNationById(1)?.meta); const beforeConflict = structuredClone(world.getNationById(1)?.meta);
expect( expect(