## Build and arena reference

Build a bot, match it against other builds, and watch the fight. Every bot brings its own controller to one shared physical arena. You can reuse a controller with your own learned policy, write a deterministic C++ controller, or combine the two. Reinforcement learning is optional.

Each bout lasts at most 30 simulated seconds. Available workers run queued bouts. Different controllers and supported body profiles can fight one another; controller families are not separate leagues.

## Watch or queue your first fight

1. Open [Watch fights](/#arena). Click a live ring to watch, or choose Latest replay for a completed bout.
2. Open [Bot library](/#bots) to see the available builds, their controllers, body profiles, and results. You can try these bots immediately without uploading or training anything.
3. Click Queue a challenge on a build, or Create a fight on the arena page. Choose Bot A and Bot B, then press Create fight.
4. Your challenge opens automatically. If all workers are busy, it waits in the queue. Otherwise its first bout begins, followed by any remaining rounds.

Challenges are best of three: first to two wins, with at most three bouts. Starting sides alternate and each bout gets a fresh seed. Draws and no contests can leave the series tied or inconclusive. Other challenges and calibration bouts can run alongside yours.

## How a fight is decided

- At the 30-second limit, the fighter with the higher **contact points** total wins. The engine compares the full fractional totals; equal totals produce a draw.
- A valid fall or simulated damage stoppage can end the bout earlier and overrides the points lead. A fighter can therefore win while behind on contact points. Simultaneous stoppages produce a draw.
- Invalid physics or an evaluation failure produces a no contest, which does not count as a competitive loss or ranking win.

### What earns contact points

The judge measures physical contact from a glove, foot, or shin against the opponent's head, torso, abdomen, or thigh. It does not award one whole point for every hit. Each contact episode must satisfy all four checks:

- Relative closing speed at the start of contact is at least **0.5 m/s**.
- Accumulated normal impulse is at least **0.5 N·s**. Impulse measures the contact force accumulated over time.
- Contact lasts no longer than **0.12 seconds**. Sustained pushing does not qualify.
- The same striking surface has been clear of legal scoring contact for at least **0.04 seconds** before this episode starts.

For an eligible contact, the award is:

```text
points = min(2, 0.05 × normal impulse in N·s × target multiplier)
target multiplier = 0.5 for thighs; 1 for head, torso, and abdomen
```

For example, a qualifying **5 N·s** contact earns **0.250 points** to the head or torso, or **0.125 points** to a thigh. Each award is capped at **2 points**. Small fractions add up: a final total of **0.235 versus 0.000** is a points win, even though rounding both totals to whole numbers would hide the difference. Displayed values are rounded; the engine keeps the full totals.

Points normally appear **after the contact separates**, once its impulse and duration are known. Contacts still open at the bell or a stoppage are finalized then. The score animation follows the confirmed award, so it may appear just after the initial impact.

### Why a held glove can earn points

The current judge uses **relative physical motion**, not the controller's intention. It does not require an active punch command, a named technique, or a minimum speed from the scoring fighter alone. If an opponent moves their head into a nearly stationary glove, that glove can earn points when the contact passes the checks above. Custom C++ controllers and built-in controllers share these rules.

A hit against an opponent's glove or forearm does not earn contact points because those are guard surfaces, not legal scoring targets. But a held glove touching a legal target can qualify. A scored contact therefore does not always mean a deliberate, clean attack.

### Reading the fight effects

- **Warm gold flash and +points:** a confirmed scored contact. The number is the fractional award added to that fighter's total.
- **Cool teal shield with BLOCKED or GUARD CONTACT:** a confirmed attack-to-guard contact. The marker itself adds no contact points.
- **Cool teal GUARD CLASH:** both attacking arms made confirmed contact and were interrupted; it is not automatically a successful defensive block by either fighter.

Predicted collision avoidance does not produce a contact effect. Older recordings may lack some guard-contact markers. Fight effects help identify recorded events; their colors and animations do not change scoring or damage.

The supported body presets are lean, balanced, and power. A learned policy must retain its exported body profile. A custom controller chooses a supported profile when it is registered. Physical torque, speed, power, fatigue, and damage limits apply to every build.

## Upload a bot build

On the public website, create an account with a username, verify your email,
then upload a registered `.bot.json` package. Start with the downloadable
example if you want to try submission. Your account owns the upload; you cannot
claim another account by editing its owner label. Only owners and administrators
can download private bot packages.

New native controllers and model assets require registration by the arena
operator before the website can accept their packages. The public upload form
does not install arbitrary source code, libraries, or checkpoints. The build
commands below are for the trusted development checkout, not the public EC2
shell or a browser.

A build contains its controller, decision logic or learned weights, observation adapter, and serving settings. To create a new one, use one of the registration workflows below on the arena machine, then upload the generated package.

1. Register the controller or exported policy from the full arena checkout inside Docker.
2. Read the JSON printed by the command. Its `package` field names the complete `package.bot.json` file to upload.
3. In the website, click Upload bot, select that package, and press Upload bot. The JSON upload limit is 16 MiB; the registered controller and model files remain on the arena machine.

The browser accepts a registered `.bot.json` package using `arena_cpp_bot_build_v1`. Source code, compiled libraries, model checkpoints, export manifests, and the SDK ZIP are inputs to development or registration, rather than files to select in the upload dialog.

If you browse from another device, copy the generated package JSON to that device first. Its referenced controller and model files must stay available inside the arena's Docker workspace. You can also submit from the container:

```sh
/tmp/kickbox-practical-arena-build/kickbox_arena league-submit \
  --directory /workspace/artifacts/arena-unified \
  --package /path/printed/in/the/package/field/package.bot.json
```

Exact reuploads are detected as the same immutable version. Changing a controller, policy, body configuration, or serving mode requires a new registered build. On the public site, ownership is assigned to the verified account that uploads the package.

## Where to run build commands

Run the following commands inside the existing Linux arena container, from the full checkout:

```sh
cd /workspace
```

Registration uses Python 3 and, for custom controllers, a C++20 compiler, binutils (`nm`), and nlohmann JSON development headers. The current arena build already uses these dependencies. A Windows terminal or a phone browser does not run these container commands directly.

The [C++ controller SDK](/controller-sdk.zip) contains authoring headers, an example, registration helpers, and these instructions. It is not a standalone simulator or a bundle of trained models. Policy registration also needs the frozen controller libraries and exports available in the full checkout. Training and policy export happen outside the browser.

## Reuse an existing controller

Export a policy using its matching SDK, then register it with `scripts/register_policy_build.py`. Supported inputs are:

- `strategy6`: an original `practical_cpp_policy_v1` policy JSON, or its legacy package. Serving is greedy only.
- `sdk3`: a sealed CT or curriculum CPU `manifest.json` with all its adjacent, hash-bound files, or the matching SDK3 export descriptor. Its serving mode is fixed by that export. Export the desired greedy, sampled, or supported curriculum hybrid variant first.
- `v4` and `v5`: an exported controller-specific descriptor, or a raw policy JSON together with its matching checkpoint. These support `greedy_joint` and `sampled_role_stream_v1`.

Use the original policy export files here. A shared-arena package downloaded from Bot library is ready for reupload, but is not itself a policy export for this command.

For an SDK3 export:

```sh
python3 scripts/register_policy_build.py \
  --controller sdk3 --source /path/to/exported/manifest.json \
  --name my_freestyle_bot --profile lean
```

For a raw V4 policy and its matching checkpoint:

```sh
python3 scripts/register_policy_build.py \
  --controller v4 --source /path/to/policy.json \
  --checkpoint /path/to/checkpoint.json \
  --name my_strike_bot --profile balanced \
  --mode sampled_role_stream_v1
```

Use `--controller v5` for a matching V5 export. The profile must match the actual policy export; changing a JSON label does not transfer learned weights to another body or controller. Use `--help` for the complete options. Registration freezes the files and records their hashes; it does not retrain the policy or start a fight.

## Custom controllers

Start with the example in the checkout, or adapt the files from the [controller SDK](/controller-sdk.zip):

```sh
python3 scripts/register_local_controller.py \
  arena/unified/examples/pd_hold_controller.cpp \
  --name my_controller --identity example_pd_hold_v1 --profile lean \
  --config '{"kp":40,"kd":8}'
```

Upload the file named by `package` in the command's JSON output. The example holds its initial joint angles; it demonstrates the API and is not a trained fighter.

For your own controller, implement the three exported functions in `arena/unified/plugin_api.hpp` and the `JointController` interface. Each fighter receives its own controller instance, reset seed, actual public measurements, and owned joint states. Keep your controller state on that instance. Return one finite torque in Nm per owned joint, in the supplied order. The shared world applies the physical limits and advances both fighters together.

Declare each runtime weights or data file with a repeated `--asset /path/to/file` argument. Pass filenames and other settings through `--config` or `--config-file`. Those assets are checked before loading; changing them requires registering a new build. Keep the registered files at their declared paths.

Add `--prepare-only` to compile and inspect without registering. To make that build uploadable, rerun without `--prepare-only`. Native C++ libraries are trusted local code in this prototype; registration is intended for code you authored and chose to run on this machine.

## Calibration and rankings

Uploads enter automatic placement. Initial placement targets about five opponents per build, depending on the available field; the starting roster gets a connected comparison graph. Calibration runs three seeds in both starting colors for each selected pairing: six bouts per opponent. This is separate from the best-of-three format used for user challenges.

Available workers share calibration and challenge work. Queue types alternate while both have work, and each challenge queues its next round after the preceding round finishes.

Only valid calibration results affect the [Leaderboard](/#leaderboard). User challenges do not change rankings. The rating combines opponent-balanced results and opponent-adjusted performance; the leaderboard explanation shows the current weights. A build remains provisional while its game count or opponent coverage is incomplete. Validation means a build can run, not that it fights well.

**Leaderboard rating is separate from fight contact points.** Contact points describe the measured contacts in one bout. Rating summarizes a build's calibration performance across opponents; a +0.250 contact award does not directly add 0.250 to its rating. Series wins also count bout outcomes, rather than summing contact points across the three bouts.

## Live viewing and replays

Challenges have a match card with the score and all three rounds. Playback starts
with round one and continues after a short countdown when **Auto-next** is on.
Turn it off to advance manually. A 2–0 win ends the match after round two;
round three is marked **Not needed**. When a later round is still queued, the
card shows that it is waiting. Replay match starts the whole matchup again.

Live viewing starts at 0:00 and plays at 1×. The viewer usually waits for at least six seconds of footage, increasing that head start when delivery is slower. Short completed bouts and fully downloaded recordings can start as soon as they are available. This buffers playback; it does not train the bots.

For a fight opened live, seeking, skipping, restarting, and speed changes stay locked until you have watched the final frame, including any buffered tail after the server finishes. Pause and resume preserve your place. Clicking the same ring or Watch live again does not jump ahead. Opening an already completed recording gives you replay controls immediately.

- Ringside, From above, and Whole ring change the camera. Drag to look around; use the zoom and Reset view buttons as needed.
- Expand or F opens the larger viewer; Escape closes it. Space pauses or resumes.
- In replay mode, use the timeline, Start over, speed selector, or arrow keys to skip five seconds.

A prolonged delivery interruption can pause playback at the current frame while more footage loads. The viewer resumes without skipping action. Camera and playback controls change how you watch, not the simulation or its outcome.

## Previous matches

Use View to select a previous match group, then open Fight archive. Those recordings and outcomes retain their original match context and are preserved for replay. The archive is read-only; new challenges use the shared arena. Earlier rankings and qualification results are not imported into current shared-arena standings.

## Local development and troubleshooting

On the Docker host's Wi-Fi network, use `http://10.0.0.241:8080/` with the existing host port forwarding. Inside the arena container, use `http://127.0.0.1:8080/`. To start or check the arena service from `/workspace`:

```sh
scripts/arena.sh start
scripts/arena.sh status
```

The shared league is stored at `artifacts/arena-unified`. Earlier league directories remain preserved. The service runs bots already built for inference; it does not launch training.

If an upload fails, check that you selected the `package` file, registered it in the arena's workspace without `--prepare-only`, retained the declared files, and used the export's real controller and body profile. If a custom controller reports an outdated SDK, rebuild it against the current headers. Never fix a compatibility error by renaming schemas or hashes in the JSON.

## Local physics validation

Download the physics engine for [Linux x86-64](/physics-engine-linux-x86_64.zip)
or [macOS Apple Silicon](/physics-engine-macos-arm64.zip) to build the shared
physics engine and test controllers locally. Both bundles use the same engine
source, with a pinned MuJoCo library for the destination platform. The README covers
building, a sample controller, action sequences, and optional JSON replay frames.
The download contains no accounts, server configuration, or trained bot weights.
Local physics validation does not replace build registration for public uploads.
You can integrate the C++ World API into your own training loop on either
platform; a training framework is not included. Rebuild native plugins for the
hosted Linux arena and revalidate there; cross-platform results may differ.
