Files
SirRoboGarage/DevControlBot_garage/AGENTS.md
T
SirStone 59fad00b5c Add DevControlBot garage: skeleton bot with release/debug build script
A minimal Tank Royale bot that boots and stands still. Body, gun and radar
are set to white once at init (team convention: white = not programmed yet);
the tick loop only calls go().

- DevControlBot/ holds the single bot module, its metadata JSON, the
  BotLauncher entry script and the nimble file
- DevControlBot.sh is a thin wrapper around the runBot nimble task:
  release build by default, --debug for the classic diagnostic build
- dependencies resolve at run time from the global nimble store via
  `nimble path`; optional .env (gitignored) supplies env vars such as
  SERVER_URL, with existing environment variables taking precedence
- build happens in a throwaway mktemp dir removed on exit; no artifacts
2026-10-03 16:32:27 +02:00

11 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.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/, tests/, or out/
  • 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.

Start here

This file is the working rules for the bot. The human-facing description lives in README.md — read it first.

Garage-specific rules for DevControlBot_garage/. 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

  • 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.
  • Body, gun and radar are WHITE (team convention: that part is not programmed yet). Colors are set once at initialization, never in the tick loop. Keep it that way: the tick loop must do nothing but go() until real logic is written.

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 <pkg>, 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:
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, cds to the .nimble dir, forwards args and signals to nimble runBot, and propagates the exit code.

./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 on ws://localhost:7654 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 on 7654 (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 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<concern>.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:

      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

# 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

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