# DevControlBot — Garage Agent Guide > ## ⚠️ READ THIS FIRST — THIS GARAGE IS *INTENTIONALLY* NOT THE ROOT TEMPLATE > > **The layout and format described in the repo-root [`../../AGENTS.md`](../../AGENTS.md) > template do NOT apply to `DevControlBot_garage/`.** The root template documents a > `src/.nim` / `tests/` / `out/` convention for garages. **This garage deviates > from it on purpose and must stay that way.** > > **Do NOT, even to "fix" a perceived inconsistency:** > - create `src/` or `out/`, or put anything inside `DevControlBot/` for testing > (the one exception is the intended `DevControlBot_garage/tests/`, see > "Layout of this folder" — it does not exist yet, create it only when the > first test is actually written) > - add a `config.nims`, `nimble.paths`, `--path` flags, or a vendor/ directory > - restructure `DevControlBot/` to match the root template > > Builds go to a `mktemp -d` throwaway dir that is removed on exit — nothing is > ever written here. The root `AGENTS.md` and its folder conventions are for the > **other** garages; here it is context only (issue tracker, Nim conventions), and > is **NOT a layout contract for this folder**. If the root template and this file > disagree about layout, **this file wins.** > ## ⚠️ DO NOT TOUCH `PLAN.md` > > **[`PLAN.md`](PLAN.md) is the user's personal working notes file.** The user > maintains it and edits it exclusively. > > - Agents must **NEVER** modify, rewrite, reformat, move, delete or commit > changes to `PLAN.md` — unless the user explicitly asks in that conversation. > - Reading it is **not** assumed or expected; do not open it by default. > - It **IS** tracked in git and is intended to be committed (deliberately added > in commit `59fad00b`). Do **NOT** gitignore it and do **NOT** > `git rm --cached` it. ## Start here The code is the source of truth; this file is an inventory of what exists and how the pieces interact — not a description of what the code does. If the two disagree, the code wins and this file is stale. The human-facing description lives in [README.md](README.md). Repo-wide rules live in [`../../AGENTS.md`](../../AGENTS.md) (issue tracker, Nim conventions, test framework) — read it for that context, but note again: **its folder-layout template does not govern this garage.** ## Conventions ### Git rule for this garage - **EVERYTHING** under `DevControlBot_garage/` gets committed — sources, modules, docs, `PLAN.md` — **unless it is explicitly listed in a `.gitignore`**. - This includes files that look like scratch or generated output, and files an agent did not create: if not gitignored, they belong in the commit. - Do not silently leave a file untracked, and do **not** add entries to `.gitignore` to avoid committing something. The only things that stay out are those the user has deliberately gitignored (e.g. `.env`). ### Version bumps and tags — the user's decision Agents **MUST NOT** bump the version in `DevControlBot.nimble` or `DevControlBot.json`, and **MUST NOT** create git tags. A version bump and its tag are honoured **only** when the user explicitly asks in that conversation; a bump is never a side effect of ordinary work or of a commit. Never amend, rewrite or retag an existing commit or tag. ### Scope and colour - This bot's code lives **only** inside `DevControlBot_garage/`. Do not edit other garages to make this bot work, and do not reuse another bot's game logic without explicit permission; shared, bot-agnostic code belongs in `common_libs/`, and only with permission. - Team colour convention: **white = not programmed, black = radar active**. - Provenance: ModularBot / common_libs (Davide Cappellini, Apache-2.0). ### Constraints worth remembering Not explanations — real invariants that broke things when violated: - **Command the radar BEFORE `go()`** — a real bug the other way round. - All radar logic is `modules/radar.nim`, in one piece. Do not split it back into harness/lock/melee modules. ## Layout of this folder ``` DevControlBot_garage/ ├── AGENTS.md # this file ├── README.md # human-facing description ├── PLAN.md # the user's own notes — never edit (see header) ├── .gitignore # ignores .env ├── DevControlBot/ │ ├── DevControlBot.nim # bot type + entry point (isMainModule) │ ├── DevControlBot.json # bot metadata │ ├── DevControlBot.nimble # single task: runBot │ ├── DevControlBot.sh # BotLauncher entry point (thin wrapper) │ ├── .env # OPTIONAL local config; secrets, never commit │ └── modules/ │ └── radar.nim # all radar logic └── tests/ # INTENDED, does not exist yet — create it on # the first real test ``` - `DevControlBot/` holds only what the bot needs to run. - `tests/` is a sibling of `DevControlBot/`, never inside it, and holds **test code worth keeping**: drivers, harnesses, reusable battle/probe scripts. - **Throwaway output does NOT go here or anywhere in the repo — it goes to `/tmp`**: logs, temporary binaries, dumps, telemetry, scratch output. - No `out/`, no binaries, no `nimbledeps/` here. Builds are throwaway by design. ## Build and run Use the folder task, never raw `nim c`: ```sh cd DevControlBot_garage/DevControlBot nimble runBot # quiet release build + run ``` Or via the wrapper, which works from any cwd: ```sh ./DevControlBot/DevControlBot.sh # bundled metadata ./DevControlBot/DevControlBot.sh my.json # first positional arg, must end in .json ./DevControlBot/DevControlBot.sh --debug # classic debug build (hints + diagnostics) ./DevControlBot/DevControlBot.sh --help # usage, exits 0 without building ``` - **No arguments means RUN, not usage.** Usage prints only for `-h` / `--help`. There is no `--json` flag; a first positional ending in `.json` overrides the metadata, anything else is forwarded to the bot verbatim. - **Gotcha:** `nimble runBot` must run from `DevControlBot/` (where the `.nimble` lives); from `DevControlBot_garage/` it fails with `Could not find a file with a .nimble extension...`. `compile`/`build` are reserved Nimble builtin names — hence `runBot`. - **Config is environment-only.** The bot API's `start()` reads `SERVER_URL` / `SERVER_SECRET` itself; **never add env-reading code to `DevControlBot.nim`.** The optional `.env` (loaded by the `runBot` task, `$DEVCONTROLBOT_ENV_FILE` overrides the path) is local-runs-only and **never overrides a variable already present in the environment** — the BotLauncher's values always win. Never print its values, never `set -x`, never commit it. - Dependency setup is `nimble install robocode_tankroyale_botapi`; dependencies come from the global nimble store. Do not vendor, do not add `nimble.paths` / `nimble develop`. - Without a game server at `SERVER_URL` the run fails with `[start] Cannot connect ... Connection refused`. That is expected locally, not a bug. - Exit codes are distinct because BotLauncher branches on them: `0` clean, `1` no server (expected), `2` dependency missing, `3` compile failure, anything else = the bot's own code. ## Tests There are **no tests yet** — no `tests/` directory and no `test` task; `nimble test` does not exist. When the first test is written, follow the repo-wide convention in [`../../AGENTS.md`](../../AGENTS.md) and `common_libs/test_framework/README.md`: `tests/config.nims` with `--path:"../../common_libs"`, one `test.nim` per concern, a `test` task in the `.nimble`, and a guard-first block so tests pass with no Java present. ## Coordinates & angles (source: robocode.dev/articles/coordinates-and-angles.html) Source: https://robocode.dev/articles/coordinates-and-angles.html (Tank Royale docs) - Cartesian coordinate system; (0, 0) is the bottom-left corner of the arena. - Y up is implied by the origin being bottom-left (page never states it explicitly); X east is confirmed via the angle rules below. - Angles follow classic trig (unlike original Robocode, whose 0/360 was north and 90 east). - 0°/360° = east; 90° = north; 180° = west; 270° = south. - Positive angles go counterclockwise; turning right decreases the angle (clockwise). - Full turn = 360°. - NOT stated on this page: distance formula, bearing/angle-between-two-points formula, angle normalization/wrapping, worked example numbers. ## Behaviour diagrams **CURRENT STATE, not a design sketch.** These describe the code as it is today. **Diagram 1 — one tick. Owned by `DevControlBot/DevControlBot.nim` (`run`, lines 34-40) and `DevControlBot/modules/radar.nim` (`onScan`, `commandRadar`) — update this diagram when either file changes.** The ordering is the whole point: the command is issued BEFORE `go()` so it travels with this tick's intent, and events arrive AFTER, so a scan can only ever steer the NEXT tick. ```mermaid sequenceDiagram participant Run as run loop participant Radar as radar.nim participant Bot as bot + API participant Srv as server Run->>Radar: commandRadar
# one command, every tick, no idle branch Radar-->>Bot: setRadarTurnRate
LOCK turn + 5deg overshoot,
or +45deg sweep Run->>Bot: go
# intent sent here, FIRST Bot->>Srv: tick intent
radar turn only,
no move, no fire Srv-->>Bot: tick result
+ pending ScannedBotEvent Bot->>Radar: onScannedBot -> onScan x, y
# AFTER go: remembers only Radar-->>Radar: target position stored,
tick counter reset Note over Run,Radar: that remembered target is used
by the NEXT commandRadar ``` **Diagram 2 — radar behaviour. Owned by `DevControlBot/modules/radar.nim` (`commandRadar`, lines 70-81) — update this diagram when that file changes.** SWEEP is not a separate lifecycle: it is the same single command the lock replaces, so switching costs no ticks. ```mermaid stateDiagram-v2 [*] --> SWEEP state "SWEEP
360 sweep at 45 deg/tick,
full revolution every 8 ticks" as SWEEP state "LOCKED
servo onto target bearing
PLUS 5 deg overshoot" as LOCKED SWEEP --> LOCKED: "getEnemyCount is 1
AND target seen within
FreshScanTurns 1" LOCKED --> SWEEP: "enemy count is not 1,
or target stale beyond 10 ticks,
or lost" LOCKED --> LOCKED: "scan every tick
because the radar crosses
the bearing" ``` The 5 deg overshoot is the reason the lock works: the radar sweeps ACROSS the bearing instead of parking on it, so a fresh `ScannedBotEvent` arrives every tick. Turning by the bare error, or holding at rate 0 inside a deadzone, stops the scans and loses the target until a sweep re-finds it. Never replace the overshoot with a deadzone hold. ## Physics (source: robocode.dev/articles/physics.html) Source: https://robocode.dev/articles/physics.html (Tank Royale docs) - Turns/rounds 1-based; each round restarts at turn 1. Distance units: floating-point double. - Acceleration: +1 unit/turn; deceleration: 2 units/turn (braking 2x faster than accelerating). - Speed: v = a * t; max speed 8 units/turn. Velocity direction == bot heading. - Distance: d = v * t. - Rotation in degrees. Base max turn rate: 10 - 3/4*|v| deg/turn (10 deg/turn standing still; 4 deg/turn at max speed 8). - Gun rotation max 20 deg/turn (added to bot's current rate). - Radar rotation max 45 deg/turn (added to gun's current rate). - Firepower: min 0.1, max 3; energy cost subtracted from bot energy. - Bullet damage: 4 * firepower, plus 2 * (firepower - 1) if firepower > 1. - Bullet speed: 20 - 3 * firepower units/turn (19.7 at fp 0.1; 11 at fp 3); constant per shot. - Gun heat gain: 1 + firepower/5. Cannot fire while heat > 0. Gun starts at heat 3 each round. - Energy gain on bullet hit: 3 * firepower. - Collision with bot/wall stops the bot, except when moving away from the bot that hit it. - Bot-vs-bot collision: 0.6 damage to each. - Ramming (moving forward into another bot): both take damage; rammer gets ramming kill bonus. Wall damage: |v|/2 - 1, clamped at 0 if negative. - NOT stated on this page: coordinate system/axes/units, Y up or down, angle origin & clockwise/CCW sense, distance/bearing formulas, angle normalization/wrapping, max energy, per-turn energy regain from inactivity.