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
@@ -221,6 +221,25 @@ integration('scenario 903 select pool through the durable turn daemon', () => {
expect(concurrentReservation).toEqual(firstReservation);
expect(firstReservation.candidates).toHaveLength(14);
expect(await db.selectPoolEntry.count({ where: { ownerUserId: userId } })).toBe(14);
const reservedNames = new Set(firstReservation.candidates.map((candidate) => candidate.uniqueName));
expect(
runtime!.world
.listGeneralPoolCandidates(new Date(firstReservation.validUntil))
?.some((candidate) => reservedNames.has(candidate.uniqueName))
).toBe(false);
await expect(
db.inputEvent.findUniqueOrThrow({
where: { requestId: `select-pool:${userId}:select-pool-reserve-a:reserve` },
})
).resolves.toMatchObject({
eventType: 'selectPoolReserve',
status: 'SUCCEEDED',
actorUserId: userId,
payload: {
acceptedGameAt: expect.any(String),
acceptedGameTick: expect.any(Number),
},
});
const attempts = await Promise.allSettled([
appRouter.createCaller(buildContext('select-pool-create-a')).join.selectPoolGeneral({
@@ -290,6 +309,10 @@ integration('scenario 903 select pool through the durable turn daemon', () => {
expect(initialRankRows.every(({ nationId, value }) => nationId === 0 && value === 0)).toBe(true);
expect(await db.selectPoolEntry.count({ where: { generalId: initial.id } })).toBe(1);
expect(await db.selectPoolEntry.count({ where: { ownerUserId: userId } })).toBe(0);
expect(runtime!.world.listGeneralPoolEntries()?.filter((entry) => entry.ownerUserId === userId)).toEqual([]);
expect(
runtime!.world.listGeneralPoolEntries()?.find((entry) => entry.generalId === initial.id)?.candidate.name
).toBe(initial.name);
expect(
await db.logEntry.count({
where: { meta: { path: ['ownerUserId'], equals: userId } },
@@ -336,6 +359,16 @@ integration('scenario 903 select pool through the durable turn daemon', () => {
.createCaller(buildContext('select-pool-reselect'))
.join.reselectPoolGeneral({ uniqueName: target.uniqueName })
).resolves.toEqual({ ok: true, generalId: initial.id });
await expect(
db.inputEvent.findUniqueOrThrow({ where: { requestId: 'select-pool-reselect:join.reselectPoolGeneral' } })
).resolves.toMatchObject({
eventType: 'selectPoolReselect',
actorUserId: userId,
payload: {
acceptedGameAt: expect.any(String),
acceptedGameTick: expect.any(Number),
},
});
const updated = await db.general.findUniqueOrThrow({ where: { id: initial.id } });
expect(updated).toMatchObject({
@@ -368,6 +401,10 @@ integration('scenario 903 select pool through the durable turn daemon', () => {
ownerUserId: null,
reservedUntil: null,
});
expect(runtime!.world.listGeneralPoolEntries()?.filter((entry) => entry.ownerUserId === userId)).toEqual([]);
expect(
runtime!.world.listGeneralPoolEntries()?.find((entry) => entry.generalId === initial.id)?.uniqueName
).toBe(target.uniqueName);
expect(
await db.logEntry.count({
where: { meta: { path: ['ownerUserId'], equals: userId } },
@@ -518,6 +555,10 @@ integration('scenario 903 select pool through the durable turn daemon', () => {
status: 'SUCCEEDED',
attempts: 1,
actorUserId: otherUserId,
payload: {
acceptedGameAt: expect.any(String),
acceptedGameTick: expect.any(Number),
},
});
}, 30_000);