de0dd50d6e
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.
239 lines
12 KiB
Markdown
239 lines
12 KiB
Markdown
# 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/<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/` 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<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.
|
|
|
|
```mermaid
|
|
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.
|
|
|
|
```mermaid
|
|
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. |