fix: Ref 게임 로직과 시나리오 풀 호환을 보정
월 경계, 전투 기술 상한, 연감과 베팅·설문·경매 정산 순서를 Ref 계약에 맞춘다.\n\n시나리오 일반 풀을 ENGINE mutation과 logical tick 기반으로 직렬화하고 914·915 catalog 및 조건부 100기 pool 실행 경계를 추가한다.\n\n경매 worker는 세대별 durable event만 만들고 ENGINE이 row lock 후 상태 전이와 정산을 단일 transaction으로 소유한다.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { parseScenarioGeneralPoolCandidate, readScenarioGeneralPoolClaim, type City } from '@sammo-ts/logic';
|
||||
|
||||
import { loadGeneralPoolEntries } from '../src/scenario/generalPoolLoader.js';
|
||||
import { loadScenarioDefinitionById } from '../src/scenario/scenarioLoader.js';
|
||||
import { InMemoryTurnWorld } from '../src/turn/inMemoryWorld.js';
|
||||
import { createCreateManyNpcHandler } from '../src/turn/monthlyCreateManyNpcAction.js';
|
||||
import { InMemoryReservedTurnStore } from '../src/turn/reservedTurnStore.js';
|
||||
import { buildCommandEnv } from '../src/turn/reservedTurnCommands.js';
|
||||
import type { TurnGeneralPoolEntry, TurnWorldSnapshot, TurnWorldState } from '../src/turn/types.js';
|
||||
|
||||
const turnTime = new Date('0180-12-01T00:00:00.000Z');
|
||||
|
||||
const city: City = {
|
||||
id: 1,
|
||||
name: '테스트성',
|
||||
nationId: 0,
|
||||
level: 4,
|
||||
state: 0,
|
||||
population: 10_000,
|
||||
populationMax: 20_000,
|
||||
agriculture: 1_000,
|
||||
agricultureMax: 2_000,
|
||||
commerce: 1_000,
|
||||
commerceMax: 2_000,
|
||||
security: 1_000,
|
||||
securityMax: 2_000,
|
||||
supplyState: 1,
|
||||
frontState: 0,
|
||||
defence: 1_000,
|
||||
defenceMax: 2_000,
|
||||
wall: 1_000,
|
||||
wallMax: 2_000,
|
||||
meta: {},
|
||||
};
|
||||
|
||||
describe('scenario 903 general-pool composition', () => {
|
||||
it('feeds the tracked U30 pool into the first 100-NPC event without losing candidate fields', async () => {
|
||||
const [scenario, seeds] = await Promise.all([
|
||||
loadScenarioDefinitionById(903),
|
||||
loadGeneralPoolEntries('SPoolUnderU30'),
|
||||
]);
|
||||
expect(scenario.config.map.targetGeneralPool).toBe('SPoolUnderU30');
|
||||
const createEvent = scenario.events.find(
|
||||
(event): event is unknown[] =>
|
||||
Array.isArray(event) &&
|
||||
event[0] === 'month' &&
|
||||
event.some((action) => Array.isArray(action) && action[0] === 'CreateManyNPC')
|
||||
);
|
||||
expect(createEvent).toEqual([
|
||||
'month',
|
||||
1_000,
|
||||
['Date', '==', null, 12],
|
||||
['CreateManyNPC', 100, 0],
|
||||
['DeleteEvent'],
|
||||
]);
|
||||
const createAction = createEvent?.find(
|
||||
(action): action is unknown[] => Array.isArray(action) && action[0] === 'CreateManyNPC'
|
||||
);
|
||||
expect(createAction).toBeDefined();
|
||||
|
||||
const generalPoolEntries: TurnGeneralPoolEntry[] = seeds.map((seed, index) => ({
|
||||
id: index + 1,
|
||||
uniqueName: seed.uniqueName,
|
||||
ownerUserId: null,
|
||||
generalId: null,
|
||||
reservedUntil: null,
|
||||
reservedUntilTick: null,
|
||||
candidate: parseScenarioGeneralPoolCandidate({ id: index + 1, ...seed }),
|
||||
}));
|
||||
const map = {
|
||||
id: 'scenario-903-pool-composition',
|
||||
name: 'scenario 903 pool composition',
|
||||
cities: [],
|
||||
defaults: { trust: 50, trade: 100, supplyState: 1, frontState: 0 },
|
||||
};
|
||||
const state: TurnWorldState = {
|
||||
id: 1,
|
||||
currentYear: 180,
|
||||
currentMonth: 12,
|
||||
tickSeconds: 600,
|
||||
lastTurnTime: turnTime,
|
||||
meta: { hiddenSeed: 'scenario-903-pool-composition' },
|
||||
};
|
||||
const snapshot: TurnWorldSnapshot = {
|
||||
scenarioConfig: scenario.config,
|
||||
scenarioMeta: {
|
||||
title: scenario.title,
|
||||
startYear: scenario.startYear,
|
||||
life: scenario.life,
|
||||
fiction: scenario.fiction,
|
||||
history: scenario.history,
|
||||
ignoreDefaultEvents: scenario.ignoreDefaultEvents,
|
||||
},
|
||||
map,
|
||||
unitSet: { id: 'test', name: 'test', crewTypes: [] },
|
||||
generals: [],
|
||||
cities: [city],
|
||||
nations: [],
|
||||
troops: [],
|
||||
diplomacy: [],
|
||||
events: [],
|
||||
initialEvents: [],
|
||||
generalPoolEntries,
|
||||
};
|
||||
const world = new InMemoryTurnWorld(state, snapshot, {
|
||||
schedule: { entries: [{ startMinute: 0, tickMinutes: 10 }] },
|
||||
});
|
||||
const reservedTurns = new InMemoryReservedTurnStore(
|
||||
{
|
||||
generalTurn: { findMany: vi.fn(), deleteMany: vi.fn(), createMany: vi.fn() },
|
||||
nationTurn: { findMany: vi.fn(), deleteMany: vi.fn(), createMany: vi.fn() },
|
||||
} as never,
|
||||
{ maxGeneralTurns: 30, maxNationTurns: 12 }
|
||||
);
|
||||
const handler = createCreateManyNpcHandler({
|
||||
getWorld: () => world,
|
||||
reservedTurns,
|
||||
env: buildCommandEnv(scenario.config),
|
||||
});
|
||||
|
||||
await handler(
|
||||
createAction!.slice(1),
|
||||
{
|
||||
year: 180,
|
||||
month: 12,
|
||||
startyear: 180,
|
||||
currentEventID: 1,
|
||||
turnTime,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
targetCode: 'month',
|
||||
priority: 1_000,
|
||||
condition: true,
|
||||
action: [],
|
||||
meta: {},
|
||||
}
|
||||
);
|
||||
|
||||
const candidatesById = new Map(generalPoolEntries.map((entry) => [entry.id, entry.candidate]));
|
||||
const created = world.peekDirtyState().createdGenerals;
|
||||
expect(created).toHaveLength(100);
|
||||
const claims = created.map((general) => readScenarioGeneralPoolClaim(general.meta));
|
||||
expect(new Set(claims.map((claim) => claim?.poolEntryId)).size).toBe(100);
|
||||
for (const [index, general] of created.entries()) {
|
||||
const claim = claims[index];
|
||||
expect(claim).not.toBeNull();
|
||||
const candidate = candidatesById.get(claim!.poolEntryId)!;
|
||||
expect(general).toMatchObject({
|
||||
name: `ⓜ${candidate.name}`,
|
||||
stats: candidate.stats,
|
||||
picture: candidate.picture,
|
||||
imageServer: candidate.imageServer,
|
||||
role: { specialDomestic: candidate.specialDomestic },
|
||||
meta: {
|
||||
dex1: candidate.dex?.[0],
|
||||
dex2: candidate.dex?.[1],
|
||||
dex3: candidate.dex?.[2],
|
||||
dex4: candidate.dex?.[3],
|
||||
dex5: candidate.dex?.[4],
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user