Files
SirRoboGarage/DevControlBot_garage/README.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

9.6 KiB

DevControlBot

Control skeleton bot: it boots, participates in the battle, and does nothing per tick except go() — no movement logic, no scanning, no firing.

Body, gun and radar are plain white, which by team convention means "this part is not programmed yet". Colors are set once at initialization (newDevControlBot), never inside the tick loop.

Layout

DevControlBot_garage/
├── AGENTS.md
├── README.md
└── DevControlBot/
    ├── DevControlBot.nim      # bot type + entry point (isMainModule)
    ├── DevControlBot.json     # bot metadata
    ├── DevControlBot.nimble   # the single `runBot` task
    ├── DevControlBot.sh       # BotLauncher entry point
    └── .env                   # OPTIONAL local config (secrets; never commit)

There is no src/, no tests/, no out/, no config.nim, no nimble.paths and no vendor directory. DevControlBot.nim is both the bot implementation and the program entry point.

Dependencies

Only two, both installed globally with Nimble and resolved from the global package store — nothing vendored, no nimble.paths, no nimble develop:

  • robocode_tankroyale_botapi >= 1.0.7 (the renamed tankroyale_botapi)
  • jsony >= 1.1.5
nimble install robocode_tankroyale_botapi

The runBot task asks Nimble where each of those packages actually lives (nimble path <pkg>) and passes explicit --path: flags to nim c. This is required because the compiler's own nimblepath="$home/.nimble/pkgs2/" only works when $HOME is set — with an empty environment (env -i, Nix build sandbox, CI) the packages are otherwise not on the search path. If a package is not installed, the build stops with run: nimble install <pkg> instead of a compiler error.

Run

./DevControlBot/DevControlBot.sh

DevControlBot.sh is the BotLauncher entry point. It is a thin wrapper: it locates itself, chdirs to the directory holding the .nimble file, forwards arguments and signals to nimble runBot, and propagates the real exit code. All build/run/cleanup logic lives in the runBot task of DevControlBot.nimble, which creates a throwaway mktemp -d build dir, compiles there, runs, and always removes the dir via an EXIT/INT/TERM trap. Nothing is left behind — this bot never creates an out/ directory.

runBot also accepts a metadata path: as the first positional argument. If it ends in .json it is used instead of the bundled DevControlBot.json; there is no --json flag (an argument that does not end in .json is forwarded to the bot as-is).

./DevControlBot/DevControlBot.sh                          # bundled metadata (default)
./DevControlBot/DevControlBot.sh /path/to/my.json          # alt metadata
./DevControlBot/DevControlBot.sh --debug /path/to/my.json  # alt metadata, debug build

With no arguments at all the wrapper does not print usage: it 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 (exit 0, nothing is built).

Configuration comes from the environment — the API's own mechanism

No configuration code exists in DevControlBot.nim, and none is needed. The bot API reads its own environment variables inside start():

variable read at default
SERVER_URL robocode_tankroyale_botapi.nim:424 (documented at :396) ws://localhost:7654
SERVER_SECRET robocode_tankroyale_botapi.nim:425 ""
BOT_NAME, BOT_VERSION, BOT_AUTHORS, … bot_info.nim:117-130, used only when no JSON is found see that file

This is the supported mechanism the official BotLauncher uses: it puts those variables in the bot's process environment before starting the binary. So the only thing the build/run wrapper does is make sure the variables are present in the bot's environment for local runs.

Optional .env (a convenience for local runs only)

DevControlBot/.env is optional. If it is absent nothing happens: no error, no warning beyond one informational line, the run behaves exactly as before and the API defaults apply. It is not required, not generated, and not committed.

When present it is loaded by the runBot task, not by DevControlBot.sh, so it applies identically to ./DevControlBot.sh and to a direct nimble runBot and the wrapper stays a thin wrapper. Properties:

  • Format: plain NAME=value and export NAME=value lines; blank lines and # comments ignored; anything that is not a simple NAME=value identifier assignment is ignored (a .env can never smuggle in a command).
  • CRLF tolerant: CR characters are stripped, so a Windows-edited file does not turn the value into bar\r.
  • Nothing is ever printed. Only the names taken from the file are reported, never a value, and set -x is not used.
  • Overridable path: $DEVCONTROLBOT_ENV_FILE points the loader at a different file (useful for testing).

Precedence: the environment always wins

A variable already present in the environment is never overridden by the .env file. This matters because the official BotLauncher's values must win: if a .env sitting in the checkout could overwrite them, this convenience would silently break a real BotLauncher run.

The .env file is therefore not simply sourced — sourcing it would do the opposite, because an assignment in a sourced file overwrites an already-exported variable of the same name. Instead the file is filtered line by line: a NAME=value line is dropped when NAME is already set in the environment (tested with [ -n "${NAME+x}" ], i.e. set but possibly empty, not merely non-empty), and only the still-unset names are then exported with set -a and sourced. The filtering is what implements the precedence.

SERVER_URL=ws://host:1234 ./DevControlBot.sh   # shell wins, .env ignored for SERVER_URL
./DevControlBot.sh                              # .env supplies SERVER_URL

.env contains secrets, so it must never be committed; see the note in AGENTS.md about the root .gitignore.

Build modes: release by default, --debug for the classic build

invocation build output
DevControlBot.sh … (default) nim c -d:release --hints:off quiet: dependency lines, one build ok (release), one running line
DevControlBot.sh --debug … plain nim c classic diagnostics: Hint: used config file …, dot-progress line, DEBUG BUILD hint

--debug is stripped by the wrapper wherever it appears in the arguments (a repeated --debug is harmless) and is handed to the runBot task via the exported env var $DEVCONTROLBOT_DEBUG (debug / release) rather than as a task argument: an env var can never be mistaken for a bot argument, cannot collide with the $DEVCONTROLBOT_STATUS_FILE exit-code mechanism, and leaves the task's argument parsing untouched. Every other argument is forwarded verbatim.

In release mode the compiler's stdout/stderr is captured to a log and replayed only if the build fails, so a failed build is always loud in both modes while a successful release build stays short (10 lines including the no-server report, vs 16 with --debug). --help / -h prints usage and exits 0 without building; no arguments does the opposite — it builds and runs.

The bot needs a running Tank Royale server on ws://localhost:7654; without one the run fails with [start] Cannot connect to ws://localhost:7654 and exits 1 (expected during local verification).

Failure reporting and exit codes

Nimble's exec raises a NimScript exception — stack trace, escaped copy of the whole script — for any non-zero exit, which made "no game server" look like a catastrophic build crash. The runBot shell therefore always exits 0 and writes the real code to the status file named by $DEVCONTROLBOT_STATUS_FILE, which DevControlBot.sh exports and then re-exits with. The real code is preserved, not masked.

exit meaning
0 clean run
1 bot could not connect to the game server on ws://localhost:7654 (expected locally)
2 a dependency is not installed — nimble install <pkg>
3 compilation failed — the compiler's own errors are printed above
other the bot's own exit code (runtime crash, bad metadata)

Each mode prints its own short line, e.g.

[start] Cannot connect to ws://localhost:7654: Connection refused
[devcontrolbot] bot exited with code 1.
[devcontrolbot] No game server on ws://localhost:7654.
[devcontrolbot] Start the Tank Royale server, or check it is listening on port 7654.

No Exception raised during nimble script execution, no stack trace, no source dump. nimble runBot run directly (no status file) keeps the old behaviour and still raises.

Interrupt handling is unchanged: SIGINT/SIGTERM are forwarded, the build dir is removed by the EXIT trap and the wrapper exits 143 (SIGTERM) / 130 (SIGINT).

Gotcha: nimble runBot must be run from DevControlBot/

The .nimble file lives in DevControlBot/, so the task is only visible from there:

cd DevControlBot && nimble runBot     # works
cd DevControlBot_garage && nimble runBot
# Error: Could not find a file with a .nimble extension inside the specified
# directory: .../DevControlBot_garage

DevControlBot.sh handles this for you by cd-ing itself. Note also that compile/build are reserved Nimble builtin names — hence the runBot name.

Test

There are no tests in this garage. The test task and the tests/ directory were removed along with the old build layout; see AGENTS.md for the convention to follow when tests are added.