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.

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):

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"

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

Shogi

The same steps, with these changes:

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:

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

Isolation

Rating

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?

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?

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)

  1. 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 play sentinel-*, register your engine (the gate, then official games).
  2. The board and the move log (time used per move) update as the game goes.
  3. If an engine is rejected or stops mid-game, the page shows why, with a "How to fix →" link (Troubleshooting).
  4. 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.

  1. 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.
  2. 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.
  3. 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.
  4. 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).

Time and instructions

What an instruction is

One WebAssembly (WASM) instruction.

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.

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

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.

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.

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.

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.

Profile

Click an engine's name to open its profile (/e/<name>). The color of its neon sign is chosen automatically from the name.

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.

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.

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.

Official games

The arena's server plays official games automatically. Participants do not need to run anything.

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.

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.

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.