fix(SAC_LSTM_Bot): un-invert dashboard axes — orientation selftest added

This commit is contained in:
2026-08-23 10:47:48 +02:00
parent 04c149ea28
commit 81718e3a4c
3 changed files with 640 additions and 573 deletions
File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 63 KiB

+8
View File
@@ -322,3 +322,11 @@ SACLSTM_LR_CRITIC=1e-4 \
### Progress graphs
One live dashboard: `docs/campaign_dashboard.svg` (current run only — test wins, real-fight wins, losses, alpha, throughput; auto-reloads every 60 s when open in Chrome). Keep it fresh with `tools/watch_dashboard.sh` (regenerates every 60 s), or one-shot `python3 tools/plot_progress.py` (pure stdlib; paths overridable via argv, `--selftest` for sanity check).
## ~10:47 — the dashboard's axes were upside-down since creation
The progress graphs have been lying since they were made: 0% was drawn at the TOP of every panel and the newest games appeared on the LEFT. The cause is a one-line formula bug in `tools/plot_progress.py`: `map_fn` interpolated as `p1 - t*(p1-p0)` instead of `p0 + t*(p1-p0)`, so all five panels plotted `100 - value` on y and reversed time on x. Tick labels were computed by separate (correct) code, which is why the numbers on the axes never matched the ink.
Fix + guard: formula corrected; every call site audited (panels 1/2/5 use `map_fn` for both axes and are fixed by the same line; panels 3/4 already used a correct local x-lambda; no other consumer of `map_fn` exists in the repo). `--selftest` now renders a known rising series through the full build path and fails loudly unless higher value = smaller SVG y and newer data = further right — proven to catch this exact bug when the old formula is re-injected. Dashboard regenerated from live logs.
Honest status while reading the now-correct charts: run 3 is only hours old and winning ~0% — recent evals are 0/10 vs Corners, Crazy and Target alike, and real-fight buckets sit at 0–1 wins per 100 games. Expected for a fresh brain. Night-1's gains were real but were intentionally reset by the stability restart that began attempt-3; the curve starts from zero again here.
+17 -1
View File
@@ -250,7 +250,7 @@ def legend(items, x, y):
def map_fn(p0, p1, vmin, vmax, log=False):
def f(v):
t = (math.log10(v) - vmin) / (vmax - vmin) if log else (v - vmin) / (vmax - vmin)
return p1 - max(0.0, min(1.0, t)) * (p1 - p0)
return p0 + max(0.0, min(1.0, t)) * (p1 - p0)
return f
@@ -532,6 +532,22 @@ def selftest():
assert text.count("<circle") == 6, text.count("<circle")
# 2 trends + 2 real-fight opp lines + 2 losses + 1 alpha + 1 throughput
assert text.count("<polyline") == 8, text.count("<polyline")
# orientation guard: a known rising series (10% -> 90%) rendered through
# the FULL build path must plot upward (smaller SVG y) and forward in
# time (larger x). Fails loudly if axis mapping is ever inverted again.
ori_log = td / "ori.log"
ori_log.write_text(
">>> [eval] win rate: 1/10 (10%) vs Corners\n"
">>> [eval] win rate: 9/10 (90%) vs Corners\n")
ori_dash = td / "ori.svg"
assert build_dashboard(ori_log, td / "m.jsonl", td / "g.jsonl", ori_dash)
m = re.search(r'<polyline points="([^"]+)"', ori_dash.read_text())
pts = [tuple(map(float, p.split(","))) for p in m.group(1).split()]
assert len(pts) == 2, pts
(xa, ya), (xb, yb) = pts
assert yb < ya, f"y-axis inverted: win % rose 10->90 but ink moved down ({ya} -> {yb})"
assert xb > xa, f"x-axis reversed: newer eval plotted left ({xa} -> {xb})"
print("selftest OK")