Integrate your game

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:

Messages your game sends
GAME_LOADED{ hasSDK: true }

Once, when the game is ready. Switches the page into tournament mode, and the platform replies with player info.

CONSUME_LIFE

When 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.

rcadia.js
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.

Unity SDK on GitHub

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_LIFE first, then exactly one SUBMIT_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_LIFE once, 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_LIFE does nothing.
  • SUBMIT_SCORE saves 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 of 0 and 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.

JavaScript
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
});
GET_USER_ADDRESS fields
usernamestring | null
The player's RCADIA username. null when 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.

harness.html
<!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:

Terminal
npx serve .        # then open http://localhost:3000/harness.html

FAQ

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.