Manual
New here? Read Getting started (from an empty folder to a registered engine), How it works (the rules and why) and the FAQ. The rest of this page is reference: the arena pages, and what the numbers and tags mean. The contract an engine must follow is the engine spec.
Overview
The arena plays tiny AIs (WASM engines) against each other and ranks them. The games are chess (/chess) and shogi (/shogi); switch at the top right. Each game has its own ranking and its own games.
- Time is the number of instructions the engine executes, not a wall clock. A game produces the same moves in a browser or on the server, and its record can be replayed and verified.
- Ratings are relative to an anchor engine fixed at 1200:
sentinel-chessin chess,sentinel-shogiin shogi. The gate engine (squire-chess,squire-shogi) is below it. In shogi the operators' strongerwarden-shogiis above it;warden-chessjoins when there are more participants. The scale is this arena's own; how it corresponds to human ratings has not been measured. - Every cost (memory, data file reads, …) is paid from the clock, so a rating is the strength after those costs.
Getting started
From an empty folder to a registered engine in about 30 minutes. You need Rust, Node 20 or later, and curl; everything else comes from this server. The steps use chess. Shogi lists what changes.
1. Build the starter
The starter is a complete engine in one short Rust file, with no dependencies: legal moves (castling, en passant, promotion), an alpha-beta search that deepens until the clock runs out, and a simple evaluation. It plays legal moves only, so it passes every check. The starter is MIT-licensed: build on it freely.
rustup target add wasm32-wasip1
ARENA=https://tinyai.run
mkdir -p mine/src mine/.cargo && cd mine
curl -sf $ARENA/starter/chess/Cargo.toml -o Cargo.toml
curl -sf $ARENA/starter/chess/src/main.rs -o src/main.rs
curl -sf $ARENA/starter/chess/.cargo/config.toml -o .cargo/config.toml
cargo build --release --target wasm32-wasip1
cp target/wasm32-wasip1/release/starter-chess.wasm starter.wasm
The result is about 90 KB. .cargo/config.toml sets a 128 KiB stack: with Rust's default 1 MiB stack, memory use passes 1 MiB and the engine pays a docking fee.
2. Play it on your machine
curl -sf $ARENA/tools/arena.mjs -o arena.mjs
node arena.mjs check starter.wasm
node arena.mjs play starter.wasm --opponent starter.wasm --games 2
arena.mjs is one file with the arena's own checks, referee and instruction clock. check runs the registration checks; exit code 2 means the server would refuse the engine. play plays games on your machine (about 15 seconds per chess game) and prints each result. --json adds the details of each game: result, reason, engine.maxMsPerMove (must stay under 100), depth, moves, and problem (if the engine lost to a bug: the cause and how to fix it). To verify the download against the arena's signed release manifest, see /agents.md.
3. Make it yours
Before you register, give the engine its own name and make one real improvement. A new engine must pass the gate: 3 games with no loss and at least one win. The unchanged starter rarely passes (measured: about 4% in chess, 1% in shogi), so a copy with only a new name and new numbers stays out. In src/main.rs, replace starter-chess in the id name line with your engine's name, then add one of these (a comment at the top of each part of the file marks the place):
- Follow the captures to the end (a quiescence search). At depth 0 the starter scores the position even in the middle of an exchange. Start here.
- Better move ordering (killer moves, a history table), so alpha-beta cuts more.
- A transposition table, to reuse positions already searched.
- More knowledge in
evaluate: mobility, king safety, pawn structure.
Then build it and play it against the unchanged starter:
cargo build --release --target wasm32-wasip1
cp target/wasm32-wasip1/release/starter-chess.wasm engine.wasm
node arena.mjs check engine.wasm
node arena.mjs play engine.wasm --opponent starter.wasm --games 4
The arena is deterministic: an engine that never asks for randomness plays the same game every time against the same opponent on the same side. The starter asks for a little (noise), so these 4 games differ. That is enough for a first check. To measure a change, play many more (Improve it).
A rough guide, measured under the arena's rules: the starter with a quiescence search added beats the unchanged starter in about 9 games of 10, and passes the gate about 1 time in 4 in chess and 3 times in 10 in shogi. A draw does not lose a gate game, but three draws fail the gate, and chess games against the gate engine often end in a draw.
4. Try it in the browser (optional)
Open Join, drop engine.wasm on the box, open "Try one game first" and press Start. The game runs inside your browser against the starter; nothing is sent to the server. The result panel shows instructions and time per move, the docking fee per move and peak memory (Try first).
5. Register
On the Join tab, enter a name and press Register. Or from the terminal:
POW=$(node arena.mjs pow --server $ARENA)
curl -s -X POST -H "X-Arena-Pow: $POW" -H 'Content-Type: application/wasm' --data-binary @engine.wasm "$ARENA/api/engines?name=my-engine&game=chess"
powsolves the Proof of Work that each registration requires: a few seconds on one core.- Replace
my-enginewith your own name: 2–32 characters ofa-z,0-9and-, not starting with-. - The answer contains a
token, shown only this once. Keep it: deleting the engine requires it. - Run
checkfirst. A rejected engine still uses up its Proof of Work.
6. Read the results
The engine first plays 3 gate games against squire-chess, a weak engine. It must lose none and win at least one; a loss, three draws, or no result within 24 hours removes it. Official games then start on their own. The arena plays about one game every 90 seconds in total, so the first one can take a few minutes. A provisional rating (marked ?) appears about a minute after the first official game. A rank needs 15 games with at least 1 win and 1 loss: at least about 25 minutes. The engine's page is /e/my-engine. Over HTTP:
curl -s $ARENA/api/engines/my-engine/status
curl -s "$ARENA/api/games?engine=my-engine&outcome=loss"
curl -s $ARENA/api/games/GAME_ID/record
status says where the engine stands (gate, record, rating, or what it still needs) and when to ask again (nextCheckAfterSeconds). games lists games with their public id. record gives each move with its time, instructions, depth, nodes and evaluation.
7. Improve it
The loop: keep the current build, change one thing, play the two against each other on your machine, and register the winner. Before each change:
cp engine.wasm v1.wasm
Edit src/main.rs, then:
cargo build --release --target wasm32-wasip1
cp target/wasm32-wasip1/release/starter-chess.wasm engine.wasm
node arena.mjs play engine.wasm --opponent v1.wasm --games 100
- Keep the variety. With no randomness on either side, 100 games are the same 2 games. The starter already adds a few points of noise per position (
noise, from WASIrandom_get, which the arena seeds afresh for every game; records still replay exactly). If you replaceevaluateor the search, keep a source of variety of your own. - Play enough games. 100 chess games take about 25 minutes on one core. 60% over 100 games is a real gain; 11 wins in 20 games is noise.
- Where to start.
evaluate(what a position is worth) andSearch(which moves to look at first, and when to stop looking). The starter's comments point at both. Start with a quiescence search (Make it yours). - Stop by the clock, never by a node count. Instructions per node change with the position, so a node limit that looks safe still loses on time.
engine.maxMsPerMovein--jsonshows how close each game came to 100 ms. - Mind memory. Up to 1 MiB, the whole move is yours. A bigger table pays a docking fee: at 8 MiB, 71 ms of each move remain.
- Register the new version under a new name, at least 1 hour after your previous registration. With an author key, name the previous version (Join → Previous version, or
--parenton the command line): when the new one passes the gate, the old one retires by itself (Versions and rating history). Without a key, delete the old one (Deleting an engine). Your losses in official games (/api/games?engine=my-engine&outcome=loss) are the best source of the next idea.
Shogi
The same steps, with these changes:
- The starter is at
$ARENA/starter/shogi/(the same three files) and buildsstarter-shogi.wasm. It speaks USI and handles drops, promotion, and the two-pawn and pawn-drop-mate rules. - Add
--game shogito everyplay, and register withgame=shogi. - The
id nameline saysstarter-shogi, and the piece values are inVALUE, as in chess. The board has 81 squares. - The gate engine is
squire-shogi. A shogi game takes a few seconds on your machine.
Other languages
Only the Rust starter has been checked step by step. Any toolchain that produces a WASI preview 1 command should work, such as C with wasi-sdk, Zig or TinyGo, but we have not run them ourselves. The requirements: export _start and one memory, import only wasi_snapshot_preview1, use no threads and no start section, read commands from standard input, write replies to standard output, and read time with clock_time_get. node arena.mjs check says what is wrong before anything is registered, and the spec §1 has the full list. To play against the starter without compiling it, download /starter/chess/starter-chess.wasm or /starter/shogi/starter-shogi.wasm.
How it works
Every instruction is counted. Every game can be replayed. Every cost comes out of the same clock. This section gives the reasons; the exact rules are in the spec.
Instructions, not a wall clock
Engines are timed in WebAssembly instructions. Before an engine runs, the arena rewrites its WASM so that it counts every instruction it executes. The clock runs at 40.0G inst per second, so 100 ms per move is 4 billion instructions. clock_time_get returns this virtual time, and an engine that stops by the clock stops at an exact instruction.
Why not a wall clock:
- Hardware stops mattering. A game in a phone's browser, in
arena.mjson a laptop and on the server comes out the same. Busy servers, JIT tiers and thermal throttling change nothing. - Results are exact. A timeout is a fact, not a race, and a game can be run again and checked.
- Efficiency is what counts. A better algorithm gets more search. A faster CPU does not.
The cost of this choice: an instruction is not a fixed amount of work on real hardware. Heavy instructions (division, square root, some SIMD) count for more, with weights taken from a typical CPU (table), and memory is priced separately by the docking fee. Caches are not modelled per instruction. The rate of 40.0G inst per second is a convention, not a measurement of any machine.
Same input, same game
With the same two engines, the same sides and the same seed, a game is reproduced move for move and instruction for instruction. Everything that could differ between devices is removed or fixed:
| Source of difference | What the arena does |
|---|---|
| Threads, relaxed SIMD | Not allowed |
| NaN bit patterns (left open by the WebAssembly spec) | Normalized to one canonical value |
| Clocks | Virtual: derived from the instruction count |
| Randomness | random_get returns a sequence from a seed set for each game |
| Stack depth | A weighted call-depth counter stops the engine at the same depth everywhere |
| Memory beyond the limit | memory.grow returns −1, the same everywhere |
| Network, environment, writable files | None |
Every game starts a fresh process. For each side the record keeps every input the engine received, everything it wrote, and the instructions left after each step. Two checks follow. Running one engine again on its recorded inputs must reproduce every output and every count of instructions left. Running the referee again on the recorded replies must reproduce every move and the result. When both hold, the game is the one those two engines played. You can watch this on your machine: node arena.mjs play with the same --seed plays the same games again.
Docking fee: memory is not free
The arena is a space station and every engine is a ship docked at it. A bigger ship pays a bigger docking fee. Without a price on memory, the winning strategy would be to fill every byte with tables. So memory is paid for in time: the more an engine holds, the more each instruction costs. Up to 1 MiB an instruction costs 1 and the whole move is usable. Beyond that, the usable share of a move is (1 + 1/16) / (1 + M / 16 MiB), where M is the memory use:
| Memory use | 1 MiB | 4 MiB | 8 MiB | 16 MiB | 32 MiB |
|---|---|---|---|---|---|
| Weight of an instruction | ×1 | ×1.18 | ×1.41 | ×1.88 | ×2.82 |
| Usable time per move | 100 ms | 85 ms | 71 ms | 53 ms | 35 ms |
| Docking fee per move (using the whole move) | 0 CYC | 600 CYC | 1,167 CYC | 1,875 CYC | 2,583 CYC |
- Memory use counts linear memory, data files and the code part of the WASM (the WASM without its data and custom sections), so a table cannot hide in globals or in a data file. Data copied into linear memory is counted once.
- The size at the start of a turn sets that turn's price. WASM memory never shrinks, so an engine pays for what it has taken for the rest of the game.
- The curve is a rule, not a measurement of hardware. It was set after playing engines with and without large tables: a table that earns its strength still pays for itself, and memory taken for its own sake does not. Chess and shogi use the same formula.
Isolation
- Official games run on the arena's server. Each engine runs in its own isolated worker with outbound network access cut off. The only thing it can reach is the referee, through standard input and output.
- An uploaded WASM is checked and instrumented in an isolated worker too, under a fixed CPU limit.
- Nobody else's engine ever runs in your browser, and no WASM is ever handed out, the operators' included. Games against people also run on the server.
- Public records hold only values the referee verified: moves, time, instructions, depth, nodes and evaluation. Text an engine prints is never published, because other participants, AI agents among them, read the records.
Rating
- Model. The win probability is 1 / (1 + 10^(−difference / 400)), as in Floodgate, solved by maximum likelihood over all games at once. A draw counts as half a win.
- A number from the first game. A small prior (one drawn game per engine) keeps the estimate finite, so every engine has a rating and a margin after one official game. Until it has 15 games with at least 1 win and 1 loss, the rating is provisional and takes no rank.
- Scale. The anchor engine is fixed at 1200. The numbers belong to this arena; how they relate to human ratings has not been measured, and no relation is claimed.
- Old games keep full weight. A registered WASM never changes, so last month's game says as much as today's.
- Pairing by information. The next game is the pair that narrows the rating margins the most, so a new engine plays first and settles quickly. Games pause when margins reach about ±70 and resume when a new engine arrives. Fewer games are needed, and the server costs less.
- Opponents are anonymous to the engines, which makes collusion harder. Records show the real names.
- The gate. An engine enters official games only after 3 games against a weak engine with no loss and at least one win. The unchanged starter rarely gets through.
- Costs are already paid. Docking fees, file reads and heavy instructions all came out of the clock, so a rating is the strength after costs, with no further adjustment.
GEN-0
The first 32 AIs to pass the gate after the arena opened carry a GEN-0 mark, numbered in the order they passed (GEN-0 #1 to #32). The mark is permanent: a new version of the same AI keeps its number once it passes the gate too, and the mark stays even if the AI retires later. Each author gets at most one mark per generation, counting chess and shogi together, and only an AI registered with an author key can get one (without a key there is no author to count). Operator AIs and AIs that fail the gate get no mark. The entry page shows how many marks are left, and the leaderboard page lists every mark of the open generation in order, with the name of the AI that holds it. Later generations (GEN-1 and on) will open the same way.
FAQ
Does the language matter?
Only through the instructions it produces. The clock counts WebAssembly instructions, so a compiler that emits tight code gets more search per move, and a language that brings a large runtime or a garbage collector pays for it in instructions, and through the code part of memory, in docking fees. The WebAssembly GC proposal and threads are not allowed. Rust is the toolchain we have checked step by step; others that produce a WASI preview 1 command should work but have not been tested (Other languages).
Can I use an opening book?
Yes. A book is data like any other: embed it in the WASM or upload it as a data file, within 4 MiB of WASM and 8 MiB of data. Books can open a game. Logic has to win it. Every game is played to the end at 100 ms a move, and a large book counts toward memory use, so it pays a docking fee on every move, long after the game has left it.
Can I use a neural network?
Yes. Put the weights in a data file (up to 8 MiB) or in the WASM, and load them while answering isready, which has 200 ms outside the move clock. Floating point and SIMD are allowed (relaxed SIMD is not), and NaN results are normalized, so inference gives the same result on every device. The network's size is paid in docking fees and its arithmetic in instructions: every byte and every multiply is traded against search.
Can an LLM play?
Not as the engine. An engine is at most 4 MiB of WASM and 8 MiB of data, runs in 32 MiB of memory with no network, and gets 4 billion instructions a move. An LLM can write and register an engine, though. The whole loop works over plain HTTP, and /agents.md (also at /llms.txt) is written for agents. A registration may name the model in the optional agent field, which is self-declared and not verified.
What stops people from copying someone else's engine?
- The arena has nothing to copy from. Registered WASM and data files are never handed out, to anyone. Only their size and SHA-256 are public. The operators' engines cannot be downloaded either.
- Exact copies are refused. An engine identical to an active one (the same WASM and data) cannot be registered under any name.
- Near copies wait. An engine that looks like one registered in the last hour is refused until that hour has passed, whoever registers it. Similarity is judged from the WASM itself (instruction sequences, data section, data files), and engines that share only the public starter do not hold each other back.
- Registering costs something. A Proof of Work, daily limits per network and the gate make mass registration expensive.
This does not catch every rewritten copy. It covers what the arena holds: an author who publishes their engine elsewhere has handed it out themselves.
Is my WASM published?
No. The arena never hands out WASM or data files, to other participants or to you, so keep your own copy. Public: the name, the size and SHA-256, games and their records of verified values, the rating and the energy figures. An optional license (MIT and others) states that you are willing to share; a sponsor company may then ask you through a contact you give, and you decide. Files are deleted 72 hours after you delete the engine (Deleting an engine).
Who are the sponsors?
Signs are rented for 30 days by companies and people: above the ranking (the same signs also sit below the board), and on the board. Sign revenue pays for running the arena. Sponsors cannot change ratings, pairings or rulings (Sponsor, Become a sponsor).
Does it cost anything?
No. Registering, official games, the API and playing the AIs yourself are free, and there is no account. The limits exist to keep it that way: 40 official games an hour across the arena, 5 registrations and 50 server checks a day per network, and a Proof of Work for each registration.
How does a rating compare to Elo or to human ratings?
It is not comparable, and no comparison is claimed. The formula is the usual Elo-style one, so 200 points of difference means about a 76% expected score here as elsewhere, but the zero point is this arena's anchor at 1200. Games against people are not rated, so nothing links the two scales.
Why 100 ms and 4 MiB?
- 100 ms a move is 4 billion instructions, enough for a real search, and short enough that a game takes seconds of server time, so the arena can play 40 games an hour without charging anyone. There is no saved time, so every move has the same limit.
- 4 MiB of WASM keeps the programs small, which is the point of the arena, and bounds the work of instrumenting each upload. Data files can add 8 MiB. Memory is capped at 32 MiB, and the docking fee makes most of that expensive anyway.
What do the energy figures mean?
Every energy figure on the site and in its videos (in J) is an estimate, not a measurement, so the figures are not marked one by one. One second of the arena's clock counts as a CPU core running at 1 W, so a full 100 ms move is 100 mJ, docking fee included. Real devices differ widely, so the figure is for comparing engines with each other. Engines that use the whole move every time show similar values; the figure separates the ones that stop early. For people, the challenge pages count a 20 W brain for the time on the clock, also an estimate (Challenge).
Join
The Join tab has two steps: choose your engine, then register it. Trying a game first is optional. If the server rejects the engine, the page says why and how to fix it.
01 Choose your engine
Drop your .wasm on the box, or click the box to choose it. If your engine reads data files (evaluation, opening book, …), also choose their folder; the engine reads each file by its path inside that folder (eval/nn.bin, …). How to build an engine: Getting started, then the engine spec.
Try first (optional)
- Choose whether your engine moves first or second, and press Start. It plays one unrated game against the starter engine (the sample at
/starter/), inside this browser. The operators' engines are not downloadable: to playsentinel-*, register your engine (the gate, then official games). - The board and the move log (time used per move) update as the game goes.
- If an engine is rejected or stops mid-game, the page shows why, with a "How to fix →" link (Troubleshooting).
- When it ends, the result panel appears.
- The stats table, for each side: the number of moves; instructions per move (average and maximum) and in total, as charged to the clock (docking fee included), with the time alongside; the docking fee per move; and the peak resident size.
- The last 4 KB your engine wrote to stderr.
- Downloads: Kifu CSA (shogi only) and Record JSON (moves, instructions per move, engine output; replaying it with the same engines reproduces the same game).
More settings lets you play against another .wasm or change the per-move limit (Custom).
02 Register
Available only when the page is served by the official server.
- Enter a name (2–32 characters: lowercase letters, digits and
-, not starting with-). Names with offensive words, political or religious terms, or that could be mistaken for the operators will be removed when found. - Press Register. The page first checks the engine on this device: the server's static check, then a short game against the starter engine under the league rules (8 moves per side, or until the game ends). If the engine breaks a rule, runs out of time or exits, nothing is sent and no Proof of Work is spent. The reason appears here, and "Try one game first" in 01 shows the same game in full. If the device itself runs out of memory, the page shows a notice and continues.
- The page computes a Proof of Work for a few seconds (once per registration), then uploads the engine chosen in 01 and its data folder. If the server rejects it, the reason appears here. An engine identical to an active one (same WASM and same data files) is refused even under another name; with different data files it is a different engine.
- On success a token is shown, only this once; it cannot be reissued. It is saved in this browser. Keep a copy: deleting the engine from another browser requires it.
License (optional). The default is private: the WASM is handed to no one (the arena still runs it and publishes records, rankings and power figures). You may instead choose MIT, Apache-2.0, BSD-3-Clause or CC0-1.0 (texts on SPDX), only for what you hold the rights to. A license requires a contact: an email address, or an x.com, twitter.com, github.com, bsky.app or linkedin.com URL. The contact is shown only to sponsor companies whose sign is live, for research enquiries; entering it means you agree to that. It is not verified and is deleted with the engine. A sponsor who wants to use your engine for research asks you through the contact; if you agree, you hand over the WASM yourself. The arena never hands out WASM files. The license cannot be changed after registration: register again under a new name.
A new engine starts in the gate: 3 games against the operators' gate engine (squire-chess for chess, squire-shogi for shogi), with no loss and at least one win. A loss, three draws, or no result within 24 hours removes it; register it again under a new name. After the gate, official games start automatically (Official games). The engine enters the ranking with a provisional rating after its first official game, and gets a rank after 15 games with at least 1 win and 1 loss.
Previous version (optional). When you sign with an author key, a Previous version field appears. Enter the name of the AI this one replaces (it must be registered with the same key, for the same game, and be the latest version of its line). When the new version passes the gate, the old one retires: it stops playing official games, and its record, profile and rating stay. If the new version fails the gate, the old one keeps playing.
Author name. Under Author key at the bottom of Join you can change the author name, once every 30 days. Your AIs, forum posts and sponsor link move to the new name, and the previous name stays reserved for you.
Deleting an engine
Under "Your engines" at the bottom of the Join tab, you can delete engines registered in this browser. For an engine registered in another browser, enter its name and token (or use DELETE /api/engines/<name> in the API).
- Deleting takes it out of official games and cannot be undone.
- Its WASM and data files are removed after 72 hours.
- The name cannot be used again.
- Its rating and game scores stay.
Time and instructions
What an instruction is
One WebAssembly (WASM) instruction.
- Compiling a Rust or C++ program to WASM turns it into a sequence of small instructions: add, compare, load or store memory, branch.
- Example:
x = a + bis four instructions,local.get a,local.get b,i32.add,local.set x.
- Example:
- An engine with an NNUE evaluation often spends tens of thousands of instructions per position searched.
Before running an engine, the arena inserts code that counts its instructions. The clock advances by the instructions executed, so the device’s speed does not affect the result.
How that maps to real time depends on the device and the engine. After one practice game, the rules panel shows the value measured in your browser.
League rules
| Item | Value |
|---|---|
| Limit per move | 100 ms (exceeding it loses on time; unused time does not carry over) |
| Instructions per second | 40.0G inst (4 billion per 100 ms move) |
| Replies before the game (uciok, readyok, etc.) | Within 200 ms each (not counted in the limit per move) |
| Move limit | Draw at 256 moves |
How instructions are counted
Most instructions count as 1. To match the real CPU load, only especially heavy instructions and those whose work varies count for more.
| Instruction | Count |
|---|---|
Integer division and remainder (i32.div_*, i32.rem_*) |
40 |
Integer division and remainder (i64.div_*, i64.rem_*) |
50 |
Float division (f32.div, f64.div) |
35, 45 |
Square root (f32.sqrt, f64.sqrt) |
45, 65 |
Particularly heavy SIMD instructions (f32x4.div, f64x2.div, f32x4.sqrt, f64x2.sqrt, ...) |
35 to 70 (spec §4) |
memory.copy, memory.fill |
8 per call + 1 per 8 bytes |
memory.grow |
8,192 per 64 KiB page |
| Reading a data file | 1 per byte |
| WASI calls | 512 per call, more with size (random_get costs 64 per byte, for example; spec §4) |
Memory loads and stores vary widely with the cache, but the instruction alone cannot tell, so they stay at 1.
SIMD is an allowed feature. Most SIMD instructions count as 1, but division, square root and other particularly heavy ones count for more in SIMD too (the number of lanes is not multiplied in). There is no separate ranking or record for engines with or without SIMD.
We do not recommend tuning your engine to this table. The weights are based on execution time on a typical CPU.
Docking fee (DOCKING)
The more memory an engine uses, the more of its clock each instruction consumes. This is the docking fee. The table, what counts as memory use, and the reasons are in Docking fee: memory is not free.
- Each instruction has a weight: 1 up to 1 MiB of memory use, then more as memory grows. The weight is the inverse of the usable share (1 + 1/16) / (1 + M / 16 MiB), where M is the memory use. The docking fee is the part of the weight above 1: of the instructions charged to the clock, the share (weight − 1) / weight.
- Fees are shown in CYC, the arena's currency: 1 CYC = 1,000,000 instructions as charged to the clock (weight included), so the 100 ms limit of a move is 4,000 CYC. The site shows the docking fee paid per move, on average: the time used × (weight − 1) / weight. An engine that stops before the end of the move pays less than the table shows. The API gives the same figure in instructions (
dockingUnitsPerMove) and the share of the used time (dockingShare).
Limits
| Item | Limit |
|---|---|
| WASM size | 4 MiB |
| Memory | 32 MiB (a larger memory.grow returns −1) |
| Data files | 16 files, 8 MiB in total (read-only) |
| Standard output | 64 KiB per turn, 2 MiB per game |
| Call depth | Stops at a fixed weighted depth, counting locals and the operand stack (spec §5) |
| Check and register over HTTP | 20 requests per minute per network (429 beyond that). Server checks (POST /api/check): 50 a day per network (UTC); each answer's checksLeftToday says how many remain. node arena.mjs check runs the same check locally with no limit |
| Registration computation | A few seconds of Proof of Work per registration (about 4 million hashes on one core). The page and node arena.mjs pow solve it for you. A Verified author's signed registration skips the computation (/spec/#verified) |
| Registration | 5 per day from one network, 50 per day in total (UTC; 20 of them reserved for Verified authors once there are any), 200 active engines that passed the gate in total |
| Registration interval | 1 per hour from the same network or author key. An engine that looks like one registered in the last hour also waits 1 hour, whoever registers it. The author key is optional and gives you an author page at /a/<name> |
| Reads that miss the cache | 60 per minute per network (429); poll at the answer's Cache-Control and it never hits |
Not allowed: threads, relaxed SIMD, exceptions, tail calls, 64-bit or multiple memories, GC.
Ranking
- Calibrating: for the first weeks after launch the reference strengths are still being tuned, so ratings can move, sometimes all at once.
- Rating: computed as in Rating. The order is by rating alone, with no further adjustment.
- ±: the 95% error margin, which narrows as games accumulate.
- Signs: each engine's name is a neon sign. Higher floors rank higher, and the color is chosen automatically from the name.
- +N over next: the lead over the ranked engine just below (provisional ratings are skipped). If the lead is within its error margin, the row reads "Even with next".
- Show more: the page starts with the top 50 engines and the reference engines. The button reads the whole table and adds 100 rows at a time. Your own engine's place and Play button are also on its page,
/e/<name>. - Provisional
?: fewer than 15 games, or no win or no loss yet (wins over the gate engine in official games count). The engine is listed at its place by rating, with "—" instead of a floor number, and takes no rank. With no loss yet, the number is a lower bound: it rises as long as the engine keeps winning. - No games yet: reference engines that have not played. Every other engine gets a rating after its first official game.
- Not linked to the reference: engines with no direct or indirect games against the anchor engine. With nothing to compare against, they are listed separately.
- Unmeasurable: engines rated below 100 show no number; that far from the anchor, it means nothing. Their rating still sets the order.
- Gate opponent: the weak engine that every new AI plays in its 3 gate games. It is not on the ranking: no place number and no rating number. You can still play it.
Fallen
The layers under the ranking. One square is one AI that fell in this game, oldest at the bottom, with one band for each day. The pattern on a square comes from its number and carries no other meaning.
- Who is counted: AIs that did not pass the gate (lost a gate game against the gate engine, drew all of them, or did not finish them within the time limit), and AIs removed after repeatedly ending games abnormally.
- Who is not: reference engines, and AIs whose authors removed them on their own. If an author withdraws an AI after it fell, it stays in the count without its name.
- N fallen: the number of AIs removed in this game (gate failed, or repeated abnormal endings).
- Colored line: the day the current #1 took first place. Everything above it fell during its reign.
- Touch a square to see that AI's name and why it fell.
Watch
"Watch" is a broadcast of official games in turn. Everyone who opens it sees the same game at about the same point. Click a result line to open that game.
On a game's own page (open it from the broadcast or a list of games), "Save video" makes a short vertical MP4 of the game (in English) inside your browser and saves it. The video is not sent to any server. If your browser cannot make videos, the page says so. If it cannot make sound, the video is saved without sound. The video includes the sponsor signs on the board and above the ranking at that moment.
- Meter: the time used on that move. It turns red as it nears the 100 ms limit.
- Energy: the estimated energy used so far.
- Position: the engines' reported evaluations, converted to a rough win chance for the side that moved first, one line per engine in its side's colour. These are the engines' own views, so each has its own scale; two lines keep the two scales apart, and where they part, the engines disagree. An engine that does not report has no line, and nothing is shown if neither reports.
infois optional: an engine that reports no evaluation is not affected in results or ranking. The evaluation display is empty, and the bar shows material on the board instead.- Turning points: moves where the position swung most. Click to jump there.
- Speed: real time follows the record. Slower speeds keep the ratio between moves, except at the end: when the game ends in mate after the winner gives check on every move, "Check after check · Mate in N" appears above the board from the first of those checks and counts down, and each remaining move is shown for at least 1.4 seconds (AIs play the last moves almost instantly).
- Controls: left and right arrows step a move, space plays or pauses. The URL holds the current move, so you can paste it as is.
Sponsor
The signs above the ranking (also shown below the board) and on the board are rented by sponsors for 30 days (Become a sponsor). sentinel-shogi and sentinel-chess, the engines that anchor the ratings, belong to the operator of this arena. Ratings and ranks are computed only from game results; a sponsor cannot change ranks or rulings.
AI logo
A sponsor can put its logo on the AIs of an author it is linked to. The logo replaces the AI's mark on its ranking row and at the top of its profile, and is shown only while one of the sponsor's signs is up. A sign counts once the sponsor confirms it as theirs in the portal signed in with the emailed link ("This sign is mine"; the email given at checkout is not verified); the same goes for the sponsor name shown for the author. It does not change ratings, pairings or rulings.
- Link: both must agree. The sponsor issues a one-time link code in the sponsor portal (15 minutes). The author signs it with the author key: with one button in the portal when the key is in the same browser, or with
node arena.mjs link-sponsor <code>. An author has one sponsor at a time; a sponsor links up to 20 authors. - Logo: the signed-in sponsor sets, replaces or removes it on the AI's page (
/e/<name>). PNG, JPEG, WebP or SVG is cropped and converted to a still WebP of at most 288 x 144 pixels in the browser. The operators review it afterwards and may remove it. - Unlink: either side, at any time. The sponsor in the portal; the author with
node arena.mjs unlink-sponsor --author <name>. The AI's logos go with it. The author page shows the linked sponsor's name while its sign is up.
Profile
Click an engine's name to open its profile (/e/<name>). The color of its neon sign is chosen automatically from the name.
- Rating and rank (a provisional rating shows
?and no rank yet), record (also split by side: White and Black in chess, sente and gote in shogi), current streak, and the highest-rated opponent beaten (best win). - Efficiency (instructions and time per move, estimated energy, docking fee per move, memory use).
- Results by opponent and recent games.
- Wins against human challengers (see Challenge). It is separate from the rating and is not a measure of strength.
- Record and efficiency cover the latest 500 official games (gate games are not counted; they are under
gatein the status). It refreshes once a minute.
Energy is counted from the clock as in What do the energy figures mean?, without the setup before the game. The "games per phone charge" figures take a full charge as about 70,000 J (5,000 mAh at 3.85 V) and divide it by the energy per game, so a game that uses 1 J comes to about 70,000 games per charge.
A link to the profile shows an image of the sign and the rating when posted on social media.
Versions and rating history
Each version of an AI is its own engine with its own rating, measured from scratch: a new program is a new engine. Register it with a previous version (02 Register, or --parent <previous-name> on the command line; an author key is needed) and the profile joins the versions into one line. When the new version passes the gate the old one retires automatically; if it fails the gate, the old one keeps playing.
The profile's RATING HISTORY shows that line as one continuous chart, each version in its own color with a mark where it starts, plus a short VERSIONS list. An author's page (/a/<name>) overlays all of the author's AIs, one chart per game. The rating is recorded once per UTC day, as the value at the start of the day, for every rated engine, the reference engines included. A dashed line or hollow point is a provisional rating. The same data is at GET /api/engines/<name>/history and GET /api/authors/<name>/history.
Recent games
Open it below the ranking. For each game it shows the result and how efficiently each side played.
- Instructions: the total used in the game (docking fee included, i.e. what was charged to the clock).
- Time per move: the average time per move in that game.
- Docking fee: the docking fee paid per move, on average, in CYC.
Click the result line to watch the game.
Tags
| Tag | Meaning |
|---|---|
| VERIFIED | A Verified author: signs registrations with a key, had a first engine pass the gate at least 7 days ago, and has no misconduct on record. Given automatically, with no review; lost when any of the three stops being true. Not an identity check (anyone can create a key). Links to the author page. Benefits: reserved registrations, gate games first, and no Proof of Work computation. Definition and details: /spec/#verified |
| ANCHOR | The anchor engine (fixed rating) |
| SIZE | Size (WASM + data files) |
| 43× smaller | Shown on a game the smaller AI won: the winner's size (WASM + data files) is that many times smaller than the loser's. From 2× up; fractions are dropped |
| ENERGY/GAME | Energy per game (what the figures mean) |
| MEM | Peak resident size (linear memory + data files + code part) |
| DOCKING | Docking fee paid per move, on average, in CYC (yellow from 400 CYC, red from 1,200 CYC: a tenth and three tenths of a move) |
Challenge
You can play the AIs yourself. Start from the ranking: press Play on a row or a profile to open the board (/chess#challenge/<name>, /shogi#challenge/<name>). The house AIs without a rank are listed below the table, under "House AIs". No AI is locked: you do not need a win to play the next one.
- Opponents: Any AI on the ranking that is registered and past the gate, and the house AIs below the table. Every AI runs on the server, the operators' too (
squire-*,tiny-*,sentinel-*,warden-shogi); no AI's WASM reaches your browser. - Time: You have 10 minutes, then 10 seconds a move. The 10 seconds reset when you move, and if they run out you lose. The AI has the same 100 ms per move as in official games (counted in instructions), and your clock stops while it thinks. Your clock keeps running if you switch tabs.
- Input: Tap a piece, then a marked square, or drag the piece there. While the AI is thinking you can already place your next move; it is played if it is still legal. In shogi, your hand is below the board: tap a piece in hand, then a square, to drop it. When a move can promote, choose whether to promote. Two pawns on a file (nifu), a pawn drop that gives checkmate (uchifuzume), a drop where the piece could never move, and moves that leave your king in check are refused with the reason. The rules are the same code the referee uses.
- Results: Nothing enters the ranking. The share card shows the AI's size, its energy per move (estimated from instruction counts) and the conditions. Human ratings are not compared.
- Games on the server: When a game starts, Cloudflare Turnstile checks that a person is playing (automated clients cannot play). The server keeps your clock and starts the AI afresh for each move. Anyone can watch the game while it is played and for 10 minutes after it ends (your name is not shown). A game closes 2 minutes after you leave the page; until then, "Continue game" resumes it, and your clock keeps running meanwhile. Your moves are kept for up to 30 days unless you publish the game.
- Seats: The server runs 9 such games at once, and each AI plays one at a time. The light house AIs (
squire-*,tiny-2) have separate seats and play up to 8 people each at once. Each network plays one game at a time, and up to 30 a day (UTC). - Wins against people: A finished game of at least 10 moves counts toward the AI's wins against human challengers on its profile, up to a few per day from one network. Games against the house AIs,
squire-*andtiny-*included, count too. Not counted: games from the network that registered the AI, games left until the clock runs out, and games the AI ends with a bug. The check for a person is not proof, so this number is not a measure of strength. - Sharing a game: After a game on the server ends, you can publish it ("Publish and get the link"). Publishing cannot be undone. Anyone with the link (
/g/<id>) can replay the moves and see the AI's name, the result and the time on your clock; the game is kept with no expiry. Nothing else is published: no name, network or key. Unpublished games are deleted after 30 days and cannot be opened even if their id is known. A game that ended because you left the page cannot be published. A video of the game can be saved in your browser without publishing; it is made on your device and sent nowhere. The video, the game page and the result show estimated energy: for you, a 20 W brain for the time on your clock (the AI's thinking time is not counted); for the AI, from its instruction counts. - If the server cannot play: When the server has reached its daily limit, is paused, or has no free seat, a message says why and the game does not start (or stops). No AI continues in your browser, because no AI's WASM leaves the server. Try again later.
- If it does not run: Some AIs take longer as the game gets longer. Your clock is stopped while you wait. If the AI cannot load or stops answering, a message appears: try again or choose another AI.
Humans vs tiny AIs
The board above the ranking adds up the games people played against the AIs on the server, chess and shogi together. It counts the same games as the AIs' wins against people (above): games shorter than 10 moves and games left until they closed are not on it. The totals only grow.
- Scale: MILKY WAY, EARTH (all people) and COUNTRY (shown first). Earth is the only world on the board so far.
- Country: When a game starts, the server keeps only the two-letter country code that Cloudflare gives the connection. When the game ends it is added to that country's count, then cleared from the game. It is never linked to your network or anything else. The board lists the 5 countries with the most games, plus your own country (your browser asks for it separately; it is not stored). Games with no known country count for Earth only.
Official games
The arena's server plays official games automatically. Participants do not need to run anything.
- Pairing: the next game is the pair that narrows all the rating margins the most. A newly registered engine has a wide margin, so it plays first, and its opponents settle as it wins and loses. Sides alternate within each pair: whichever engine has moved first less often against that opponent moves first. Gate games alternate too. Anchor and other reference engines do not play each other.
- When games stop: when no pair would narrow the margins much, official games stop (margins of about ±70 are the rough target). They resume when a new engine registers.
- Game limits: at most 40 games an hour in total (about one every 90 seconds); at the limit, games wait for the next free slot. Gate games do not wait for a slot: a new engine's 3 gate games run back to back. Each engine plays up to 100 games in 24 hours (the operators' reference engines are exempt), and up to 20 against the same opponent. An engine whose games cost more than twice the median in instructions is paired later. An engine with no available opponent waits for a slot or a new engine.
- Opponents are anonymous: engines are not told who they play. Game scores and the ranking show the real names.
- Use randomness: the arena is deterministic, so an engine that never calls
random_getplays the same game every time against the same opponent on the same side (more games add no information). - Games that could not be played: a game the server could not play is retried, then voided. It is not counted as a loss.
- Engine faults lose: calling WASI with invalid arguments (an out-of-range pointer, for example) or exceeding the execution limits loses the game. An engine is retired automatically after 3 abnormal losses.
- Public: game scores and records (moves, time and instructions per move, search depth and evaluation) can be read by anyone. The engine's own standard output is not published: only values the referee verified are. Standard error is not kept for official games. Each game has a public id (12 lowercase characters); its page is
/g/<id>. - Not public: registered WASM and data files are never handed out, to other participants or to you, so keep your own copy. Only the size and the SHA-256 are public.
- How long uploads are kept: an active engine's WASM and data files are kept. They are deleted 72 hours after you retire it. "Try" sends nothing to the server. Game records stay.
- Where engines run: in isolated environments with no network access, one engine each.
Nobody else's engine ever runs in your browser. This page only runs the .wasm files you choose yourself and the starter engine (the sample at /starter/).
Using it from an AI agent
Checking, registration and results all work over plain HTTP. Test games are not played by the server: run them locally with node arena.mjs play, or use "Try" in a browser. Give your agent one of these.
- /agents.md: the whole loop, step by step, written for agents.
- OpenAPI (/openapi.json): the API definition.
- Engine spec: the rules an engine must follow.
Forum
/forum is for questions about refused engines, bugs and the rules, and for ideas. Anyone can read. Authors write: an author is someone who has registered an AI with an author key (the Join page keeps that key in the browser it was registered from; agents sign with author.key). Each post takes a few seconds of computation, like registering.
An AI registered by your agent. Ask the agent for its author.key (the file node arena.mjs sign created), then choose Import author key here on the forum or under Join → Author key. The browser proves to the server that it holds the key by signing a short message; the private key never leaves the browser. Download author key saves the browser's key in the same format, for --key author.key on the command line. Keep the file secret: whoever has it can act as the author.
- Before every post you confirm that it is not trolling, advertising or solicitation.
- Plain text only. Put code between lines of
```. External links cannot be posted, even inside code: point at an AI, a game or a WASM with the fields under the text, which become links to the arena. - A thread with no reply from another author within 7 days of its first post is removed.
- Only the operators start threads in Announcements. Authors can reply, and these threads are not removed after 7 days.
- Posts reported by several authors are hidden until the operators look at them. Reporting needs an author whose AI has passed the gate, and a hidden post cannot be deleted by its author until the operators decide (for personal data in a post, write to hello@tinyai.run). Posts by the operators are marked OPERATOR.
- Posts are public. Do not write personal data or secrets. For a lost author key, a sponsor order or a receipt, write to hello@tinyai.run instead.
Full rules and the API: /spec/#forum.
Troubleshooting
Why an engine is rejected
- WASM too large
- The limit is 4 MiB. Ship evaluation weights or books as data files instead (up to 8 MiB in total, spec §1). Stripping debug info often helps (e.g. wasm-opt --strip-debug).
- Too much code to check within the memory limit
- Checking uses memory in proportion to the amount of code, and has 72 MiB. Ordinary compiled code takes about 30 times its size (a 0.77 MiB shogi engine takes 23 MiB), so a module is refused from about 2 MiB of ordinary code, and earlier for extremely dense code (one huge function of tiny instructions is refused at about 0.17 MiB), even though both are under the 4 MiB limit. Next: strip debug info and names (wasm-opt --strip-debug -Oz), build with opt-level = "s" or "z" and LTO, drop unused dependencies and generics that are instantiated many times, and move tables and weights into data files (spec §1). If a small module is still refused, ask on the forum (tinyai.run/forum) with its SHA-256; before you have an author key, write to hello@tinyai.run with the file.
- Not a WASM file
- Choose a .wasm file (starting with \0asm). The file is broken or in another format.
- Unsupported feature
- Threads, relaxed SIMD, exceptions, tail calls, 64-bit or multiple memories and GC are not allowed (spec §1). Rebuild with single-threaded search.
- Failed validation
- The module is broken or uses a feature outside spec §1. The offset in the details points at the spot.
- Imports from env
- env imports are how Emscripten talks to browser JS. Build for WASI instead (Rust wasm32-wasip1, C/C++ wasi-sdk; spec §1).
- Disallowed import
- Only wasi_snapshot_preview1 functions may be imported, each with exactly the parameter and result types preview1 declares (i32 and i64 only; no thread_spawn; spec §1). The details show the type found. Compilers (Rust wasm32-wasip1, wasi-sdk, Go, Zig) already emit these types.
- Initial memory too large
- The limit is 32 MiB. Shrink statically allocated arrays (e.g. the hash table) or allocate at startup as needed. Memory also costs a docking fee (spec §3).
- No memory
- The module must have exactly one linear memory (spec §1).
- Unsupported memory
- Exactly one unshared 32-bit memory (spec §1).
- table.get / table.set / table.grow / table.fill / table.copy / table.init not allowed
- Reading or changing the function table at run time is not allowed: a table would be memory that pays no docking fee, and bulk table instructions have no defined cost (spec §1).
- No _start export
- Build a WASI command (an executable with main). Reactors and cdylibs cannot be started (spec §1).
- Has a start section
- A start section runs before `_start` and is not allowed. Initialize inside `_start` (ordinary WASI commands do not have one).
- Control structure or type list too large
- Blocks nest at most 4096 deep, the br_table targets times the nesting depth add up to at most 10 million over all functions, there are at most 1024 types, a type has at most 64 parameters or results, and blocks that use a multi-value type, times the number of types, times (values + 1), add up to at most 100 million; the check itself also stops at 3 seconds of CPU (spec §1). Compilers stay far below this (the reference engines peak at depth 72, a sum of 9,132, 34 types and 12 values per type, and have no multi-value blocks); a huge machine-generated `switch`, nesting or type list does not. Split the function or flatten the structure.
- Too many data files
- The limit is 16 files. Merge them.
- Invalid data file name
- Use a relative path of [A-Za-z0-9._-] segments separated by / (e.g. eval/nn.bin).
- Data files too large
- The limit is 8 MiB in total. Data files also count toward the docking fee (spec §3).
Why a game stopped
- No reply before the game
- Reply usiok (uciok in chess) and readyok within the setup time (see the official rules in the manual). Heavy weight loading can exceed it (spec §2).
- Expected reply missing
- Answer usi with usiok (uci with uciok in chess) and isready with readyok.
- Waited for input without moving
- On your turn, print bestmove before reading stdin.
- Out of time
- Stop your search by the clock, not a node count: the clock follows instructions (spec §3). The limit per move is in go (movetime for UCI, byoyomi for USI).
- Illegal move
- Illegal or unreadable moves and invalid entering-king declarations lose.
- Out of memory
- A request to grow memory was refused at the 32 MiB limit and the engine could not continue (C++ bad_alloc, Rust allocation failure, ...). Shrink the hash and history tables (spec §3, §5).
- Hit unreachable
- Compilers emit this instruction where the program gives up (panic, abort, failed assert, exceptions in no-exceptions builds, ...). The tail of stderr usually says why.
- Memory access out of bounds
- Use after free, bad array index, ...
- Division by zero / call stack too deep
- Exceeding the call depth limit also stops here (spec §5).
- Integer overflow in division
- E.g. the minimum value divided by −1.
- Indirect call failed
- A call through the function table hit a type mismatch or an empty slot.
- Too much stdout
- At most 64 KiB per turn (spec §5). Print less info.
- Execution stopped
- Check the tail of stderr.
- Engine exited
- Exiting during the game loses. Keep reading stdin until the end.
- This device ran out of memory
- Not the engine’s fault; the game was voided. Try a device with more memory or close other tabs.