diff --git a/SAC_LSTM_Bot/docs/graph_eval_winrates.svg b/SAC_LSTM_Bot/docs/graph_eval_winrates.svg
index e2ae9f2..e5f2fad 100644
--- a/SAC_LSTM_Bot/docs/graph_eval_winrates.svg
+++ b/SAC_LSTM_Bot/docs/graph_eval_winrates.svg
@@ -11,20 +11,20 @@
win % per test match — thick line = trend
-
-10
-
-20
-
-30
-
-40
-
-50
-
-60
-
-70
+
+10
+
+20
+
+30
+
+40
+
+50
+
+60
+
+70
0
@@ -40,237 +40,250 @@
eval number
win rate (%)
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-Corners — 75 evals
+Corners — 79 evals
-Crazy — 74 evals
+Crazy — 79 evals
-Target — 74 evals
+Target — 78 evals
@@ -307,8 +320,9 @@
fraction of run (start → end)
win rate (%)
-
+
Run 1 (old) — 1287 evals in 40 buckets
Run 3 trend (rolling-10)
+
diff --git a/SAC_LSTM_Bot/docs/graph_losses.svg b/SAC_LSTM_Bot/docs/graph_losses.svg
index 3fd04f0..2297aa5 100644
--- a/SAC_LSTM_Bot/docs/graph_losses.svg
+++ b/SAC_LSTM_Bot/docs/graph_losses.svg
@@ -7,13 +7,13 @@
1
-25
+41
-48
+81
-72
+121
-96
+161
0.1
@@ -26,46 +26,46 @@
1e+17
critic_loss
-
+
actor_loss |abs| (log scale)
1
-25
+41
-48
+81
-72
+121
-96
+161
1
-56.2
+100
-3.16e+03
+1e+04
-1.78e+05
+1e+06
-1e+07
+1e+08
|actor_loss|
-
+
alpha (temperature)
1
-25
+41
-48
+81
-72
+121
-96
+161
0
@@ -78,6 +78,6 @@
1.01
alpha
-
-metric line number (96 rows)
+
+metric line number (161 rows)
diff --git a/SAC_LSTM_Bot/tools/plot_progress.py b/SAC_LSTM_Bot/tools/plot_progress.py
index 0448515..2eae4dc 100644
--- a/SAC_LSTM_Bot/tools/plot_progress.py
+++ b/SAC_LSTM_Bot/tools/plot_progress.py
@@ -15,9 +15,11 @@ All args optional; defaults relative to the SAC_LSTM_Bot/ root (parent of tools/
"""
import json
import math
+import os
import re
import sys
import tempfile
+import xml.etree.ElementTree as ET
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
@@ -80,6 +82,22 @@ def esc(s):
return str(s).replace("&", "&").replace("<", "<").replace(">", ">")
+def write_svg(path, text):
+ """Validate the finished SVG, then atomically swap it into place.
+
+ Readers never see partial output; an invalid render aborts without
+ touching the previous good file."""
+ try:
+ ET.fromstring(text)
+ except ET.ParseError as e:
+ print(f"[error] {path.name}: generated SVG invalid, keeping old file ({e})")
+ return False
+ tmp = path.with_name(path.name + ".tmp")
+ tmp.write_text(text)
+ os.replace(tmp, path)
+ return True
+
+
def svg_open(w, h, title):
return (f'\n"
+ return 1 if write_svg(out, s) else 0
# ---------- graph 2: loss curves ----------
@@ -319,8 +337,7 @@ def graph_losses(rows, out):
fixed_range=(0, max(1.0, max(alpha))))
s += (f'metric line number ({n} rows)\n\n')
- out.write_text(s)
- return 1
+ return 1 if write_svg(out, s) else 0
# ---------- selftest ----------
@@ -351,6 +368,8 @@ def selftest():
ok = graph_losses(rows, td / "g2.svg") and graph_eval(ser, {"Corners": [0, 10]}, td / "g1.svg")
assert ok and (td / "g1.svg").stat().st_size > 500
g1 = (td / "g1.svg").read_text()
+ ET.fromstring(g1) # whole doc must parse -> closing tag present
+ ET.fromstring((td / "g2.svg").read_text())
assert 'width="1200"' in g1, "canvas must be >=1200 wide"
assert g1.count("= 2) + 2 # trends + v1 buckets + v3 trend
@@ -377,11 +396,17 @@ def main():
series_v1 = parse_eval_series(v1log)
print(f"[info] v1 evals parsed: " +
", ".join(f"{k}={len(v)}" for k, v in sorted(series_v1.items())) or "(none)")
- made += graph_eval(series_v2, series_v1, outdir / "graph_eval_winrates.svg")
+ try:
+ made += graph_eval(series_v2, series_v1, outdir / "graph_eval_winrates.svg")
+ except Exception as e:
+ print(f"[error] win-rate graph failed (loss graph still attempted): {e}")
rows = parse_metrics(metrics)
print(f"[info] metric rows parsed: {len(rows)}")
- made += graph_losses(rows, outdir / "graph_losses.svg")
+ try:
+ made += graph_losses(rows, outdir / "graph_losses.svg")
+ except Exception as e:
+ print(f"[error] loss graph failed: {e}")
if made:
print(f"[done] {made} graph(s) written to {outdir}")