The Rumble Client lets your computer contribute ranked battles to the Tank Royale Rumble. It downloads the reviewed bot catalog, chooses an under-sampled matchup, runs the battle locally, keeps replay evidence, and submits the result through the Rumble data repository.
This project is part of the Tank Royale ecosystem; the client's public contracts (configuration, journal, and submission format) are documented there.
For the complete newcomer-friendly walkthrough, including registration and token setup, read Run ranked Rumble battles. This README is the technical reference for the source checkout.
Docker is the recommended way to run the client: it supplies the complete Java, .NET, Python, and Node.js environment every ranked bot needs, and is the isolation boundary for running reviewed-but-untrusted bot code. Docker Engine or Docker Desktop is required for this path. You do not need to build anything: the ready-made image is published at ghcr.io/robocode-dev/rumble-client.
- To develop or debug an unsubmitted bot, use the Tank Royale GUI battle setup. Those are private practice battles and do not affect the Rumble rankings.
- To submit a bot for ranked battles, follow the Rumble bot-author guide. The bot must be reviewed and present in the published catalog before the Rumble client can select it.
- To contribute ranked battles with catalogued bots, use the published image and the launcher commands in the Quickstart below. Add active bot or team names to
myBotsif you want the client to prioritize useful matchups involving them. - To verify an image build itself, use
scripts/verify-container.sh. It runs the four-language sample-bot smoke test under the same hardened container settings used by CI; it is not a general command for running an arbitrary local bot folder.
The Rumble client does not currently take a local bot path as input. Its normal run workflow synchronizes the reviewed catalog, prepares an immutable cache of the catalogued bot sources, and selects a valid matchup from that cache. myBots contains catalog names without version numbers; it is a scheduling preference, not a local source mount.
- Clone this repository. You only need it for the launcher scripts and the example configuration.
- Pull the published image:
docker pull ghcr.io/robocode-dev/rumble-client:latest. The launchers use this image by default and pull it on first use, so this step only saves waiting later. - Copy
rumble-client.example.jsontorumble-client.jsonand edit it — see Configuration below. Never commit the resulting file. - Check your settings:
docker/rumble.sh validate rumble-client.json - Check the bundled runtimes:
docker/rumble.sh runtimes - Resolve the ranked catalog and prepare the bot cache:
docker/rumble.sh sync rumble-client.json - Run one ranked battle:
docker/rumble.sh run rumble-client.json - Submit pending results:
export RUMBLE_CLIENT_TOKEN=<your token>thendocker/rumble.sh submit rumble-client.json
On PowerShell, use docker/rumble.ps1 <validate|runtimes|sync|run|submit> [config-path] [image] instead — for example docker/rumble.ps1 run rumble-client.json, and set $env:RUMBLE_CLIENT_TOKEN before submit.
Run docker pull again to pick up a newer latest. To pin a release, pass a version tag as the image argument, for example docker/rumble.sh run rumble-client.json ghcr.io/robocode-dev/rumble-client:0.1.0; see CHANGELOG.md for what changed in each one. If you want to build the image yourself, see Building rumble-client itself.
Every command runs the container read-only, with capabilities dropped and resource limits applied. Only runtimes blocks network access outright; validate, sync, run, and submit use normal outbound network because synchronization and submission need it, and run re-synchronizes the ranked snapshot before executing a battle.
The client tracks posted batches locally and only drops them once their receipt comment appears on the closed issue; retrying an already-accepted submission is acknowledged idempotently rather than double-submitted.
Most people only need the Quickstart above and the published image. Build it yourself only if you are changing this repository or prefer not to use the published image.
Build the image locally:
TANK_ROYALE_COMMIT="$(tr -d '[:space:]' < TANK_ROYALE_COMMIT)"
docker build --tag rumble-client:dev --build-arg "TANK_ROYALE_COMMIT=${TANK_ROYALE_COMMIT}" .On PowerShell, use:
$env:TANK_ROYALE_COMMIT = (Get-Content TANK_ROYALE_COMMIT -Raw).Trim()
docker build --tag rumble-client:dev --build-arg "TANK_ROYALE_COMMIT=$env:TANK_ROYALE_COMMIT" .With Podman, replace docker with podman in the corresponding command.
The launchers default to the published image, so pass the local name as their image argument to run your build, for example docker/rumble.sh runtimes rumble-client.json rumble-client:dev or docker/rumble.ps1 runtimes rumble-client.json rumble-client:dev.
To run the four-language container smoke check locally, use WSL or Git Bash with a Tank Royale checkout beside this repository. The checkout must match the commit in TANK_ROYALE_COMMIT:
./scripts/build-sample-bots.sh ../tank-royale
TANK_ROYALE_SOURCE=../tank-royale ./scripts/verify-container.shThe script builds a local rumble-client:test image, checks the bundled runtimes and non-root hardening, and runs one-round Java, Python, C#, and TypeScript battles with networking disabled. Set CONTAINER_ENGINE=podman before the second command to use Podman. This is image verification with sample bots, not the normal workflow for testing an unsubmitted bot.
If you're changing this repository's own Java code, you need to build and test it, which still needs Gradle — but not installed on your machine. Run it inside a Gradle image matching this repository's pinned wrapper version (gradle/wrapper/gradle-wrapper.properties, currently 9.7.1), with your checkout bind-mounted:
docker run --rm -it -v "${PWD}:/workspace" -w /workspace gradle:9.7.1-jdk17 gradle buildThe same command works unchanged on PowerShell. The Dockerfile's build stage uses the same Gradle 9.7.1 image, and its wrapper invocation keeps the build reproducible. Keep the image and wrapper versions in sync if either changes.
The client uses the published Tank Royale Battle Runner 1.3.1 artifact from Maven Central, so an ordinary Java build needs no Tank Royale checkout. The Docker build still checks out the pinned Tank Royale source because it packages the matching Python API and schema and builds the sample bots used by the container smoke test. To run the full real-runner integration test locally, mount a checkout at the pinned commit alongside your rumble-client checkout and add this property:
docker run --rm -it -v "${PWD}:/workspace" -v "${PWD}/../tank-royale:/tank-royale" -w /workspace gradle:9.7.1-jdk17 gradle -PtankRoyaleSource=/tank-royale buildWithout -PtankRoyaleSource, gradle build runs the unit and contract tests and skips only the source-dependent real-runner integration test. CI and the container verification workflow provide the source checkout, so they run that integration coverage as well.
If you already have JDK 17 and Gradle installed on your machine, the equivalent host command is ./gradlew --no-configuration-cache clean build.
The build produces native ZIP and TAR archives under build/distributions/. Run ./gradlew run --args="--check-runtimes" to verify the required native installations; the check never installs or changes them. CI builds and tests the native client on Linux only, so running it natively on Windows or macOS works but is not CI-tested; the container image is the tested path on every host OS.
The container and native preflight currently target Java 25, .NET 10, Python 3.14, and Node.js 24 (Node.js installer 24.21.0). This block is refreshed by the scheduled runtime update workflow.
The client validates configuration and can synchronize the current ranked input snapshot. Run ./gradlew run --args="--validate-config" to check local settings, then run ./gradlew run --args="--sync" to resolve the canonical data repository, validate its engine pin, catalog, client registration, and matchmaking advice, and prepare an immutable bot cache at the catalog's exact source commit. Every cached source tree is checked against its catalog SHA-256 before it can be used. Ranked battle selection uses a recorded random seed, prioritizes under-sampled pairings involving myBots, and falls back to distinct active catalog bots when no advice is available. Each game type declares how many bots one catalog entry expands to, so TwinDuel selects two team entries for its four pinned participants while 1v1 and melee select individual bots, and a selection never contains two entries that share a member bot. Run ./gradlew run --args="--run" to execute one pinned ranked battle through Battle Runner and retain its replay evidence locally. Run ./gradlew run --args="--submit" to post pending records through the rumble-data issue inbox. It reads RUMBLE_CLIENT_TOKEN only at runtime; use a GitHub fine-grained token limited to read and write Issues access for that repository. The client records posted batches locally and removes records only after their result-data receipt comments appear. See Docker and Podman container image below for the isolated multi-runtime container.
Copy rumble-client.example.json to rumble-client.json. Ranked mode requires a registered clientId — see rumble-data's contributing guide for the one-time registration pull request; practice mode may omit it. The optional workDirectory selects the local cache, journal, and replay-evidence root and defaults to .rumble-client beside the configuration file. Do not commit the resulting file or any token.
Use one game type per configuration with the current command-line client. --run executes one battle using the first configured game type in contract-name order. myBots may list the names of active bots or teams owned by you, without version numbers; under-sampled matchups involving those entries receive priority. These names must refer to entries already available in the synchronized catalog; they do not identify local source directories. battlesPerSession is validated for the session contract, but the current one-battle command does not consume it.
Released versions are published to ghcr.io/robocode-dev/rumble-client, and this is the default way to get the image:
docker pull ghcr.io/robocode-dev/rumble-client:latestWith Podman, run podman pull ghcr.io/robocode-dev/rumble-client:latest instead.
Substitute a specific version, e.g. ghcr.io/robocode-dev/rumble-client:0.1.0, to pin to a release; see CHANGELOG.md for what changed in each one. The launchers default to ghcr.io/robocode-dev/rumble-client:latest; pass another image name as their image argument to use a pinned version or a locally built image. The image runs with Docker Engine, Docker Desktop, or Podman. To build the image yourself instead, see Building the container image. The examples below use Docker; replace docker with podman when invoking the image directly. On Windows, Podman Desktop needs a running Linux virtual machine and can use WSL2 or Hyper-V as the provider; choose the provider when creating the machine. Podman Desktop/WSL2 on Windows and rootless Podman on Linux have both been manually verified for this image; neither is part of CI.
Two flag differences from Docker are handled for you by the launcher scripts and do not need manual workarounds:
- The
Dockerfile's base images are fully qualified (docker.io/library/...) because a stock Podman install has no default unqualified-search-registry, unlike Docker's implicit Docker Hub default. docker/rumble.shanddocker/rumble.ps1add--userns=keep-idonly when the selected engine is Podman, so the bind-mounted.rumble-clientstate directory stays writable and correctly owned by the invoking host user. Rootless Podman's--userdoes not map to the host UID inside the container's user namespace the way Docker's does; without--userns=keep-idthe state directory is unwritable.
One flag difference is not handled by the scripts and needs a one-time host setting instead: the launchers pass --cpus, --memory, and --pids-limit unconditionally, and rootless Podman honors them only when the cpu, memory, and pids cgroup controllers are delegated to your user session (cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers lists them if so). Recent systemd (245+) delegates all three by default on most current Linux distributions, including the Ubuntu install these launchers were verified against, so this is normally a non-issue; if a run fails with a cgroup- or resource-limit-related error instead of an application error, delegation is the first thing to check.
Use the launcher scripts for configuration validation, runtime checks, snapshot synchronization, ranked battles, and result submission. The shell launcher selects Docker by default and accepts CONTAINER_ENGINE=podman; the PowerShell launcher accepts -Engine podman or the same CONTAINER_ENGINE environment variable:
./docker/rumble.sh runtimes
CONTAINER_ENGINE=podman ./docker/rumble.sh runtimes
./docker/rumble.sh run rumble-client.json
export RUMBLE_CLIENT_TOKEN=<your token>
./docker/rumble.sh submit rumble-client.json./docker/rumble.ps1 runtimes
./docker/rumble.ps1 runtimes -Engine podman
$env:CONTAINER_ENGINE = 'podman'
./docker/rumble.ps1 runtimes
./docker/rumble.ps1 run rumble-client.json
$env:RUMBLE_CLIENT_TOKEN = '<your token>'
./docker/rumble.ps1 submit rumble-client.jsonFor validate, sync, run, and submit, the launcher mounts the configuration file and .rumble-client state directory. Keep the state directory writable; it contains the bot cache, journal, and replay evidence. The runtimes check uses no network, while validate, sync, and run require network access to synchronize the configured repositories and submit requires network access to the GitHub Issues API. Submission forwards RUMBLE_CLIENT_TOKEN from the environment and never writes it to disk.
The image contains a pinned Tank Royale Python API and its runtime dependencies in an image-owned virtual environment; no host Python environment or package installation is required. The normal launcher workflow runs the immutable bot sources prepared from the reviewed catalog. For local bot development before submission, use the Tank Royale GUI rather than the Rumble client image. The lower-level containerized Battle Runner can boot bot roots directly for specialized harnesses: mount Java and Python archives read-only, but copy C# and TypeScript archives into writable container storage before booting them because dependency setup may write files and change permissions. This is especially important for Windows bind mounts, where chmod can fail with EPERM.
If Podman Desktop on Windows reports ssh-keygen cannot be found, install or enable Windows OpenSSH and add the OpenSSH installation directory to the user PATH, then restart the terminal and Podman Desktop.
Read CONTRIBUTING.md, SECURITY.md, and GOVERNANCE.md before opening a pull request.