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:
2026-08-23 16:26:14 +00:00
parent bf6b7be7b0
commit 85591c68ad
114 changed files with 13327 additions and 901 deletions
@@ -97,6 +97,9 @@ const buildContext = (
hasGeneral?: boolean;
worldMeta?: unknown;
liveLogs?: { history: string[]; action: string[] };
liveNations?: Array<Record<string, unknown>>;
liveCities?: Array<Record<string, unknown>>;
liveGenerals?: Array<Record<string, unknown>>;
} = {}
): GameApiContext => {
const db = {
@@ -104,13 +107,13 @@ const buildContext = (
general: {
findFirst: async ({ where }: { where: { userId: string } }) =>
options.hasGeneral === false ? null : { id: where.userId === 'owner-a' ? 1 : 2, userId: where.userId },
findMany: async () => [],
findMany: async () => options.liveGenerals ?? [],
},
city: {
findMany: async () => [],
findMany: async () => options.liveCities ?? [],
},
nation: {
findMany: async () => [],
findMany: async () => options.liveNations ?? [],
},
logEntry: {
findMany: async ({ where }: { where: { category: unknown } }) => {
@@ -326,4 +329,82 @@ describe('historical yearbook access from dynasty', () => {
},
});
});
it('uses stored Ref nation projections, canonical neutral values, and descending power in the live month', async () => {
const zeroCityStats = {
population: 0,
agriculture: 0,
commerce: 0,
security: 0,
defence: 0,
wall: 0,
populationMax: 1,
agricultureMax: 1,
commerceMax: 1,
securityMax: 1,
defenceMax: 1,
wallMax: 1,
};
const caller = appRouter.createCaller(
buildContext(authFor('owner-a'), {
liveNations: [
{
id: 0,
name: '오염된 재야',
color: '#ffffff',
level: 9,
gold: 0,
rice: 0,
tech: 0,
meta: { power: 90, gennum: 90 },
},
{
id: 1,
name: '촉',
color: '#ff0000',
level: 7,
gold: 0,
rice: 0,
tech: 0,
meta: { power: 777, gennum: 9 },
},
{
id: 2,
name: '위',
color: '#0000ff',
level: 7,
gold: 0,
rice: 0,
tech: 0,
meta: { power: 0, gennum: 2 },
},
],
liveCities: [
{ id: 0, name: '낙양', nationId: 0, ...zeroCityStats },
{ id: 1, name: '성도', nationId: 1, ...zeroCityStats },
{ id: 2, name: '허창', nationId: 2, ...zeroCityStats },
],
})
);
const result = await caller.yearbook.getHistory({ year: 220, month: 1 });
expect(result).toMatchObject({
notModified: false,
data: {
nations: [
expect.objectContaining({ id: 1, power: 777, generalCount: 9 }),
expect.objectContaining({
id: 0,
name: '재야',
color: '#000000',
level: 0,
power: 1,
generalCount: 1,
}),
expect.objectContaining({ id: 2, power: 0, generalCount: 2 }),
],
},
});
});
});