AGENTS.md is now an inventory of what exists plus the mermaid behaviour diagrams — not a narration of the code. The source and its doc comments are the carrier of truth for how the bot works; a prose retelling in AGENTS.md only goes stale against them. Removed: blocks that duplicated what the source already says, a stale line that contradicted DevControlBot.sh's actual no-argument behaviour, and the prior bug-fix history (which is a record of the past, not guidance for the next change). 384 -> 238 lines. Kept byte-identical: the behaviour diagrams, the rules (git, version/tag, colour convention), the build and run commands, and the physics and coordinates references. Two constraints stay as one-liners, because they are the two ways this bot has actually been broken: command the radar BEFORE go(), and keep all radar logic in modules/radar.nim.
12 KiB
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.mdtemplate do NOT apply toDevControlBot_garage/. The root template documents asrc/<bot>.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/orout/, or put anything insideDevControlBot/for testing (the one exception is the intendedDevControlBot_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,--pathflags, or a vendor/ directory- restructure
DevControlBot/to match the root templateBuilds go to a
mktemp -dthrowaway dir that is removed on exit — nothing is ever written here. The rootAGENTS.mdand 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.mdis 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 NOTgit rm --cachedit.
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. Repo-wide rules live
in ../../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
.gitignoreto 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 incommon_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 ofDevControlBot/, 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, nonimbledeps/here. Builds are throwaway by design.
Build and run
Use the folder task, never raw nim c:
cd DevControlBot_garage/DevControlBot
nimble runBot # quiet release build + run
Or via the wrapper, which works from any cwd:
./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--jsonflag; a first positional ending in.jsonoverrides the metadata, anything else is forwarded to the bot verbatim. - Gotcha:
nimble runBotmust run fromDevControlBot/(where the.nimblelives); fromDevControlBot_garage/it fails withCould not find a file with a .nimble extension....compile/buildare reserved Nimble builtin names — hencerunBot. - Config is environment-only. The bot API's
start()readsSERVER_URL/SERVER_SECRETitself; never add env-reading code toDevControlBot.nim. The optional.env(loaded by therunBottask,$DEVCONTROLBOT_ENV_FILEoverrides 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, neverset -x, never commit it. - Dependency setup is
nimble install robocode_tankroyale_botapi; dependencies come from the global nimble store. Do not vendor, do not addnimble.paths/nimble develop. - Without a game server at
SERVER_URLthe run fails with[start] Cannot connect ... Connection refused. That is expected locally, not a bug. - Exit codes are distinct because BotLauncher branches on them:
0clean,1no server (expected),2dependency missing,3compile 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 and
common_libs/test_framework/README.md: tests/config.nims with
--path:"../../common_libs", one test<concern>.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.
sequenceDiagram
participant Run as run loop
participant Radar as radar.nim
participant Bot as bot + API
participant Srv as server
Run->>Radar: commandRadar<br/># one command, every tick, no idle branch
Radar-->>Bot: setRadarTurnRate<br/>LOCK turn + 5deg overshoot,<br/>or +45deg sweep
Run->>Bot: go<br/># intent sent here, FIRST
Bot->>Srv: tick intent<br/>radar turn only,<br/>no move, no fire
Srv-->>Bot: tick result<br/>+ pending ScannedBotEvent
Bot->>Radar: onScannedBot -> onScan x, y<br/># AFTER go: remembers only
Radar-->>Radar: target position stored,<br/>tick counter reset
Note over Run,Radar: that remembered target is used<br/>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.
stateDiagram-v2
[*] --> SWEEP
state "SWEEP<br/>360 sweep at 45 deg/tick,<br/>full revolution every 8 ticks" as SWEEP
state "LOCKED<br/>servo onto target bearing<br/>PLUS 5 deg overshoot" as LOCKED
SWEEP --> LOCKED: "getEnemyCount is 1<br/>AND target seen within<br/>FreshScanTurns 1"
LOCKED --> SWEEP: "enemy count is not 1,<br/>or target stale beyond 10 ticks,<br/>or lost"
LOCKED --> LOCKED: "scan every tick<br/>because the radar crosses<br/>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.