diff --git a/apps/client/src/scenes/game/application/network/adapters/GameNetworkEventAdapter.ts b/apps/client/src/scenes/game/application/network/adapters/GameNetworkEventAdapter.ts index 0ea57ac..b1c9a60 100644 --- a/apps/client/src/scenes/game/application/network/adapters/GameNetworkEventAdapter.ts +++ b/apps/client/src/scenes/game/application/network/adapters/GameNetworkEventAdapter.ts @@ -10,13 +10,18 @@ PlayerDeadPayload, } from "@repo/shared"; -/** ゲーム開始受信ペイロードから開始時刻を抽出する */ +/** ゲーム開始受信ペイロードから開始時刻を抽出する + * serverNow を用いてクライアントとサーバーの時計差を補正し, + * クライアント時計基準の開始時刻を返す + */ export const toGameStartedAt = (payload: GameStartPayload): number | null => { if (!payload || !payload.startTime) { return null; } - return payload.startTime; + // clockOffset > 0: サーバーがクライアントより進んでいる + const clockOffset = payload.serverNow - Date.now(); + return payload.startTime - clockOffset; }; /** 爆弾設置受信ペイロードを内部ペイロードへ正規化する */ diff --git a/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts b/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts index 59c3c59..f61ceda 100644 --- a/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/readyForGameUseCase.ts @@ -47,7 +47,7 @@ return; } - output.publishGameStartToSocket({ startTime }); + output.publishGameStartToSocket({ startTime, serverNow: Date.now() }); logEvent(logScopes.GAME_USE_CASE, { event: gameUseCaseLogEvents.GAME_START, result: logResults.EMITTED, diff --git a/apps/server/src/domains/game/application/useCases/startGameUseCase.ts b/apps/server/src/domains/game/application/useCases/startGameUseCase.ts index f7caf86..1df8e88 100644 --- a/apps/server/src/domains/game/application/useCases/startGameUseCase.ts +++ b/apps/server/src/domains/game/application/useCases/startGameUseCase.ts @@ -93,5 +93,5 @@ ); const startTime = gameSession.getRoomStartTime() || Date.now(); - output.publishGameStartToRoom(roomId, { startTime }); + output.publishGameStartToRoom(roomId, { startTime, serverNow: Date.now() }); }; diff --git a/packages/shared/src/protocol/payloads/gamePayloads.ts b/packages/shared/src/protocol/payloads/gamePayloads.ts index a2f18e0..d0f704d 100644 --- a/packages/shared/src/protocol/payloads/gamePayloads.ts +++ b/packages/shared/src/protocol/payloads/gamePayloads.ts @@ -65,7 +65,12 @@ export type RemovePlayerPayload = PlayerData["id"]; /** game-start イベントで送受信するゲーム開始情報 */ -export type GameStartPayload = { startTime: number }; +export type GameStartPayload = { + /** ゲーム開始予定のUNIXタイムスタンプ(サーバー時計基準, ms) */ + startTime: number; + /** ペイロード送信時のサーバー時刻(クライアント側クロックオフセット補正用, ms) */ + serverNow: number; +}; /** start-game イベントで受信するゲーム開始要求 */ export type StartGameRequestPayload = {