diff --git a/common_libs/test_framework/adversaries/ConstantSpinner/ConstantSpinner.json b/common_libs/test_framework/adversaries/ConstantSpinner/ConstantSpinner.json new file mode 100644 index 0000000..ac0ba1a --- /dev/null +++ b/common_libs/test_framework/adversaries/ConstantSpinner/ConstantSpinner.json @@ -0,0 +1,11 @@ +{ + "name": "ConstantSpinner", + "version": "0.1.0", + "authors": ["Davide Cappellini"], + "description": "True constant-turn spinner adversary — constant speed + constant body turn rate, head-on firing", + "homepage": "", + "countryCodes": ["IT"], + "gameTypes": ["classic", "1v1"], + "platform": "Nim", + "programmingLang": "Nim" +} diff --git a/common_libs/test_framework/adversaries/ConstantSpinner/ConstantSpinner.nimble b/common_libs/test_framework/adversaries/ConstantSpinner/ConstantSpinner.nimble new file mode 100644 index 0000000..53db8ab --- /dev/null +++ b/common_libs/test_framework/adversaries/ConstantSpinner/ConstantSpinner.nimble @@ -0,0 +1,11 @@ +# Package +version = "0.1.0" +author = "Davide Cappellini" +description = "True constant-turn spinner adversary — constant speed + constant body turn rate" +license = "MIT" +srcDir = "src" +bin = @["ConstantSpinner"] + +# Dependencies +requires "nim >= 2.0.0" +requires "robocode_tankroyale_botapi >= 1.0.7" diff --git a/common_libs/test_framework/adversaries/ConstantSpinner/ConstantSpinner.sh b/common_libs/test_framework/adversaries/ConstantSpinner/ConstantSpinner.sh new file mode 100755 index 0000000..b5a3c68 --- /dev/null +++ b/common_libs/test_framework/adversaries/ConstantSpinner/ConstantSpinner.sh @@ -0,0 +1,3 @@ +#!/bin/sh +cd "$(dirname "$0")" +exec ./out/ConstantSpinner 2>> /tmp/constantspinner_stderr.log diff --git a/common_libs/test_framework/adversaries/ConstantSpinner/config.nims b/common_libs/test_framework/adversaries/ConstantSpinner/config.nims new file mode 100644 index 0000000..594b66e --- /dev/null +++ b/common_libs/test_framework/adversaries/ConstantSpinner/config.nims @@ -0,0 +1,2 @@ +--path:"../../.." +switch("outdir", "out") diff --git a/common_libs/test_framework/adversaries/ConstantSpinner/make_variant.sh b/common_libs/test_framework/adversaries/ConstantSpinner/make_variant.sh new file mode 100755 index 0000000..8fad565 --- /dev/null +++ b/common_libs/test_framework/adversaries/ConstantSpinner/make_variant.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# make_variant.sh — generate a spin-rate preset of the ConstantSpinner adversary. +# +# make_variant.sh [OUTDIR] +# +# e.g. +# make_variant.sh ConstantSpinner_s3 3 5 /tmp/tr_spinners +# make_variant.sh ConstantSpinner_s6 6 5 /tmp/tr_spinners +# +# The variant dir is a normal Tank Royale bot directory named : the runner +# matches the connecting bot's identity against //.json, so +# the launcher exports BOT_JSON to make the binary handshake under . +# +# The single committed ConstantSpinner binary is reused (no per-variant build); +# only SPIN_TURN_RATE / SPIN_SPEED differ. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +BIN="$HERE/out/ConstantSpinner" + +NAME="${1:?usage: make_variant.sh [OUTDIR]}" +TURN="${2:?usage: make_variant.sh [OUTDIR]}" +SPEED="${3:?usage: make_variant.sh [OUTDIR]}" +OUTDIR="${4:-/tmp/tr_spinners}" + +[[ -x "$BIN" ]] || { echo "ERROR: build first: nim c -d:release src/ConstantSpinner.nim" >&2; exit 1; } + +DIR="$OUTDIR/$NAME" +mkdir -p "$DIR" + +cat > "$DIR/$NAME.json" < "$DIR/$NAME.sh" <> /tmp/constantspinner_stderr.log +SH +chmod +x "$DIR/$NAME.sh" + +echo "generated $DIR (turn=$TURN deg/tick, speed=$SPEED)" diff --git a/common_libs/test_framework/adversaries/ConstantSpinner/out/ConstantSpinner b/common_libs/test_framework/adversaries/ConstantSpinner/out/ConstantSpinner new file mode 100755 index 0000000..e8a6da1 Binary files /dev/null and b/common_libs/test_framework/adversaries/ConstantSpinner/out/ConstantSpinner differ diff --git a/common_libs/test_framework/adversaries/ConstantSpinner/src/ConstantSpinner.json b/common_libs/test_framework/adversaries/ConstantSpinner/src/ConstantSpinner.json new file mode 100644 index 0000000..ac0ba1a --- /dev/null +++ b/common_libs/test_framework/adversaries/ConstantSpinner/src/ConstantSpinner.json @@ -0,0 +1,11 @@ +{ + "name": "ConstantSpinner", + "version": "0.1.0", + "authors": ["Davide Cappellini"], + "description": "True constant-turn spinner adversary — constant speed + constant body turn rate, head-on firing", + "homepage": "", + "countryCodes": ["IT"], + "gameTypes": ["classic", "1v1"], + "platform": "Nim", + "programmingLang": "Nim" +} diff --git a/common_libs/test_framework/adversaries/ConstantSpinner/src/ConstantSpinner.nim b/common_libs/test_framework/adversaries/ConstantSpinner/src/ConstantSpinner.nim new file mode 100644 index 0000000..09b2fe2 --- /dev/null +++ b/common_libs/test_framework/adversaries/ConstantSpinner/src/ConstantSpinner.nim @@ -0,0 +1,85 @@ +# ConstantSpinner — a TRUE constant-turn spinner. +# +# Purpose: the legacy roster has no constant-turn spinner. `SpinBot` (the sample +# bot) and the in-repo movers are periodic/oscillating, but this fixture is the +# degenerate case the owner's gun claim is really about: a body that moves at a +# CONSTANT forward speed while turning at a CONSTANT rate, forever. Its path is a +# perfect circle of radius v / omega, i.e. a single-frequency, low-entropy, +# perfectly periodic movement — the easiest possible target for a fast-adapting +# or pattern-matching gun. +# +# It is a MOVEMENT FIXTURE, not a fighter: the gun is plain head-on, fired at low +# power when the turret happens to be on target. No adaptation, no randomness, no +# wall logic (the server's wall collision is the only non-periodic perturbation). +# +# Env (read at round start, so one binary serves every spin rate): +# SPIN_TURN_RATE body turn rate, deg/tick (default 6.0) +# SPIN_SPEED forward speed, units/tick (default 5.0) +# SPIN_FIRE_POWER bullet power (default 1.0) +# +# The requested turn rate is clamped to the maximum reachable at the target +# speed (`MAX_TURN_RATE - 0.75 * speed`), so the ACTUAL turn rate stays exactly +# constant instead of being server-clamped while the bot accelerates or slides +# along a wall. + +import std/[math, os, strutils] +import robocode_tankroyale_botapi + +# The identity JSON. A generated spin-rate variant directory (see +# `make_variant.sh`) sets BOT_JSON so the bot handshakes under the variant's own +# name, which is what the Tank Royale runner matches against `/.json`. +const defaultBotJsonPath = currentSourcePath().parentDir / "ConstantSpinner.json" + +proc envFloat(name: string, default: float): float = + let v = getEnv(name) + if v.len == 0: return default + try: + parseFloat(v) + except ValueError: + default + +type ConstantSpinner = ref object of Bot + turnRate: float + speed: float + firePower: float + +method onRoundStarted*(bot: ConstantSpinner, e: RoundStartedEvent) = + # Gun and radar stay independent of the (continuously turning) body. + setAdjustGunForBodyTurn(true) + setAdjustRadarForBodyTurn(true) + setAdjustRadarForGunTurn(true) + + bot.speed = envFloat("SPIN_SPEED", 5.0) + bot.turnRate = envFloat("SPIN_TURN_RATE", 6.0) + bot.firePower = envFloat("SPIN_FIRE_POWER", 1.0) + + # A turn rate above the speed-dependent maximum would be clamped by the + # server, making the rate non-constant during acceleration / wall slides. + let maxAtSpeed = MAX_TURN_RATE - 0.75 * abs(bot.speed) + if bot.turnRate > maxAtSpeed: + bot.turnRate = maxAtSpeed + if bot.turnRate < 0.0: + bot.turnRate = 0.0 + +method onScannedBot*(bot: ConstantSpinner, e: ScannedBotEvent) = + # Head-on: aim at where the enemy is right now, fire low power whenever the + # gun is cool. It is a movement fixture, so the shot goes wherever the turret + # happens to point; the radar sweep keeps scans coming. + let bearing = directionTo(getX(), getY(), e.x, e.y) + let gunDelta = normalizeRelativeAngle(bearing - getGunDirection()) + setGunTurnRate(gunDelta.clamp(-MAX_GUN_TURN_RATE, MAX_GUN_TURN_RATE)) + if getGunHeat() <= 0.0: + discard setFire(bot.firePower) + +method run*(bot: ConstantSpinner) = + while isRunning(): + # The whole movement: constant turn, constant speed. Nothing else. + setRadarTurnRate(45.0) + setTurnRate(bot.turnRate) + setTargetSpeed(bot.speed) + go() + +when isMainModule: + var bot = ConstantSpinner() + let jsonPath = getEnv("BOT_JSON", defaultBotJsonPath) + start(bot, jsonPath) diff --git a/tools/ab/arms_spinner.txt b/tools/ab/arms_spinner.txt new file mode 100644 index 0000000..f231362 --- /dev/null +++ b/tools/ab/arms_spinner.txt @@ -0,0 +1,21 @@ +# ───────────────────────────────────────────────────────────────────────────── +# arms_spinner.txt — the SPINNER-CLAIM A/B (j124, owner claim 1). +# +# Claim: "I never seen a gun that learns wall movement or circular movement like +# spinning bot so fast - like a gun made on purpose for those movements." +# +# Three arms, identical frozen binary, MOVEMENT PINNED (`TR_MOVEMENT=strafe`) in +# every arm (same standard as the gun campaign). Only the gun rack differs. +# Format: name | ENV=value ENV=value | label +# ───────────────────────────────────────────────────────────────────────────── + +# 1. REFERENCE — shipped onlyPattern rack, movement pinned. +pattern | TR_MOVEMENT=strafe | shipped onlyPattern rack (REFERENCE) + +# 2. BitBrain learned-gain, decay memory (the config the owner most likely ran; +# the arm used in the 32-opponent gauntlet and both j121 batches). +bitbrain | TR_MOVEMENT=strafe TR_RACK_PATTERN=off TR_RACK_BITBRAIN=both TR_BITBRAIN_GAINS=1.0,1.25,1.5,2.0 TR_BITBRAIN_MEM=decay | BitBrain-only, learned gain, decay + +# 3. BitBrain learned-gain, RETAINED memory (the "adapt across the whole battle" +# mode the owner described; not previously panel-tested). +bitbrain_ret | TR_MOVEMENT=strafe TR_RACK_PATTERN=off TR_RACK_BITBRAIN=both TR_BITBRAIN_GAINS=1.0,1.25,1.5,2.0 TR_BITBRAIN_MEM=retained | BitBrain-only, learned gain, retained diff --git a/tools/ab/panel_spinner.txt b/tools/ab/panel_spinner.txt new file mode 100644 index 0000000..a47af06 --- /dev/null +++ b/tools/ab/panel_spinner.txt @@ -0,0 +1,21 @@ +# ───────────────────────────────────────────────────────────────────────────── +# panel_spinner.txt — the SPINNER-CLAIM panel (j124). +# +# The legacy roster has no constant-turn spinner (docs/gauntlet_bitbrain_vs_pattern.md, +# docs/gun_campaign.md). This panel adds two: the new ConstantSpinner adversary at +# two spin rates, plus the sample SpinBot as the j121 "spinner" control, plus the +# same kind of regular/dodger controls the j117 gauntlet used, so the +# regular-vs-dodger comparison is directly readable. +# +# Format: |