feat(dashboard): max-score-per-eval-cycle panel

This commit is contained in:
2026-08-24 09:15:24 +02:00
parent 8872be3ff6
commit 4a6e3347c6
2 changed files with 333 additions and 130 deletions
+224 -121
View File
@@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="1720" viewBox="0 0 1400 1720" font-family="sans-serif"> <svg xmlns="http://www.w3.org/2000/svg" width="1400" height="2520" viewBox="0 0 1400 2520" font-family="sans-serif">
<rect width="1400" height="1720" fill="white"/> <rect width="1400" height="2520" fill="white"/>
<text x="700" y="32" text-anchor="middle" font-size="21" font-weight="bold">SAC-LSTM campaign dashboard - live run (current only)</text> <text x="700" y="32" text-anchor="middle" font-size="21" font-weight="bold">SAC-LSTM campaign dashboard - live run (current only)</text>
<text x="700" y="56" text-anchor="middle" font-size="12" fill="#555">generated 2026-08-24 09:10:32 - auto-reloads every 60 s (open this file in Chrome)</text> <text x="700" y="56" text-anchor="middle" font-size="12" fill="#555">generated 2026-08-24 09:14:55 - auto-reloads every 60 s (open this file in Chrome)</text>
<text x="70" y="100" font-size="15" font-weight="bold">Test matches - win % vs opponents</text> <text x="70" y="100" font-size="15" font-weight="bold">Test matches - win % vs opponents</text>
<text x="70" y="115" font-size="11" fill="#555">raw dots = single test matches, thick = rolling-mean-10</text> <text x="70" y="115" font-size="11" fill="#555">raw dots = single test matches, thick = rolling-mean-10</text>
<line x1="70" y1="720.0" x2="697" y2="720.0" stroke="#dddddd"/> <line x1="70" y1="720.0" x2="697" y2="720.0" stroke="#dddddd"/>
@@ -14,26 +14,28 @@
<line x1="70" y1="720" x2="70" y2="120" stroke="black"/> <line x1="70" y1="720" x2="70" y2="120" stroke="black"/>
<line x1="70.0" y1="720" x2="70.0" y2="724" stroke="black"/> <line x1="70.0" y1="720" x2="70.0" y2="724" stroke="black"/>
<text x="70.0" y="737" text-anchor="middle" font-size="11">1</text> <text x="70.0" y="737" text-anchor="middle" font-size="11">1</text>
<line x1="132.7" y1="720" x2="132.7" y2="724" stroke="black"/> <line x1="127.0" y1="720" x2="127.0" y2="724" stroke="black"/>
<text x="132.7" y="737" text-anchor="middle" font-size="11">2</text> <text x="127.0" y="737" text-anchor="middle" font-size="11">2</text>
<line x1="195.4" y1="720" x2="195.4" y2="724" stroke="black"/> <line x1="184.0" y1="720" x2="184.0" y2="724" stroke="black"/>
<text x="195.4" y="737" text-anchor="middle" font-size="11">3</text> <text x="184.0" y="737" text-anchor="middle" font-size="11">3</text>
<line x1="258.1" y1="720" x2="258.1" y2="724" stroke="black"/> <line x1="241.0" y1="720" x2="241.0" y2="724" stroke="black"/>
<text x="258.1" y="737" text-anchor="middle" font-size="11">4</text> <text x="241.0" y="737" text-anchor="middle" font-size="11">4</text>
<line x1="320.8" y1="720" x2="320.8" y2="724" stroke="black"/> <line x1="298.0" y1="720" x2="298.0" y2="724" stroke="black"/>
<text x="320.8" y="737" text-anchor="middle" font-size="11">5</text> <text x="298.0" y="737" text-anchor="middle" font-size="11">5</text>
<line x1="383.5" y1="720" x2="383.5" y2="724" stroke="black"/> <line x1="355.0" y1="720" x2="355.0" y2="724" stroke="black"/>
<text x="383.5" y="737" text-anchor="middle" font-size="11">6</text> <text x="355.0" y="737" text-anchor="middle" font-size="11">6</text>
<line x1="446.2" y1="720" x2="446.2" y2="724" stroke="black"/> <line x1="412.0" y1="720" x2="412.0" y2="724" stroke="black"/>
<text x="446.2" y="737" text-anchor="middle" font-size="11">7</text> <text x="412.0" y="737" text-anchor="middle" font-size="11">7</text>
<line x1="508.9" y1="720" x2="508.9" y2="724" stroke="black"/> <line x1="469.0" y1="720" x2="469.0" y2="724" stroke="black"/>
<text x="508.9" y="737" text-anchor="middle" font-size="11">8</text> <text x="469.0" y="737" text-anchor="middle" font-size="11">8</text>
<line x1="571.6" y1="720" x2="571.6" y2="724" stroke="black"/> <line x1="526.0" y1="720" x2="526.0" y2="724" stroke="black"/>
<text x="571.6" y="737" text-anchor="middle" font-size="11">9</text> <text x="526.0" y="737" text-anchor="middle" font-size="11">9</text>
<line x1="634.3" y1="720" x2="634.3" y2="724" stroke="black"/> <line x1="583.0" y1="720" x2="583.0" y2="724" stroke="black"/>
<text x="634.3" y="737" text-anchor="middle" font-size="11">10</text> <text x="583.0" y="737" text-anchor="middle" font-size="11">10</text>
<line x1="640.0" y1="720" x2="640.0" y2="724" stroke="black"/>
<text x="640.0" y="737" text-anchor="middle" font-size="11">11</text>
<line x1="697.0" y1="720" x2="697.0" y2="724" stroke="black"/> <line x1="697.0" y1="720" x2="697.0" y2="724" stroke="black"/>
<text x="697.0" y="737" text-anchor="middle" font-size="11">11</text> <text x="697.0" y="737" text-anchor="middle" font-size="11">12</text>
<line x1="66" y1="720.0" x2="70" y2="720.0" stroke="black"/> <line x1="66" y1="720.0" x2="70" y2="720.0" stroke="black"/>
<text x="63" y="724.0" text-anchor="end" font-size="11">0</text> <text x="63" y="724.0" text-anchor="end" font-size="11">0</text>
<line x1="66" y1="600.0" x2="70" y2="600.0" stroke="black"/> <line x1="66" y1="600.0" x2="70" y2="600.0" stroke="black"/>
@@ -49,47 +51,50 @@
<text x="383" y="753" text-anchor="middle" font-size="12">test match number (each opponent)</text> <text x="383" y="753" text-anchor="middle" font-size="12">test match number (each opponent)</text>
<text x="16" y="420" text-anchor="middle" font-size="12" transform="rotate(-90 16 420)">win rate (%)</text> <text x="16" y="420" text-anchor="middle" font-size="12" transform="rotate(-90 16 420)">win rate (%)</text>
<circle cx="70.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="70.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="132.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="127.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="195.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="184.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="258.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="241.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="320.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="298.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="383.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="355.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="446.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="412.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="508.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="469.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="571.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="526.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="634.3" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="583.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="697.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="640.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<polyline points="70.0,720.0 132.7,720.0 195.4,720.0 258.1,720.0 320.8,720.0 383.5,720.0 446.2,720.0 508.9,720.0 571.6,720.0 634.3,720.0 697.0,720.0" fill="none" stroke="#d62728" stroke-width="3.5" opacity="1.0"/> <circle cx="697.0" cy="660.0" r="2" fill="#d62728" opacity="0.25"/>
<polyline points="70.0,720.0 127.0,720.0 184.0,720.0 241.0,720.0 298.0,720.0 355.0,720.0 412.0,720.0 469.0,720.0 526.0,720.0 583.0,720.0 640.0,720.0 697.0,714.0" fill="none" stroke="#d62728" stroke-width="3.5" opacity="1.0"/>
<circle cx="70.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="70.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="132.7" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="127.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="195.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="184.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="258.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="241.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="320.8" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="298.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="383.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="355.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="446.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="412.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="508.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="469.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="571.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="526.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="634.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="583.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="697.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="640.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<polyline points="70.0,720.0 132.7,720.0 195.4,720.0 258.1,720.0 320.8,720.0 383.5,720.0 446.2,720.0 508.9,720.0 571.6,720.0 634.3,720.0 697.0,720.0" fill="none" stroke="#1f77b4" stroke-width="3.5" opacity="1.0"/> <circle cx="697.0" cy="480.0" r="2" fill="#1f77b4" opacity="0.25"/>
<polyline points="70.0,720.0 127.0,720.0 184.0,720.0 241.0,720.0 298.0,720.0 355.0,720.0 412.0,720.0 469.0,720.0 526.0,720.0 583.0,720.0 640.0,720.0 697.0,696.0" fill="none" stroke="#1f77b4" stroke-width="3.5" opacity="1.0"/>
<circle cx="70.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="70.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="132.7" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="127.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="195.4" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="184.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="258.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="241.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="320.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="298.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="383.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="355.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="446.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="412.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="508.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="469.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="571.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="526.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="634.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="583.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="640.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="697.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="697.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<polyline points="70.0,720.0 132.7,720.0 195.4,720.0 258.1,720.0 320.8,720.0 383.5,720.0 446.2,720.0 508.9,720.0 571.6,720.0 634.3,720.0 697.0,720.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/> <polyline points="70.0,720.0 127.0,720.0 184.0,720.0 241.0,720.0 298.0,720.0 355.0,720.0 412.0,720.0 469.0,720.0 526.0,720.0 583.0,720.0 640.0,720.0 697.0,720.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<line x1="82" y1="772" x2="110" y2="772" stroke="#d62728" stroke-width="3"/> <line x1="82" y1="772" x2="110" y2="772" stroke="#d62728" stroke-width="3"/>
<text x="116" y="776" font-size="12">Corners - 11 evals</text> <text x="116" y="776" font-size="12">Corners - 12 evals</text>
<line x1="82" y1="790" x2="110" y2="790" stroke="#1f77b4" stroke-width="3"/> <line x1="82" y1="790" x2="110" y2="790" stroke="#1f77b4" stroke-width="3"/>
<text x="116" y="794" font-size="12">Crazy - 11 evals</text> <text x="116" y="794" font-size="12">Crazy - 12 evals</text>
<line x1="82" y1="808" x2="110" y2="808" stroke="#2ca02c" stroke-width="3"/> <line x1="82" y1="808" x2="110" y2="808" stroke="#2ca02c" stroke-width="3"/>
<text x="116" y="812" font-size="12">Target - 11 evals</text> <text x="116" y="812" font-size="12">Target - 12 evals</text>
<text x="747" y="100" font-size="15" font-weight="bold">Training losses &amp; alpha (log scale)</text> <text x="747" y="100" font-size="15" font-weight="bold">Training losses &amp; alpha (log scale)</text>
<text x="747" y="115" font-size="11" fill="#555">training_metrics.jsonl - big early spikes are normal</text> <text x="747" y="115" font-size="11" fill="#555">training_metrics.jsonl - big early spikes are normal</text>
<line x1="747" y1="720.0" x2="1375" y2="720.0" stroke="#dddddd"/> <line x1="747" y1="720.0" x2="1375" y2="720.0" stroke="#dddddd"/>
@@ -104,11 +109,11 @@
<line x1="904.0" y1="720" x2="904.0" y2="724" stroke="black"/> <line x1="904.0" y1="720" x2="904.0" y2="724" stroke="black"/>
<text x="904.0" y="737" text-anchor="middle" font-size="11">7</text> <text x="904.0" y="737" text-anchor="middle" font-size="11">7</text>
<line x1="1061.0" y1="720" x2="1061.0" y2="724" stroke="black"/> <line x1="1061.0" y1="720" x2="1061.0" y2="724" stroke="black"/>
<text x="1061.0" y="737" text-anchor="middle" font-size="11">12</text> <text x="1061.0" y="737" text-anchor="middle" font-size="11">14</text>
<line x1="1218.0" y1="720" x2="1218.0" y2="724" stroke="black"/> <line x1="1218.0" y1="720" x2="1218.0" y2="724" stroke="black"/>
<text x="1218.0" y="737" text-anchor="middle" font-size="11">18</text> <text x="1218.0" y="737" text-anchor="middle" font-size="11">20</text>
<line x1="1375.0" y1="720" x2="1375.0" y2="724" stroke="black"/> <line x1="1375.0" y1="720" x2="1375.0" y2="724" stroke="black"/>
<text x="1375.0" y="737" text-anchor="middle" font-size="11">24</text> <text x="1375.0" y="737" text-anchor="middle" font-size="11">26</text>
<line x1="743" y1="720.0" x2="747" y2="720.0" stroke="black"/> <line x1="743" y1="720.0" x2="747" y2="720.0" stroke="black"/>
<text x="740" y="724.0" text-anchor="end" font-size="11">0.01</text> <text x="740" y="724.0" text-anchor="end" font-size="11">0.01</text>
<line x1="743" y1="570.0" x2="747" y2="570.0" stroke="black"/> <line x1="743" y1="570.0" x2="747" y2="570.0" stroke="black"/>
@@ -121,9 +126,9 @@
<text x="740" y="124.0" text-anchor="end" font-size="11">100</text> <text x="740" y="124.0" text-anchor="end" font-size="11">100</text>
<text x="1061" y="753" text-anchor="middle" font-size="12">metric line number</text> <text x="1061" y="753" text-anchor="middle" font-size="12">metric line number</text>
<text x="16" y="420" text-anchor="middle" font-size="12" transform="rotate(-90 16 420)">loss (log)</text> <text x="16" y="420" text-anchor="middle" font-size="12" transform="rotate(-90 16 420)">loss (log)</text>
<polyline points="747.0,281.9 774.3,253.6 801.6,243.9 828.9,213.5 856.2,207.6 883.5,386.8 910.8,424.4 938.1,307.1 965.4,293.6 992.7,282.6 1020.0,242.7 1047.3,171.4 1074.7,158.6 1102.0,169.7 1129.3,191.2 1156.6,253.6 1183.9,327.8 1211.2,625.2 1238.5,506.4 1265.8,374.2 1293.1,425.9 1320.4,351.1 1347.7,357.9 1375.0,330.0" fill="none" stroke="#1f77b4" stroke-width="1.8" opacity="1.0"/> <polyline points="747.0,281.9 772.1,253.6 797.2,243.9 822.4,213.5 847.5,207.6 872.6,386.8 897.7,424.4 922.8,307.1 948.0,293.6 973.1,282.6 998.2,242.7 1023.3,171.4 1048.4,158.6 1073.6,169.7 1098.7,191.2 1123.8,253.6 1148.9,327.8 1174.0,625.2 1199.2,506.4 1224.3,374.2 1249.4,425.9 1274.5,351.1 1299.6,357.9 1324.8,330.0 1349.9,251.4 1375.0,339.4" fill="none" stroke="#1f77b4" stroke-width="1.8" opacity="1.0"/>
<polyline points="747.0,348.9 774.3,307.9 801.6,284.5 828.9,261.3 856.2,244.2 883.5,235.2 910.8,236.5 938.1,241.4 965.4,270.7 992.7,292.4 1020.0,378.9 1047.3,302.9 1074.7,277.7 1102.0,264.4 1129.3,254.8 1156.6,246.5 1183.9,242.0 1211.2,251.8 1238.5,262.0 1265.8,273.4 1293.1,295.3 1320.4,327.5 1347.7,335.9 1375.0,438.0" fill="none" stroke="#ff7f0e" stroke-width="1.8" opacity="1.0"/> <polyline points="747.0,348.9 772.1,307.9 797.2,284.5 822.4,261.3 847.5,244.2 872.6,235.2 897.7,236.5 922.8,241.4 948.0,270.7 973.1,292.4 998.2,378.9 1023.3,302.9 1048.4,277.7 1073.6,264.4 1098.7,254.8 1123.8,246.5 1148.9,242.0 1174.0,251.8 1199.2,262.0 1224.3,273.4 1249.4,295.3 1274.5,327.5 1299.6,335.9 1324.8,438.0 1349.9,400.5 1375.0,500.9" fill="none" stroke="#ff7f0e" stroke-width="1.8" opacity="1.0"/>
<polyline points="747.0,420.0 774.3,420.1 801.6,420.2 828.9,420.3 856.2,420.3 883.5,420.4 910.8,420.5 938.1,420.5 965.4,420.6 992.7,420.6 1020.0,420.7 1047.3,420.7 1074.7,420.7 1102.0,420.6 1129.3,420.5 1156.6,420.4 1183.9,420.4 1211.2,420.3 1238.5,420.2 1265.8,420.1 1293.1,420.2 1320.4,420.3 1347.7,420.3 1375.0,420.4" fill="none" stroke="#9467bd" stroke-width="1.8" opacity="1.0"/> <polyline points="747.0,420.0 772.1,420.1 797.2,420.2 822.4,420.3 847.5,420.3 872.6,420.4 897.7,420.5 922.8,420.5 948.0,420.6 973.1,420.6 998.2,420.7 1023.3,420.7 1048.4,420.7 1073.6,420.6 1098.7,420.5 1123.8,420.4 1148.9,420.4 1174.0,420.3 1199.2,420.2 1224.3,420.1 1249.4,420.2 1274.5,420.3 1299.6,420.3 1324.8,420.4 1349.9,420.5 1375.0,420.7" fill="none" stroke="#9467bd" stroke-width="1.8" opacity="1.0"/>
<line x1="759" y1="772" x2="787" y2="772" stroke="#1f77b4" stroke-width="3"/> <line x1="759" y1="772" x2="787" y2="772" stroke="#1f77b4" stroke-width="3"/>
<text x="793" y="776" font-size="12">critic_loss</text> <text x="793" y="776" font-size="12">critic_loss</text>
<line x1="759" y1="790" x2="787" y2="790" stroke="#ff7f0e" stroke-width="3"/> <line x1="759" y1="790" x2="787" y2="790" stroke="#ff7f0e" stroke-width="3"/>
@@ -144,11 +149,11 @@
<line x1="226.8" y1="1428" x2="226.8" y2="1432" stroke="black"/> <line x1="226.8" y1="1428" x2="226.8" y2="1432" stroke="black"/>
<text x="226.8" y="1445" text-anchor="middle" font-size="11">7</text> <text x="226.8" y="1445" text-anchor="middle" font-size="11">7</text>
<line x1="383.5" y1="1428" x2="383.5" y2="1432" stroke="black"/> <line x1="383.5" y1="1428" x2="383.5" y2="1432" stroke="black"/>
<text x="383.5" y="1445" text-anchor="middle" font-size="11">12</text> <text x="383.5" y="1445" text-anchor="middle" font-size="11">14</text>
<line x1="540.2" y1="1428" x2="540.2" y2="1432" stroke="black"/> <line x1="540.2" y1="1428" x2="540.2" y2="1432" stroke="black"/>
<text x="540.2" y="1445" text-anchor="middle" font-size="11">18</text> <text x="540.2" y="1445" text-anchor="middle" font-size="11">20</text>
<line x1="697.0" y1="1428" x2="697.0" y2="1432" stroke="black"/> <line x1="697.0" y1="1428" x2="697.0" y2="1432" stroke="black"/>
<text x="697.0" y="1445" text-anchor="middle" font-size="11">24</text> <text x="697.0" y="1445" text-anchor="middle" font-size="11">26</text>
<line x1="66" y1="1428.0" x2="70" y2="1428.0" stroke="black"/> <line x1="66" y1="1428.0" x2="70" y2="1428.0" stroke="black"/>
<text x="63" y="1432.0" text-anchor="end" font-size="11">0</text> <text x="63" y="1432.0" text-anchor="end" font-size="11">0</text>
<line x1="66" y1="1310.5" x2="70" y2="1310.5" stroke="black"/> <line x1="66" y1="1310.5" x2="70" y2="1310.5" stroke="black"/>
@@ -161,7 +166,7 @@
<text x="63" y="962.0" text-anchor="end" font-size="11">1</text> <text x="63" y="962.0" text-anchor="end" font-size="11">1</text>
<text x="383" y="1461" text-anchor="middle" font-size="12">metric line number</text> <text x="383" y="1461" text-anchor="middle" font-size="12">metric line number</text>
<text x="16" y="1193" text-anchor="middle" font-size="12" transform="rotate(-90 16 1193)">alpha</text> <text x="16" y="1193" text-anchor="middle" font-size="12" transform="rotate(-90 16 1193)">alpha</text>
<polyline points="70.0,958.1 97.3,958.7 124.5,959.3 151.8,959.8 179.0,960.4 206.3,961.0 233.6,961.2 260.8,961.5 288.1,962.1 315.3,962.4 342.6,962.9 369.9,963.4 397.1,962.8 424.4,962.3 451.7,961.7 478.9,961.1 506.2,960.6 533.4,960.0 560.7,959.5 588.0,958.9 615.2,959.3 642.5,959.9 669.7,960.5 697.0,961.0" fill="none" stroke="#9467bd" stroke-width="1.8" opacity="1.0"/> <polyline points="70.0,958.1 95.1,958.7 120.2,959.3 145.2,959.8 170.3,960.4 195.4,961.0 220.5,961.2 245.6,961.5 270.6,962.1 295.7,962.4 320.8,962.9 345.9,963.4 371.0,962.8 396.0,962.3 421.1,961.7 446.2,961.1 471.3,960.6 496.4,960.0 521.4,959.5 546.5,958.9 571.6,959.3 596.7,959.9 621.8,960.5 646.8,961.0 671.9,961.6 697.0,962.7" fill="none" stroke="#9467bd" stroke-width="1.8" opacity="1.0"/>
<text x="747" y="940" font-size="15" font-weight="bold">Throughput - games per hour</text> <text x="747" y="940" font-size="15" font-weight="bold">Throughput - games per hour</text>
<text x="747" y="955" font-size="11" fill="#555">method: training_metrics.jsonl 'epoch' deltas; 1 row = one 10-game chunk</text> <text x="747" y="955" font-size="11" fill="#555">method: training_metrics.jsonl 'epoch' deltas; 1 row = one 10-game chunk</text>
<line x1="747" y1="1428.0" x2="1375" y2="1428.0" stroke="#dddddd"/> <line x1="747" y1="1428.0" x2="1375" y2="1428.0" stroke="#dddddd"/>
@@ -171,28 +176,30 @@
<line x1="747" y1="958.0" x2="1375" y2="958.0" stroke="#dddddd"/> <line x1="747" y1="958.0" x2="1375" y2="958.0" stroke="#dddddd"/>
<line x1="747" y1="1428" x2="1375" y2="1428" stroke="black"/> <line x1="747" y1="1428" x2="1375" y2="1428" stroke="black"/>
<line x1="747" y1="1428" x2="747" y2="958" stroke="black"/> <line x1="747" y1="1428" x2="747" y2="958" stroke="black"/>
<line x1="775.5" y1="1428" x2="775.5" y2="1432" stroke="black"/> <line x1="773.2" y1="1428" x2="773.2" y2="1432" stroke="black"/>
<text x="775.5" y="1445" text-anchor="middle" font-size="11">2</text> <text x="773.2" y="1445" text-anchor="middle" font-size="11">2</text>
<line x1="832.6" y1="1428" x2="832.6" y2="1432" stroke="black"/> <line x1="825.5" y1="1428" x2="825.5" y2="1432" stroke="black"/>
<text x="832.6" y="1445" text-anchor="middle" font-size="11">4</text> <text x="825.5" y="1445" text-anchor="middle" font-size="11">4</text>
<line x1="889.7" y1="1428" x2="889.7" y2="1432" stroke="black"/> <line x1="877.8" y1="1428" x2="877.8" y2="1432" stroke="black"/>
<text x="889.7" y="1445" text-anchor="middle" font-size="11">6</text> <text x="877.8" y="1445" text-anchor="middle" font-size="11">6</text>
<line x1="946.8" y1="1428" x2="946.8" y2="1432" stroke="black"/> <line x1="930.2" y1="1428" x2="930.2" y2="1432" stroke="black"/>
<text x="946.8" y="1445" text-anchor="middle" font-size="11">8</text> <text x="930.2" y="1445" text-anchor="middle" font-size="11">8</text>
<line x1="1003.9" y1="1428" x2="1003.9" y2="1432" stroke="black"/> <line x1="982.5" y1="1428" x2="982.5" y2="1432" stroke="black"/>
<text x="1003.9" y="1445" text-anchor="middle" font-size="11">10</text> <text x="982.5" y="1445" text-anchor="middle" font-size="11">10</text>
<line x1="1061.0" y1="1428" x2="1061.0" y2="1432" stroke="black"/> <line x1="1034.8" y1="1428" x2="1034.8" y2="1432" stroke="black"/>
<text x="1061.0" y="1445" text-anchor="middle" font-size="11">12</text> <text x="1034.8" y="1445" text-anchor="middle" font-size="11">12</text>
<line x1="1118.1" y1="1428" x2="1118.1" y2="1432" stroke="black"/> <line x1="1087.2" y1="1428" x2="1087.2" y2="1432" stroke="black"/>
<text x="1118.1" y="1445" text-anchor="middle" font-size="11">14</text> <text x="1087.2" y="1445" text-anchor="middle" font-size="11">14</text>
<line x1="1175.2" y1="1428" x2="1175.2" y2="1432" stroke="black"/> <line x1="1139.5" y1="1428" x2="1139.5" y2="1432" stroke="black"/>
<text x="1175.2" y="1445" text-anchor="middle" font-size="11">16</text> <text x="1139.5" y="1445" text-anchor="middle" font-size="11">16</text>
<line x1="1232.3" y1="1428" x2="1232.3" y2="1432" stroke="black"/> <line x1="1191.8" y1="1428" x2="1191.8" y2="1432" stroke="black"/>
<text x="1232.3" y="1445" text-anchor="middle" font-size="11">18</text> <text x="1191.8" y="1445" text-anchor="middle" font-size="11">18</text>
<line x1="1289.4" y1="1428" x2="1289.4" y2="1432" stroke="black"/> <line x1="1244.2" y1="1428" x2="1244.2" y2="1432" stroke="black"/>
<text x="1289.4" y="1445" text-anchor="middle" font-size="11">20</text> <text x="1244.2" y="1445" text-anchor="middle" font-size="11">20</text>
<line x1="1346.5" y1="1428" x2="1346.5" y2="1432" stroke="black"/> <line x1="1296.5" y1="1428" x2="1296.5" y2="1432" stroke="black"/>
<text x="1346.5" y="1445" text-anchor="middle" font-size="11">22</text> <text x="1296.5" y="1445" text-anchor="middle" font-size="11">22</text>
<line x1="1348.8" y1="1428" x2="1348.8" y2="1432" stroke="black"/>
<text x="1348.8" y="1445" text-anchor="middle" font-size="11">24</text>
<line x1="743" y1="1428.0" x2="747" y2="1428.0" stroke="black"/> <line x1="743" y1="1428.0" x2="747" y2="1428.0" stroke="black"/>
<text x="740" y="1432.0" text-anchor="end" font-size="11">0</text> <text x="740" y="1432.0" text-anchor="end" font-size="11">0</text>
<line x1="743" y1="1310.5" x2="747" y2="1310.5" stroke="black"/> <line x1="743" y1="1310.5" x2="747" y2="1310.5" stroke="black"/>
@@ -206,37 +213,133 @@
<text x="1061" y="1461" text-anchor="middle" font-size="12">chunk interval (10-game chunks)</text> <text x="1061" y="1461" text-anchor="middle" font-size="12">chunk interval (10-game chunks)</text>
<text x="16" y="1193" text-anchor="middle" font-size="12" transform="rotate(-90 16 1193)">games / hour</text> <text x="16" y="1193" text-anchor="middle" font-size="12" transform="rotate(-90 16 1193)">games / hour</text>
<circle cx="747.0" cy="1159.8" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="747.0" cy="1159.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="775.5" cy="1413.4" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="773.2" cy="1413.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="804.1" cy="1148.0" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="799.3" cy="1148.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="832.6" cy="1404.7" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="825.5" cy="1404.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="861.2" cy="1153.6" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="851.7" cy="1153.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="889.7" cy="1399.6" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="877.8" cy="1399.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="918.3" cy="1000.7" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="904.0" cy="1000.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="946.8" cy="1399.5" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="930.2" cy="1399.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="975.4" cy="1021.4" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="956.3" cy="1021.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1003.9" cy="1394.8" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="982.5" cy="1394.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1032.5" cy="1376.2" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1008.7" cy="1376.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1061.0" cy="1394.4" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1034.8" cy="1394.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1089.5" cy="1130.1" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1061.0" cy="1130.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1118.1" cy="1399.7" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1087.2" cy="1399.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1146.6" cy="1176.5" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1113.3" cy="1176.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1175.2" cy="1402.0" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1139.5" cy="1402.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1203.7" cy="1158.0" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1165.7" cy="1158.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1232.3" cy="1400.0" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1191.8" cy="1400.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1260.8" cy="1149.2" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1218.0" cy="1149.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1289.4" cy="1400.6" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1244.2" cy="1400.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1317.9" cy="1171.7" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1270.3" cy="1171.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1346.5" cy="1405.2" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1296.5" cy="1405.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1375.0" cy="1162.2" r="1.6" fill="#7f7f7f" opacity="0.25"/> <circle cx="1322.7" cy="1162.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<polyline points="747.0,1286.6 775.5,1276.3 804.1,1276.6 832.6,1000.7 861.2,1399.5 889.7,1021.4 918.3,1394.8 975.4,1376.2 1003.9,1394.4 1032.5,1130.1 1061.0,1399.7 1089.5,1176.5 1118.1,1402.0 1175.2,1158.0 1203.7,1400.0 1232.3,1149.2 1260.8,1400.6 1289.4,1171.7 1317.9,1405.2 1346.5,1162.2" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/> <circle cx="1348.8" cy="1416.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<rect x="0" y="1540" width="1400" height="180" fill="#f2f2f2"/> <circle cx="1375.0" cy="1375.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<text x="16" y="1561" font-size="15" font-weight="bold">How to read</text> <polyline points="747.0,1286.6 773.2,1276.3 799.3,1276.6 825.5,1200.1 877.8,1208.1 904.0,1376.2 930.2,1394.4 956.3,1130.1 1008.7,1399.7 1034.8,1176.5 1061.0,1402.0 1087.2,1158.0 1139.5,1400.0 1165.7,1149.2 1191.8,1400.6 1218.0,1171.7 1270.3,1405.2 1296.5,1162.2 1322.7,1416.5 1348.8,1375.5" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<text x="1384" y="1560" text-anchor="end" font-size="11" fill="#666">Regenerate anytime: python3 tools/plot_progress.py</text> <text x="70" y="1648" font-size="15" font-weight="bold">Max score per eval cycle</text>
<text x="16" y="1583" font-size="13">Test matches: dots are single fights, thick line shows trend.</text> <text x="70" y="1663" font-size="11" fill="#555">campaign_v4_stdout.log - max of the 10 deterministic round scores per eval</text>
<text x="16" y="1600" font-size="13">Loss spikes are normal early; endless growth is bad.</text> <line x1="70" y1="2270.0" x2="697" y2="2270.0" stroke="#dddddd"/>
<text x="16" y="1617" font-size="13">Alpha high means experimenting; falling too fast freezes habits.</text> <line x1="70" y1="2119.0" x2="697" y2="2119.0" stroke="#dddddd"/>
<text x="16" y="1634" font-size="13">Throughput flat is healthy; dips mean something slowed.</text> <line x1="70" y1="1968.0" x2="697" y2="1968.0" stroke="#dddddd"/>
<text x="16" y="1651" font-size="13">This file reloads itself in Chrome every sixty seconds.</text> <line x1="70" y1="1817.0" x2="697" y2="1817.0" stroke="#dddddd"/>
<text x="16" y="1668" font-size="13">Regenerate anytime with tools/watch_dashboard.sh or the python command.</text> <line x1="70" y1="1666.0" x2="697" y2="1666.0" stroke="#dddddd"/>
<line x1="70" y1="2270" x2="697" y2="2270" stroke="black"/>
<line x1="70" y1="2270" x2="70" y2="1666" stroke="black"/>
<line x1="70.0" y1="2270" x2="70.0" y2="2274" stroke="black"/>
<text x="70.0" y="2287" text-anchor="middle" font-size="11">1</text>
<line x1="127.0" y1="2270" x2="127.0" y2="2274" stroke="black"/>
<text x="127.0" y="2287" text-anchor="middle" font-size="11">2</text>
<line x1="184.0" y1="2270" x2="184.0" y2="2274" stroke="black"/>
<text x="184.0" y="2287" text-anchor="middle" font-size="11">3</text>
<line x1="241.0" y1="2270" x2="241.0" y2="2274" stroke="black"/>
<text x="241.0" y="2287" text-anchor="middle" font-size="11">4</text>
<line x1="298.0" y1="2270" x2="298.0" y2="2274" stroke="black"/>
<text x="298.0" y="2287" text-anchor="middle" font-size="11">5</text>
<line x1="355.0" y1="2270" x2="355.0" y2="2274" stroke="black"/>
<text x="355.0" y="2287" text-anchor="middle" font-size="11">6</text>
<line x1="412.0" y1="2270" x2="412.0" y2="2274" stroke="black"/>
<text x="412.0" y="2287" text-anchor="middle" font-size="11">7</text>
<line x1="469.0" y1="2270" x2="469.0" y2="2274" stroke="black"/>
<text x="469.0" y="2287" text-anchor="middle" font-size="11">8</text>
<line x1="526.0" y1="2270" x2="526.0" y2="2274" stroke="black"/>
<text x="526.0" y="2287" text-anchor="middle" font-size="11">9</text>
<line x1="583.0" y1="2270" x2="583.0" y2="2274" stroke="black"/>
<text x="583.0" y="2287" text-anchor="middle" font-size="11">10</text>
<line x1="640.0" y1="2270" x2="640.0" y2="2274" stroke="black"/>
<text x="640.0" y="2287" text-anchor="middle" font-size="11">11</text>
<line x1="697.0" y1="2270" x2="697.0" y2="2274" stroke="black"/>
<text x="697.0" y="2287" text-anchor="middle" font-size="11">12</text>
<line x1="66" y1="2270.0" x2="70" y2="2270.0" stroke="black"/>
<text x="63" y="2274.0" text-anchor="end" font-size="11">0</text>
<line x1="66" y1="2119.0" x2="70" y2="2119.0" stroke="black"/>
<text x="63" y="2123.0" text-anchor="end" font-size="11">52</text>
<line x1="66" y1="1968.0" x2="70" y2="1968.0" stroke="black"/>
<text x="63" y="1972.0" text-anchor="end" font-size="11">103</text>
<line x1="66" y1="1817.0" x2="70" y2="1817.0" stroke="black"/>
<text x="63" y="1821.0" text-anchor="end" font-size="11">155</text>
<line x1="66" y1="1666.0" x2="70" y2="1666.0" stroke="black"/>
<text x="63" y="1670.0" text-anchor="end" font-size="11">207</text>
<text x="383" y="2303" text-anchor="middle" font-size="12">eval cycle number</text>
<text x="16" y="1968" text-anchor="middle" font-size="12" transform="rotate(-90 16 1968)">best single-round score</text>
<circle cx="70.0" cy="2053.9" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="127.0" cy="1977.9" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="184.0" cy="2182.4" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="241.0" cy="2229.1" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="298.0" cy="2232.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="355.0" cy="2179.5" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="412.0" cy="2223.3" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="469.0" cy="2179.5" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="526.0" cy="2223.3" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="583.0" cy="2205.7" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="640.0" cy="2185.3" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="697.0" cy="2077.2" r="2" fill="#d62728" opacity="0.25"/>
<polyline points="70.0,2053.9 127.0,1977.9 184.0,2182.4 241.0,2229.1 298.0,2232.0 355.0,2179.5 412.0,2223.3 469.0,2179.5 526.0,2223.3 583.0,2205.7 640.0,2185.3 697.0,2077.2" fill="none" stroke="#d62728" stroke-width="2.5" opacity="1.0"/>
<circle cx="70.0" cy="2237.9" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="127.0" cy="2243.7" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="184.0" cy="2205.7" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="241.0" cy="2188.2" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="298.0" cy="2199.9" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="355.0" cy="2086.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="412.0" cy="2144.4" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="469.0" cy="2129.8" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="526.0" cy="2138.6" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="583.0" cy="1919.5" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="640.0" cy="2182.4" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="697.0" cy="2065.6" r="2" fill="#1f77b4" opacity="0.25"/>
<polyline points="70.0,2237.9 127.0,2243.7 184.0,2205.7 241.0,2188.2 298.0,2199.9 355.0,2086.0 412.0,2144.4 469.0,2129.8 526.0,2138.6 583.0,1919.5 640.0,2182.4 697.0,2065.6" fill="none" stroke="#1f77b4" stroke-width="2.5" opacity="1.0"/>
<circle cx="70.0" cy="2001.3" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="127.0" cy="2007.1" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="184.0" cy="2001.3" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="241.0" cy="2167.8" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="298.0" cy="2235.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="355.0" cy="2182.4" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="412.0" cy="1720.9" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="469.0" cy="2176.5" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="526.0" cy="2194.1" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="583.0" cy="2077.2" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="640.0" cy="2115.2" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="697.0" cy="2118.1" r="2" fill="#2ca02c" opacity="0.25"/>
<polyline points="70.0,2001.3 127.0,2007.1 184.0,2001.3 241.0,2167.8 298.0,2235.0 355.0,2182.4 412.0,1720.9 469.0,2176.5 526.0,2194.1 583.0,2077.2 640.0,2115.2 697.0,2118.1" fill="none" stroke="#2ca02c" stroke-width="2.5" opacity="1.0"/>
<polyline points="70.0,2001.3 127.0,1977.9 184.0,2001.3 241.0,2167.8 298.0,2199.9 355.0,2086.0 412.0,1720.9 469.0,2129.8 526.0,2138.6 583.0,1919.5 640.0,2115.2 697.0,2065.6" fill="none" stroke="#555555" stroke-width="2.5" opacity="1.0" stroke-dasharray="6 4"/>
<line x1="82" y1="2322" x2="110" y2="2322" stroke="#d62728" stroke-width="3"/>
<text x="116" y="2326" font-size="12">Corners - 12 evals</text>
<line x1="82" y1="2340" x2="110" y2="2340" stroke="#1f77b4" stroke-width="3"/>
<text x="116" y="2344" font-size="12">Crazy - 12 evals</text>
<line x1="82" y1="2358" x2="110" y2="2358" stroke="#2ca02c" stroke-width="3"/>
<text x="116" y="2362" font-size="12">Target - 12 evals</text>
<line x1="82" y1="2376" x2="110" y2="2376" stroke="#555555" stroke-width="3"/>
<text x="116" y="2380" font-size="12">combined max</text>
<rect x="0" y="2340" width="1400" height="180" fill="#f2f2f2"/>
<text x="16" y="2361" font-size="15" font-weight="bold">How to read</text>
<text x="1384" y="2360" text-anchor="end" font-size="11" fill="#666">Regenerate anytime: python3 tools/plot_progress.py</text>
<text x="16" y="2383" font-size="13">Test matches: dots are single fights, thick line shows trend.</text>
<text x="16" y="2400" font-size="13">Loss spikes are normal early; endless growth is bad.</text>
<text x="16" y="2417" font-size="13">Alpha high means experimenting; falling too fast freezes habits.</text>
<text x="16" y="2434" font-size="13">Throughput flat is healthy; dips mean something slowed.</text>
<text x="16" y="2451" font-size="13">Max score: best single-round score the bot managed in that eval cycle.</text>
<text x="16" y="2468" font-size="13">This file reloads itself in Chrome every sixty seconds.</text>
<text x="16" y="2485" font-size="13">Regenerate anytime with tools/watch_dashboard.sh or the python command.</text>
<script type="text/javascript"><![CDATA[ setTimeout(function(){ location.reload(); }, 60000); ]]></script> <script type="text/javascript"><![CDATA[ setTimeout(function(){ location.reload(); }, 60000); ]]></script>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 27 KiB

+109 -9
View File
@@ -3,13 +3,17 @@
Pure-stdlib SVG output (matplotlib not available on this box). Pure-stdlib SVG output (matplotlib not available on this box).
Generates ONE file: Generates ONE file:
docs/campaign_dashboard.svg - four panels, current (v2) run only: docs/campaign_dashboard.svg - five panels, current (v2) run only:
1. test-match win % vs opponents (campaign_v4_stdout.log eval lines) 1. test-match win % vs opponents (campaign_v4_stdout.log eval lines)
2. critic_loss / |actor_loss| / alpha (training_metrics.jsonl, shared log-y) 2. critic_loss / |actor_loss| (training_metrics.jsonl, shared log-y)
3. alpha temperature (training_metrics.jsonl, linear) 3. alpha temperature (training_metrics.jsonl, linear)
4. throughput, games/hour buckets (training_metrics.jsonl 'epoch' deltas; 4. throughput, games/hour buckets (training_metrics.jsonl 'epoch' deltas;
1 metrics row == one 10-game chunk, 1 metrics row == one 10-game chunk,
counts match stdout chunk markers) counts match stdout chunk markers)
5. max score per eval cycle (campaign_v4_stdout.log eval blocks;
eval_log.jsonl only ever holds the
LATEST cycle, so history comes from
the stdout log)
plus an embedded JS snippet that reloads the page every 60 s when the SVG is plus an embedded JS snippet that reloads the page every 60 s when the SVG is
opened as a top-level document in Chrome. opened as a top-level document in Chrome.
@@ -30,6 +34,8 @@ import xml.etree.ElementTree as ET
ROOT = Path(__file__).resolve().parent.parent ROOT = Path(__file__).resolve().parent.parent
EVAL_RE = re.compile(r">>> \[eval\] win rate: (\d+)/(\d+) \(([\d.]+)%\) vs (\S+)") EVAL_RE = re.compile(r">>> \[eval\] win rate: (\d+)/(\d+) \(([\d.]+)%\) vs (\S+)")
EVAL_BLOCK_RE = re.compile(r">>> \[eval\] \d+ deterministic rounds vs (\S+)")
ROUND_RE = re.compile(r"Round \d+/\d+\D+ticks:\d+ score:(\d+) win:")
TREND_WINDOW = 10 # rolling mean shown as the thick trend line (panel 1) TREND_WINDOW = 10 # rolling mean shown as the thick trend line (panel 1)
RATE_BUCKET = 20 # metric intervals per throughput bucket (~200 games) RATE_BUCKET = 20 # metric intervals per throughput bucket (~200 games)
GAMES_PER_ROW = 10 # one training_metrics.jsonl row per 10-round chunk GAMES_PER_ROW = 10 # one training_metrics.jsonl row per 10-round chunk
@@ -39,7 +45,7 @@ CRITIC_C, ACTOR_C = "#1f77b4", "#ff7f0e"
ALPHA_C = "#9467bd" # same purple as the dedicated alpha panel ALPHA_C = "#9467bd" # same purple as the dedicated alpha panel
RELOAD_JS = ('<script type="text/javascript"><![CDATA[ ' RELOAD_JS = ('<script type="text/javascript"><![CDATA[ '
'setTimeout(function(){ location.reload(); }, 60000); ]]></script>') 'setTimeout(function(){ location.reload(); }, 60000); ]]></script>')
W, H = 1400, 1720 W, H = 1400, 2520
TITLE_H, GUIDE_H = 80, 180 TITLE_H, GUIDE_H = 80, 180
# rows: (header_y, panel_top_y, panel_bottom_y, x_left, x_right) # rows: (header_y, panel_top_y, panel_bottom_y, x_left, x_right)
C1_L, C1_R = 70, 697 C1_L, C1_R = 70, 697
@@ -49,18 +55,21 @@ ROWS = {
2: (100, 120, 720, C2_L, C2_R), 2: (100, 120, 720, C2_L, C2_R),
3: (940, 958, 1428, C1_L, C1_R), 3: (940, 958, 1428, C1_L, C1_R),
4: (940, 958, 1428, C2_L, C2_R), 4: (940, 958, 1428, C2_L, C2_R),
5: (1648, 1666, 2270, C1_L, C1_R), # third row, left column; right slot empty
} }
PANEL_TITLES = [ PANEL_TITLES = [
"Test matches - win % vs opponents", "Test matches - win % vs opponents",
"Training losses & alpha (log scale)", "Training losses & alpha (log scale)",
"Alpha temperature", "Alpha temperature",
"Throughput - games per hour", "Throughput - games per hour",
"Max score per eval cycle",
] ]
GUIDE_LINES = [ GUIDE_LINES = [
"Test matches: dots are single fights, thick line shows trend.", "Test matches: dots are single fights, thick line shows trend.",
"Loss spikes are normal early; endless growth is bad.", "Loss spikes are normal early; endless growth is bad.",
"Alpha high means experimenting; falling too fast freezes habits.", "Alpha high means experimenting; falling too fast freezes habits.",
"Throughput flat is healthy; dips mean something slowed.", "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.", "This file reloads itself in Chrome every sixty seconds.",
"Regenerate anytime with tools/watch_dashboard.sh or the python command.", "Regenerate anytime with tools/watch_dashboard.sh or the python command.",
] ]
@@ -86,6 +95,38 @@ def parse_eval_series(path):
return series return series
def parse_max_scores(path):
"""Return {opponent: [best single-round score per eval cycle, in file order]}.
eval_log.jsonl is atomically overwritten every cycle (sac_train.sh mv), so
per-cycle history only exists in the stdout log: each eval prints a
'>>> [eval] N deterministic rounds vs X' header, then Round/score lines,
closed by the '[eval] win rate' (or crashed / no results) line. Training
rounds share the Round-line format, so they are ignored unless inside a block.
"""
out, cur, best = {}, None, None
if not path.is_file():
print(f"[skip] campaign log not found: {path}")
return out
for line in path.read_text(errors="replace").splitlines():
m = EVAL_BLOCK_RE.search(line)
if m:
cur, best = m.group(1), None
continue
if cur is None:
continue
if "[eval]" in line: # win-rate / crashed / no-results closes the block
if best is not None:
out.setdefault(cur, []).append(best)
cur, best = None, None
continue
m = ROUND_RE.search(line)
if m:
v = int(m.group(1))
best = v if best is None else max(best, v)
return out
def parse_metrics(path): def parse_metrics(path):
"""Return list of metric dicts, skipping malformed lines.""" """Return list of metric dicts, skipping malformed lines."""
rows = [] rows = []
@@ -280,6 +321,42 @@ def panel_test_matches(series, geo):
s += legend(items, x0 + 12, pb + 52) s += legend(items, x0 + 12, pb + 52)
return s return s
def panel_max_score(maxes, geo):
_, pt, pb, x0, x1 = geo
s = ""
if not maxes:
s += f'<text x="{x0 + 10}" y="{pt + 40}" font-size="12" fill="#a00">' \
"no eval lines found</text>\n"
return
ncyc = max(len(v) for v in maxes.values())
xmax = max(ncyc, 2)
hi = max(1, max(v for vals in maxes.values() for v in vals)) * 1.1
xm, ym = map_fn(x0, x1, 1, xmax), map_fn(pb, pt, 0, hi)
s += hgrid(x0, x1, [ym(hi * k / 4) for k in range(5)])
step = max(1, xmax // 8)
xt = [(str(v), xm(v)) for v in range(step, xmax + 1, step)] or [("1", xm(1))]
s += axis(x0, pb, x1, pt, xt, ticks_linear(0, hi, pb, pt, n=5),
"eval cycle number", "best single-round score")
items, per_opp = [], {}
for name in ("Corners", "Crazy", "Target"):
vals = maxes.get(name, [])
if not vals:
continue
c = COLORS[name]
pts = [(xm(i + 1), ym(v)) for i, v in enumerate(vals)]
per_opp[name] = vals
s += dots(pts, c)
s += polyline(pts, c, 2.5)
items.append((c, f"{name} - {len(vals)} evals"))
if len(items) >= 2: # combined best across opponents, cycle-aligned
comb = [max(vals[i] for vals in per_opp.values() if i < len(vals))
for i in range(ncyc)]
s += polyline([(xm(i + 1), ym(v)) for i, v in enumerate(comb)],
"#555555", 2.5, dash="6 4")
items.append(("#555555", "combined max"))
s += legend(items, x0 + 12, pb + 52)
return s
def panel_losses(rows, geo): def panel_losses(rows, geo):
_, pt, pb, x0, x1 = geo _, pt, pb, x0, x1 = geo
s = "" s = ""
@@ -370,6 +447,9 @@ def build_dashboard(campaign, metrics_f, out):
series = parse_eval_series(campaign) series = parse_eval_series(campaign)
print("[info] evals parsed: " + print("[info] evals parsed: " +
(", ".join(f"{k}={len(v)}" for k, v in sorted(series.items())) or "(none)")) (", ".join(f"{k}={len(v)}" for k, v in sorted(series.items())) or "(none)"))
maxes = parse_max_scores(campaign)
print("[info] eval max-score cycles parsed: " +
(", ".join(f"{k}={len(v)}" for k, v in sorted(maxes.items())) or "(none)"))
rows = parse_metrics(metrics_f) rows = parse_metrics(metrics_f)
print(f"[info] metric rows parsed: {len(rows)}") print(f"[info] metric rows parsed: {len(rows)}")
@@ -395,10 +475,13 @@ def build_dashboard(campaign, metrics_f, out):
(ROWS[4], PANEL_TITLES[3], (ROWS[4], PANEL_TITLES[3],
"method: training_metrics.jsonl 'epoch' deltas; 1 row = one 10-game chunk", "method: training_metrics.jsonl 'epoch' deltas; 1 row = one 10-game chunk",
lambda: panel_throughput(rows, ROWS[4])), lambda: panel_throughput(rows, ROWS[4])),
(ROWS[5], PANEL_TITLES[4],
"campaign_v4_stdout.log - max of the 10 deterministic round scores per eval",
lambda: panel_max_score(maxes, ROWS[5])),
] ]
for geo, title, sub, drawer in drawers: for geo, title, sub, drawer in drawers:
s += header(geo[3], geo[0], title, sub) s += header(geo[3], geo[0], title, sub)
s += drawer() s += drawer() or "" # panels bare-return None on their no-data path
s += guide_block(W, H, GUIDE_LINES) s += guide_block(W, H, GUIDE_LINES)
s += RELOAD_JS + "\n" s += RELOAD_JS + "\n"
@@ -412,14 +495,28 @@ def selftest():
with tempfile.TemporaryDirectory() as td: with tempfile.TemporaryDirectory() as td:
td = Path(td) td = Path(td)
(td / "log").write_text( (td / "log").write_text(
">>> [eval] win rate: 3/10 (30%) vs Corners\n" ">>> [eval] 10 deterministic rounds vs Corners\n"
"Round 1/10 - ticks:100 score:3 win:true\n"
"garbage line\n" "garbage line\n"
"Round 2/10 - ticks:100 score:7 win:false\n"
">>> [eval] win rate: 3/10 (30%) vs Corners\n"
">>> [eval] 10 deterministic rounds vs Crazy\n"
"Round 1/10 - ticks:100 score:70 win:false\n"
">>> [eval] win rate: 7/10 (70%) vs Crazy\n" ">>> [eval] win rate: 7/10 (70%) vs Crazy\n"
">>> [eval] win rate: broken\n" ">>> [eval] broken\n"
"Round 9/9 - ticks:1 score:999 win:false\n"
">>> [eval] 10 deterministic rounds vs Corners\n"
"Round 1/10 - ticks:100 score:50 win:true\n"
">>> [eval] win rate: 5/10 (50%) vs Corners\n" ">>> [eval] win rate: 5/10 (50%) vs Corners\n"
">>> [eval] 10 deterministic rounds vs Crazy\n"
"Round 1/10 - ticks:100 score:40 win:true\n"
">>> [eval] win rate: 4/10 (40%) vs Crazy\n") ">>> [eval] win rate: 4/10 (40%) vs Crazy\n")
ser = parse_eval_series(td / "log") ser = parse_eval_series(td / "log")
assert ser == {"Corners": [30.0, 50.0], "Crazy": [70.0, 40.0]}, ser assert ser == {"Corners": [30.0, 50.0], "Crazy": [70.0, 40.0]}, ser
mxs = parse_max_scores(td / "log")
# stray Round 999 after the unclosed '[eval] broken' line is ignored;
# per-cycle max of the Round scores above
assert mxs == {"Corners": [7, 50], "Crazy": [70, 40]}, mxs
assert rolling([10] * 25, 20)[-1] == 10.0 assert rolling([10] * 25, 20)[-1] == 10.0
assert rolling([1, 2, 3], 20) == [1.0, 1.5, 2.0] assert rolling([1, 2, 3], 20) == [1.0, 1.5, 2.0]
assert len(bucket_means(list(range(1287)), RATE_BUCKET)) == RATE_BUCKET assert len(bucket_means(list(range(1287)), RATE_BUCKET)) == RATE_BUCKET
@@ -443,12 +540,15 @@ def selftest():
assert esc(t) in text, f"panel title missing: {t}" assert esc(t) in text, f"panel title missing: {t}"
assert text.count(PANEL_TITLES[0]) == 1 assert text.count(PANEL_TITLES[0]) == 1
assert "How to read" in text, "reading guide missing" assert "How to read" in text, "reading guide missing"
assert 'width="1400"' in text and 'height="1720"' in text assert 'width="1400"' in text and 'height="2520"' in text
# circles: 4 eval dots (panel 1) + 2 throughput rate dots (panel 4) # circles: 4 eval dots (panel 1) + 2 throughput rate dots (panel 4)
assert text.count("<circle") == 6, text.count("<circle") # + 2 cycles x 2 opponents max-score dots (panel 5)
assert text.count("<circle") == 10, text.count("<circle")
# 2 trends + 3 losses-panel polylines (critic, actor, alpha) # 2 trends + 3 losses-panel polylines (critic, actor, alpha)
# + 1 dedicated alpha panel + 1 throughput # + 1 dedicated alpha panel + 1 throughput
assert text.count("<polyline") == 7, text.count("<polyline") # + 3 max-score panel (Corners, Crazy, combined; Target absent in fixture)
assert text.count("<polyline") == 10, text.count("<polyline")
assert "combined max" in text, "max-score combined line missing"
# orientation guard: a known rising series (10% -> 90%) rendered through # orientation guard: a known rising series (10% -> 90%) rendered through
# the FULL build path must plot upward (smaller SVG y) and forward in # the FULL build path must plot upward (smaller SVG y) and forward in