
検証済みアクション
サーバーコードを書かずに、リーダーボードとゲーム内通貨のプレイをサーバーで検証します。プレイごとの使い捨てチケット、サーバー上だけに存在するルール、上位プレイの入力ログ、そしてモデレーションにより、チーターが得られるものを制限します。クラウド限定で、セルフホストの simpleServer には含まれません。
プレイごとの使い捨てチケット
各プレイは、プレイヤー、リーダーボード、サーバーシードに紐づいたサーバーチケットから始まります。チケットは一度だけ有効で、拒否されたプレイでも消費されるため、1枚のチケットで上限を探ることはできません。
ルールはサーバー上だけに
スコアの上限と下限、最短時間、秒間スコア、ステージごとのルール、プレイごとの通貨上限はサーバー上にあります。アプリの応答やエラーメッセージに現れることはなく、通貨を書き込むのはサーバーだけです。
入力ログとモデレーション
上位プレイやフラグ付きのプレイについては、サーバーがゲームに入力ログを要求します。疑わしいプレイはシードとログを使って自分で再現し、対応を決められます。
クイック統合
検証済みアクションを設定する
動画は近日公開
リーダーボードで検証済みの送信をオンにし、「検証済みアクション」でサーバールールを設定します。その後、ダッシュボードで検証されたプレイを追跡し、上位プレイの証拠を確認できます。
// Unity C#: Validated Actions
// Dashboard: turn on "Validated submissions only" for the leaderboard "weekly"
var va = ValidatedActionsManager.Instance;
// Round start: single-use ticket plus server seed
var run = await va.StartRun("weekly");
if (run == null) { Debug.Log(va.LastErrorCode); return; } // e.g. RUN_RATE_LIMITED
var random = new System.Random(run.seed);
// ... play, record the inputs into inputLog (byte[]) ...
// Round end: score plus input log (the SDK sends its SHA-256 hash)
var result = await va.SubmitValidated(18250, inputLog);
if (result == null)
{
Debug.Log($"{va.LastErrorCode}, run kept: {va.HasActiveRun}"); // e.g. DURATION_TOO_SHORT
}# REST API: Validated Actions (needs the player's session token)
# Start a run: returns runId, a single-use ticket, seed and expiresAt
curl -X POST https://eu.horizon.pm/api/v1/app/validated-actions/runs \
-H "X-API-Key: YOUR_API_KEY" \
-H "Authorization: Bearer SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"userId":"USER_ID","leaderboardKey":"weekly"}'
# Submit the run: score plus the SHA-256 hex of the input log
curl -X POST https://eu.horizon.pm/api/v1/app/validated-actions/submit \
-H "X-API-Key: YOUR_API_KEY" \
-H "Authorization: Bearer SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{"userId":"USER_ID","ticket":"TICKET","leaderboardKey":"weekly","score":18250,"inputLogHash":"SHA256_HEX"}'
# Rejected runs answer 422 with a code, e.g. TICKET_EXPIRED or DURATION_TOO_SHORT

