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:
@@ -16,11 +16,7 @@ import {
|
||||
import { readInheritancePoint, resolveInheritConstants } from '../../services/inheritance.js';
|
||||
import { loadAuthoritativeAccountIcon } from '../../services/accountIconSync.js';
|
||||
import { loadCurrentGameTime } from '../../services/gameClock.js';
|
||||
import {
|
||||
getSelectionPoolStatus,
|
||||
reserveSelectionPool,
|
||||
resolveSelectionMaxGeneral,
|
||||
} from '@sammo-ts/game-engine/turn/selectPoolService.js';
|
||||
import { getSelectionPoolStatus, resolveSelectionMaxGeneral } from '@sammo-ts/game-engine/turn/selectPoolService.js';
|
||||
import {
|
||||
ConflictingTurnDaemonCommandError,
|
||||
RejectedNpcPossessionCommandError,
|
||||
@@ -54,6 +50,31 @@ const resolveSelectionCommandResult = (
|
||||
return { ok: true, generalId: result.generalId };
|
||||
};
|
||||
|
||||
const resolveSelectionReservationCommandResult = (
|
||||
result: Awaited<ReturnType<GameApiContext['turnDaemon']['requestCommand']>> | null
|
||||
) => {
|
||||
if (!result) {
|
||||
throw new TRPCError({
|
||||
code: 'TIMEOUT',
|
||||
message:
|
||||
'장수 선택 목록 요청은 접수됐지만 처리 결과를 아직 확인하지 못했습니다. 같은 요청으로 다시 시도해 주세요.',
|
||||
});
|
||||
}
|
||||
if (result.type !== 'selectPoolReserve') {
|
||||
throw new TRPCError({
|
||||
code: 'INTERNAL_SERVER_ERROR',
|
||||
message: '턴 데몬이 올바르지 않은 장수 선택 목록 결과를 반환했습니다.',
|
||||
});
|
||||
}
|
||||
if (!result.ok) {
|
||||
throw new TRPCError({ code: result.code, message: result.reason });
|
||||
}
|
||||
return result.reservation;
|
||||
};
|
||||
|
||||
const resolveSelectionReservationRequestId = (contextRequestId: string | undefined, userId: string) =>
|
||||
contextRequestId ? `select-pool:${userId}:${contextRequestId}:reserve` : undefined;
|
||||
|
||||
const resolveSelectionRequestId = (
|
||||
contextRequestId: string | undefined,
|
||||
userId: string,
|
||||
@@ -366,26 +387,22 @@ export const joinRouter = router({
|
||||
},
|
||||
};
|
||||
}),
|
||||
getSelectionPool: authedProcedure.mutation(async ({ ctx }) => {
|
||||
getSelectionPool: engineAuthedProcedure.mutation(async ({ ctx }) => {
|
||||
const userId = ctx.auth?.user.id;
|
||||
if (!userId) {
|
||||
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
||||
}
|
||||
const worldState = await ctx.db.worldState.findFirst();
|
||||
if (!worldState) {
|
||||
throw new TRPCError({
|
||||
code: 'PRECONDITION_FAILED',
|
||||
message: 'World state is not initialized.',
|
||||
});
|
||||
}
|
||||
const gameTime = await loadCurrentGameTime(ctx.db);
|
||||
return reserveSelectionPool({
|
||||
db: ctx.db,
|
||||
worldState,
|
||||
const commandRequestId = resolveSelectionReservationRequestId(ctx.requestId, userId);
|
||||
const result = await ctx.turnDaemon.requestCommand({
|
||||
type: 'selectPoolReserve',
|
||||
...(commandRequestId ? { requestId: commandRequestId } : {}),
|
||||
userId,
|
||||
now: gameTime.now,
|
||||
seedOwnerIdentity: ctx.auth?.user.legacyMemberNo ?? userId,
|
||||
acceptedGameAt: gameTime.now.toISOString(),
|
||||
...(gameTime.tick === null ? {} : { acceptedGameTick: gameTime.tick }),
|
||||
});
|
||||
return resolveSelectionReservationCommandResult(result);
|
||||
}),
|
||||
selectPoolGeneral: engineAuthedProcedure
|
||||
.input(
|
||||
@@ -413,6 +430,7 @@ export const joinRouter = router({
|
||||
});
|
||||
}
|
||||
const commandRequestId = resolveSelectionRequestId(ctx.requestId, userId, input.clientRequestId, 'create');
|
||||
const gameTime = await loadCurrentGameTime(ctx.db);
|
||||
const result = await ctx.turnDaemon.requestCommand({
|
||||
type: 'selectPoolCreate',
|
||||
...(commandRequestId ? { requestId: commandRequestId } : {}),
|
||||
@@ -421,6 +439,8 @@ export const joinRouter = router({
|
||||
uniqueName: input.uniqueName,
|
||||
personality: input.personality,
|
||||
seedOwnerIdentity: auth.user.legacyMemberNo ?? userId,
|
||||
acceptedGameAt: gameTime.now.toISOString(),
|
||||
...(gameTime.tick === null ? {} : { acceptedGameTick: gameTime.tick }),
|
||||
...(selectedIcon
|
||||
? {
|
||||
ownerPicture: selectedIcon.picture,
|
||||
@@ -450,12 +470,15 @@ export const joinRouter = router({
|
||||
input.clientRequestId,
|
||||
'reselect'
|
||||
);
|
||||
const gameTime = await loadCurrentGameTime(ctx.db);
|
||||
const result = await ctx.turnDaemon.requestCommand({
|
||||
type: 'selectPoolReselect',
|
||||
...(commandRequestId ? { requestId: commandRequestId } : {}),
|
||||
userId,
|
||||
ownerDisplayName: auth.user.displayName,
|
||||
uniqueName: input.uniqueName,
|
||||
acceptedGameAt: gameTime.now.toISOString(),
|
||||
...(gameTime.tick === null ? {} : { acceptedGameTick: gameTime.tick }),
|
||||
});
|
||||
return resolveSelectionCommandResult(result, 'selectPoolReselect');
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user