Skip to main content

Papersseum

A real-time, multi-agent territory-capture tournament. Write an agent, submit it, and watch it fight four others for the map.

Five agents drop into a bounded circular arena for 180 seconds. Leave your land and you draw a trail behind you. Close the loop and everything you enclosed becomes yours. Cut a rival's trail and you don't just kill them, you take their whole territory and get a short speed burst to go after the next one. Whoever owns the most of the map when the clock runs out wins.

Anyone can enter. Your agent can be a few lines of rules, a trained neural net, a search routine, or anything in between. The server runs ladder rounds of random 5-agent lobbies, rates agents by how they place, and a final round sets the leaderboard. Every match is deterministic from its seed and action log, so any game can be replayed and any dispute settled by running it again.

The name is paper plus colosseum: an arena where paper-thin territories are fought over to the last tick.

The game

  • Arena. Circular, 11,304 playable cells on a 120x120 grid. The wall is solid. Steer into it and you slide along the edge instead of dying.
  • Match. Five players, 180 seconds. Everyone starts with a small disc worth 1% of the map. The engine runs at 30 ticks per second; agents decide 10 times per second.
  • Movement. Three actions: 0 straight, 1 left, 2 right. You move faster the more land you hold (+1 cell per second for every 5% of the map), and you get a 1.5x burst for 5 seconds after a kill. Speed is capped so you never move more than one cell per tick.
  • Capture. Re-enter your own land with an open trail and everything the loop encloses becomes yours, including other players' land.
  • Death. Two bots never share a cell: one waits a tick. You can cross your own trail freely. Get cut by a rival, lose a head-on, or get caught inside a loop a rival closes, and the killer takes the part of your land that touches its own (the rest goes neutral; a bot's land is always one connected piece). You respawn with a fresh 1% patch on empty ground.
  • Score. Your share of the map at the end. Ties break on time-averaged coverage, then fewer deaths.

A few rules (taking territory on a kill, the kill speed burst, solid walls) are intentional changes from a stock paper.io-style game.

What your agent sees

Every decision, your agent gets a view built from its own point of view. There are 16 channels: your territory, trail, and head, the same three for each of the four opponents, and the arena mask.

  • Global view, shape (16, 120, 120). The whole arena, north up. Use it for planning.
  • Local view, shape (16, 31, 31). Cropped on your head and rotated so your heading points up, so "straight", "left", and "right" always mean the same pixels. Use it for quick reactions.
  • Scalars: your coverage, rank, speed, boost remaining, time left, and each opponent's coverage, speed, and alive flag.

Writing an agent

Your file defines a class named Agent with reset(config) and act(obs) -> int:

class Agent:
    def reset(self, config: dict) -> None:
        """Called once per match with the constants and a seed."""

    def act(self, obs: dict) -> int:
        """Called every decision. Return 0, 1 or 2."""

Run papersseum new my_agent.py to drop a copy-ready starter next to you. Four baselines ship in papersseum/agents/: random, greedy (grinds small capture loops), safe_expander, and hunter (chases and cuts exposed trails).

A submission is one .py file, at most 1 MB: no zip and no separate weight files. If your agent is a neural network, train it anywhere (PyTorch, JAX, ...) and embed the weights in the file:

papersseum pack model.pt --into my_agent.py         # writes WEIGHTS = "..." into your agent
from papersseum import nn, unpack_weights

class Agent:
    def reset(self, config):
        self.W = unpack_weights(WEIGHTS)             # {"conv1.weight": ndarray, ...}

    def act(self, obs):
        h = nn.relu(nn.conv2d(obs["local"], self.W["conv1.weight"], self.W["conv1.bias"], padding=1))
        return int(nn.linear(nn.global_avg_pool(h), self.W["fc.weight"]).argmax())

papersseum.nn is a small numpy-only toolkit (conv2d, linear, pooling, batch_norm, relu, softmax, ...) in PyTorch layouts. The sandbox has no PyTorch, so import torch is rejected on upload; inference runs in numpy. About 350k float16 or 700k int8 weights fit in 1 MB. See examples/nn_agent.py for a complete example, and COMPETITION.md for the full participant guide. (papersseum.load_weights reads files from your agent's folder and works for local experiments only; it is rejected on upload.)

Running it

pip install papersseum             # or: pip install -e ".[dev,viewer]" from a clone

papersseum new my_agent.py                         # write a starter agent
papersseum play my_agent.py --vs greedy,hunter --seed 7 --render match.mp4
papersseum eval my_agent.py --games 30             # benchmark vs a baseline field
papersseum validate my_agent.py                    # the platform's upload check: scan + a full match
papersseum pack model.pt --into my_agent.py        # embed trained weights in your single-file agent
papersseum version                                 # library + engine hash (must match the server)

MP4 rendering needs ffmpeg on PATH; everything else is pure Python plus NumPy. Or from Python:

import papersseum
result = papersseum.play("my_agent.py", vs=["greedy", "hunter"], seed=7, render="match.mp4")
print(result["placements"])

match.mp4 is a real H.264 video with one frame per engine tick, so 1x playback matches the real 180 second match. papersseum.ENGINE_HASH identifies the engine build; a match with the server's hash means local results equal ladder results.

Limits

A submission is one .py file of at most 1 MB. reset() gets 2 seconds and each act() gets 50 ms; a late or invalid answer counts as action 0 (straight). Only a curated set of imports is allowed (numpy, math, collections, and similar; no torch), and open, eval, getattr, vars and raw numpy.load are blocked. papersseum validate runs the same check the platform runs on upload, locally. Full rules are in COMPETITION.md.

Replays

Every match is a JSONL file (add .gz to compress): a header line (seed, engine hash, numpy version, players), one line of five actions per decision, and a result line. A match is about 1 KB gzipped.

papersseum play my_agent.py --save-replay game.jsonl.gz
papersseum render game.jsonl.gz game.mp4
from papersseum.replay_io import load_replay
from papersseum import replay_match, iter_frames

rep = load_replay("game.jsonl.gz")
same = replay_match(rep["seed"], rep["action_log"])          # re-run without observations
for frame in iter_frames(rep["seed"], rep["action_log"]):    # owner, trail, heads per decision
    ...

On the tournament site every finished match plays in the browser: the page re-runs it with this engine (in Pyodide) and checks the result. Local files open on its /replay page and are not uploaded.

Test and iterate locally

Measure your agent against a field of baselines before you ever submit:

papersseum eval examples/my_agent.py --games 30     # placements, win rate, coverage, rating, strength

It plays your agent in slot 0 against a varied baseline field over many seeds and reports the same signals the ladder uses: how often you place 1st to 5th, mean coverage, and a rating (online Elo plus a batch Plackett-Luce strength).

See exactly what your agent sees, as one character per cell (@ your head, M your land, t your trail, O opponent land, ~ opponent trail, X opponent head, . empty, # wall):

import papersseum
from papersseum import channels as ch

env = papersseum.PapersseumEnv(seed=7)
obs = env.reset()
print(papersseum.ascii_obs(obs[0], "local"))         # your agent's eyes
on_own_land = obs[0]["local"][ch.OWN_TERRITORY, 15, 15] > 0.5

Use papersseum.channels for named indices (OWN_TERRITORY, OWN_TRAIL, OWN_HEAD, opponent(i), ARENA_MASK) instead of magic numbers.

Learn

New to reinforcement learning? The site has a free beginner course, Learn RL from zero, for anyone with JEE Mains maths. Its lessons and code live in resources/.

Status

The engine, reference environment, baselines, self-eval, JSONL replays and rating are done. The tournament platform (Docker-sandboxed match workers, queue, ladder and web app) is built separately and uses this package as its engine.

Running matches in a sandbox (platform operators)

from papersseum.sandbox import run_sandboxed_match, validate_sandboxed, DockerBackend

backend = DockerBackend(image="papersseum-sandbox")     # build with docker/Dockerfile
check = validate_sandboxed("agent.py", backend=backend)  # {ok, error, violations, warnings, stats}
result = run_sandboxed_match(seed, [path0, path1, path2, path3, path4], backend=backend)
open("match.jsonl", "wb").write(result["replay"])

Each agent gets its own container (no network, no capabilities, read-only root, 512 MB, 1 CPU, 64 processes, code mounted read-only, no environment). Agents think in parallel; a late act() becomes action 0 plus a strike, and an agent that exits or goes silent is killed and plays straight for the rest of the match. Time is measured by the runner, not the agent. An agent late on more than 20% of decisions (or keeping the match waiting more than 20 s in total) is disqualified as too_slow; occasional slow decisions only cost that move. validate_sandboxed plays a full match (the agent against itself plus three baselines) under the same rule. SubprocessBackend (the default) is for tests and development only and is not a security boundary.

Metadata

Release files for papersseum 0.6.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for papersseum 0.6.1
File Size Uploaded
papersseum-0.6.1.tar.gz 65.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for papersseum 0.6.1
File Interpreter ABI Platform
papersseum-0.6.1-py3-none-any.whl Python 3 none any Details

Total release size: 123.2 kB

Release files / papersseum-0.6.1.tar.gz

Download URL papersseum-0.6.1.tar.gz
Size 65.8 kB
Tags Source
SHA-256 checksum
How to use checksums
3477ed0f19fb00e2b195942aa90d2294821566693955e5856b4297d552e00e34
BLAKE2b-256 checksum
How to use checksums
82bf3cb09a5677fedec578b1ab75d0d0f73b96b11cb05ca18b043d82a09caf27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / papersseum-0.6.1-py3-none-any.whl

Download URL papersseum-0.6.1-py3-none-any.whl
Size 57.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8308899589404e71f25c94be30c14da509747b14b91f762ca345306c4c9cd899
BLAKE2b-256 checksum
How to use checksums
93d1670f87ab3864dd0c18d06e21bb8df45f9e6eae8e463041b2e9ff340964b3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.1 This release

2 release files

0.6.0

2 release files

0.5.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page