Tournament SDKstable
Three messages connect your game to entry-fee tournaments, lives and leaderboards. RCADIA handles entries, prize pools, payouts and anti-cheat.
How a run works
A player enters a tournament with XRP, VT, or for free, and gets a batch of lives (10 by default). Each attempt costs one life and records one score. When the tournament ends, the best scores win the prize pool.
Your game only reports what happens:
GAME_LOADED{ hasSDK: true }Once, when the game is ready. Switches the page into tournament mode, and the platform replies with player info.
CONSUME_LIFEWhen an attempt ends. Spends one tournament life and opens a one-time ticket for the score.
SUBMIT_SCORE{ score: number }Right after
CONSUME_LIFE, with that attempt's score.
Install
No file to install. Post the messages to the parent window from any engine: Three.js, Phaser, PixiJS, Godot, or plain canvas.
const rcadia = (type, data = {}) => window.parent.postMessage({ type, data }, "*");
// 1. Once your game has loaded
rcadia("GAME_LOADED", { hasSDK: true });
// 2. When a run ends: spend a life, then report the score
function onRunOver(score) {
rcadia("CONSUME_LIFE");
rcadia("SUBMIT_SCORE", { score: Math.floor(score) });
}Outside an iframe, window.parent is the page itself, so the calls do nothing. The same build runs fine on its own.
Download the SDK from GitHub (Unity 2021.3 or later, WebGL target) and add two files:
| File | Put it in |
|---|---|
RCADIA.cs | Anywhere under Assets/ |
RCADIABridge.jslib | Assets/Plugins/WebGL/ |
using UnityEngine;
public class GameManager : MonoBehaviour
{
void Start()
{
// Sends GAME_LOADED. Use your RCADIA game title.
RCADIA.Initialize("Neon Drift");
}
public void OnRunOver(int finalScore)
{
RCADIA.ConsumeLive(); // spend one tournament life
RCADIA.SubmitScore(finalScore); // then report the score
ShowGameOverScreen();
}
}In the Editor, the SDK logs each call to the Console instead of sending it. Call RCADIA.SetTournamentMode(true, 10) to simulate lives while you test.
When you upload, turn on RCADIA SDK integrated (or send hasSdk=true). Only flagged games can host tournaments. The flag also requires a skill.md in your ZIP.
Scoring rules
RCADIA's anti-cheat checks every tournament score. Follow these rules and scores go through:
- One life, one score. Send
CONSUME_LIFEfirst, then exactly oneSUBMIT_SCORE. Every score needs a fresh life. - One life per run, not per death. If your game has its own lives or continues, send
CONSUME_LIFEonce, when the whole run is over. Every call spends a tournament life. - Whole numbers. Scores are integers from 0 to 2,147,483,647. Round anything else.
- Score cap. If your game has a maximum possible score, set it with an edit request. Higher scores are then rejected. Golf-style games can also choose lowest score wins.
A second CONSUME_LIFE within about a second of the first is ignored. That protects players from double-fired death events.
Outside tournaments
The same messages work when there's no tournament:
CONSUME_LIFEdoes nothing.SUBMIT_SCOREsaves the player's best score on the game's all-time leaderboard. Each submission uses one of the player's arcade lives, and a player with none left sees "No lives remaining". Scores of0and scores from signed-out players aren't saved.
So you don't need separate builds for free play and tournaments.
Player info
After GAME_LOADED, the platform posts the signed-in player's details into your game. Use them to greet the player or to unlock perks for Genesis Pass holders.
window.addEventListener("message", (event) => {
if (event.source !== window.parent) return;
const msg = event.data;
if (msg?.type !== "GET_USER_ADDRESS") return;
if (!msg.username) return showGuestMode(); // not signed in
greet(msg.username); // "fishrider"
const wallet = msg.xrpAddress; // "rP9jd…" (Xaman, XRPL)
const hasPass = msg.genesisPass.length > 0; // Genesis Pass NFTs held
});usernamestring | null- The player's RCADIA username.
nullwhen signed out. xrpAddressstring | null- The player's XRPL address.
walletsarray[{ chain: "xrpl", address, source: "xaman" }]genesisPassarray- Genesis Pass NFTs the player holds. Empty if none.
The info is read-only: your game can't sign transactions or move funds.
Test locally
A pending game can't be played on rcadia.xyz yet. Save this page as harness.html next to your index.html. It loads your game the way RCADIA does, logs every message, sends back fake player info, and warns when a score would be rejected.
<!doctype html>
<meta charset="utf-8">
<title>RCADIA test harness</title>
<style>
body { margin: 0; height: 100vh; display: grid; grid-template-rows: 1fr 200px; background: #0a0a0c; color: #ccc; font: 13px ui-monospace, monospace; }
iframe { width: 100%; height: 100%; border: 0; background: #000; }
#log { overflow: auto; padding: 8px 12px; border-top: 1px solid #333; }
.warn { color: #fbbf24; }
</style>
<iframe id="game" src="index.html" allow="fullscreen"></iframe>
<div id="log"></div>
<script>
const game = document.getElementById("game");
let lifeSpent = false;
function log(text, warn) {
const line = document.createElement("div");
line.textContent = new Date().toLocaleTimeString() + " " + text;
if (warn) line.className = "warn";
document.getElementById("log").prepend(line);
}
window.addEventListener("message", (event) => {
if (event.source !== game.contentWindow) return;
const msg = event.data || {};
log("game → " + JSON.stringify(msg));
if (msg.type === "GAME_LOADED") {
const player = { type: "GET_USER_ADDRESS", username: "test-player", xrpAddress: "rTestAddress",
wallets: [{ chain: "xrpl", address: "rTestAddress", source: "xaman" }], genesisPass: [] };
game.contentWindow.postMessage(player, "*");
log("platform → GET_USER_ADDRESS");
}
if (msg.type === "CONSUME_LIFE") lifeSpent = true;
if (msg.type === "SUBMIT_SCORE") {
const score = msg.data?.score ?? msg.score;
if (!lifeSpent) log("⚠ SUBMIT_SCORE without CONSUME_LIFE first: a tournament rejects this", true);
if (!Number.isInteger(score) || score < 0) log("⚠ score must be a whole number, 0 or more", true);
lifeSpent = false;
}
});
</script>Serve the folder over HTTP (Unity builds don't load from file://) and open the harness:
npx serve . # then open http://localhost:3000/harness.htmlFAQ
Does a game need a tournament to use the SDK?
No. The same build works in free play and in tournaments. See Outside tournaments.
What about IsInTournament() and GetRemainingLives()?
They exist in the Unity SDK, but the platform doesn't send tournament state to games yet. In production they return false and 0. Show lives from your own UI if you need to, and let RCADIA's page show the tournament count.
Can I use both SDKs?
Yes. The Tournament SDK and the Agent SDK use separate messages and don't interfere with each other.
Unity on mobile behaves oddly. What should I check?
Canvas focus, touch handling and the input system. The WebGL template guide covers the fixes.