fix: 이민족 첫 턴을 정지 시각에 맞춤

통일 대기 중 실시간 투영 시각이 새 이민족 첫 턴으로 새던 경로를 정지된 월 경계로 교정한다. 잘못 추가한 NPC 포상 비용 100배 변경도 되돌린다.
This commit is contained in:
2026-08-26 15:28:19 +00:00
parent 71b8eac8ee
commit 816762ab56
5 changed files with 29 additions and 49 deletions
@@ -277,6 +277,9 @@ describe('invader monthly actions', () => {
it('moves a level-4 capital and scales turn times when the general limit requires a longer term', async () => {
const cities = [buildCity(1, 1, 4), buildCity(2, 1, 3), buildCity(3, 0, 4)];
const harness = buildHarness({ cities });
// A message can be accepted long after the frozen monthly boundary.
// The realtime projection must not become the new invaders' first turn.
harness.environment.turnTime = new Date('0200-01-01T01:00:00.000Z');
const handler = createRaiseInvaderHandler({
getWorld: () => harness.world,
reservedTurns: harness.reservedTurns,
@@ -290,6 +293,11 @@ describe('invader monthly actions', () => {
expect(harness.world.getGeneralById(1)?.cityId).toBe(2);
expect(harness.world.getState().tickSeconds).toBe(30 * 60);
expect(harness.world.getGeneralById(1)?.turnTime.toISOString()).toBe('0200-01-01T00:15:00.000Z');
const invaderTurnTimes = harness.world
.peekDirtyState()
.createdGenerals.map((general) => general.turnTime.getTime());
expect(Math.min(...invaderTurnTimes)).toBeGreaterThanOrEqual(new Date('0200-01-01T00:00:00.000Z').getTime());
expect(Math.max(...invaderTurnTimes)).toBeLessThan(new Date('0200-01-01T00:30:00.000Z').getTime());
expect(harness.world.peekDirtyState().logs[0]?.text).toBe('<R>★</>턴시간이 <C>30분</>으로 변경됩니다.');
});
@@ -418,10 +426,7 @@ describe('invader monthly actions', () => {
action: [['InvaderEnding']],
meta: {},
};
const invaderNations = [
buildNation(2, 1, 'ⓞ남만족'),
buildNation(3, 2, 'ⓞ산월족'),
];
const invaderNations = [buildNation(2, 1, 'ⓞ남만족'), buildNation(3, 2, 'ⓞ산월족')];
const harness = buildHarness({
cities: [buildCity(1, 2, 4), buildCity(2, 3, 4)],
nations: [buildNeutralNation(), ...invaderNations],
@@ -464,12 +469,7 @@ describe('invader monthly actions', () => {
};
const harness = buildHarness({
cities: [buildCity(1, 1, 3), buildCity(2, 2, 4), buildCity(3, 3, 4)],
nations: [
buildNeutralNation(),
buildNation(1),
buildNation(2, 2, 'ⓞ남만족'),
buildNation(3, 3, 'ⓞ산월족'),
],
nations: [buildNeutralNation(), buildNation(1), buildNation(2, 2, 'ⓞ남만족'), buildNation(3, 3, 'ⓞ산월족')],
events: [endingEvent],
meta: { isunited: 1, refreshLimit: 3 },
});
@@ -176,32 +176,6 @@ const unitSet: UnitSetDefinition = {
};
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', () => {
const world = new InMemoryTurnWorld(state, snapshot, { schedule });
const first = applyNpcPolicyMutation({
@@ -280,10 +254,8 @@ describe('NPC policy lifecycle', () => {
expect(policy.reqNationGold).toBe(4_321);
expect(policy.priority).toEqual(['천도']);
expect(policy.reqNpcDevelGold).toBe(540);
expect(policy.reqNpcWarGold).toBe(391_500);
expect(policy.reqNpcWarRice).toBe(391_500);
expect(policy.reqHumanWarUrgentGold).toBe(626_400);
expect(policy.reqHumanWarUrgentRice).toBe(626_400);
expect(policy.reqNpcWarGold).toBe(3_900);
expect(policy.reqNpcWarRice).toBe(3_900);
const beforeConflict = structuredClone(world.getNationById(1)?.meta);
expect(