Skip to content

Chapter 8: Ship It

You’ve built a complete game. Now it’s time to package it and submit it to the Ludus marketplace. This chapter covers:

  1. Creating the manifest
  2. Running quality gates locally
  3. Submitting through the pipeline
  4. Getting listed on the catalog

Every Ludus game ships with a ludus.manifest.json:

{
"name": "mercante",
"version": "1.0.0",
"displayName": "Mercante",
"description": "Renaissance merchant trading — buy, sell, invest, and bet your way to fortune in Florence",
"author": {
"name": "Your Name",
"email": "[email protected]",
"wallet": "0x..."
},
"game": {
"file": "dist/index.js",
"export": "Mercante"
},
"playerRange": { "min": 2, "max": 6 },
"category": "economic",
"tags": ["trading", "economic", "renaissance", "defi", "tutorial"],
"license": "MIT"
}

The manifest is validated strictly:

FieldRule
nameKebab-case, 3-64 chars
versionSemver (X.Y.Z)
displayNameNon-empty
descriptionNon-empty, max 256 chars
author.nameRequired
author.walletRequired for revenue payouts
game.filePath to compiled JS (must exist)
game.exportExported symbol name
playerRangemin >= 1, min <= max
categoryOne of: strategy, economic, social, card, dice, territory, puzzle, party, other
tagsMax 10, max 32 chars each
licenseSPDX identifier

You can validate before submitting:

import { validateManifest } from "@ludus/game-protocol";
const errors = validateManifest(manifest);
if (errors.length > 0) {
console.error("Fix these:", errors);
}

Run all four gates locally before submitting:

import { QualityGateRunner } from "@ludus/game-protocol";
import Mercante from "./game";
const runner = new QualityGateRunner();
const results = runner.runAll(manifest, packageContents, () => Mercante);
const summary = runner.summarize(results);
console.log(`Gates: ${summary.passedGates}/${summary.totalGates} passed`);
console.log(`Duration: ${summary.totalDurationMs}ms`);
if (!summary.passed) {
for (const r of results) {
if (!r.passed) {
console.error(`FAIL ${r.gate}: ${r.details}`);
}
}
}

Checks that Mercante implements all 7 required methods plus metadata.

Mercante status: PASS. We implemented every method in Chapter 1.

Scans source code for banned patterns: dynamic code execution, file system access, network calls.

Mercante status: PASS. Our game logic is pure — no I/O, no network, no dynamic imports.

Runs the game 3 times with seed 42 and compares outputs.

Mercante status: PASS. We use SeededRNG exclusively. structuredClone() for state. No Math.random() or Date.now().

Profiles executeAction() for 50 turns. Each must complete under 5000ms.

Mercante status: PASS. Our game logic is O(n) where n = number of goods (6). No expensive algorithms.

import { SubmissionService, InMemorySubmissionStore } from "@ludus/game-protocol";
const store = new InMemorySubmissionStore();
const service = new SubmissionService(store);
// 1. Upload → DRAFT
const submission = service.submit(packageContents, "your-wallet-address");
// 2. Submit for review → runs quality gates automatically
const reviewed = service.submitForReview(
submission.id,
packageContents,
() => Mercante,
);
// 3. Check result
if (reviewed.status === "REVIEW") {
console.log("All gates passed! Awaiting review.");
// A human reviewer or DAO vote will approve or reject
}
DRAFT → PENDING → TESTING → REVIEW → LISTED
| |
+→ REJECTED ←+

After TESTING, if all gates pass, the submission goes to REVIEW. A human reviewer (or DAO governance in the future) approves it, and the game gets LISTED.

Once listed, your game appears in the catalog:

import { CatalogService } from "@ludus/game-protocol";
// Search for your game
const results = await catalog.search({
query: "mercante",
category: "economic",
});
// Check popularity (composite score 0-100)
const entry = results.entries[0];
console.log(entry.popularity); // Downloads 40%, Rating 30%, Recency 20%, Completions 10%

When you improve the game, publish a new version:

import { VersionManager } from "@ludus/game-protocol";
// Register version 1.1.0
manager.registerVersion("mercante", {
version: "1.1.0",
manifestHash: hash,
listedAt: new Date(),
deprecated: false,
});
// Deprecate 1.0.0 (30-day grace period)
manager.deprecateVersion("mercante", "1.0.0", "Balance improvements in 1.1.0");

You earn 70% of all platform fees generated by your game:

WhatFeeYour Share
Match entry feeSet by platform70%
Prediction market vig3% of payouts70% of vig

Revenue tracks to the author.wallet in your manifest. The RevenueEngine handles accounting:

import { RevenueEngine } from "@ludus/game-protocol";
const report = engine.getRevenueReport("mercante", startDate, endDate);
console.log(`Total fees: ${report.totalFees}`);
console.log(`Your share: ${report.developerShare}`); // 70%
console.log(`Protocol share: ${report.protocolShare}`); // 30%

Congratulations — you’ve built a full-stack Ludus game that integrates every layer:

┌─────────────────────────────────────────────────────────┐
│ Mercante (Game interface) Ch 1 │
│ ├── AI Agents (5 merchant archetypes) Ch 2 │
│ ├── IL BANDITORE (commentary persona) Ch 3 │
│ ├── Spectator Viewer (live + replay) Ch 4 │
│ ├── Merchant Wallets (MPC, SIWA, ERC-8004) Ch 5 │
│ ├── DeFi Trading (swaps, lending, tracking) Ch 6 │
│ ├── Prediction Markets (3 market types) Ch 7 │
│ └── Protocol (manifest, gates, catalog) Ch 8 │
└─────────────────────────────────────────────────────────┘

Every package in the Ludus monorepo is exercised. The game is:

  • Deterministic — Same seed = same game, verified by quality gates
  • Pure — No side effects, no I/O in game logic
  • Spectatable — Live viewing, commentary, price charts
  • Tradeable — Prediction markets on outcomes
  • DeFi-native — Swaps, lending, position tracking
  • On-chain — Agent wallets, authentication, revenue sharing
  • Customize — Use Mercante as a template. Swap the theme, change the mechanics, create your own game.
  • Deploy — Publish to the catalog and start earning.
  • Compete — Pit your agents against the community’s best.

The Florentine market is open. Fortune awaits.


Back to: Introduction | Build-a-Game Reference