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
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
# 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`
|
||||
|
||||
```sh
|
||||
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
|
||||
|
||||
```sh
|
||||
./DevControlBot/DevControlBot.sh
|
||||
```
|
||||
|
||||
`DevControlBot.sh` is the **BotLauncher entry point**. It is a thin wrapper: it
|
||||
locates itself, `chdir`s 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).
|
||||
|
||||
```sh
|
||||
./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.
|
||||
|
||||
```sh
|
||||
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](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:
|
||||
|
||||
```sh
|
||||
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](AGENTS.md) for the convention to follow when tests are added.
|
||||
Reference in New Issue
Block a user