diff --git a/SAC_LSTM_Bot/docs/campaign_notebook.md b/SAC_LSTM_Bot/docs/campaign_notebook.md index 8067b73..81f7a82 100644 --- a/SAC_LSTM_Bot/docs/campaign_notebook.md +++ b/SAC_LSTM_Bot/docs/campaign_notebook.md @@ -210,3 +210,66 @@ Slowdown attribution complete: **89% of wall clock = eval rotation by design** ( Ceiling net re-armed to match the extended budget: fires **Thu 2026-08-27 07:59:40 CEST (epoch 1787810380)** — supersedes the 2026-08-23 15:59:40 fire noted under Safety net. Attempt-1 artifacts preserved in `/tmp/v2_attempt1_backup/`. +# Campaign v2 — attempt-3 (2026-08-23) + +## ~06:45 — LEVER-5 TRIGGERED (watchman shift 2, 02:11–06:22 CEST) + +Evidence-gated activation of the lever-5 conditional posture (`SACLSTM_LR_CRITIC`), human signed off. Trigger evidence: + +| Signal | Observation | +|--------|-------------| +| actor_loss | \|34M\| → \|106M\| monotone (+15M/h), **no plateau** | +| critic_loss | spikes >1e12 in **100%** of NEW metric records; campaign share 80.2% | +| Signature | exact `1.5625e16` recurring (= float32 saturation neighborhood) | +| alpha | decayed 0.951 → 0.711 (entropy collapse under runaway Q scale) | +| best_score.txt | frozen 00:32 (=83.3333) through 5h50m of composite oscillation 3.3–43.3 | +| Per-opponent MAs | whipsawing (Crazy 30→100→90→0→60→90→10→0) | + +## DECISION — LR_CRITIC=1e-4, single knob, fresh start + +- **Adjudication**: primary suspect is critic/Q value-scale growth. Actor loss inherits Q magnitude through the policy gradient, so the actor explosion is downstream; alpha decay is a *symptom* (entropy temperature chasing a blown value scale), not a cause. Therefore ONE knob moves: `SACLSTM_LR_CRITIC=1e-4` (critic learns slower → Q estimates stay estimable); LR_ACTOR/LR_ALPHA stay at default 3e-4, TARGET_ENTROPY −4. Multi-knob changes would confound attempt-3's attribution. +- **Fresh start over warm start**: attempt-2's weights carry saturated Q representations; warm-starting them under a new LR would inherit the pathology we're trying to escape (contamination risk). Attempt-2 archived intact, nothing discarded. + +## MA-wipe root cause FOUND & FIXED (`167bcc4`) + +Watchman anomaly explained: `ma_history_*.txt` held exactly one leading-space value per file (e.g. `[ 10 ]`, `[ 20 ]`, `[ 0 ]` in the attempt-2 backup) instead of the designed 5-value history, degrading the composite best-gate to last-cycle mean — which fully accounts for the composite whipsaw 3.3–43.3. + +Root cause: in `eval_checkpoint`, the append line was + +```bash +printf '%s\n' "$(cat "$(ma_hist_file "$opp")")" "$wr" | tail -n 5 | tr '\n' ' ' > "$(ma_hist_file "$opp")" +``` + +Bash sets up the `>` redirect **before** running the command substitution, so `$(cat f)` always read the freshly truncated file ⇒ every cycle wiped history to `" $wr "`. Reproduced standalone on bash 5.3 before touching the script; no other writer exists (grep). Fix hoists the read into its own statement and word-splits it so `tail -n 5` keeps exactly the last MA_WINDOW values. Verified live in attempt-3: two eval cycles produced `[0 0 ]` per opponent (previously impossible). + +## Attempt-3 launch (2026-08-23 07:01:28 CEST, epoch 1787461288) + +- Attempt-2 stopped cleanly (tmux kill; no stray procs); artifacts (76 items, 336 MB incl. `.part` corpses + MA/best/latest/counter) → `/tmp/v2_attempt2_backup/`; final round_counter **8452**; `weights/` verified empty. +- Twin regenerated via documented transient-seed method: throwaway Nim script (`initSACTrainer(35,4)` + `saveWeights`, hidden=256 env, alpha=1.0) seeded as transient `weights/sac_best.zip` → `./make_twin.sh` → twin zips byte-identical to seed (`cmp` OK), twin counter=0 → transient removed. +- Launch line = locked config verbatim plus the two live deltas: + +```bash +SAC_OPPONENTS='Corners:3,Crazy:2,RamFire:2,Target:1,SacTwin:1' \ +SAC_EVAL_OPPONENTS='Corners,Crazy,Target' \ +SAC_EVAL_INTERVAL=2 SAC_EVAL_ROUNDS=10 \ +SAC_TOTAL_ROUNDS=25000 SAC_CHUNK_SIZE=10 SAC_MAX_CRASHES=5 \ +SACLSTM_HIDDEN_SIZE=256 SACLSTM_BATCH_SIZE=16 SACLSTM_SAVE_INTERVAL=1 \ +SACLSTM_LR_CRITIC=1e-4 \ +./sac_train.sh 2>&1 | tee -a campaign_v2_stdout.log +``` + +- Safety net: existing transient unit `sac-ceiling-net-v2.service` re-verified active; fire epoch start+384587 s = **1787810380 = Thu 2026-08-27 07:59:40 CEST exactly** (delta 0 vs mandate) — kept, not duplicated. + +### Health baseline (t+15 m) + +| Check | Evidence | +|-------|----------| +| Counter | 33 (t+8m) → **125** (t+15m) | +| Metrics JSONL | flowing (6 lines); baseline line: `critic_loss=23.09, actor_loss=-3.228, alpha_loss=0.0, alpha=0.99970` (random-init magnitudes — attempt-3's divergence curve starts here); t+15m line: critic 84.5, actor −9.99, alpha 0.9937 | +| Saves | `SAVE_INTERVAL=1`: `sac_latest.zip` written from first grad-step onward | +| Eval rotation | full cycle landed: Corners/Crazy/Target ×10 games each in `eval_log.jsonl` | +| MA files | `[0 0 ]` per opponent after two cycles — fix confirmed in vivo | +| Best gate | `best_score.txt=0.0000` written on first composite (0 > −1 default) | +| Crash banners | 0 | + +