diff --git a/DevControlBot_garage/AGENTS.md b/DevControlBot_garage/AGENTS.md index 4ce1179..1553c4c 100644 --- a/DevControlBot_garage/AGENTS.md +++ b/DevControlBot_garage/AGENTS.md @@ -8,7 +8,10 @@ > from it on purpose and must stay that way.** > > **Do NOT, even to "fix" a perceived inconsistency:** -> - create `src/`, `tests/`, or `out/` +> - 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 > @@ -32,222 +35,51 @@ ## Start here -This file is the working rules for the bot. The human-facing description lives in -[README.md](README.md) — read it first. +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. -Garage-specific rules for `DevControlBot_garage/`. Repo-wide rules live in -[`../../AGENTS.md`](../../AGENTS.md) (issue tracker, Nim conventions, test +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. -- Do not reuse or copy another bot's game logic (movement/scanning/firing, radar - tables, targeting heuristics) without explicit permission from the user. Shared, - bot-agnostic code belongs in `common_libs/`, and only with permission. -- The radar is the one part currently driven. **All of it is - `DevControlBot/modules/radar.nim`** — two procs (`onScan`, `commandRadar`), - three variables (the target's position, whether we have one, ticks since it was - last scanned), three constants. Nothing else; do not split it back into + 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. - `commandRadar()` issues exactly one `setRadarTurnRate` per tick on every path: - lock when `getEnemyCount() == 1` and we hold a target scanned within - `FreshScanTurns`, the 45 deg/tick sweep otherwise (that same sweep is both the - search and the multi-enemy answer — there is no separate melee mode). No - counter, no seen-id set, no mode enum; the server's enemy count drives the - choice, so a death switches modes on the spot and no tick is ever left without - a command. - The lock commands the delta to the target's bearing **plus `OvershootDeg` past - it**, so the radar sweeps across the bearing every tick and re-scans the target - every tick. That overshoot is the whole reason the lock works, and it is why - `setRescan()` is not needed (the radar is never idle). **Never** turn by the - bare error and never hold at rate 0: that parks the radar on the bearing, the - scans stop, and the target is lost until a sweep re-finds it. - A lock is only taken on a target seen within `FreshScanTurns`; a working lock - re-scans every tick, so that threshold can never throw a good lock away, and it - prevents a blind slew at a stale position just after the count drops to 1. - `run()` commands the radar before every `go()`, so the command is part of the - tick's intent; `onScannedBot` is a one-liner, `onScan(e.x, e.y)`, which - remembers the target's absolute position and issues no command. Angles are - 0° = east, positive = counter-clockwise = **left** (see README for the API - proof). Never the blocking `rescan()`. - Provenance: ModularBot / common_libs (Davide Cappellini, Apache-2.0); see README. - -## Build artifacts / binaries - -- **Rule:** never write a binary, log or build output into this garage. The - `runBot` task builds into a `mktemp -d` throwaway dir and removes it with an - `EXIT`/`INT`/`TERM` trap, so **no `out/` directory is ever created** here. -- **Rule:** never commit binaries or nimble cache dirs (`~/.nimble`, - `nimbledeps/`). `nimbledeps/` only appears with `nimble develop`; ignore it. -- **Rule:** dependencies come from the global nimble store. Do **not** vendor a - copy or add `nimble.paths`/`nimble develop` — `nimble install - robocode_tankroyale_botapi` is the only setup step. (The package was renamed - from `tankroyale_botapi`; the old name is gone.) - Exception: the `runBot` task *derives* `--path:` flags at run time from - `nimble path `, because the compiler's bundled - `nimblepath="$home/.nimble/pkgs2/"` only resolves when `$HOME` is set. That is - resolution, not vendoring; keep it. -- Build/run with the folder task, never raw `nim c`: - -```sh -cd DevControlBot_garage/DevControlBot -nimble runBot -``` - -- Note: the root `.gitignore` claims "all builds go to `*_garage/out/`". That is - no longer true for this garage — do not rely on that rule, and do not add an - `out/` directory just to satisfy it. (Root `.gitignore` is not this garage's - file; leave it alone unless the user asks.) - -## Running the bot - -`DevControlBot/DevControlBot.sh` is the canonical entry point and works from any -cwd — it resolves `SCRIPT_DIR` from `BASH_SOURCE`, `cd`s to the `.nimble` dir, -forwards args and signals to `nimble runBot`, and propagates the exit code. - -```sh -./DevControlBot/DevControlBot.sh # from the garage root: bundled metadata -./DevControlBot/DevControlBot.sh /path.json # alternate metadata (first arg, must end in .json) -./DevControlBot/DevControlBot.sh --debug # classic debug build -./DevControlBot/DevControlBot.sh --help # usage, exits 0 without building -``` - -- **Rule: no arguments means RUN, not usage.** A bare `./DevControlBot.sh` - performs a quiet RELEASE build and runs the bot with the bundled - `DevControlBot.json`, exactly as if that path had been passed. Usage is - printed **only** for an explicit `-h` / `--help`, which exits 0 without - building. (An earlier version printed usage and exited 0 with no arguments — - that was the bug, and it is fixed.) -- There is **no `--json` flag**: the metadata path is the first positional - argument and is used when it ends in `.json`. Anything else is forwarded to - the bot verbatim. - -- **Build mode:** default is **release** (`nim c -d:release`, quiet: no compiler - hints, no dot-progress line, just `[devcontrolbot] build ok (release)`). - `--debug` selects the classic debug build (`nim c`, full hints/diagnostics). - The flag is stripped by the wrapper in any position and reaches the `runBot` - task through the exported env var `$DEVCONTROLBOT_DEBUG` (`debug`/`release`), - never as a bot argument. Compiler errors are shown in **both** modes; a failed - release build replays the captured compiler log, so it is never silent. -- With no arguments (or `--help`) the wrapper prints usage and exits 0 without - building. - -- **Gotcha:** `nimble runBot` must be invoked from `DevControlBot/`, the - directory containing the `.nimble`. From `DevControlBot_garage/` it fails with - `Could not find a file with a .nimble extension inside the specified directory`. -- `compile`/`build` are reserved Nimble builtin names — hence `runBot`. -- Without a Tank Royale server at `SERVER_URL` the run fails with - `[start] Cannot connect ... Connection refused` and exit code 1. That is - expected during local verification, not a bug. - -## Configuration = environment variables (the API's own mechanism) - -- **Rule: never add env-reading code to `DevControlBot.nim`.** The bot API - already reads its configuration from the process environment inside - `start()`: `SERVER_URL` and `SERVER_SECRET` - (`robocode_tankroyale_botapi.nim:424-425`, documented at `:396-397`) and - `BOT_NAME` / `BOT_VERSION` / `BOT_AUTHORS` / … in `bot_info.nim:117-130` - (used when no JSON metadata is found). That is the supported mechanism the - official BotLauncher uses; `DevControlBot.nim` calls plain `start(bot, - jsonPath)` and needs no change. -- The `runBot` task makes those variables **present** for local runs (see the - `.env` section). That is all it does; it does not interpret them. - -## Optional `.env` (local runs only) - -`DevControlBot/.env` holds the local configuration (typically `SERVER_URL` and -`SERVER_SECRET`). Rules: - -- **It is optional.** If it is missing the run is unchanged: no error, no - non-zero exit, just one informational line and the API defaults. -- **Loaded in the `runBot` task's shell**, not in `DevControlBot.sh`, so it works - for both `./DevControlBot.sh` and a direct `nimble runBot` and the wrapper - stays a thin wrapper. `$DEVCONTROLBOT_ENV_FILE` overrides the path. -- **Precedence — CRITICAL: the environment always wins.** A variable already - present in the environment is **never** overridden by `.env`, so the official - BotLauncher's values keep winning. It is achieved by *filtering*: a - `NAME=value` line is dropped when `NAME` is already set (`[ -n "${NAME+x}" ]`, - set-but-maybe-empty), and only the still-unset names are `set -a`-exported and - sourced. Do **not** "simplify" this into a plain `set -a; . .env` — sourcing - does the opposite and would let the file beat the launcher. -- Accepts `FOO=bar` and `export FOO=bar`, strips `CR` (CRLF), skips blanks, - `#` comments and any line that is not a plain `NAME=value` identifier. -- **Never print values** and never use `set -x`: report variable *names* only. -- **Never commit it** — it holds secrets. The root `.gitignore` covers - `ModularBot_garage/.env` only and does **not** ignore this one; the rule to - add (ask the user before touching the root file) is - `DevControlBot_garage/DevControlBot/.env`. - -## Failure reporting / exit codes - -- **Rule:** an expected failure must never surface as a Nimble/NimScript - exception. `exec` raises on any non-zero exit, printing a stack trace and an - escaped copy of `runScript`. So the shell always exits 0 and writes the real - code to `$DEVCONTROLBOT_STATUS_FILE`; `DevControlBot.sh` exports that path and - re-exits with the code. Never mask it to 0, never let the shell's code be - swallowed. -- Exit codes (keep them distinct, they are what BotLauncher branches on): - `0` clean, `1` no game server at `SERVER_URL` (expected), `2` dependency not - installed, `3` compile failure, anything else = the bot's own code. - Each prints its own short, actionable line; compile failures keep the raw - compiler errors. -- Running `nimble runBot` directly (no status file) intentionally falls back to - the legacy behaviour: the shell exits with the real code and Nimble raises. - -## Test organization - -- **There are currently no tests in this garage.** No `tests/` directory, no - `tests/config.nims`, and the `.nimble` has exactly one task (`runBot`) — the old - `test`, `compileBot` and `setupVendor` tasks are gone. `nimble test` does not - exist; do not invoke it and do not document it. -- Convention **when** tests are added (repo-wide, see - [`../../AGENTS.md`](../../AGENTS.md) and - `common_libs/test_framework/README.md`): - - create `DevControlBot_garage/tests/` with `tests/config.nims` containing - `--path:"../../common_libs"` (depth must match the garage location), one - `test.nim` per concern (e.g. `test_basic_battle.nim`); - - add a `test` task to `DevControlBot.nimble` running - `nim c -r --path:../common_libs tests/test_basic_battle.nim`; - - guard first, import second — tests must pass with no Java present: - - ```nim - import std/os - if not existsEnv("TR_SERVER_JAR") or not existsEnv("TR_BATTLE_RUNNER"): - echo "Skipping: TR_SERVER_JAR / TR_BATTLE_RUNNER not set" - quit(0) - - import test_framework/test_framework - ``` - - - shared adversaries: `common_libs/test_framework/adversaries/SittingDuck` - (passive) and `OscillatorBot` (fights back); standard call is - `runBattle(@[myBotDir, adversaryDir], rounds = 10)`. - - env vars: `TR_SERVER_JAR`, `TR_BATTLE_RUNNER`. Failures surface as `OSError` - (bot compile failed), `IOError` (runner non-zero), `TimeoutError` - (server 15s / compile 30s per bot / battle runner). - -## Onboarding — full sequence - -```sh -# 1. dependency (once; already global if nimble install says "already installed") -nimble install robocode_tankroyale_botapi - -# 2. run the bot (compiles to a temp dir, runs, cleans up; nothing left behind) -./DevControlBot/DevControlBot.sh - -# 2b. same thing via the task — must be run from DevControlBot/ -cd DevControlBot && nimble runBot - -# 3. tests — none exist yet; see "Test organization" above -``` - -Checklist after touching build config: -- `cd DevControlBot && nimble runBot` compiles (fails only on "Cannot connect" - without a server) and leaves the garage tree unchanged (`ls DevControlBot_garage` - still shows only `AGENTS.md`, `README.md`, `DevControlBot/`). ## Layout of this folder @@ -255,14 +87,77 @@ Checklist after touching build config: DevControlBot_garage/ ├── AGENTS.md # this file ├── README.md # human-facing description -└── 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 +├── 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) @@ -341,4 +236,4 @@ Source: https://robocode.dev/articles/physics.html (Tank Royale docs) - 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. +- 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. \ No newline at end of file