Add radar lock and melee sweep to DevControlBot
The bot now drives its radar every tick. It still never moves and never
fires: body, turret and gun stay white ("not programmed"), while the
radar paints itself black at init to mark itself as working.
- modules/radar.nim replaces the earlier multi-module radar design with a
single module holding the whole of it: target memory, mode selection
and both commands
- two commands, picked from the only fact the bot is sure of (the API's
getEnemyCount()): one enemy plus a target in hand -> LOCK, servo onto
its bearing; anything else -> SWEEP at 45 deg/tick, the radar's physics
cap, a full revolution every 8 ticks
- the lock turns by the delta to the target's bearing plus 5 deg PAST it,
so the radar CROSSES the bearing every tick instead of settling on it.
That crossing is what produces a fresh ScannedBotEvent every tick: a
scan per tick, with no rescan to wait for. Turning by the bare error
parks the radar on the bearing and the scans stop
- commandRadar() is called before go() on every path, so there is no idle
branch to fall into and switching modes costs zero turns
- idea derived from ModularBot's radar (Apache-2.0) via common_libs/;
common_libs/ is not edited in place
- AGENTS.md gains mermaid behaviour diagrams plus pointers to the physics
and coordinate references
- version bumped to 1.1.0 in both DevControlBot.nimble and
DevControlBot.json so the two agree
Compiled against robocode_tankroyale_botapi 1.0.7.
This commit is contained in:
@@ -47,9 +47,33 @@ template does not govern this garage.**
|
||||
- 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.
|
||||
- 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
|
||||
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
|
||||
|
||||
@@ -237,4 +261,84 @@ DevControlBot_garage/
|
||||
├── DevControlBot.nimble # single task: runBot
|
||||
├── DevControlBot.sh # BotLauncher entry point (thin wrapper)
|
||||
└── .env # OPTIONAL local config; secrets, never commit
|
||||
```
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
Reference in New Issue
Block a user