From 002a568c7ee35cce43aafb755b92f32b50502090 Mon Sep 17 00:00:00 2001 From: Davide Cappellini Date: Sun, 23 Aug 2026 10:06:05 +0200 Subject: [PATCH] fix(SAC_LSTM_Bot): atomic+validated SVG output for progress graphs --- SAC_LSTM_Bot/docs/graph_eval_winrates.svg | 488 +++++++++++----------- SAC_LSTM_Bot/docs/graph_losses.svg | 40 +- SAC_LSTM_Bot/tools/plot_progress.py | 37 +- 3 files changed, 302 insertions(+), 263 deletions(-) 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' @@ -238,8 +256,8 @@ def graph_eval(series_v2, series_v1, out): items_b.append((COLORS["Corners"], f"Run 3 trend (rolling-{TREND_WINDOW})")) s += legend(items_b, x0 + 12, y1b + 14) - out.write_text(s) - return 1 + s += "\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}")