Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

## [Unreleased]

### Changed

- `submit` no longer posts result issues through the GitHub API and no longer needs a token. It writes each batch of results to a plain text file under `.rumble-client/submissions` (split by size as well as by the limit of sixty results) and prints the result form address; you paste the file into the form, press Create, and run `submit` again later to collect the receipt. Outside contributors could not create a repository-scoped token and the API dropped the submission label, so their issues were never processed.

## [0.2.0] - 2026-09-15

### Fixed
Expand Down
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The Rumble Client lets your computer contribute ranked battles to the Tank Royal

This project is part of the [Tank Royale](https://github.com/robocode-dev/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](https://robocode.dev/rumble/client-guide). This README is the technical reference for the source checkout.
For the complete newcomer-friendly walkthrough, including registration, read [Run ranked Rumble battles](https://robocode.dev/rumble/client-guide). This README is the technical reference for the source checkout.

## Quickstart (Docker, recommended)

Expand All @@ -26,15 +26,15 @@ The Rumble client does not currently take a local bot path as input. Its normal
5. Check the bundled runtimes: `docker/rumble.sh runtimes`
6. Resolve the ranked catalog and prepare the bot cache: `docker/rumble.sh sync rumble-client.json`
7. Run one ranked battle: `docker/rumble.sh run rumble-client.json`
8. Submit pending results: `export RUMBLE_CLIENT_TOKEN=<your token>` then `docker/rumble.sh submit rumble-client.json`
8. Prepare pending results for posting: `docker/rumble.sh submit rumble-client.json`. It writes each batch to a plain text file under `.rumble-client/submissions` and prints the result form address. Open the address while signed in to GitHub, paste the whole file into the batch field, press Create, and run `submit` again later to collect the receipt. No token is needed.

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`.
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 no token is needed for `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`](CHANGELOG.md) for what changed in each one. If you want to build the image yourself, see [Building `rumble-client` itself](#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.
The client retains each result until it observes an acceptance receipt from the ingestion workflow and verifies the identical fact in the canonical repository, including compacted monthly rollups. It correlates a posted form with the registered account, prepared title and JSON batch. An open retry remains in flight even when an earlier attempt was rejected. Network failures and API rate limits leave the journal pending; run `submit` again later with the same state directory. A single result exceeding the batch size limit produces an error and remains pending.

## Building `rumble-client` itself

Expand Down Expand Up @@ -95,7 +95,7 @@ The build produces native ZIP and TAR archives under `build/distributions/`. Run
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.
<!-- runtime-versions:end -->

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](#docker-and-podman-container-image) below for the isolated multi-runtime container.
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 prepare plain text batches for the `rumble-data` result form and collect receipts after posting them. No token is required; an optional `RUMBLE_CLIENT_TOKEN` is used only for read-only API requests and can raise the anonymous request limit. Results remain pending until a workflow receipt and the identical canonical fact are observed. See [Docker and Podman container image](#docker-and-podman-container-image) below for the isolated multi-runtime container.

## Configuration

Expand Down Expand Up @@ -128,7 +128,6 @@ Use the launcher scripts for configuration validation, runtime checks, snapshot
./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
```

Expand All @@ -138,11 +137,10 @@ export RUMBLE_CLIENT_TOKEN=<your token>
$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.json
```

For `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.
For `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 read public result issues and their receipts. Submission needs no credential: you post each prepared batch yourself through the result form.

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`.

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The client treats the remote bot catalog, source trees, engine pin, matchmaking

## Credentials

The submission token belongs only to the submission phase. Use a fine-grained GitHub token limited to read and write Issues access for `robocode-dev/rumble-data`; never grant repository-content, branch, release, package, or Pages write permission. Supply it through `RUMBLE_CLIENT_TOKEN` at runtime and do not store it in configuration or scripts.
Submission needs no credential. The client prepares each batch as plain text and you post it yourself through the result form while signed in to GitHub, so the client never holds a token that could write to a repository. Do not hand the client a GitHub token; if `RUMBLE_CLIENT_TOKEN` is set, it is used only to read public issues with a higher request limit.

## Container execution

Expand Down
6 changes: 0 additions & 6 deletions docker/rumble.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ if ($Command -eq 'runtimes') {
'--mount', "type=bind,source=${stateDirectory},target=/work/.rumble-client"
)
}
if ($Command -eq 'submit') {
if (-not (Test-Path env:RUMBLE_CLIENT_TOKEN) -or [string]::IsNullOrEmpty($env:RUMBLE_CLIENT_TOKEN)) {
throw 'RUMBLE_CLIENT_TOKEN must be set in the environment to submit results'
}
$containerArguments += @('--env', 'RUMBLE_CLIENT_TOKEN')
}
$containerArguments += $Image
$containerArguments += $clientArguments

Expand Down
4 changes: 0 additions & 4 deletions docker/rumble.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,6 @@ state_directory="${config_directory}/.rumble-client"
mkdir -p "${state_directory}"

env_args=""
if [ "${command_name}" = "submit" ]; then
: "${RUMBLE_CLIENT_TOKEN:?RUMBLE_CLIENT_TOKEN must be set in the environment to submit results}"
env_args="--env RUMBLE_CLIENT_TOKEN"
fi

exec "${container_engine}" run --rm --read-only --tmpfs /tmp:rw,nosuid,nodev,size=1g \
--user "$(id -u):$(id -g)" ${userns_args} \
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The Rumble Client is a Java 17 command-line application packaged as a container

## Boundaries and actors

- Contributors provide a JSON configuration, local state storage, and an optional GitHub token for result submission.
- Contributors provide a JSON configuration and local state storage, and post prepared result batches through the GitHub form. Reading public receipts requires no token; an optional token can raise the API request limit.
- The Tank Royale data repository supplies the canonical client configuration, engine pin, ranked catalog, matchmaking advice, and bot source archives.
- The Battle Runner executes the selected bots and produces battle and replay output; the client consumes its published 1.3.1 Maven artifact. Container builds also check out the pinned Tank Royale source to package the matching Python API and schema and to build sample bots for smoke verification.
- GitHub Issues provide the result inbox and receipt comments used by submission reconciliation.
Expand All @@ -18,7 +18,7 @@ The Rumble Client is a Java 17 command-line application packaged as a container
- `RumbleSynchronizer`, `RepositoryReader`, `GitRepositoryReader`, `RumbleSnapshotParser`, `SourceTreeHash`, and `BotCachePreparer` resolve the canonical snapshot and prepare an immutable local bot cache.
- `RankedBattleSelector`, `RankedBattleRecord`, `RankedBattleExecution`, and `RankedJournal` select under-sampled matchups and persist battle state and evidence.
- `RunnerBattleExecutor` adapts the selected battle to the Tank Royale Battle Runner.
- `IssueOpsSubmission` and `GitHubIssueOpsTransport` submit pending batches and remove them only after receipt comments confirm acceptance.
- `IssueOpsSubmission` prepares pending batches for the contributor to post through the form. `GitHubIssueOpsTransport` reads matching issues and workflow receipts; `CanonicalPublishedResults` checks identical canonical raw facts or rollups through `RepositoryReader` before journal acknowledgement.
- `docker/rumble.sh` and `docker/rumble.ps1` are the supported operational boundary for containerized validation, synchronization, execution, and submission.

```mermaid
Expand Down
9 changes: 5 additions & 4 deletions docs/design/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ flowchart TD
Cache --> Select[Select ranked matchup with recorded seed]
Select --> Run[Run through Battle Runner]
Run --> Journal[Persist journal and replay evidence]
Journal --> Submit[Submit pending batch]
Submit --> Receipt[Poll for receipt comment]
Receipt -->|accepted| Complete[Remove acknowledged local records]
Journal --> Submit[Prepare plain text batch and form link]
Submit --> Form[Contributor posts through GitHub form]
Form --> Receipt[Read workflow receipt and verify published fact]
Receipt -->|accepted and identical fact published| Complete[Acknowledge local records]
Receipt -->|missing or retryable| Submit
```

Synchronization reads repository contracts and materializes an immutable cache at the catalog's exact source commit. Selection prioritizes under-sampled pairings involving `myBots`, falls back to distinct active catalog entries when advice is unavailable, and records the random seed so the decision can be reproduced. Game-type expansion determines how many catalog entries are required, and selection rejects entries that share a member bot.

Battle execution is delegated to the pinned Tank Royale Battle Runner. The container image carries the supported Java, .NET, Python, and Node.js runtimes; launcher scripts provide the isolation and resource limits needed for reviewed-but-untrusted bot archives. The runtime preflight reports missing prerequisites without installing them.

The local work directory is the durable boundary for the bot cache, journal, and replay evidence. Submission reads pending records, sends batches through the GitHub Issues transport, and retains them until a matching result-data receipt comment is observed. Re-sending an already accepted batch is treated as idempotent, allowing interrupted sessions to resume without knowingly duplicating results.
The local work directory is the durable boundary for the bot cache, journal, replay evidence and prepared submissions. Submission writes bounded plain text batches and short form links; the contributor posts each batch while signed in to GitHub. The read-only Issues transport scans the registered account's issues across pages and correlates the prepared title and JSON envelope. Receipt collection accepts comments from the GitHub Actions bot and independently checks each result against a fresh canonical repository checkout, reading raw facts or compacted monthly rollups and matching the submitted account and complete payload. Records remain pending through network errors, API limits and delayed publication. In-flight state is calculated per issue attempt so an old rejection cannot hide a newer open retry; prepared titles remain unique across restarts, including retries within the same clock millisecond. A single oversized record fails preparation while remaining in the journal. Server-side idempotency protects already published facts when a submission is repeated.

<!-- clue:index:start -->
<!-- clue:index:end -->
Loading
Loading