マルチプレイの不正行為を検知する:ログと異常値
遊びをつくる執筆にClaudeを使用しています。

マルチプレイゲームに不正行為が現れるのは、ある程度の規模になれば避けられません。問題は「不正が起きているかどうか」ではなく、「起きていることに気づけるか」です。クライアント側でいくら値を検証しても、サーバーに届いたデータが異常かどうかを判断する仕組みがなければ、不正は静かに続きます。 この記事では、ログの設計と異常値の判断基準に絞って説明します。チート対策の全体像や通信プロトコルの選定は別の問題です。ここでは「何を記録し、どこに閾値を置き、どう対応するか」という運用上の判断を具体的に示します。
不正行為がログに残る仕組みを理解する
不正行為の多くは、ゲームの状態をクライアントが書き換えることで発生します。移動速度の改ざん、スコアの直接操作、クールダウンの無視などがその典型です。これらはサーバー側で受け取るイベントの「頻度」「値の範囲」「時系列の整合性」のどこかに必ず痕跡を残します。
ログとはその痕跡を記録したものです。単なるエラーログではなく、正常な挙動と比較できる数値の記録が必要です。たとえば「プレイヤーAが座標(x, y)に移動した」というイベントを記録するとき、前回の座標・前回のタイムスタンプ・移動距離を同時に保存しておくと、後から「1秒間に何マス移動したか」を計算できます。このような設計を構造化ログと呼びます。
構造化ログの最小単位として、次の情報をセットで保存することを目安にしてください。
- プレイヤーID(セッションIDではなくアカウントID)
- イベント種別(move / attack / score_update など)
- タイムスタンプ(サーバー受信時刻、ミリ秒単位)
- イベントの主要な数値(座標、ダメージ量、スコア差分など)
- 直前のイベントとの差分(オプションだが検知精度が上がる)
異常値の閾値をどう決めるか
閾値の設定は「正常な挙動の上限を知る」ことから始まります。チートをしていないプレイヤーが最速で操作した場合の数値を、まず自分で測定してください。たとえば移動速度なら、ゲーム設計上の最大速度にわずかな通信遅延の余裕(5〜10%程度)を加えた値が上限の目安になります。
問題は、通信遅延やフレームレートの揺れで正常なプレイヤーでも値が瞬間的に振れることです。単発の超過を即座に不正と判定すると、正常なプレイヤーを誤検知します。そこで使うのがスライディングウィンドウの考え方です。直近N回のイベントの中でM回以上閾値を超えたときに初めてフラグを立てる、という方式です。
具体的な例を示します。移動速度の上限を毎秒10マスと設計したとします。
- 単発超過、推奨の考え方:最大1.5倍(15マス/秒)まで許容
- 連続超過、推奨の考え方:10秒間に3回以上1.2倍超えでフラグ
- 極端な超過、推奨の考え方:単発で3倍(30マス/秒)超えは即時フラグ
この数値はゲームのジャンルや通信環境によって変わります。重要なのは「即時判定」「累積判定」「極端値判定」の3段階を設けることで、誤検知と見逃しのバランスをとることです。
スコア系の不正は移動系より検知しやすい場合があります。「1撃でスコアが最大値を超えた」「スコアが減少方向に動いた(仕様上ありえない)」のような論理的な矛盾は、閾値以前の問題として弾けます。ゲームのルールをサーバー側で再現するサーバーサイドバリデーションと、ログによる異常値検知は別の層として両立させてください。
ログを実際に使う:検索と集計の設計
ログを取っていても検索できなければ意味がありません。サーバーの規模が小さい段階では、JSONLファイル(1行1イベントのJSON)に書き出してローカルでgrepやjqで調べる方法で十分です。
フラグが立ったイベントだけを別のファイルに分けて書き出すと、後の確認作業が楽になります。たとえばNode.jsのサーバーなら次のような方針で実装できます。
- すべてのイベントをevents.jsonlに書き込む
- 異常値フラグが立ったイベントは同時にflags.jsonlにも書き込む
- 1日1回、flags.jsonlをプレイヤーIDでグループ化して件数を集計する
- 件数が一定以上のプレイヤーIDを「要確認リスト」として出力する
この集計の一定数(たとえば「24時間で10件以上」)も、自分のゲームの通常プレイを観察して決めます。リリース直後は閾値を緩めに設定し、正常なプレイヤーの挙動データが蓄積したら引き締めるのが実際的な順序です。
規模が大きくなりデータ量が増えたら、PostgreSQLやClickHouseのような集計に強いデータベースへの移行を検討します。ただし小規模の段階で複雑なインフラを先に作ると、肝心のゲームの開発が止まります。ログのフォーマットだけ最初から構造化しておけば、後で移行しやすくなります。
検知後の対応と誤検知への配慮
フラグが立ったプレイヤーへの対応は、自動化と人間の判断を分けて設計してください。極端な値(設計上ありえない数値など)は自動でセッション切断、累積フラグは人間が確認してからアカウント停止、という2段階が一般的です。
誤検知を放置すると正常なプレイヤーが離れます。対応ログを残し、誤検知と判断した場合は「なぜ誤検知だったか」をメモしておくと、閾値の改善に使えます。通信環境の悪いプレイヤーが繰り返しフラグに引っかかる場合は、クライアントの通信品質スコアを別途記録し、低品質な接続からの超過には別の閾値を設けることも選択肢です。
プレイヤーへの通知は、不正でないときに誤ってBANしてしまった場合の異議申し立て窓口をあらかじめ用意しておくことが重要です。問い合わせ先のないゲームで突然停止されると、プレイヤーは不信感を持ちます。
次の行動
まず自分のゲームのサーバーが受け取っているイベントを書き出し、「この値が設計上ありえない範囲に入ったら何が起きているか」を1項目だけ特定してみてください。そのイベントにタイムスタンプと差分を加えた構造化ログを1つ実装することが、最初の一歩です。ブラウザゲームをアソバースで公開している場合は、サーバー側ログの設計はゲームの外部インフラとして自分で用意する必要があります。ログの仕組みを固めてから公開することで、リリース後の不正対応が大幅に楽になります。
参考資料
- OWASP Testing Guide(ゲームセキュリティ・入力値検証の節)
- Valve Anti-Cheat(VAC)公式開発者ドキュメント(設計思想の参考として)
- MDN Web Docs — WebSocket API


