diff --git a/SAC_LSTM_Bot/docs/campaign_dashboard.svg b/SAC_LSTM_Bot/docs/campaign_dashboard.svg
index fa0ab5a..cf0a607 100644
--- a/SAC_LSTM_Bot/docs/campaign_dashboard.svg
+++ b/SAC_LSTM_Bot/docs/campaign_dashboard.svg
@@ -1,363 +1,369 @@
diff --git a/SAC_LSTM_Bot/tools/plot_progress.py b/SAC_LSTM_Bot/tools/plot_progress.py
index aca71ce..6272568 100644
--- a/SAC_LSTM_Bot/tools/plot_progress.py
+++ b/SAC_LSTM_Bot/tools/plot_progress.py
@@ -46,15 +46,17 @@ ALPHA_C = "#9467bd" # alpha line on the losses panel
RELOAD_JS = ('')
W, H = 1400, 1720
-TITLE_H, GUIDE_H = 80, 180
+TITLE_H, GUIDE_H = 80, 80
# rows: (header_y, panel_top_y, panel_bottom_y, x_left, x_right)
+# panel_top sits low enough to leave room under header+sub for the
+# per-panel how-to-read guide (3 italic lines, see panel_guide)
C1_L, C1_R = 70, 697
C2_L, C2_R = 747, 1375
ROWS = {
- 1: (100, 120, 720, C1_L, C1_R),
- 2: (100, 120, 720, C2_L, C2_R),
- 3: (940, 958, 1428, C1_L, C1_R),
- 4: (940, 958, 1428, C2_L, C2_R),
+ 1: (100, 184, 784, C1_L, C1_R),
+ 2: (100, 184, 784, C2_L, C2_R),
+ 3: (940, 1024, 1494, C1_L, C1_R),
+ 4: (940, 1024, 1494, C2_L, C2_R),
}
PANEL_TITLES = [
"Test matches - win % vs opponents",
@@ -62,11 +64,22 @@ PANEL_TITLES = [
"Throughput - games per hour",
"Max score per eval cycle",
]
+# per-panel how-to-read notes: (what it shows, axes, which direction is better)
+GUIDES = {
+ 1: ("How often the bot wins against each opponent in test battles.",
+ "X = training progress (match number); Y = win rate, 0-100%.",
+ "higher is better."),
+ 2: ("How well the brain is learning: losses should fall; alpha sets explore/exploit.",
+ "X = training progress; left Y (log) = losses; right Y (linear) = alpha.",
+ "lower losses = better; alpha falls over time as the bot gets confident."),
+ 3: ("How many games per hour the bot trains (its learning speed).",
+ "X = training progress (10-game chunks); Y = games per hour.",
+ "higher = faster learning; a steady line beats a spiky one."),
+ 4: ("Best single-round score the bot managed in each test cycle.",
+ "X = eval cycle number; Y = best score achieved.",
+ "higher is better; a rising trend means the bot is improving."),
+}
GUIDE_LINES = [
- "Test matches: dots are single fights, thick line shows trend.",
- "Loss spikes are normal early; endless growth is bad.",
- "Throughput flat is healthy; dips mean something slowed.",
- "Max score: best single-round score the bot managed in that eval cycle.",
"This file reloads itself in Chrome every sixty seconds.",
"Regenerate anytime with tools/watch_dashboard.sh or the python command.",
]
@@ -289,6 +302,16 @@ def header(x, y, title, sub=None):
return s
+def panel_guide(x0, y, guide):
+ """Italic how-to-read note between a panel's header and its plot area."""
+ what, axes, better = guide
+ s = ""
+ for i, txt in enumerate((f"What: {what}", f"Axes: {axes}", f"Better: {better}")):
+ s += (f'{esc(txt)}\n')
+ return s
+
+
# ---------- panels ----------
def panel_test_matches(series, geo):
@@ -455,21 +478,22 @@ def build_dashboard(campaign, metrics_f, out):
f'(open this file in Chrome)\n')
drawers = [
- (ROWS[1], PANEL_TITLES[0],
+ (ROWS[1], PANEL_TITLES[0], GUIDES[1],
"raw dots = single test matches, thick = rolling-mean-%d" % TREND_WINDOW,
lambda: panel_test_matches(series, ROWS[1])),
- (ROWS[2], PANEL_TITLES[1],
+ (ROWS[2], PANEL_TITLES[1], GUIDES[2],
"training_metrics.jsonl - big early spikes are normal",
lambda: panel_losses(rows, ROWS[2])),
- (ROWS[3], PANEL_TITLES[2],
+ (ROWS[3], PANEL_TITLES[2], GUIDES[3],
"method: training_metrics.jsonl 'epoch' deltas; 1 row = one 10-game chunk",
lambda: panel_throughput(rows, ROWS[3])),
- (ROWS[4], PANEL_TITLES[3],
+ (ROWS[4], PANEL_TITLES[3], GUIDES[4],
"campaign_v4_stdout.log - max of the 10 deterministic round scores per eval",
lambda: panel_max_score(maxes, ROWS[4])),
]
- for geo, title, sub, drawer in drawers:
+ for geo, title, guide, sub, drawer in drawers:
s += header(geo[3], geo[0], title, sub)
+ s += panel_guide(geo[3], geo[0] + 33, guide)
s += drawer() or "" # panels bare-return None on their no-data path
s += guide_block(W, H, GUIDE_LINES)
@@ -530,6 +554,10 @@ def selftest():
assert text.count(PANEL_TITLES[0]) == 1
assert "How to read" in text, "reading guide missing"
assert 'width="1400"' in text and 'height="1720"' in text
+ # every panel carries its own What/Axes/Better how-to-read note
+ assert text.count("What:") == len(PANEL_TITLES), text.count("What:")
+ for g in GUIDES.values():
+ assert esc(g[0]) in text, f"panel guide missing: {g[0]}"
# circles: 4 eval dots (panel 1) + 2 throughput rate dots (panel 3)
# + 2 cycles x 2 opponents max-score dots (panel 4)
assert text.count("