Skip to content

Deploy to Arena

This guide covers the full deployment pipeline — from packaging your game through quality gates, review, catalog listing, versioning, and revenue. For building the game itself, see Build a Game.

Before deploying, your game must:

  • Implement the complete Game<TState, TAction> interface
  • Include a valid ludus.manifest.json
  • Pass all four quality gates locally (SDK Compliance, Security, Determinism, Performance)
  1. Build your game

    Terminal window
    npm run build
  2. Verify the package structure

    my-game/
    ├── ludus.manifest.json
    ├── dist/
    │ └── index.js
    ├── public/ # optional assets
    └── README.md # optional, shown in catalog
  3. Create the archive

    Terminal window
    tar -czf my-game.ludus.tar.gz -C my-game .

    All files referenced in the manifest (game.file, assets[].path) must exist in the archive.

Submit your package through the SubmissionService:

import {
SubmissionService,
InMemorySubmissionStore,
} from "@ludus/game-protocol";
const service = new SubmissionService(new InMemorySubmissionStore());
// Step 1: Upload package → DRAFT
const submission = service.submit(packageContents, "your-wallet-address");
console.log(`Submission ${submission.id} created as DRAFT`);
// Step 2: Submit for review → runs all quality gates
const reviewed = service.submitForReview(
submission.id,
packageContents,
() => MyGameImplementation,
);

The submitForReview() call transitions the submission through PENDINGTESTING (runs all quality gates) → REVIEW (all pass) or REJECTED (any fail).

You cannot submit a game with the same name + version if an active submission already exists. Bump the version or wait for a previous REJECTED submission to clear.

Four gates run sequentially. All must pass.

Checks that your game object has all 8 required methods/properties:

Method/PropertyWhat It Checks
metadataProperty exists with required fields
initialize()Returns valid initial state
getValidActions()Returns action array
executeAction()Returns new state
isGameOver()Returns boolean
getWinner()Returns Player or null
getRankings()Returns PlayerRanking array
describeAction()Returns string

Fix failures: Implement every method. No stubs that throw.

Scans source code for banned patterns:

PatternWhy Banned
Arbitrary code executionDynamic code generation
Function()Dynamic code generation
require()Module loading
fs.*, readFile, writeFileFile system access
fetch, XMLHttpRequest, http.Network access
process.envEnvironment leakage
child_processProcess spawning
__proto__Prototype pollution
Dynamic import()Dynamic module loading

Fix failures: Remove all I/O, network, and dynamic code from game logic. Keep it pure.

Runs your game 3 times with seed 42 and 2 players. All runs must produce identical state sequences.

Fix failures:

  • Replace Math.random() with rng.next() or rng.nextInt()
  • Replace Date.now() with turn counters
  • Use structuredClone() in executeAction() instead of mutation

Runs executeAction() for 50 turns. Each turn must complete within 5 seconds.

Fix failures: Profile your game logic. Avoid O(n!) or O(2^n) algorithms. Cache expensive computations.

if (reviewed.status === "REVIEW") {
console.log("All gates passed!");
} else if (reviewed.status === "REJECTED") {
console.error("Rejected:", reviewed.rejectionReason);
for (const result of reviewed.qualityResults) {
if (!result.passed) {
console.error(` ${result.gate}: ${result.details}`);
}
}
}

After passing all quality gates, the submission enters REVIEW status. Human reviewers (or DAO governance) evaluate:

  • Gameplay quality — Is the game fun and well-balanced?
  • Originality — Does it add value to the catalog?
  • Documentation — Is the README clear?
  • Fair play — No exploits or degenerate strategies?

The reviewer approves or rejects with notes:

// Approve → LISTED
service.approve(submission.id, "Great game, well-tested.");
// Or reject → REJECTED
service.reject(submission.id, "Needs balancing work", "Player 1 wins 95% of the time.");

Once approved, the game appears in the catalog:

import { CatalogService, InMemoryCatalogStore } from "@ludus/game-protocol";
const catalog = new CatalogService(new InMemoryCatalogStore());
// List the approved game
catalog.listGame(approvedSubmission);

Your game is now discoverable through:

  • Text search — by name, description, or tags
  • Category browsing — strategy, economic, social, card, dice, territory, puzzle, party
  • Popularity ranking — weighted score from downloads, ratings, recency, completions
  • Recent listings — newest games first

Players can rate your game (0-5 stars), and the popularity score updates automatically.

Publish updates using semver:

import { VersionManager, InMemoryVersionStore } from "@ludus/game-protocol";
const manager = new VersionManager(new InMemoryVersionStore());
// Register initial version
manager.registerVersion("my-game", "1.0.0", "hash-abc");
// Publish a patch (bug fix)
manager.registerVersion("my-game", "1.0.1", "hash-def");
// Publish a minor update (new feature)
manager.registerVersion("my-game", "1.1.0", "hash-ghi");

Each new version goes through the same submission and quality gate process.

When you publish a new version, you can deprecate the old one:

manager.deprecateVersion("my-game", "1.0.0", "Security fix in 1.0.1");

Deprecated versions remain playable for a 30-day grace period. Players see a warning and a pointer to the latest version. After the grace period, the old version is unlisted.

Check whether an upgrade is breaking:

const path = manager.getMigrationPath("my-game", "1.0.0", "2.0.0");
// path.breaking: true (major version jump)
// path.intermediateVersions: count of versions between

If a new version has issues, roll back:

manager.rollback("my-game", "1.0.0", "immediate");

Rollback policies: "immediate" (instant), "grace-period" (scheduled cutover), "manual" (requires confirmation).

The Ludus protocol distributes platform fees to game developers.

RecipientShare
Game developer70%
Ludus protocol30%

Fees are recorded from match entries, betting, and in-game transactions:

import { RevenueEngine, InMemoryRevenueStore } from "@ludus/game-protocol";
const engine = new RevenueEngine(new InMemoryRevenueStore());
// Fees accumulate automatically as players use your game
engine.recordFee("my-game", "dev-wallet", 100, "USDC");

Track your game’s performance:

const report = engine.getRevenueReport(
"my-game",
new Date("2026-01-01"),
new Date("2026-03-31"),
);
// report.totalFees — gross platform fees
// report.developerShare — your 70% cut
// report.protocolShare — protocol's 30%
// report.feeCount — number of fee events

Request a payout when your earnings meet the minimum threshold:

const payout = engine.createPayout("dev-wallet", periodStart, periodEnd);
// payout.status: "pending"
// After on-chain settlement:
engine.completePayout(payout.id, "0xtxhash...");
// payout.status: "completed"

Payout lifecycle: pendingprocessingcompleted (or failed with reason).

Set author.wallet in your ludus.manifest.json to receive payouts. Without a wallet address, fees accumulate but cannot be paid out.

Once your game is listed, monitor it through the catalog:

  • Downloads — tracked automatically via catalog.recordDownload()
  • Completions — tracked when games finish via catalog.recordCompletion()
  • Ratings — player ratings on a 0-5 scale
  • Popularity score — composite score (0-100) based on downloads (40%), rating (30%), recency (20%), completions (10%)
  • Revenue — fee reports and payout history
ErrorCauseFix
ManifestValidationErrorInvalid manifest fieldsCheck field rules in Game Protocol
DuplicateSubmissionErrorSame name+version existsBump version
InvalidTransitionErrorIllegal status transitionFollow the submission flow
DeterminismGateErrorNon-deterministic gameUse SeededRNG, no Math.random()
SecurityGateErrorBanned code pattern foundRemove I/O, network, dynamic code
SDKComplianceErrorMissing Game interface methodsImplement all 8 methods/properties