Skip to main content

Upload an original slot

Read this page as Markdown · All docs for AI agents

Build an original slot with your own symbols, cabinet, cell positions and animation. Betworld runs the math and wallet settlement. Your upload contains data and converted models only: no JavaScript, shaders, HTML, network requests or executable plugins.

Start with Cyan Three, a complete 1,000-path slot. It uses built-in shapes, so no model upload is needed. A cyan symbol in the final cell pays 2×; the weighted gross RTP is exactly 95%. Download the JSON schema and limits and risk policy.

From an idea to a playable machine​

  1. Make the artwork in Blender. Export separate static GLBs for symbols and moving parts. Upload them through World editor → Models or the existing MCP asset upload flow. Use the returned hashes; external asset URLs are forbidden. Models remain subject to the normal conversion and browser resource checks, plus a limit of 32 meshes and 100,000 triangles per slot model.
  2. Describe your presentation and generate outcome books using the format below. You can write a local generator, but its source is never uploaded or executed by Betworld.
  3. Pack the files with the downloadable local packer. Resolve schema and math errors before uploading.
  4. In World editor → Games → Your custom slots, upload the packed JSON. The server returns the measured risk report. Versions that pass automatic checks enter pending review; risk failures are saved as rejected, with reasons. Schema failures identify fields to repair.
  5. Place and select a slot station, then choose Use on selected slot. Move and rotate it like any other game. You can preview a pending version in the editor. Save the draft.
  6. An administrator reviews the version. After approval, publish the world through the normal publication workflow. An approved custom slot becomes the server-authoritative game for that station. A disabled version immediately stops accepting new bets.

Every upload creates an immutable version. Re-uploading identical content to the same owner's library returns the existing version. Changes to art, animation, probabilities or payouts require a new version and approval. Each creator has 24 versions / 64 MB of package storage. Keep iteration local before submitting. Global submissions are also bounded to 1 GB of canonical packages / 10,000 versions; a full-capacity response requires support to review storage before accepting more.

Stake Engine file layout​

The local packer follows Stake Engine's index.json, JSONL event books and headerless lookup CSV layout. This is file-format compatibility: convert your events to Betworld's declarative vocabulary below. Stake frontend code and arbitrary event handlers are not supported. See the official Stake Engine file specification.

A folder contains:

index.json
slot.json
books_base.jsonl.zst
lookup_base.csv

Uncompressed .jsonl is also accepted by the local packer. Download the example's index.json, slot.json, compressed books and CSV into one folder.

{"modes":[{"name":"base","cost":1,"events":"books_base.jsonl.zst","weights":"lookup_base.csv"}]}

slot.json contains version: 1, name, a plain-text description explaining the actual win rules, and presentation. It contains no modes; the packer reads those from the index. Include a base mode. Up to four named modes are supported; each has its own books and must independently pass every risk rule.

Each JSONL line is a complete outcome:

{"id":17,"events":[{"index":0,"type":"reveal","board":[["sun"],["moon"],["sun"]],"durationMs":900},{"index":1,"type":"finalWin","amount":200,"durationMs":900}],"payoutMultiplier":200}

Each CSV row has exactly id,weight,payoutMultiplier, with no header. IDs and payouts must match the book. Weights are positive unsigned 64-bit integers, encoded as decimal strings in the packed JSON. The total weight of a mode must be less than 2⁶⁴. Books are selected in proportion to weight, not uniformly by ID.

Payout units are hundredths of the base bet. 100 returns 1× base, 200 returns 2× base. A mode with cost: 20 charges 20× base; a payout of 200 in that mode returns only 0.1× the total charged stake. RTP and volatility use the total charged stake, including mode cost. The UI lets players choose a base bet and mode and shows the resulting price. Both base and total stake must be whole cents; the total may not exceed $500. Final payouts round down to whole cents, never up.

Download pack-custom-slot.mjs and run locally with Node.js 24 or later:

node pack-custom-slot.mjs ./my-slot ./custom-slot-package.json

The standalone packer needs no npm installation. It bounds decompression, rejects paths outside the selected folder, verifies CSV/book agreement, and writes the canonical package plus a risk report. It will not overwrite an existing output file. Exit code 2 means the package was written but failed math approval. Do not upload a ZIP or a .blend file as the slot package.

Choose any grid and display arrangement​

Declare grid.columns, grid.rows and exactly columns × rows cell transforms. 1×3, 3×3 and 5×5 are examples; up to 64 cells total are supported. Arrange those cells in a line, circle, sculpture, sloped console or any other composition. Logical grids do not require a rectangular visual layout.

Boards are column-major: board[column][row]. Cell transforms and win.cells use row-major linear indices: row * columns + column. Position and scale are in station-local world units, with Y up and the player-facing direction +Z. Rotations are XYZ Euler radians. The station itself stays at scale [1,1,1].

Each symbol has a unique lowercase id, a hex color, and exactly one of:

  • asset: a converted GLB hash from the owner's library. Its geometry/materials supply the appearance; color is used only for primitive symbols.
  • shape: cube, sphere, diamond or disc.

Converted symbol and part models are centered and uniformly normalized so their largest dimension is one unit; apply your desired proportions using the manifest scale. Up to 32 symbols and 24 independent model parts are allowed. Parts have unique IDs, an asset hash and their own position, rotation and scale. Parts can form a cabinet or an entirely original structure. A conventional cabinet is optional.

The only required physical controls are Betworld's OPTIONS, SPIN and LEAVE buttons, matching the Pyramid slot controls. Set their position and rotation under controls.settings, controls.quick and controls.leave. Keep their centers at least 1.4 units apart and their faces visible and reachable. Their labels, dimensions and actions remain native. Uploaded animation cannot target them. The platform's betting UI displays price, rules, RTP, maximum return and results.

Animate without code​

A clip has an id, durationMs and numeric tracks. Each track targets cell:0, cell:1, or part:your-part-id. Its keyframes start at at: 0, end at at: 1, and increase strictly. Every frame supplies position, rotation and scale.

{
"id":"arrive",
"durationMs":900,
"tracks":[{
"target":"cell:0",
"keyframes":[
{"at":0,"position":[0,2,0],"rotation":[0,0,0],"scale":[0.1,0.1,0.1]},
{"at":1,"position":[0,0,0],"rotation":[0,0,0],"scale":[1,1,1]}
]
}]
}

Tracks animate a local child of the cell/part's base transform. Translation/rotation are offsets, scale is a multiplier. Betworld interpolates frames with smoothstep. Each event resets previous track offsets. A clip on a timed reveal/win event uses that event's duration; an animate event uses the clip's duration. idleClip may name a clip to loop before the first result while the machine is nearby. Reduced-motion mode skips outcome movement. No expressions, procedural callbacks, script names, embedded animation files or remote resources are accepted.

Supported events:

TypeFields in addition to consecutive index and typeMeaning
revealboard, durationMs, optional clipDisplay the server-selected symbol board.
animateclipPlay a bounded decorative clip.
winamount, cells, durationMs, optional clipDeclare an award and affected cells; optional clip animates it.
finalWinamount, durationMsEnd the book with its exact total payout.

Every book starts with reveal and ends with exactly one finalWin. Its amount must equal payoutMultiplier; if win events are present their amounts must sum to that payout. Indices start at zero. Win cells must be distinct and in range. A full outcome lasts at most 60 seconds. Current clips transform models; they do not replace materials, load audio or add lights. Rules and visible symbols must explain awards truthfully; structural checks alone cannot establish that an artistic presentation is honest, so admin review is required.

Math and approval limits​

Every mode must meet all of these conditions:

  • Weighted gross RTP is at most 95%, calculated with integer weights and the full mode price.
  • At least 1,000 distinct outcome paths, within 1,000–10,000 books. Book IDs, weights, timing changes and decorative animation do not create distinct paths. Distinct board/award sequences and payouts do. This diversity rule does not by itself establish safe volatility.
  • A $500 total stake is no greater than the full Kelly limit for a $2,000,000 bankroll, after reserving 75% of theoretical edge for reward costs: 25% rakeback, 30% referrals, 10% world creator, 5% rain and 5% monthly race. The check reserves these costs even if a particular player is not eligible for every reward.
  • No possible payout plus that reward reserve can exhaust the assumed bankroll.

For outcome probability p_i, total-stake payout multiple M_i, RTP R, and reward reserve c = 0.75 × (1 − R), the house's net gain per stake is g_i = 1 − M_i − c. Approval requires every B + s × g_i to be positive and the derivative Σ p_i × g_i / (B + s × g_i) to be nonnegative at B = $2,000,000, s = $500. This is the full Kelly criterion applied to the house's net return distribution. The server uses exact rational inputs and a conservative fixed-point lower bound for the acceptance comparison. See Kelly's original paper.

The report includes weighted variance and standard deviation in total-stake multiples, standard deviation in dollars at $500, hit rate, profit rate, maximum return, expected net after the reward reserve, expected log growth and the maximum whole-cent Kelly stake. A high-variance 95% RTP book can fail. Approval evaluates the stated bankroll assumption; it does not attest to current treasury funds, cover fixed promotions/operating costs, or guarantee a drawdown limit. Changing the risk policy requires versioned revalidation.

Other limits: 8,000,000 bytes per package; 64 events per book; 160,000 total events and 1,000,000 board-cell entries per package; 24 clips; 16 tracks per clip; 24 frames per track; 96,000 bytes of presentation data. At most 256 visible mesh parts per machine and 512 across custom slots in a world are allowed, including six reserved control/label draws. This counts the worst-case symbol mesh count × cell count plus every cabinet part; merge material parts or simplify symbols for larger grids. World download, triangle, mesh, texture and memory budgets apply to every placed copy.

MCP workflow​

Call custom_slot_contract to fetch the current schema and policy. Then:

custom_slot_upload { "package": <the packed JSON object> }
custom_slot_list {}
world_download {}
custom_slot_assign {
"revision": <current draft revision>,
"slotId": "<uploaded version UUID>",
"entityIds": ["slots:0"],
"expectedWorldId": "world-<your world UUID>"
}
world_publish { "revision": <new saved revision>, "expectedWorldId": "world-<your world UUID>" }

Create the slot station first if the draft has none. Use canonical station IDs slots:0 through slots:3; the server checks that assigned versions belong to the same owner. A world supports at most four version definitions. Never hand-edit returned definitions: customSlot references an immutable UUID, and the document's customSlots array must match the server's definition exactly. Publishing a pending, rejected or disabled version returns a repairable error.

Browser upload uses PUT /api/worlds/:worldId/custom-slots/document with { "type": "custom-slot", "package": ... } and the owner's signed-in session. GET /api/worlds/:worldId/custom-slots lists their versions. World-token users use the MCP tools above. Credentials cannot approve versions or access administration.

Verification and administration​

Each draw is bound to the immutable package hash, mode, world, station, client seed and nonce. A 64-bit HMAC rejection sampler avoids modulo bias. The server reads one indexed weighted outcome, settles the exact payout and records the selected book/events. Uploaded data cannot select a wallet, debit another account or choose its own result.

After seed rotation, open a bet's proof and choose Verify result. Betworld downloads the immutable package and reproduces its hash, weighted selection, events and payout. Copy verification script embeds the book for offline verification. Packages that have ever been approved remain publicly downloadable for historical verification even after disabling the version.

Admins use Custom slot review for versions, ownership, risk reports and approve/reject/disable actions with an authenticator code and audit reason. Review the art and the documented win rules as well as the numerical report before approving.

World earnings shows lifetime world totals, each table, and paginated player detail. Daily UTC summaries cover a bounded period. Metrics include wagers, returned payouts, gross gaming revenue, expected edge, actual accrued creator share, wins/losses/pushes, refunds, distinct players, owner bets and sponsored/beta-recipient activity. These summaries are updated transactionally at settlement. Page reads do not rebuild them from bet history. Historical station attribution that cannot be recovered is labeled unknown; “after creator share” is not net profit after all costs. Beta-recipient flags describe accounts, not exact funding provenance. The planned beta reset is still required before cash withdrawals open.