diff --git a/SAC_LSTM_Bot/make_twin.sh b/SAC_LSTM_Bot/make_twin.sh new file mode 100755 index 0000000..2858cee --- /dev/null +++ b/SAC_LSTM_Bot/make_twin.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# make_twin.sh — #54 mirror-twin sparring partner generator. +# +# Builds a self-contained `SacTwin` bot dir inside the sample-bots archive so +# RunTraining.java resolves it like any sample bot ($SAMPLE_BOTS_DIR/). +# The twin is the SAME binary as SAC_LSTM_Bot but with: +# - distinct identity (SacTwin.json; SACLSTM_BOT_JSON overrides the baked-in +# src json — loadBotInfo gives json total precedence over env, #49 lesson) +# - its OWN weights dir, seeded from a FROZEN copy of weights/sac_best.zip +# (no checkpoint write races with the main bot) +# - its OWN round_counter.txt (main liveness guard untouched) +# +# Re-running resets the twin to the frozen baseline (reproducible opponent). +# +# Usage: ./make_twin.sh [target-archive-dir] +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +TARGET="${1:-${SAMPLE_BOTS_DIR:-/home/davide/Projects/tank-royale/sample-bots/java/build/archive}}/SacTwin" +BIN="$SCRIPT_DIR/SAC_LSTM_Bot" # nimble build -d:release output +SEED="$SCRIPT_DIR/weights/sac_best.zip" # frozen baseline + +[ -x "$BIN" ] || { echo ">>> $BIN missing — run 'nimble build -d:release' first"; exit 1; } +[ -f "$SEED" ] || { echo ">>> $SEED missing — need at least one eval'd checkpoint"; exit 1; } + +mkdir -p "$TARGET/weights" +cp "$BIN" "$TARGET/SAC_LSTM_Bot" + +cat > "$TARGET/SacTwin.json" < "$TARGET/SacTwin.sh" <<'EOF' +#!/bin/sh +# Twin launcher (#54): identity + weights fully decoupled from the main bot. +DIR="$(cd "$(dirname "$0")" && pwd)" +export SACLSTM_BOT_JSON="$DIR/SacTwin.json" +export SACLSTM_WEIGHTS_PATH="$DIR/weights/sac_latest.zip" +exec "$DIR/SAC_LSTM_Bot" +EOF +chmod +x "$TARGET/SacTwin.sh" "$TARGET/SAC_LSTM_Bot" + +cp "$SEED" "$TARGET/weights/sac_latest.zip" +cp "$SEED" "$TARGET/weights/sac_best.zip" +echo 0 > "$TARGET/weights/round_counter.txt" +echo ">>> twin ready: $TARGET (seeded from $(stat -c %y "$SEED" | cut -d. -f1) snapshot of sac_best.zip)" diff --git a/SAC_LSTM_Bot/sac_train.sh b/SAC_LSTM_Bot/sac_train.sh index 4bcd2e1..cfd52f5 100755 --- a/SAC_LSTM_Bot/sac_train.sh +++ b/SAC_LSTM_Bot/sac_train.sh @@ -101,7 +101,11 @@ eval_checkpoint() { NUM_CHUNKS=$(( (TOTAL_ROUNDS + CHUNK_SIZE - 1) / CHUNK_SIZE )) fails=0 -for chunk in $(seq 1 "$NUM_CHUNKS"); do +chunk=1 +# while, not `for chunk in $(seq ...)`: a crash on the FINAL chunk must rerun +# it (#54 — seq list is exhausted by then, so ((chunk--));continue fell through +# and the harness exited 0 with the budget incomplete). +while (( chunk <= NUM_CHUNKS )); do ROUNDS=$CHUNK_SIZE (( TOTAL_ROUNDS - (chunk - 1) * CHUNK_SIZE < CHUNK_SIZE )) && \ ROUNDS=$(( TOTAL_ROUNDS - (chunk - 1) * CHUNK_SIZE )) @@ -120,6 +124,7 @@ for chunk in $(seq 1 "$NUM_CHUNKS"); do fi fails=0 (( chunk % EVAL_INTERVAL == 0 )) && eval_checkpoint + ((chunk += 1)) done echo ">>> training complete: $NUM_CHUNKS chunks. Logs:" diff --git a/SAC_LSTM_Bot/src/SAC_LSTM_Bot.nim b/SAC_LSTM_Bot/src/SAC_LSTM_Bot.nim index ac5fe22..33d0988 100644 --- a/SAC_LSTM_Bot/src/SAC_LSTM_Bot.nim +++ b/SAC_LSTM_Bot/src/SAC_LSTM_Bot.nim @@ -17,7 +17,11 @@ import SAC_LSTM_Bot/actions import SAC_LSTM_Bot/rewards import SAC_LSTM_Bot/integration -const botJsonPath = currentSourcePath().parentDir / "SAC_LSTM_Bot.json" +# Identity json: baked-in src json by default; SACLSTM_BOT_JSON lets a mirror +# twin (#54) boot the same binary under its own name (loadBotInfo gives the +# json total precedence over env, so the twin must point at its own file). +let botJsonPath = getEnv("SACLSTM_BOT_JSON", + currentSourcePath().parentDir / "SAC_LSTM_Bot.json") # ── Colors (Recurrent Royalty palette) ───────────────────────────────────────