feat(SAC_LSTM_Bot): live campaign dashboard — five panels, auto-reload, run-1 comparison dropped

This commit is contained in:
2026-08-23 10:25:51 +02:00
parent 03a1853e57
commit 533a146342
6 changed files with 1043 additions and 672 deletions
+688
View File
@@ -0,0 +1,688 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="2000" viewBox="0 0 1400 2000" font-family="sans-serif">
<rect width="1400" height="2000" 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="56" text-anchor="middle" font-size="12" fill="#555">generated 2026-08-23 10:25:46 - 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="115" font-size="11" fill="#555">raw dots = single test matches, thick = rolling-mean-10</text>
<line x1="70" y1="120.0" x2="697" y2="120.0" stroke="#dddddd"/>
<line x1="70" y1="240.0" x2="697" y2="240.0" stroke="#dddddd"/>
<line x1="70" y1="360.0" x2="697" y2="360.0" stroke="#dddddd"/>
<line x1="70" y1="480.0" x2="697" y2="480.0" stroke="#dddddd"/>
<line x1="70" y1="600.0" x2="697" y2="600.0" stroke="#dddddd"/>
<line x1="70" y1="720.0" x2="697" y2="720.0" stroke="#dddddd"/>
<line x1="70" y1="720" x2="697" y2="720" stroke="black"/>
<line x1="70" y1="720" x2="70" y2="120" stroke="black"/>
<line x1="632.1" y1="720" x2="632.1" y2="724" stroke="black"/>
<text x="632.1" y="737" text-anchor="middle" font-size="11">10</text>
<line x1="560.1" y1="720" x2="560.1" y2="724" stroke="black"/>
<text x="560.1" y="737" text-anchor="middle" font-size="11">20</text>
<line x1="488.0" y1="720" x2="488.0" y2="724" stroke="black"/>
<text x="488.0" y="737" text-anchor="middle" font-size="11">30</text>
<line x1="415.9" y1="720" x2="415.9" y2="724" stroke="black"/>
<text x="415.9" y="737" text-anchor="middle" font-size="11">40</text>
<line x1="343.9" y1="720" x2="343.9" y2="724" stroke="black"/>
<text x="343.9" y="737" text-anchor="middle" font-size="11">50</text>
<line x1="271.8" y1="720" x2="271.8" y2="724" stroke="black"/>
<text x="271.8" y="737" text-anchor="middle" font-size="11">60</text>
<line x1="199.7" y1="720" x2="199.7" y2="724" stroke="black"/>
<text x="199.7" y="737" text-anchor="middle" font-size="11">70</text>
<line x1="127.7" y1="720" x2="127.7" y2="724" stroke="black"/>
<text x="127.7" y="737" text-anchor="middle" font-size="11">80</text>
<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>
<line x1="66" y1="600.0" x2="70" y2="600.0" stroke="black"/>
<text x="63" y="604.0" text-anchor="end" font-size="11">20</text>
<line x1="66" y1="480.0" x2="70" y2="480.0" stroke="black"/>
<text x="63" y="484.0" text-anchor="end" font-size="11">40</text>
<line x1="66" y1="360.0" x2="70" y2="360.0" stroke="black"/>
<text x="63" y="364.0" text-anchor="end" font-size="11">60</text>
<line x1="66" y1="240.0" x2="70" y2="240.0" stroke="black"/>
<text x="63" y="244.0" text-anchor="end" font-size="11">80</text>
<line x1="66" y1="120.0" x2="70" y2="120.0" stroke="black"/>
<text x="63" y="124.0" text-anchor="end" font-size="11">100</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>
<circle cx="697.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="689.8" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="682.6" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="675.4" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="668.2" cy="300.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="661.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="653.8" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="646.6" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="639.3" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="632.1" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="624.9" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="617.7" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="610.5" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="603.3" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="596.1" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="588.9" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="581.7" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="574.5" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="567.3" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="560.1" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="552.9" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="545.7" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="538.4" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="531.2" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="524.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="516.8" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="509.6" cy="180.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="502.4" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="495.2" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="488.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="480.8" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="473.6" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="466.4" cy="180.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="459.2" cy="180.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="452.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="444.8" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="437.6" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="430.3" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="423.1" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="415.9" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="408.7" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="401.5" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="394.3" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="387.1" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="379.9" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="372.7" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="365.5" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="358.3" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="351.1" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="343.9" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="336.7" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="329.4" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="322.2" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="315.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="307.8" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="300.6" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="293.4" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="286.2" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="279.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="271.8" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="264.6" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="257.4" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="250.2" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="243.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="235.8" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="228.6" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="221.3" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="214.1" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="206.9" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="199.7" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="192.5" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="185.3" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="178.1" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="170.9" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="163.7" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="156.5" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="149.3" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="142.1" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="134.9" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="127.7" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="120.4" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="113.2" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="106.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="98.8" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="91.6" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="84.4" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="77.2" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="70.0" cy="120.0" r="2" fill="#d62728" opacity="0.25"/>
<polyline points="697.0,120.0 689.8,120.0 682.6,120.0 675.4,120.0 668.2,156.0 661.0,150.0 653.8,145.7 646.6,142.5 639.3,140.0 632.1,138.0 624.9,138.0 617.7,138.0 610.5,138.0 603.3,138.0 596.1,120.0 588.9,120.0 581.7,120.0 574.5,120.0 567.3,120.0 560.1,120.0 552.9,120.0 545.7,120.0 538.4,120.0 531.2,120.0 524.0,120.0 516.8,120.0 509.6,126.0 502.4,126.0 495.2,126.0 488.0,126.0 480.8,126.0 473.6,126.0 466.4,132.0 459.2,138.0 452.0,138.0 444.8,138.0 437.6,132.0 430.3,132.0 423.1,132.0 415.9,132.0 408.7,132.0 401.5,132.0 394.3,126.0 387.1,120.0 379.9,120.0 372.7,120.0 365.5,120.0 358.3,120.0 351.1,120.0 343.9,120.0 336.7,120.0 329.4,120.0 322.2,120.0 315.0,120.0 307.8,120.0 300.6,120.0 293.4,120.0 286.2,120.0 279.0,120.0 271.8,120.0 264.6,120.0 257.4,120.0 250.2,120.0 243.0,120.0 235.8,120.0 228.6,120.0 221.3,120.0 214.1,120.0 206.9,120.0 199.7,120.0 192.5,120.0 185.3,120.0 178.1,120.0 170.9,120.0 163.7,120.0 156.5,120.0 149.3,120.0 142.1,120.0 134.9,120.0 127.7,120.0 120.4,120.0 113.2,120.0 106.0,120.0 98.8,120.0 91.6,120.0 84.4,120.0 77.2,120.0 70.0,120.0" fill="none" stroke="#d62728" stroke-width="3.5" opacity="1.0"/>
<circle cx="697.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="689.8" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="682.6" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="675.4" cy="660.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="668.2" cy="360.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="661.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="653.8" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="646.6" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="639.3" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="632.1" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="624.9" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="617.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="610.5" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="603.3" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="596.1" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="588.9" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="581.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="574.5" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="567.3" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="560.1" cy="480.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="552.9" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="545.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="538.4" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="531.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="524.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="516.8" cy="240.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="509.6" cy="240.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="502.4" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="495.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="488.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="480.8" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="473.6" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="466.4" cy="300.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="459.2" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="452.0" cy="240.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="444.8" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="437.6" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="430.3" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="423.1" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="415.9" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="408.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="401.5" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="394.3" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="387.1" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="379.9" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="372.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="365.5" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="358.3" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="351.1" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="343.9" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="336.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="329.4" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="322.2" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="315.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="307.8" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="300.6" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="293.4" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="286.2" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="279.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="271.8" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="264.6" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="257.4" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="250.2" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="243.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="235.8" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="228.6" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="221.3" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="214.1" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="206.9" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="199.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="192.5" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="185.3" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="178.1" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="170.9" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="163.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="156.5" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="149.3" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="142.1" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="134.9" cy="300.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="127.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="120.4" cy="240.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="113.2" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="106.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="98.8" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="91.6" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="84.4" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="77.2" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="70.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<polyline points="697.0,120.0 689.8,120.0 682.6,120.0 675.4,255.0 668.2,276.0 661.0,250.0 653.8,231.4 646.6,217.5 639.3,206.7 632.1,198.0 624.9,198.0 617.7,198.0 610.5,198.0 603.3,144.0 596.1,120.0 588.9,120.0 581.7,120.0 574.5,120.0 567.3,120.0 560.1,156.0 552.9,156.0 545.7,156.0 538.4,156.0 531.2,216.0 524.0,216.0 516.8,228.0 509.6,240.0 502.4,240.0 495.2,300.0 488.0,264.0 480.8,264.0 473.6,264.0 466.4,282.0 459.2,222.0 452.0,234.0 444.8,222.0 437.6,210.0 430.3,210.0 423.1,150.0 415.9,150.0 408.7,150.0 401.5,150.0 394.3,132.0 387.1,132.0 379.9,120.0 372.7,120.0 365.5,120.0 358.3,120.0 351.1,120.0 343.9,120.0 336.7,120.0 329.4,120.0 322.2,120.0 315.0,120.0 307.8,120.0 300.6,120.0 293.4,120.0 286.2,120.0 279.0,120.0 271.8,120.0 264.6,120.0 257.4,120.0 250.2,120.0 243.0,120.0 235.8,120.0 228.6,120.0 221.3,120.0 214.1,120.0 206.9,120.0 199.7,120.0 192.5,120.0 185.3,120.0 178.1,120.0 170.9,120.0 163.7,120.0 156.5,120.0 149.3,120.0 142.1,120.0 134.9,138.0 127.7,138.0 120.4,150.0 113.2,150.0 106.0,150.0 98.8,150.0 91.6,150.0 84.4,150.0 77.2,150.0 70.0,150.0" fill="none" stroke="#1f77b4" stroke-width="3.5" opacity="1.0"/>
<circle cx="697.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="689.8" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="682.6" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="675.4" cy="240.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="668.2" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="661.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="653.8" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="646.6" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="639.3" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="632.1" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="624.9" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="617.7" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="610.5" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="603.3" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="596.1" cy="180.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="588.9" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="581.7" cy="600.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="574.5" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="567.3" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="560.1" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="552.9" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="545.7" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="538.4" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="531.2" cy="180.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="524.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="516.8" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="509.6" cy="480.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="502.4" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="495.2" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="488.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="480.8" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="473.6" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="466.4" cy="180.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="459.2" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="452.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="444.8" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="437.6" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="430.3" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="423.1" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="415.9" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="408.7" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="401.5" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="394.3" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="387.1" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="379.9" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="372.7" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="365.5" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="358.3" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="351.1" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="343.9" cy="180.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="336.7" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="329.4" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="322.2" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="315.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="307.8" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="300.6" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="293.4" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="286.2" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="279.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="271.8" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="264.6" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="257.4" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="250.2" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="243.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="235.8" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="228.6" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="221.3" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="214.1" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="206.9" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="199.7" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="192.5" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="185.3" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="178.1" cy="180.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="170.9" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="163.7" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="156.5" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="149.3" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="142.1" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="134.9" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="127.7" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="120.4" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="113.2" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="106.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="98.8" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="91.6" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="84.4" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="77.2" cy="180.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="70.0" cy="120.0" r="2" fill="#2ca02c" opacity="0.25"/>
<polyline points="697.0,120.0 689.8,120.0 682.6,120.0 675.4,150.0 668.2,144.0 661.0,140.0 653.8,137.1 646.6,135.0 639.3,133.3 632.1,132.0 624.9,132.0 617.7,132.0 610.5,132.0 603.3,120.0 596.1,126.0 588.9,126.0 581.7,174.0 574.5,174.0 567.3,174.0 560.1,174.0 552.9,174.0 545.7,174.0 538.4,174.0 531.2,180.0 524.0,174.0 516.8,174.0 509.6,162.0 502.4,162.0 495.2,162.0 488.0,162.0 480.8,162.0 473.6,162.0 466.4,168.0 459.2,162.0 452.0,162.0 444.8,162.0 437.6,126.0 430.3,126.0 423.1,126.0 415.9,126.0 408.7,126.0 401.5,126.0 394.3,120.0 387.1,120.0 379.9,120.0 372.7,120.0 365.5,120.0 358.3,120.0 351.1,120.0 343.9,126.0 336.7,126.0 329.4,126.0 322.2,126.0 315.0,126.0 307.8,126.0 300.6,126.0 293.4,126.0 286.2,126.0 279.0,126.0 271.8,120.0 264.6,120.0 257.4,120.0 250.2,120.0 243.0,120.0 235.8,120.0 228.6,120.0 221.3,120.0 214.1,120.0 206.9,120.0 199.7,120.0 192.5,120.0 185.3,120.0 178.1,126.0 170.9,126.0 163.7,126.0 156.5,126.0 149.3,126.0 142.1,126.0 134.9,126.0 127.7,126.0 120.4,126.0 113.2,126.0 106.0,120.0 98.8,120.0 91.6,120.0 84.4,120.0 77.2,126.0 70.0,126.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"/>
<text x="116" y="776" font-size="12">Corners - 88 evals</text>
<line x1="82" y1="790" x2="110" y2="790" stroke="#1f77b4" stroke-width="3"/>
<text x="116" y="794" font-size="12">Crazy - 88 evals</text>
<line x1="82" y1="808" x2="110" y2="808" stroke="#2ca02c" stroke-width="3"/>
<text x="116" y="812" font-size="12">Target - 88 evals</text>
<text x="747" y="100" font-size="15" font-weight="bold">Real fights - win % per opponent</text>
<text x="747" y="115" font-size="11" fill="#555">training_log.jsonl only - learning in REAL battles, not tests</text>
<line x1="747" y1="120.0" x2="1375" y2="120.0" stroke="#dddddd"/>
<line x1="747" y1="240.0" x2="1375" y2="240.0" stroke="#dddddd"/>
<line x1="747" y1="360.0" x2="1375" y2="360.0" stroke="#dddddd"/>
<line x1="747" y1="480.0" x2="1375" y2="480.0" stroke="#dddddd"/>
<line x1="747" y1="600.0" x2="1375" y2="600.0" stroke="#dddddd"/>
<line x1="747" y1="720.0" x2="1375" y2="720.0" stroke="#dddddd"/>
<line x1="747" y1="720" x2="1375" y2="720" stroke="black"/>
<line x1="747" y1="720" x2="747" y2="120" stroke="black"/>
<line x1="1270.2" y1="720" x2="1270.2" y2="724" stroke="black"/>
<text x="1270.2" y="737" text-anchor="middle" font-size="11">300</text>
<line x1="1165.0" y1="720" x2="1165.0" y2="724" stroke="black"/>
<text x="1165.0" y="737" text-anchor="middle" font-size="11">600</text>
<line x1="1059.8" y1="720" x2="1059.8" y2="724" stroke="black"/>
<text x="1059.8" y="737" text-anchor="middle" font-size="11">900</text>
<line x1="954.6" y1="720" x2="954.6" y2="724" stroke="black"/>
<text x="954.6" y="737" text-anchor="middle" font-size="11">1200</text>
<line x1="849.4" y1="720" x2="849.4" y2="724" stroke="black"/>
<text x="849.4" y="737" text-anchor="middle" font-size="11">1500</text>
<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</text>
<line x1="743" y1="600.0" x2="747" y2="600.0" stroke="black"/>
<text x="740" y="604.0" text-anchor="end" font-size="11">20</text>
<line x1="743" y1="480.0" x2="747" y2="480.0" stroke="black"/>
<text x="740" y="484.0" text-anchor="end" font-size="11">40</text>
<line x1="743" y1="360.0" x2="747" y2="360.0" stroke="black"/>
<text x="740" y="364.0" text-anchor="end" font-size="11">60</text>
<line x1="743" y1="240.0" x2="747" y2="240.0" stroke="black"/>
<text x="740" y="244.0" text-anchor="end" font-size="11">80</text>
<line x1="743" y1="120.0" x2="747" y2="120.0" stroke="black"/>
<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">game number (100-game buckets)</text>
<text x="16" y="420" text-anchor="middle" font-size="12" transform="rotate(-90 16 420)">win %</text>
<polyline points="1357.8,120.0 1322.8,120.0 1287.7,120.0 1252.6,120.0 1217.6,120.0 1182.5,120.0 1147.4,120.0 1112.4,120.0 1077.3,120.0 1042.2,120.0 1007.2,120.0 972.1,120.0 937.0,120.0 902.0,120.0 866.9,120.0 831.9,120.0 796.8,120.0 763.1,120.0" fill="none" stroke="#1f77b4" stroke-width="3.5" opacity="1.0"/>
<polyline points="1287.7,120.0 1252.6,120.0 1217.6,120.0 1182.5,120.0 1147.4,120.0 1112.4,120.0 1077.3,120.0 1042.2,120.0" fill="none" stroke="#ff7f0e" stroke-width="3.5" opacity="1.0"/>
<polyline points="972.1,120.0 937.0,120.0 902.0,120.0" fill="none" stroke="#ff7f0e" stroke-width="3.5" opacity="1.0"/>
<polyline points="831.9,120.0 796.8,120.0 763.1,120.0" fill="none" stroke="#ff7f0e" stroke-width="3.5" opacity="1.0"/>
<polyline points="1357.8,120.0 1322.8,120.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<polyline points="1217.6,120.0 1182.5,120.0 1147.4,120.0 1112.4,120.0 1077.3,120.0 1042.2,120.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<polyline points="866.9,150.0 831.9,120.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<polyline points="1357.8,120.0 1322.8,120.0 1287.7,120.0 1252.6,120.0 1217.6,120.0 1182.5,120.0 1147.4,180.0 1112.4,120.0 1077.3,120.0" fill="none" stroke="#9467bd" stroke-width="3.5" opacity="1.0"/>
<polyline points="937.0,120.0 902.0,120.0" fill="none" stroke="#9467bd" stroke-width="3.5" opacity="1.0"/>
<polyline points="1357.8,120.0 1322.8,120.0 1287.7,120.0 1252.6,120.0 1217.6,120.0 1182.5,120.0 1147.4,120.0 1112.4,120.0 1077.3,120.0 1042.2,120.0 1007.2,120.0 972.1,120.0 937.0,120.0 902.0,120.0 866.9,120.0 831.9,120.0 796.8,120.0 763.1,120.0" fill="none" stroke="#d62728" stroke-width="3.5" opacity="1.0"/>
<line x1="759" y1="772" x2="787" y2="772" stroke="#1f77b4" stroke-width="3"/>
<text x="793" y="776" font-size="12">Crazy (420 games)</text>
<line x1="759" y1="790" x2="787" y2="790" stroke="#ff7f0e" stroke-width="3"/>
<text x="793" y="794" font-size="12">RamFire (420 games)</text>
<line x1="759" y1="808" x2="787" y2="808" stroke="#2ca02c" stroke-width="3"/>
<text x="793" y="812" font-size="12">Target (180 games)</text>
<line x1="759" y1="826" x2="787" y2="826" stroke="#9467bd" stroke-width="3"/>
<text x="793" y="830" font-size="12">SacTwin (170 games)</text>
<line x1="759" y1="844" x2="787" y2="844" stroke="#d62728" stroke-width="3"/>
<text x="793" y="848" font-size="12">Corners (602 games)</text>
<text x="70" y="940" font-size="15" font-weight="bold">Training losses (log scale)</text>
<text x="70" y="955" font-size="11" fill="#555">training_metrics.jsonl - big early spikes are normal</text>
<line x1="70" y1="958.0" x2="697" y2="958.0" stroke="#dddddd"/>
<line x1="70" y1="984.1" x2="697" y2="984.1" stroke="#dddddd"/>
<line x1="70" y1="1010.2" x2="697" y2="1010.2" stroke="#dddddd"/>
<line x1="70" y1="1036.3" x2="697" y2="1036.3" stroke="#dddddd"/>
<line x1="70" y1="1062.4" x2="697" y2="1062.4" stroke="#dddddd"/>
<line x1="70" y1="1088.6" x2="697" y2="1088.6" stroke="#dddddd"/>
<line x1="70" y1="1114.7" x2="697" y2="1114.7" stroke="#dddddd"/>
<line x1="70" y1="1140.8" x2="697" y2="1140.8" stroke="#dddddd"/>
<line x1="70" y1="1166.9" x2="697" y2="1166.9" stroke="#dddddd"/>
<line x1="70" y1="1193.0" x2="697" y2="1193.0" stroke="#dddddd"/>
<line x1="70" y1="1219.1" x2="697" y2="1219.1" stroke="#dddddd"/>
<line x1="70" y1="1245.2" x2="697" y2="1245.2" stroke="#dddddd"/>
<line x1="70" y1="1271.3" x2="697" y2="1271.3" stroke="#dddddd"/>
<line x1="70" y1="1297.4" x2="697" y2="1297.4" stroke="#dddddd"/>
<line x1="70" y1="1323.6" x2="697" y2="1323.6" stroke="#dddddd"/>
<line x1="70" y1="1349.7" x2="697" y2="1349.7" stroke="#dddddd"/>
<line x1="70" y1="1375.8" x2="697" y2="1375.8" stroke="#dddddd"/>
<line x1="70" y1="1401.9" x2="697" y2="1401.9" stroke="#dddddd"/>
<line x1="70" y1="1428.0" x2="697" y2="1428.0" stroke="#dddddd"/>
<line x1="70" y1="1428" x2="697" y2="1428" stroke="black"/>
<line x1="70" y1="1428" x2="70" y2="958" stroke="black"/>
<line x1="70.0" y1="1428" x2="70.0" y2="1432" stroke="black"/>
<text x="70.0" y="1445" text-anchor="middle" font-size="11">1</text>
<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">46</text>
<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">91</text>
<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">136</text>
<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">181</text>
<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.1</text>
<line x1="66" y1="1310.5" x2="70" y2="1310.5" stroke="black"/>
<text x="63" y="1314.5" text-anchor="end" font-size="11">3.16e+03</text>
<line x1="66" y1="1193.0" x2="70" y2="1193.0" stroke="black"/>
<text x="63" y="1197.0" text-anchor="end" font-size="11">1e+08</text>
<line x1="66" y1="1075.5" x2="70" y2="1075.5" stroke="black"/>
<text x="63" y="1079.5" text-anchor="end" font-size="11">3.16e+12</text>
<line x1="66" y1="958.0" x2="70" y2="958.0" stroke="black"/>
<text x="63" y="962.0" text-anchor="end" font-size="11">1e+17</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)">loss (log)</text>
<polyline points="70.0,1019.7 73.5,1017.3 77.0,1020.5 80.5,1025.1 83.9,1031.0 87.4,1034.4 90.9,1032.1 94.4,1028.3 97.9,1406.9 101.3,1015.1 104.8,992.8 108.3,1006.6 111.8,1019.3 115.3,1028.4 118.8,1034.5 122.2,1036.1 125.7,1037.9 129.2,1038.2 132.7,1040.5 136.2,1039.7 139.7,1037.4 143.2,1036.0 146.6,1282.9 150.1,1026.3 153.6,1022.0 157.1,1019.3 160.6,1321.5 164.1,1003.5 167.5,1003.6 171.0,1000.5 174.5,1002.1 178.0,999.7 181.5,995.6 184.9,993.1 188.4,993.9 191.9,993.5 195.4,981.3 198.9,982.4 202.4,988.2 205.8,976.7 209.3,970.8 212.8,1406.9 216.3,1406.9 219.8,994.3 223.3,1000.7 226.8,1010.6 230.2,1013.9 233.7,1406.9 237.2,1021.5 240.7,1026.3 244.2,1284.3 247.7,1028.8 251.1,1031.4 254.6,1023.0 258.1,1008.5 261.6,995.8 265.1,1010.7 268.6,1406.9 272.0,1406.9 275.5,1032.9 279.0,1035.7 282.5,1033.5 286.0,1033.4 289.4,1406.9 292.9,1033.4 296.4,1035.5 299.9,1031.4 303.4,1026.2 306.9,1030.7 310.4,1026.7 313.8,1032.0 317.3,1023.9 320.8,1020.4 324.3,1018.7 327.8,1011.6 331.2,988.2 334.7,1281.6 338.2,1010.6 341.7,1124.9 345.2,1229.8 348.7,1309.4 352.1,1326.7 355.6,1327.3 359.1,1328.0 362.6,1328.3 366.1,1328.6 369.6,1328.8 373.1,1328.6 376.5,1329.5 380.0,1330.0 383.5,1330.3 387.0,1330.7 390.5,1331.4 393.9,1331.9 397.4,1406.8 400.9,1406.8 404.4,1406.8 407.9,1406.7 411.4,1333.6 414.9,1334.1 418.3,1334.4 421.8,1334.8 425.3,1335.3 428.8,1335.8 432.3,1336.3 435.8,1336.7 439.2,1337.2 442.7,1337.7 446.2,1338.2 449.7,1339.1 453.2,1339.6 456.6,1340.1 460.1,1340.5 463.6,1341.0 467.1,1341.4 470.6,1341.9 474.1,1342.4 477.6,1342.9 481.0,1406.6 484.5,1343.7 488.0,1344.0 491.5,1344.4 495.0,1344.7 498.4,1345.1 501.9,1345.8 505.4,1346.1 508.9,1346.4 512.4,1346.8 515.9,1347.1 519.4,1347.5 522.8,1347.1 526.3,1348.1 529.8,1348.6 533.3,1406.6 536.8,1349.1 540.2,1349.5 543.7,1350.0 547.2,1406.6 550.7,1350.6 554.2,1350.9 557.7,1351.3 561.1,1351.6 564.6,1352.0 568.1,1352.3 571.6,1352.8 575.1,1353.2 578.6,1406.5 582.0,1354.0 585.5,1406.5 589.0,1354.7 592.5,1355.1 596.0,1355.5 599.5,1355.8 603.0,1356.1 606.4,1356.4 609.9,1356.6 613.4,1356.9 616.9,1357.3 620.4,1357.7 623.9,1358.0 627.3,1357.9 630.8,1358.7 634.3,1359.1 637.8,1406.4 641.3,1359.8 644.8,1360.4 648.2,1360.7 651.7,1361.0 655.2,1406.4 658.7,1406.4 662.2,1362.2 665.6,1362.8 669.1,1363.1 672.6,1363.4 676.1,1363.7 679.6,1364.0 683.1,1364.3 686.5,1364.6 690.0,1364.9 693.5,1365.2 697.0,1365.5" fill="none" stroke="#1f77b4" stroke-width="1.8" opacity="1.0"/>
<polyline points="70.0,997.4 73.5,1001.7 77.0,1003.6 80.5,1005.9 83.9,1008.8 87.4,1010.2 90.9,1009.3 94.4,1007.8 97.9,1004.3 101.3,1001.8 104.8,995.6 108.3,991.8 111.8,1001.7 115.3,1006.8 118.8,1010.0 122.2,1012.1 125.7,1012.9 129.2,1013.4 132.7,1014.3 136.2,1015.0 139.7,1014.8 143.2,1014.7 146.6,1014.2 150.1,1014.0 153.6,1013.0 157.1,1012.6 160.6,1011.0 164.1,1010.8 167.5,1008.1 171.0,1006.7 174.5,1005.5 178.0,1005.0 181.5,1005.2 184.9,1005.6 188.4,1003.9 191.9,1003.7 195.4,1005.1 198.9,1005.5 202.4,1003.9 205.8,1006.1 209.3,1006.8 212.8,1008.0 216.3,1010.9 219.8,1013.3 223.3,1014.3 226.8,1015.8 230.2,1016.7 233.7,1018.2 237.2,1019.2 240.7,1020.3 244.2,1021.2 247.7,1022.0 251.1,1022.7 254.6,1022.3 258.1,1022.1 261.6,1021.5 265.1,1020.4 268.6,1018.9 272.0,1016.9 275.5,1016.0 279.0,1015.0 282.5,1014.2 286.0,1014.1 289.4,1014.8 292.9,1015.7 296.4,1014.9 299.9,1015.7 303.4,1016.7 306.9,1015.7 310.4,1016.0 313.8,1015.0 317.3,1016.3 320.8,1016.7 324.3,1016.5 327.8,1016.9 331.2,1018.5 334.7,1021.0 338.2,1021.3 341.7,1054.0 345.2,1107.1 348.7,1146.9 352.1,1155.5 355.6,1155.8 359.1,1156.2 362.6,1156.3 366.1,1156.5 369.6,1156.6 373.1,1156.8 376.5,1156.9 380.0,1157.2 383.5,1157.3 387.0,1157.5 390.5,1157.9 393.9,1158.1 397.4,1158.2 400.9,1158.4 404.4,1158.5 407.9,1158.7 411.4,1159.0 414.9,1159.2 418.3,1159.4 421.8,1159.6 425.3,1159.8 428.8,1160.1 432.3,1160.3 435.8,1160.5 439.2,1160.8 442.7,1161.0 446.2,1161.3 449.7,1161.7 453.2,1162.0 456.6,1162.2 460.1,1162.4 463.6,1162.7 467.1,1162.9 470.6,1163.1 474.1,1163.4 477.6,1163.6 481.0,1163.8 484.5,1164.0 488.0,1164.2 491.5,1164.3 495.0,1164.5 498.4,1164.7 501.9,1165.1 505.4,1165.2 508.9,1165.4 512.4,1165.5 515.9,1165.7 519.4,1165.9 522.8,1166.1 526.3,1166.2 529.8,1166.5 533.3,1166.6 536.8,1166.7 540.2,1166.9 543.7,1167.2 547.2,1167.4 550.7,1167.5 554.2,1167.6 557.7,1167.8 561.1,1168.0 564.6,1168.2 568.1,1168.3 571.6,1168.5 575.1,1168.7 578.6,1169.0 582.0,1169.2 585.5,1169.3 589.0,1169.5 592.5,1169.7 596.0,1169.9 599.5,1170.1 603.0,1170.2 606.4,1170.4 609.9,1170.5 613.4,1170.6 616.9,1170.8 620.4,1171.0 623.9,1171.2 627.3,1171.4 630.8,1171.5 634.3,1171.7 637.8,1171.9 641.3,1172.2 644.8,1172.4 648.2,1172.5 651.7,1172.7 655.2,1172.9 658.7,1173.0 662.2,1173.2 665.6,1173.6 669.1,1173.7 672.6,1173.9 676.1,1174.0 679.6,1174.2 683.1,1174.3 686.5,1174.5 690.0,1174.6 693.5,1174.8 697.0,1174.9" fill="none" stroke="#ff7f0e" stroke-width="1.8" opacity="1.0"/>
<line x1="82" y1="1480" x2="110" y2="1480" stroke="#1f77b4" stroke-width="3"/>
<text x="116" y="1484" font-size="12">critic_loss</text>
<line x1="82" y1="1498" x2="110" y2="1498" stroke="#ff7f0e" stroke-width="3"/>
<text x="116" y="1502" font-size="12">|actor_loss|</text>
<text x="747" y="940" font-size="15" font-weight="bold">Alpha temperature</text>
<text x="747" y="955" font-size="11" fill="#555">training_metrics.jsonl - high = exploring, low = exploiting</text>
<line x1="747" y1="958.0" x2="1375" y2="958.0" stroke="#dddddd"/>
<line x1="747" y1="1075.5" x2="1375" y2="1075.5" stroke="#dddddd"/>
<line x1="747" y1="1193.0" x2="1375" y2="1193.0" stroke="#dddddd"/>
<line x1="747" y1="1310.5" x2="1375" y2="1310.5" stroke="#dddddd"/>
<line x1="747" y1="1428.0" x2="1375" y2="1428.0" stroke="#dddddd"/>
<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.0" y1="1428" x2="747.0" y2="1432" stroke="black"/>
<text x="747.0" y="1445" text-anchor="middle" font-size="11">1</text>
<line x1="904.0" y1="1428" x2="904.0" y2="1432" stroke="black"/>
<text x="904.0" y="1445" text-anchor="middle" font-size="11">46</text>
<line x1="1061.0" y1="1428" x2="1061.0" y2="1432" stroke="black"/>
<text x="1061.0" y="1445" text-anchor="middle" font-size="11">91</text>
<line x1="1218.0" y1="1428" x2="1218.0" y2="1432" stroke="black"/>
<text x="1218.0" y="1445" text-anchor="middle" font-size="11">136</text>
<line x1="1375.0" y1="1428" x2="1375.0" y2="1432" stroke="black"/>
<text x="1375.0" y="1445" text-anchor="middle" font-size="11">181</text>
<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>
<line x1="743" y1="1310.5" x2="747" y2="1310.5" stroke="black"/>
<text x="740" y="1314.5" text-anchor="end" font-size="11">0.252</text>
<line x1="743" y1="1193.0" x2="747" y2="1193.0" stroke="black"/>
<text x="740" y="1197.0" text-anchor="end" font-size="11">0.504</text>
<line x1="743" y1="1075.5" x2="747" y2="1075.5" stroke="black"/>
<text x="740" y="1079.5" text-anchor="end" font-size="11">0.756</text>
<line x1="743" y1="958.0" x2="747" y2="958.0" stroke="black"/>
<text x="740" y="962.0" text-anchor="end" font-size="11">1.01</text>
<text x="1061" 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>
<polyline points="747.0,1424.3 750.5,1423.7 754.0,1423.4 757.5,1423.0 761.0,1422.4 764.4,1421.5 767.9,1421.2 771.4,1420.9 774.9,1420.4 778.4,1420.1 781.9,1419.5 785.4,1418.6 788.9,1418.4 792.4,1419.0 795.8,1419.5 799.3,1420.1 802.8,1420.6 806.3,1421.2 809.8,1421.9 813.3,1422.4 816.8,1423.0 820.3,1423.5 823.8,1424.1 827.2,1424.7 830.7,1425.2 834.2,1425.5 837.7,1426.4 841.2,1426.7 844.7,1426.2 848.2,1425.9 851.7,1425.5 855.2,1425.2 858.6,1424.9 862.1,1424.5 865.6,1424.0 869.1,1423.5 872.6,1422.7 876.1,1422.1 879.6,1421.6 883.1,1421.0 886.6,1420.5 890.0,1420.0 893.5,1419.7 897.0,1420.1 900.5,1420.5 904.0,1420.8 907.5,1421.1 911.0,1421.6 914.5,1422.0 918.0,1422.5 921.4,1423.0 924.9,1423.6 928.4,1424.0 931.9,1424.4 935.4,1425.4 938.9,1425.9 942.4,1426.5 945.9,1427.5 949.4,1428.0 952.8,1427.6 956.3,1427.2 959.8,1426.6 963.3,1426.0 966.8,1425.5 970.3,1424.5 973.8,1423.9 977.3,1423.4 980.8,1422.8 984.2,1422.3 987.7,1422.0 991.2,1421.4 994.7,1420.9 998.2,1420.3 1001.7,1420.1 1005.2,1420.1 1008.7,1420.6 1012.2,1421.7 1015.6,1422.3 1019.1,1422.5 1022.6,1421.9 1026.1,1421.4 1029.6,1421.1 1033.1,1420.4 1036.6,1419.6 1040.1,1419.3 1043.6,1419.0 1047.0,1418.8 1050.5,1418.2 1054.0,1417.9 1057.5,1417.4 1061.0,1416.8 1064.5,1416.3 1068.0,1415.6 1071.5,1415.0 1075.0,1414.8 1078.4,1414.5 1081.9,1414.2 1085.4,1413.7 1088.9,1413.1 1092.4,1412.6 1095.9,1412.3 1099.4,1411.8 1102.9,1411.2 1106.4,1410.7 1109.8,1410.1 1113.3,1409.6 1116.8,1409.0 1120.3,1408.5 1123.8,1408.0 1127.3,1407.0 1130.8,1406.5 1134.3,1405.9 1137.8,1405.5 1141.2,1405.0 1144.7,1404.6 1148.2,1404.1 1151.7,1403.5 1155.2,1403.0 1158.7,1402.5 1162.2,1402.1 1165.7,1401.7 1169.2,1401.3 1172.6,1400.7 1176.1,1400.2 1179.6,1399.3 1183.1,1398.7 1186.6,1398.3 1190.1,1397.8 1193.6,1397.3 1197.1,1396.8 1200.6,1396.2 1204.0,1395.8 1207.5,1395.1 1211.0,1394.7 1214.5,1394.3 1218.0,1393.7 1221.5,1393.0 1225.0,1392.4 1228.5,1392.0 1232.0,1391.7 1235.4,1391.1 1238.9,1390.6 1242.4,1390.1 1245.9,1389.6 1249.4,1389.1 1252.9,1388.5 1256.4,1388.0 1259.9,1387.5 1263.4,1387.0 1266.8,1386.5 1270.3,1386.0 1273.8,1385.5 1277.3,1385.1 1280.8,1384.7 1284.3,1384.3 1287.8,1383.9 1291.3,1383.5 1294.8,1383.0 1298.2,1382.5 1301.7,1382.0 1305.2,1381.5 1308.7,1381.0 1312.2,1380.5 1315.7,1380.0 1319.2,1379.1 1322.7,1378.6 1326.2,1378.1 1329.6,1377.6 1333.1,1377.1 1336.6,1376.6 1340.1,1375.9 1343.6,1374.9 1347.1,1374.4 1350.6,1373.9 1354.1,1373.4 1357.6,1372.9 1361.0,1372.4 1364.5,1371.9 1368.0,1371.5 1371.5,1371.0 1375.0,1370.3" fill="none" stroke="#9467bd" stroke-width="1.8" opacity="1.0"/>
<text x="70" y="1600" font-size="15" font-weight="bold">Throughput - games per hour</text>
<text x="70" y="1615" font-size="11" fill="#555">method: training_metrics.jsonl 'epoch' deltas (training_log.jsonl has no timestamps); 1 row = one 10-game chunk</text>
<line x1="70" y1="1618.0" x2="1375" y2="1618.0" stroke="#dddddd"/>
<line x1="70" y1="1658.5" x2="1375" y2="1658.5" stroke="#dddddd"/>
<line x1="70" y1="1699.0" x2="1375" y2="1699.0" stroke="#dddddd"/>
<line x1="70" y1="1739.5" x2="1375" y2="1739.5" stroke="#dddddd"/>
<line x1="70" y1="1780.0" x2="1375" y2="1780.0" stroke="#dddddd"/>
<line x1="70" y1="1780" x2="1375" y2="1780" stroke="black"/>
<line x1="70" y1="1780" x2="70" y2="1618" stroke="black"/>
<line x1="1251.1" y1="1780" x2="1251.1" y2="1784" stroke="black"/>
<text x="1251.1" y="1797" text-anchor="middle" font-size="11">18</text>
<line x1="1119.8" y1="1780" x2="1119.8" y2="1784" stroke="black"/>
<text x="1119.8" y="1797" text-anchor="middle" font-size="11">36</text>
<line x1="988.6" y1="1780" x2="988.6" y2="1784" stroke="black"/>
<text x="988.6" y="1797" text-anchor="middle" font-size="11">54</text>
<line x1="857.4" y1="1780" x2="857.4" y2="1784" stroke="black"/>
<text x="857.4" y="1797" text-anchor="middle" font-size="11">72</text>
<line x1="726.1" y1="1780" x2="726.1" y2="1784" stroke="black"/>
<text x="726.1" y="1797" text-anchor="middle" font-size="11">90</text>
<line x1="594.9" y1="1780" x2="594.9" y2="1784" stroke="black"/>
<text x="594.9" y="1797" text-anchor="middle" font-size="11">108</text>
<line x1="463.7" y1="1780" x2="463.7" y2="1784" stroke="black"/>
<text x="463.7" y="1797" text-anchor="middle" font-size="11">126</text>
<line x1="332.5" y1="1780" x2="332.5" y2="1784" stroke="black"/>
<text x="332.5" y="1797" text-anchor="middle" font-size="11">144</text>
<line x1="201.2" y1="1780" x2="201.2" y2="1784" stroke="black"/>
<text x="201.2" y="1797" text-anchor="middle" font-size="11">162</text>
<line x1="70.0" y1="1780" x2="70.0" y2="1784" stroke="black"/>
<text x="70.0" y="1797" text-anchor="middle" font-size="11">180</text>
<line x1="66" y1="1780.0" x2="70" y2="1780.0" stroke="black"/>
<text x="63" y="1784.0" text-anchor="end" font-size="11">0</text>
<line x1="66" y1="1739.5" x2="70" y2="1739.5" stroke="black"/>
<text x="63" y="1743.5" text-anchor="end" font-size="11">1688</text>
<line x1="66" y1="1699.0" x2="70" y2="1699.0" stroke="black"/>
<text x="63" y="1703.0" text-anchor="end" font-size="11">3375</text>
<line x1="66" y1="1658.5" x2="70" y2="1658.5" stroke="black"/>
<text x="63" y="1662.5" text-anchor="end" font-size="11">5063</text>
<line x1="66" y1="1618.0" x2="70" y2="1618.0" stroke="black"/>
<text x="63" y="1622.0" text-anchor="end" font-size="11">6750</text>
<text x="722" y="1813" text-anchor="middle" font-size="12">chunk interval (10-game chunks)</text>
<text x="16" y="1699" text-anchor="middle" font-size="12" transform="rotate(-90 16 1699)">games / hour</text>
<circle cx="1375.0" cy="1700.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1367.7" cy="1623.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1360.4" cy="1739.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1353.1" cy="1621.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1345.8" cy="1635.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1338.5" cy="1622.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1331.3" cy="1742.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1324.0" cy="1622.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1316.7" cy="1743.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1309.4" cy="1622.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1302.1" cy="1635.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1294.8" cy="1624.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1287.5" cy="1708.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1280.2" cy="1625.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1272.9" cy="1699.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1265.6" cy="1626.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1258.4" cy="1704.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1251.1" cy="1625.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1243.8" cy="1701.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1236.5" cy="1626.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1229.2" cy="1706.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1221.9" cy="1626.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1214.6" cy="1708.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1207.3" cy="1626.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1200.0" cy="1744.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1192.7" cy="1624.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1185.4" cy="1710.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1178.2" cy="1625.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1170.9" cy="1744.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1163.6" cy="1622.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1156.3" cy="1747.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1149.0" cy="1624.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1141.7" cy="1716.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1134.4" cy="1623.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1127.1" cy="1710.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1119.8" cy="1624.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1112.5" cy="1706.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1105.3" cy="1623.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1098.0" cy="1707.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1090.7" cy="1622.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1083.4" cy="1707.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1076.1" cy="1621.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1068.8" cy="1738.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1061.5" cy="1625.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1054.2" cy="1752.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1046.9" cy="1624.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1039.6" cy="1717.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1032.3" cy="1625.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1025.1" cy="1731.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1017.8" cy="1622.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1010.5" cy="1708.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1003.2" cy="1625.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="995.9" cy="1718.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="988.6" cy="1622.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="981.3" cy="1712.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="974.0" cy="1622.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="966.7" cy="1635.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="959.4" cy="1622.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="952.2" cy="1703.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="944.9" cy="1622.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="937.6" cy="1717.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="930.3" cy="1622.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="923.0" cy="1716.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="915.7" cy="1622.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="908.4" cy="1706.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="901.1" cy="1625.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="893.8" cy="1714.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="886.5" cy="1623.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="879.2" cy="1755.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="872.0" cy="1622.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="864.7" cy="1714.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="857.4" cy="1623.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="850.1" cy="1758.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="842.8" cy="1622.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="835.5" cy="1701.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="828.2" cy="1622.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="820.9" cy="1706.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="813.6" cy="1624.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="806.3" cy="1701.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="799.1" cy="1626.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="791.8" cy="1765.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="784.5" cy="1640.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="777.2" cy="1626.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="769.9" cy="1756.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="762.6" cy="1628.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="755.3" cy="1747.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="748.0" cy="1626.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="740.7" cy="1759.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="733.4" cy="1627.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="726.1" cy="1708.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="718.9" cy="1627.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="711.6" cy="1692.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="704.3" cy="1626.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="697.0" cy="1745.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="689.7" cy="1626.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="682.4" cy="1737.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="675.1" cy="1626.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="667.8" cy="1716.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="660.5" cy="1627.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="653.2" cy="1754.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="645.9" cy="1627.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="638.7" cy="1707.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="631.4" cy="1626.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="624.1" cy="1713.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="616.8" cy="1628.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="609.5" cy="1709.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="602.2" cy="1628.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="594.9" cy="1702.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="587.6" cy="1624.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="580.3" cy="1703.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="573.0" cy="1627.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="565.8" cy="1724.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="558.5" cy="1627.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="551.2" cy="1721.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="543.9" cy="1627.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="536.6" cy="1706.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="529.3" cy="1628.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="522.0" cy="1705.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="514.7" cy="1627.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="507.4" cy="1727.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="500.1" cy="1627.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="492.8" cy="1700.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="485.6" cy="1627.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="478.3" cy="1635.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="471.0" cy="1627.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="463.7" cy="1709.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="456.4" cy="1627.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="449.1" cy="1707.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="441.8" cy="1626.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="434.5" cy="1704.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="427.2" cy="1628.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="419.9" cy="1637.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="412.7" cy="1628.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="405.4" cy="1715.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="398.1" cy="1627.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="390.8" cy="1636.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="383.5" cy="1627.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="376.2" cy="1717.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="368.9" cy="1628.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="361.6" cy="1716.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="354.3" cy="1628.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="347.0" cy="1705.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="339.7" cy="1626.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="332.5" cy="1712.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="325.2" cy="1626.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="317.9" cy="1707.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="310.6" cy="1626.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="303.3" cy="1701.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="296.0" cy="1627.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="288.7" cy="1715.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="281.4" cy="1626.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="274.1" cy="1725.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="266.8" cy="1627.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="259.6" cy="1722.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="252.3" cy="1628.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="245.0" cy="1729.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="237.7" cy="1626.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="230.4" cy="1703.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="223.1" cy="1626.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="215.8" cy="1702.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="208.5" cy="1626.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="201.2" cy="1708.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="193.9" cy="1626.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="186.6" cy="1636.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="179.4" cy="1626.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="172.1" cy="1709.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="164.8" cy="1626.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="157.5" cy="1713.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="150.2" cy="1625.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="142.9" cy="1695.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="135.6" cy="1623.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="128.3" cy="1696.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="121.0" cy="1625.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="113.7" cy="1709.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="106.5" cy="1625.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="99.2" cy="1701.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="91.9" cy="1625.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="84.6" cy="1703.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="77.3" cy="1626.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="70.0" cy="1690.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<polyline points="1353.1,1672.3 1280.2,1652.5 1221.9,1675.0 1149.0,1670.9 1090.7,1678.4 1017.8,1666.3 959.4,1663.9 886.5,1667.5 828.2,1681.1 755.3,1680.1 697.0,1669.6 624.1,1677.7 565.8,1665.7 492.8,1665.2 434.5,1656.0 361.6,1666.7 303.3,1665.0 230.4,1674.8 172.1,1653.7 99.2,1667.1" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<rect x="0" y="1820" width="1400" height="180" fill="#f2f2f2"/>
<text x="16" y="1841" font-size="15" font-weight="bold">How to read</text>
<text x="1384" y="1840" text-anchor="end" font-size="11" fill="#666">Regenerate anytime: python3 tools/plot_progress.py</text>
<text x="16" y="1863" font-size="13">Test matches: dots are single fights, thick line shows trend.</text>
<text x="16" y="1880" font-size="13">Real battles only. Rising lines mean the bot improves.</text>
<text x="16" y="1897" font-size="13">Loss spikes are normal early; endless growth is bad.</text>
<text x="16" y="1914" font-size="13">Alpha high means experimenting; falling too fast freezes habits.</text>
<text x="16" y="1931" font-size="13">Throughput flat is healthy; dips mean something slowed.</text>
<text x="16" y="1948" font-size="13">This file reloads itself in Chrome every sixty seconds.</text>
<text x="16" y="1965" 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>
</svg>

After

Width:  |  Height:  |  Size: 59 KiB

+1 -1
View File
@@ -321,4 +321,4 @@ SACLSTM_LR_CRITIC=1e-4 \
### Progress graphs ### Progress graphs
Graphs live in `docs/` (`graph_eval_winrates.svg`, `graph_losses.svg`). Regenerate anytime: `python3 tools/plot_progress.py` (pure stdlib, ~0.05 s; paths overridable via argv, `--selftest` for sanity check). 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).
-346
View File
@@ -1,346 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="962" viewBox="0 0 1200 962" font-family="sans-serif">
<rect width="1200" height="962" fill="white"/>
<text x="600" y="22" text-anchor="middle" font-size="17" font-weight="bold">SAC-LSTM campaign: eval win rate vs opponents (Run 3 live, Run 1 archived)</text>
<line x1="64" y1="78.0" x2="1176" y2="78.0" stroke="#dddddd"/>
<line x1="64" y1="140.0" x2="1176" y2="140.0" stroke="#dddddd"/>
<line x1="64" y1="202.0" x2="1176" y2="202.0" stroke="#dddddd"/>
<line x1="64" y1="264.0" x2="1176" y2="264.0" stroke="#dddddd"/>
<line x1="64" y1="326.0" x2="1176" y2="326.0" stroke="#dddddd"/>
<line x1="64" y1="388.0" x2="1176" y2="388.0" stroke="#dddddd"/>
<text x="70" y="44" font-size="14" font-weight="bold">Run 3 — recent trend</text>
<text x="70" y="62" font-size="11" fill="#555">win % per test match — thick line = trend</text>
<line x1="64" y1="388" x2="1176" y2="388" stroke="black"/>
<line x1="64" y1="388" x2="64" y2="78" stroke="black"/>
<line x1="1052.4" y1="388" x2="1052.4" y2="392" stroke="black"/>
<text x="1052.4" y="405" text-anchor="middle" font-size="11">10</text>
<line x1="915.2" y1="388" x2="915.2" y2="392" stroke="black"/>
<text x="915.2" y="405" text-anchor="middle" font-size="11">20</text>
<line x1="777.9" y1="388" x2="777.9" y2="392" stroke="black"/>
<text x="777.9" y="405" text-anchor="middle" font-size="11">30</text>
<line x1="640.6" y1="388" x2="640.6" y2="392" stroke="black"/>
<text x="640.6" y="405" text-anchor="middle" font-size="11">40</text>
<line x1="503.3" y1="388" x2="503.3" y2="392" stroke="black"/>
<text x="503.3" y="405" text-anchor="middle" font-size="11">50</text>
<line x1="366.0" y1="388" x2="366.0" y2="392" stroke="black"/>
<text x="366.0" y="405" text-anchor="middle" font-size="11">60</text>
<line x1="228.7" y1="388" x2="228.7" y2="392" stroke="black"/>
<text x="228.7" y="405" text-anchor="middle" font-size="11">70</text>
<line x1="91.5" y1="388" x2="91.5" y2="392" stroke="black"/>
<text x="91.5" y="405" text-anchor="middle" font-size="11">80</text>
<line x1="60" y1="388.0" x2="64" y2="388.0" stroke="black"/>
<text x="57" y="392.0" text-anchor="end" font-size="11">0</text>
<line x1="60" y1="326.0" x2="64" y2="326.0" stroke="black"/>
<text x="57" y="330.0" text-anchor="end" font-size="11">20</text>
<line x1="60" y1="264.0" x2="64" y2="264.0" stroke="black"/>
<text x="57" y="268.0" text-anchor="end" font-size="11">40</text>
<line x1="60" y1="202.0" x2="64" y2="202.0" stroke="black"/>
<text x="57" y="206.0" text-anchor="end" font-size="11">60</text>
<line x1="60" y1="140.0" x2="64" y2="140.0" stroke="black"/>
<text x="57" y="144.0" text-anchor="end" font-size="11">80</text>
<line x1="60" y1="78.0" x2="64" y2="78.0" stroke="black"/>
<text x="57" y="82.0" text-anchor="end" font-size="11">100</text>
<text x="620" y="421" text-anchor="middle" font-size="12">eval number</text>
<text x="16" y="233" text-anchor="middle" font-size="12" transform="rotate(-90 16 233)">win rate (%)</text>
<circle cx="1176.0" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1162.3" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1148.5" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1134.8" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1121.1" cy="171.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1107.4" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1093.6" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1079.9" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1066.2" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1052.4" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1038.7" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1025.0" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="1011.3" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="997.5" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="983.8" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="970.1" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="956.3" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="942.6" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="928.9" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="915.2" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="901.4" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="887.7" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="874.0" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="860.2" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="846.5" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="832.8" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="819.1" cy="109.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="805.3" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="791.6" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="777.9" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="764.1" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="750.4" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="736.7" cy="109.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="723.0" cy="109.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="709.2" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="695.5" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="681.8" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="668.0" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="654.3" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="640.6" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="626.9" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="613.1" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="599.4" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="585.7" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="572.0" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="558.2" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="544.5" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="530.8" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="517.0" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="503.3" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="489.6" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="475.9" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="462.1" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="448.4" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="434.7" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="420.9" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="407.2" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="393.5" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="379.8" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="366.0" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="352.3" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="338.6" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="324.8" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="311.1" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="297.4" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="283.7" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="269.9" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="256.2" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="242.5" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="228.7" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="215.0" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="201.3" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="187.6" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="173.8" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="160.1" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="146.4" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="132.6" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="118.9" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="105.2" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="91.5" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="77.7" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="64.0" cy="78.0" r="2" fill="#d62728" opacity="0.25"/>
<polyline points="1176.0,78.0 1162.3,78.0 1148.5,78.0 1134.8,78.0 1121.1,96.6 1107.4,93.5 1093.6,91.3 1079.9,89.6 1066.2,88.3 1052.4,87.3 1038.7,87.3 1025.0,87.3 1011.3,87.3 997.5,87.3 983.8,78.0 970.1,78.0 956.3,78.0 942.6,78.0 928.9,78.0 915.2,78.0 901.4,78.0 887.7,78.0 874.0,78.0 860.2,78.0 846.5,78.0 832.8,78.0 819.1,81.1 805.3,81.1 791.6,81.1 777.9,81.1 764.1,81.1 750.4,81.1 736.7,84.2 723.0,87.3 709.2,87.3 695.5,87.3 681.8,84.2 668.0,84.2 654.3,84.2 640.6,84.2 626.9,84.2 613.1,84.2 599.4,81.1 585.7,78.0 572.0,78.0 558.2,78.0 544.5,78.0 530.8,78.0 517.0,78.0 503.3,78.0 489.6,78.0 475.9,78.0 462.1,78.0 448.4,78.0 434.7,78.0 420.9,78.0 407.2,78.0 393.5,78.0 379.8,78.0 366.0,78.0 352.3,78.0 338.6,78.0 324.8,78.0 311.1,78.0 297.4,78.0 283.7,78.0 269.9,78.0 256.2,78.0 242.5,78.0 228.7,78.0 215.0,78.0 201.3,78.0 187.6,78.0 173.8,78.0 160.1,78.0 146.4,78.0 132.6,78.0 118.9,78.0 105.2,78.0 91.5,78.0 77.7,78.0 64.0,78.0" fill="none" stroke="#d62728" stroke-width="3.5" opacity="1.0"/>
<circle cx="1176.0" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1162.3" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1148.5" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1134.8" cy="357.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1121.1" cy="202.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1107.4" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1093.6" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1079.9" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1066.2" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1052.4" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1038.7" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1025.0" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="1011.3" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="997.5" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="983.8" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="970.1" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="956.3" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="942.6" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="928.9" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="915.2" cy="264.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="901.4" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="887.7" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="874.0" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="860.2" cy="388.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="846.5" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="832.8" cy="140.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="819.1" cy="140.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="805.3" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="791.6" cy="388.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="777.9" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="764.1" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="750.4" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="736.7" cy="171.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="723.0" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="709.2" cy="140.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="695.5" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="681.8" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="668.0" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="654.3" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="640.6" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="626.9" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="613.1" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="599.4" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="585.7" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="572.0" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="558.2" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="544.5" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="530.8" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="517.0" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="503.3" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="489.6" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="475.9" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="462.1" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="448.4" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="434.7" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="420.9" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="407.2" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="393.5" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="379.8" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="366.0" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="352.3" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="338.6" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="324.8" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="311.1" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="297.4" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="283.7" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="269.9" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="256.2" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="242.5" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="228.7" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="215.0" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="201.3" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="187.6" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="173.8" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="160.1" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="146.4" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="132.6" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="118.9" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="105.2" cy="171.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="91.5" cy="78.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="77.7" cy="140.0" r="2" fill="#1f77b4" opacity="0.25"/>
<polyline points="1176.0,78.0 1162.3,78.0 1148.5,78.0 1134.8,147.8 1121.1,158.6 1107.4,145.2 1093.6,135.6 1079.9,128.4 1066.2,122.8 1052.4,118.3 1038.7,118.3 1025.0,118.3 1011.3,118.3 997.5,90.4 983.8,78.0 970.1,78.0 956.3,78.0 942.6,78.0 928.9,78.0 915.2,96.6 901.4,96.6 887.7,96.6 874.0,96.6 860.2,127.6 846.5,127.6 832.8,133.8 819.1,140.0 805.3,140.0 791.6,171.0 777.9,152.4 764.1,152.4 750.4,152.4 736.7,161.7 723.0,130.7 709.2,136.9 695.5,130.7 681.8,124.5 668.0,124.5 654.3,93.5 640.6,93.5 626.9,93.5 613.1,93.5 599.4,84.2 585.7,84.2 572.0,78.0 558.2,78.0 544.5,78.0 530.8,78.0 517.0,78.0 503.3,78.0 489.6,78.0 475.9,78.0 462.1,78.0 448.4,78.0 434.7,78.0 420.9,78.0 407.2,78.0 393.5,78.0 379.8,78.0 366.0,78.0 352.3,78.0 338.6,78.0 324.8,78.0 311.1,78.0 297.4,78.0 283.7,78.0 269.9,78.0 256.2,78.0 242.5,78.0 228.7,78.0 215.0,78.0 201.3,78.0 187.6,78.0 173.8,78.0 160.1,78.0 146.4,78.0 132.6,78.0 118.9,78.0 105.2,87.3 91.5,87.3 77.7,93.5" fill="none" stroke="#1f77b4" stroke-width="3.5" opacity="1.0"/>
<circle cx="1176.0" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1162.3" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1148.5" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1134.8" cy="140.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1121.1" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1107.4" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1093.6" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1079.9" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1066.2" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1052.4" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1038.7" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1025.0" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="1011.3" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="997.5" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="983.8" cy="109.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="970.1" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="956.3" cy="326.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="942.6" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="928.9" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="915.2" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="901.4" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="887.7" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="874.0" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="860.2" cy="109.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="846.5" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="832.8" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="819.1" cy="264.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="805.3" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="791.6" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="777.9" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="764.1" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="750.4" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="736.7" cy="109.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="723.0" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="709.2" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="695.5" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="681.8" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="668.0" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="654.3" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="640.6" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="626.9" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="613.1" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="599.4" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="585.7" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="572.0" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="558.2" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="544.5" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="530.8" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="517.0" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="503.3" cy="109.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="489.6" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="475.9" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="462.1" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="448.4" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="434.7" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="420.9" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="407.2" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="393.5" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="379.8" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="366.0" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="352.3" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="338.6" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="324.8" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="311.1" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="297.4" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="283.7" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="269.9" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="256.2" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="242.5" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="228.7" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="215.0" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="201.3" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="187.6" cy="109.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="173.8" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="160.1" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="146.4" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="132.6" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="118.9" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="105.2" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="91.5" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="77.7" cy="78.0" r="2" fill="#2ca02c" opacity="0.25"/>
<polyline points="1176.0,78.0 1162.3,78.0 1148.5,78.0 1134.8,93.5 1121.1,90.4 1107.4,88.3 1093.6,86.9 1079.9,85.8 1066.2,84.9 1052.4,84.2 1038.7,84.2 1025.0,84.2 1011.3,84.2 997.5,78.0 983.8,81.1 970.1,81.1 956.3,105.9 942.6,105.9 928.9,105.9 915.2,105.9 901.4,105.9 887.7,105.9 874.0,105.9 860.2,109.0 846.5,105.9 832.8,105.9 819.1,99.7 805.3,99.7 791.6,99.7 777.9,99.7 764.1,99.7 750.4,99.7 736.7,102.8 723.0,99.7 709.2,99.7 695.5,99.7 681.8,81.1 668.0,81.1 654.3,81.1 640.6,81.1 626.9,81.1 613.1,81.1 599.4,78.0 585.7,78.0 572.0,78.0 558.2,78.0 544.5,78.0 530.8,78.0 517.0,78.0 503.3,81.1 489.6,81.1 475.9,81.1 462.1,81.1 448.4,81.1 434.7,81.1 420.9,81.1 407.2,81.1 393.5,81.1 379.8,81.1 366.0,78.0 352.3,78.0 338.6,78.0 324.8,78.0 311.1,78.0 297.4,78.0 283.7,78.0 269.9,78.0 256.2,78.0 242.5,78.0 228.7,78.0 215.0,78.0 201.3,78.0 187.6,81.1 173.8,81.1 160.1,81.1 146.4,81.1 132.6,81.1 118.9,81.1 105.2,81.1 91.5,81.1 77.7,81.1" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<line x1="76" y1="92" x2="104" y2="92" stroke="#d62728" stroke-width="3"/>
<text x="110" y="96" font-size="12">Corners — 82 evals</text>
<line x1="76" y1="110" x2="104" y2="110" stroke="#1f77b4" stroke-width="3"/>
<text x="110" y="114" font-size="12">Crazy — 81 evals</text>
<line x1="76" y1="128" x2="104" y2="128" stroke="#2ca02c" stroke-width="3"/>
<text x="110" y="132" font-size="12">Target — 81 evals</text>
<line x1="64" y1="472.0" x2="1176" y2="472.0" stroke="#dddddd"/>
<line x1="64" y1="534.0" x2="1176" y2="534.0" stroke="#dddddd"/>
<line x1="64" y1="596.0" x2="1176" y2="596.0" stroke="#dddddd"/>
<line x1="64" y1="658.0" x2="1176" y2="658.0" stroke="#dddddd"/>
<line x1="64" y1="720.0" x2="1176" y2="720.0" stroke="#dddddd"/>
<line x1="64" y1="782.0" x2="1176" y2="782.0" stroke="#dddddd"/>
<text x="70" y="450" font-size="14" font-weight="bold">Run 1 vs Run 3 (Corners) — same enemy — did learning stick?</text>
<line x1="64" y1="782" x2="1176" y2="782" stroke="black"/>
<line x1="64" y1="782" x2="64" y2="472" stroke="black"/>
<line x1="64.0" y1="782" x2="64.0" y2="786" stroke="black"/>
<text x="64.0" y="799" text-anchor="middle" font-size="11">0</text>
<line x1="286.4" y1="782" x2="286.4" y2="786" stroke="black"/>
<text x="286.4" y="799" text-anchor="middle" font-size="11">0.2</text>
<line x1="508.8" y1="782" x2="508.8" y2="786" stroke="black"/>
<text x="508.8" y="799" text-anchor="middle" font-size="11">0.4</text>
<line x1="731.2" y1="782" x2="731.2" y2="786" stroke="black"/>
<text x="731.2" y="799" text-anchor="middle" font-size="11">0.6</text>
<line x1="953.6" y1="782" x2="953.6" y2="786" stroke="black"/>
<text x="953.6" y="799" text-anchor="middle" font-size="11">0.8</text>
<line x1="1176.0" y1="782" x2="1176.0" y2="786" stroke="black"/>
<text x="1176.0" y="799" text-anchor="middle" font-size="11">1</text>
<line x1="60" y1="782.0" x2="64" y2="782.0" stroke="black"/>
<text x="57" y="786.0" text-anchor="end" font-size="11">0</text>
<line x1="60" y1="720.0" x2="64" y2="720.0" stroke="black"/>
<text x="57" y="724.0" text-anchor="end" font-size="11">20</text>
<line x1="60" y1="658.0" x2="64" y2="658.0" stroke="black"/>
<text x="57" y="662.0" text-anchor="end" font-size="11">40</text>
<line x1="60" y1="596.0" x2="64" y2="596.0" stroke="black"/>
<text x="57" y="600.0" text-anchor="end" font-size="11">60</text>
<line x1="60" y1="534.0" x2="64" y2="534.0" stroke="black"/>
<text x="57" y="538.0" text-anchor="end" font-size="11">80</text>
<line x1="60" y1="472.0" x2="64" y2="472.0" stroke="black"/>
<text x="57" y="476.0" text-anchor="end" font-size="11">100</text>
<text x="620" y="815" text-anchor="middle" font-size="12">fraction of run (start → end)</text>
<text x="16" y="627" text-anchor="middle" font-size="12" transform="rotate(-90 16 627)">win rate (%)</text>
<polyline points="77.9,473.9 105.7,476.7 133.5,484.2 161.3,508.6 189.1,499.2 216.9,494.5 244.7,495.5 272.5,492.3 300.3,505.9 328.1,489.4 355.9,491.4 383.7,514.6 411.5,495.2 439.3,492.3 467.1,503.0 494.9,491.4 522.7,497.2 550.5,502.0 578.3,504.0 606.1,495.2 633.9,507.8 661.7,510.8 689.5,494.3 717.3,481.7 745.1,511.7 772.9,501.1 800.7,502.0 828.5,492.3 856.3,489.4 884.1,500.1 911.9,477.8 939.7,492.3 967.5,491.4 995.3,519.5 1023.1,502.0 1050.9,490.4 1078.7,487.5 1106.5,490.4 1134.3,490.4 1162.1,496.2" fill="none" stroke="#999999" stroke-width="1.5" opacity="1.0"/>
<polyline points="77.6,472.0 91.1,472.0 104.7,472.0 118.2,472.0 131.8,490.6 145.4,487.5 158.9,485.3 172.5,483.6 186.0,482.3 199.6,481.3 213.2,481.3 226.7,481.3 240.3,481.3 253.9,481.3 267.4,472.0 281.0,472.0 294.5,472.0 308.1,472.0 321.7,472.0 335.2,472.0 348.8,472.0 362.3,472.0 375.9,472.0 389.5,472.0 403.0,472.0 416.6,472.0 430.1,475.1 443.7,475.1 457.3,475.1 470.8,475.1 484.4,475.1 498.0,475.1 511.5,478.2 525.1,481.3 538.6,481.3 552.2,481.3 565.8,478.2 579.3,478.2 592.9,478.2 606.4,478.2 620.0,478.2 633.6,478.2 647.1,475.1 660.7,472.0 674.2,472.0 687.8,472.0 701.4,472.0 714.9,472.0 728.5,472.0 742.0,472.0 755.6,472.0 769.2,472.0 782.7,472.0 796.3,472.0 809.9,472.0 823.4,472.0 837.0,472.0 850.5,472.0 864.1,472.0 877.7,472.0 891.2,472.0 904.8,472.0 918.3,472.0 931.9,472.0 945.5,472.0 959.0,472.0 972.6,472.0 986.1,472.0 999.7,472.0 1013.3,472.0 1026.8,472.0 1040.4,472.0 1054.0,472.0 1067.5,472.0 1081.1,472.0 1094.6,472.0 1108.2,472.0 1121.8,472.0 1135.3,472.0 1148.9,472.0 1162.4,472.0 1176.0,472.0" fill="none" stroke="#d62728" stroke-width="3.5" opacity="1.0"/>
<line x1="76" y1="486" x2="104" y2="486" stroke="#999999" stroke-width="3"/>
<text x="110" y="490" font-size="12">Run 1 (old) — 1287 evals in 40 buckets</text>
<line x1="76" y1="504" x2="104" y2="504" stroke="#d62728" stroke-width="3"/>
<text x="110" y="508" font-size="12">Run 3 trend (rolling-10)</text>
<rect x="0" y="838" width="1200" height="124" fill="#f2f2f2"/>
<text x="16" y="859" font-size="15" font-weight="bold">How to read</text>
<text x="1184" y="858" text-anchor="end" font-size="11" fill="#666">Regenerate anytime: python3 tools/plot_progress.py</text>
<text x="16" y="879" font-size="13">One small dot = one test match. 0 means lost all ten fights.</text>
<text x="16" y="896" font-size="13">A thick line shows the recent trend. Up = learning.</text>
<text x="16" y="913" font-size="13">Watch the three colors. Red = Corners. Blue = Crazy. Green = Target.</text>
<text x="16" y="930" font-size="13">Gray line = old run. Red line = new run. Same enemy.</text>
<text x="16" y="947" font-size="13">If red ends higher than gray, the new training worked.</text>
</svg>

Before

Width:  |  Height:  |  Size: 27 KiB

-91
View File
@@ -1,91 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="949" viewBox="0 0 1000 949" font-family="sans-serif">
<rect width="1000" height="949" fill="white"/>
<text x="500" y="22" text-anchor="middle" font-size="17" font-weight="bold">SAC-LSTM training losses (x = metric line number)</text>
<text x="76" y="56" font-size="13" font-weight="bold">critic_loss (log scale)</text>
<line x1="70" y1="250" x2="980" y2="250" stroke="black"/>
<line x1="70" y1="250" x2="70" y2="40" stroke="black"/>
<line x1="70.0" y1="250" x2="70.0" y2="254" stroke="black"/>
<text x="70.0" y="267" text-anchor="middle" font-size="11">1</text>
<line x1="297.5" y1="250" x2="297.5" y2="254" stroke="black"/>
<text x="297.5" y="267" text-anchor="middle" font-size="11">42</text>
<line x1="525.0" y1="250" x2="525.0" y2="254" stroke="black"/>
<text x="525.0" y="267" text-anchor="middle" font-size="11">84</text>
<line x1="752.5" y1="250" x2="752.5" y2="254" stroke="black"/>
<text x="752.5" y="267" text-anchor="middle" font-size="11">126</text>
<line x1="980.0" y1="250" x2="980.0" y2="254" stroke="black"/>
<text x="980.0" y="267" text-anchor="middle" font-size="11">167</text>
<line x1="66" y1="250.0" x2="70" y2="250.0" stroke="black"/>
<text x="63" y="254.0" text-anchor="end" font-size="11">0.1</text>
<line x1="66" y1="197.5" x2="70" y2="197.5" stroke="black"/>
<text x="63" y="201.5" text-anchor="end" font-size="11">3.16e+03</text>
<line x1="66" y1="145.0" x2="70" y2="145.0" stroke="black"/>
<text x="63" y="149.0" text-anchor="end" font-size="11">1e+08</text>
<line x1="66" y1="92.5" x2="70" y2="92.5" stroke="black"/>
<text x="63" y="96.5" text-anchor="end" font-size="11">3.16e+12</text>
<line x1="66" y1="40.0" x2="70" y2="40.0" stroke="black"/>
<text x="63" y="44.0" text-anchor="end" font-size="11">1e+17</text>
<text x="525" y="283" text-anchor="middle" font-size="12"></text>
<text x="16" y="145" text-anchor="middle" font-size="12" transform="rotate(-90 16 145)">critic_loss</text>
<polyline points="70.0,67.6 75.5,66.5 81.0,67.9 86.4,70.0 91.9,72.6 97.4,74.1 102.9,73.1 108.4,71.4 113.9,240.6 119.3,65.5 124.8,55.6 130.3,61.7 135.8,67.4 141.3,71.4 146.7,74.2 152.2,74.9 157.7,75.7 163.2,75.9 168.7,76.9 174.2,76.5 179.6,75.5 185.1,74.8 190.6,185.2 196.1,70.5 201.6,68.6 207.0,67.4 212.5,202.4 218.0,60.3 223.5,60.4 229.0,59.0 234.5,59.7 239.9,58.6 245.4,56.8 250.9,55.7 256.4,56.0 261.9,55.9 267.3,50.4 272.8,50.9 278.3,53.5 283.8,48.3 289.3,45.7 294.8,240.6 300.2,240.6 305.7,56.2 311.2,59.1 316.7,63.5 322.2,65.0 327.7,240.6 333.1,68.4 338.6,70.5 344.1,185.8 349.6,71.7 355.1,72.8 360.5,69.0 366.0,62.6 371.5,56.9 377.0,63.6 382.5,240.6 388.0,240.6 393.4,73.5 398.9,74.7 404.4,73.7 409.9,73.7 415.4,240.6 420.8,73.7 426.3,74.6 431.8,72.8 437.3,70.5 442.8,72.5 448.3,70.7 453.7,73.1 459.2,69.5 464.7,67.9 470.2,67.1 475.7,64.0 481.1,53.5 486.6,184.6 492.1,63.5 497.6,114.6 503.1,161.5 508.6,197.0 514.0,204.7 519.5,205.0 525.0,205.3 530.5,205.5 536.0,205.6 541.4,205.7 546.9,205.6 552.4,206.0 557.9,206.2 563.4,206.4 568.9,206.5 574.3,206.8 579.8,207.0 585.3,240.5 590.8,240.5 596.3,240.5 601.7,240.5 607.2,207.8 612.7,208.1 618.2,208.2 623.7,208.4 629.2,208.6 634.6,208.8 640.1,209.0 645.6,209.2 651.1,209.4 656.6,209.7 662.0,209.9 667.5,210.3 673.0,210.5 678.5,210.7 684.0,210.9 689.5,211.1 694.9,211.3 700.4,211.5 705.9,211.7 711.4,212.0 716.9,240.5 722.3,212.3 727.8,212.5 733.3,212.6 738.8,212.8 744.3,213.0 749.8,213.3 755.2,213.4 760.7,213.5 766.2,213.7 771.7,213.8 777.2,214.0 782.7,213.9 788.1,214.3 793.6,214.5 799.1,240.4 804.6,214.8 810.1,214.9 815.5,215.1 821.0,240.4 826.5,215.4 832.0,215.6 837.5,215.7 843.0,215.9 848.4,216.0 853.9,216.2 859.4,216.4 864.9,216.6 870.4,240.4 875.8,216.9 881.3,240.4 886.8,217.3 892.3,217.4 897.8,217.6 903.3,217.7 908.7,217.9 914.2,218.0 919.7,218.1 925.2,218.2 930.7,218.4 936.1,218.6 941.6,218.7 947.1,218.7 952.6,219.0 958.1,219.2 963.6,240.4 969.0,219.5 974.5,219.8 980.0,219.9" fill="none" stroke="#1f77b4" stroke-width="1.6" opacity="1.0"/>
<text x="76" y="321" font-size="13" font-weight="bold">actor_loss |abs| (log scale)</text>
<line x1="70" y1="515" x2="980" y2="515" stroke="black"/>
<line x1="70" y1="515" x2="70" y2="305" stroke="black"/>
<line x1="70.0" y1="515" x2="70.0" y2="519" stroke="black"/>
<text x="70.0" y="532" text-anchor="middle" font-size="11">1</text>
<line x1="297.5" y1="515" x2="297.5" y2="519" stroke="black"/>
<text x="297.5" y="532" text-anchor="middle" font-size="11">42</text>
<line x1="525.0" y1="515" x2="525.0" y2="519" stroke="black"/>
<text x="525.0" y="532" text-anchor="middle" font-size="11">84</text>
<line x1="752.5" y1="515" x2="752.5" y2="519" stroke="black"/>
<text x="752.5" y="532" text-anchor="middle" font-size="11">126</text>
<line x1="980.0" y1="515" x2="980.0" y2="519" stroke="black"/>
<text x="980.0" y="532" text-anchor="middle" font-size="11">167</text>
<line x1="66" y1="515.0" x2="70" y2="515.0" stroke="black"/>
<text x="63" y="519.0" text-anchor="end" font-size="11">1</text>
<line x1="66" y1="462.5" x2="70" y2="462.5" stroke="black"/>
<text x="63" y="466.5" text-anchor="end" font-size="11">100</text>
<line x1="66" y1="410.0" x2="70" y2="410.0" stroke="black"/>
<text x="63" y="414.0" text-anchor="end" font-size="11">1e+04</text>
<line x1="66" y1="357.5" x2="70" y2="357.5" stroke="black"/>
<text x="63" y="361.5" text-anchor="end" font-size="11">1e+06</text>
<line x1="66" y1="305.0" x2="70" y2="305.0" stroke="black"/>
<text x="63" y="309.0" text-anchor="end" font-size="11">1e+08</text>
<text x="525" y="548" text-anchor="middle" font-size="12"></text>
<text x="16" y="410" text-anchor="middle" font-size="12" transform="rotate(-90 16 410)">|actor_loss|</text>
<polyline points="70.0,318.4 75.5,322.7 81.0,324.6 86.4,326.9 91.9,329.8 97.4,331.2 102.9,330.3 108.4,328.8 113.9,325.3 119.3,322.8 124.8,316.5 130.3,312.8 135.8,322.7 141.3,327.9 146.7,331.0 152.2,333.1 157.7,334.0 163.2,334.4 168.7,335.4 174.2,336.1 179.6,335.9 185.1,335.7 190.6,335.2 196.1,335.1 201.6,334.0 207.0,333.6 212.5,332.0 218.0,331.8 223.5,329.1 229.0,327.7 234.5,326.5 239.9,326.0 245.4,326.2 250.9,326.6 256.4,324.9 261.9,324.7 267.3,326.1 272.8,326.5 278.3,324.9 283.8,327.1 289.3,327.8 294.8,329.0 300.2,332.0 305.7,334.4 311.2,335.4 316.7,336.9 322.2,337.8 327.7,339.3 333.1,340.2 338.6,341.4 344.1,342.3 349.6,343.1 355.1,343.8 360.5,343.4 366.0,343.2 371.5,342.6 377.0,341.5 382.5,339.9 388.0,337.9 393.4,337.0 398.9,336.0 404.4,335.3 409.9,335.2 415.4,335.9 420.8,336.8 426.3,335.9 431.8,336.7 437.3,337.7 442.8,336.8 448.3,337.1 453.7,336.0 459.2,337.4 464.7,337.8 470.2,337.6 475.7,338.0 481.1,339.6 486.6,342.1 492.1,342.4 497.6,375.3 503.1,428.6 508.6,468.6 514.0,477.3 519.5,477.6 525.0,478.0 530.5,478.1 536.0,478.3 541.4,478.4 546.9,478.6 552.4,478.7 557.9,479.0 563.4,479.2 568.9,479.3 574.3,479.7 579.8,479.9 585.3,480.0 590.8,480.2 596.3,480.3 601.7,480.6 607.2,480.8 612.7,481.1 618.2,481.2 623.7,481.4 629.2,481.7 634.6,481.9 640.1,482.1 645.6,482.4 651.1,482.6 656.6,482.9 662.0,483.1 667.5,483.6 673.0,483.8 678.5,484.1 684.0,484.3 689.5,484.5 694.9,484.7 700.4,485.0 705.9,485.2 711.4,485.4 716.9,485.7 722.3,485.9 727.8,486.0 733.3,486.2 738.8,486.4 744.3,486.6 749.8,486.9 755.2,487.1 760.7,487.2 766.2,487.4 771.7,487.6 777.2,487.8 782.7,487.9 788.1,488.1 793.6,488.3 799.1,488.5 804.6,488.6 810.1,488.8 815.5,489.0 821.0,489.2 826.5,489.4 832.0,489.5 837.5,489.7 843.0,489.9 848.4,490.0 853.9,490.2 859.4,490.4 864.9,490.6 870.4,490.8 875.8,491.0 881.3,491.2 886.8,491.4 892.3,491.6 897.8,491.8 903.3,491.9 908.7,492.1 914.2,492.2 919.7,492.4 925.2,492.5 930.7,492.7 936.1,492.9 941.6,493.1 947.1,493.2 952.6,493.4 958.1,493.6 963.6,493.8 969.0,494.1 974.5,494.2 980.0,494.4" fill="none" stroke="#1f77b4" stroke-width="1.6" opacity="1.0"/>
<text x="76" y="586" font-size="13" font-weight="bold">alpha (temperature)</text>
<line x1="70" y1="780" x2="980" y2="780" stroke="black"/>
<line x1="70" y1="780" x2="70" y2="570" stroke="black"/>
<line x1="70.0" y1="780" x2="70.0" y2="784" stroke="black"/>
<text x="70.0" y="797" text-anchor="middle" font-size="11">1</text>
<line x1="297.5" y1="780" x2="297.5" y2="784" stroke="black"/>
<text x="297.5" y="797" text-anchor="middle" font-size="11">42</text>
<line x1="525.0" y1="780" x2="525.0" y2="784" stroke="black"/>
<text x="525.0" y="797" text-anchor="middle" font-size="11">84</text>
<line x1="752.5" y1="780" x2="752.5" y2="784" stroke="black"/>
<text x="752.5" y="797" text-anchor="middle" font-size="11">126</text>
<line x1="980.0" y1="780" x2="980.0" y2="784" stroke="black"/>
<text x="980.0" y="797" text-anchor="middle" font-size="11">167</text>
<line x1="66" y1="780.0" x2="70" y2="780.0" stroke="black"/>
<text x="63" y="784.0" text-anchor="end" font-size="11">0</text>
<line x1="66" y1="727.5" x2="70" y2="727.5" stroke="black"/>
<text x="63" y="731.5" text-anchor="end" font-size="11">0.252</text>
<line x1="66" y1="675.0" x2="70" y2="675.0" stroke="black"/>
<text x="63" y="679.0" text-anchor="end" font-size="11">0.504</text>
<line x1="66" y1="622.5" x2="70" y2="622.5" stroke="black"/>
<text x="63" y="626.5" text-anchor="end" font-size="11">0.756</text>
<line x1="66" y1="570.0" x2="70" y2="570.0" stroke="black"/>
<text x="63" y="574.0" text-anchor="end" font-size="11">1.01</text>
<text x="525" y="813" text-anchor="middle" font-size="12"></text>
<text x="16" y="675" text-anchor="middle" font-size="12" transform="rotate(-90 16 675)">alpha</text>
<polyline points="70.0,778.3 75.5,778.1 81.0,778.0 86.4,777.8 91.9,777.5 97.4,777.1 102.9,777.0 108.4,776.8 113.9,776.6 119.3,776.5 124.8,776.2 130.3,775.8 135.8,775.7 141.3,776.0 146.7,776.2 152.2,776.5 157.7,776.7 163.2,777.0 168.7,777.3 174.2,777.5 179.6,777.8 185.1,778.0 190.6,778.3 196.1,778.5 201.6,778.8 207.0,778.9 212.5,779.3 218.0,779.4 223.5,779.2 229.0,779.1 234.5,778.9 239.9,778.8 245.4,778.6 250.9,778.4 256.4,778.2 261.9,778.0 267.3,777.6 272.8,777.4 278.3,777.1 283.8,776.9 289.3,776.6 294.8,776.4 300.2,776.3 305.7,776.5 311.2,776.6 316.7,776.8 322.2,776.9 327.7,777.1 333.1,777.3 338.6,777.5 344.1,777.8 349.6,778.0 355.1,778.2 360.5,778.4 366.0,778.8 371.5,779.1 377.0,779.3 382.5,779.8 388.0,780.0 393.4,779.8 398.9,779.6 404.4,779.4 409.9,779.1 415.4,778.9 420.8,778.4 426.3,778.2 431.8,777.9 437.3,777.7 442.8,777.4 448.3,777.3 453.7,777.1 459.2,776.8 464.7,776.6 470.2,776.5 475.7,776.5 481.1,776.7 486.6,777.2 492.1,777.4 497.6,777.5 503.1,777.3 508.6,777.0 514.0,776.9 519.5,776.6 525.0,776.2 530.5,776.1 536.0,776.0 541.4,775.9 546.9,775.6 552.4,775.5 557.9,775.3 563.4,775.0 568.9,774.8 574.3,774.5 579.8,774.2 585.3,774.1 590.8,774.0 596.3,773.8 601.7,773.6 607.2,773.4 612.7,773.1 618.2,773.0 623.7,772.7 629.2,772.5 634.6,772.3 640.1,772.0 645.6,771.8 651.1,771.5 656.6,771.3 662.0,771.0 667.5,770.6 673.0,770.4 678.5,770.1 684.0,770.0 689.5,769.7 694.9,769.5 700.4,769.3 705.9,769.1 711.4,768.8 716.9,768.6 722.3,768.4 727.8,768.2 733.3,768.1 738.8,767.8 744.3,767.6 749.8,767.2 755.2,766.9 760.7,766.7 766.2,766.5 771.7,766.3 777.2,766.0 782.7,765.8 788.1,765.6 793.6,765.3 799.1,765.1 804.6,764.9 810.1,764.7 815.5,764.3 821.0,764.1 826.5,763.9 832.0,763.8 837.5,763.5 843.0,763.3 848.4,763.1 853.9,762.8 859.4,762.6 864.9,762.4 870.4,762.1 875.8,761.9 881.3,761.7 886.8,761.4 892.3,761.2 897.8,761.0 903.3,760.8 908.7,760.6 914.2,760.5 919.7,760.3 925.2,760.1 930.7,759.9 936.1,759.7 941.6,759.4 947.1,759.2 952.6,759.0 958.1,758.8 963.6,758.5 969.0,758.1 974.5,757.9 980.0,757.7" fill="none" stroke="#1f77b4" stroke-width="1.6" opacity="1.0"/>
<text x="525" y="817" text-anchor="middle" font-size="12">metric line number (167 rows)</text>
<rect x="0" y="825" width="1000" height="124" fill="#f2f2f2"/>
<text x="16" y="846" font-size="15" font-weight="bold">How to read</text>
<text x="984" y="845" text-anchor="end" font-size="11" fill="#666">Regenerate anytime: python3 tools/plot_progress.py</text>
<text x="16" y="866" font-size="13">These are internal error numbers. They are not game scores.</text>
<text x="16" y="883" font-size="13">Critic and actor panels: big spikes are normal early.</text>
<text x="16" y="900" font-size="13">Steady growth forever is bad.</text>
<text x="16" y="917" font-size="13">Alpha near 1 = bot still experimenting.</text>
<text x="16" y="934" font-size="13">If alpha falls too fast, the bot freezes its habits.</text>
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

+344 -234
View File
@@ -1,15 +1,23 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Plot SAC training progress from live campaign logs. """Build the single self-updating SAC training dashboard.
Pure-stdlib SVG output (matplotlib not available on this box). Pure-stdlib SVG output (matplotlib not available on this box).
Generates: Generates ONE file:
docs/graph_eval_winrates.svg - two stacked panels: Run 3 trend (raw dots + docs/campaign_dashboard.svg - five panels, current (v2) run only:
rolling-10 thick line) per opponent, and 1. test-match win % vs opponents (campaign_v2_stdout.log eval lines)
Run 1 vs Run 3 Corners on a fraction-of-run axis 2. real-fight win % per opponent (training_log.jsonl, ~100-game buckets)
docs/graph_losses.svg - critic/actor/alpha loss curves from training_metrics.jsonl 3. critic_loss / |actor_loss| (training_metrics.jsonl, shared log-y)
4. alpha temperature (training_metrics.jsonl, linear)
5. throughput, games/hour buckets (training_metrics.jsonl 'epoch' deltas;
training_log.jsonl has NO timestamps -
verified field names - and one metrics
row == one 10-game chunk, counts match
the stdout "=== Chunk N/N ===" markers)
plus an embedded JS snippet that reloads the page every 60 s when the SVG is
opened as a top-level document in Chrome.
Usage: Usage:
python3 tools/plot_progress.py [campaign_log] [metrics_jsonl] [v1_log] [outdir] python3 tools/plot_progress.py [campaign_log] [metrics_jsonl] [games_jsonl] [outdir]
All args optional; defaults relative to the SAC_LSTM_Bot/ root (parent of tools/). All args optional; defaults relative to the SAC_LSTM_Bot/ root (parent of tools/).
python3 tools/plot_progress.py --selftest # tiny built-in sanity check python3 tools/plot_progress.py --selftest # tiny built-in sanity check
""" """
@@ -19,33 +27,53 @@ import os
import re import re
import sys import sys
import tempfile import tempfile
import xml.etree.ElementTree as ET from datetime import datetime
from pathlib import Path from pathlib import Path
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+)")
TREND_WINDOW = 10 # rolling mean shown as the thick trend line TREND_WINDOW = 10 # rolling mean shown as the thick trend line (panel 1)
BUCKETS = 40 # v1 downsampling for the Run1-vs-Run3 panel GAME_BUCKET = 100 # games per bucket, real-fight panel
COLORS = {"Corners": "#d62728", "Crazy": "#1f77b4", "Target": "#2ca02c"} RATE_BUCKET = 20 # metric intervals per throughput bucket (~200 games)
V1_COLOR = "#999999" GAMES_PER_ROW = 10 # one training_metrics.jsonl row per 10-round chunk
W, M_L, M_R, M_T, M_B = 1000, 70, 20, 40, 45 # graph 2 geometry (unchanged) COLORS = {"Corners": "#d62728", "Crazy": "#1f77b4", "Target": "#2ca02c",
GUIDE_H = 124 # bottom band holding the plain-English "How to read" panel "RamFire": "#ff7f0e", "SacTwin": "#9467bd"}
WINRATE_GUIDE = [ CRITIC_C, ACTOR_C = "#1f77b4", "#ff7f0e"
"One small dot = one test match. 0 means lost all ten fights.", RELOAD_JS = ('<script type="text/javascript"><![CDATA[ '
"A thick line shows the recent trend. Up = learning.", 'setTimeout(function(){ location.reload(); }, 60000); ]]></script>')
"Watch the three colors. Red = Corners. Blue = Crazy. Green = Target.", W, H = 1400, 2000
"Gray line = old run. Red line = new run. Same enemy.", TITLE_H, GUIDE_H = 80, 180
"If red ends higher than gray, the new training worked.", # rows: (header_y, panel_top_y, panel_bottom_y, x_left, x_right)
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),
5: (1600, 1618, 1780, C1_L, C2_R),
}
PANEL_TITLES = [
"Test matches - win % vs opponents",
"Real fights - win % per opponent",
"Training losses (log scale)",
"Alpha temperature",
"Throughput - games per hour",
] ]
LOSSES_GUIDE = [ GUIDE_LINES = [
"These are internal error numbers. They are not game scores.", "Test matches: dots are single fights, thick line shows trend.",
"Critic and actor panels: big spikes are normal early.", "Real battles only. Rising lines mean the bot improves.",
"Steady growth forever is bad.", "Loss spikes are normal early; endless growth is bad.",
"Alpha near 1 = bot still experimenting.", "Alpha high means experimenting; falling too fast freezes habits.",
"If alpha falls too fast, the bot freezes its habits.", "Throughput flat is healthy; dips mean something slowed.",
"This file reloads itself in Chrome every sixty seconds.",
"Regenerate anytime with tools/watch_dashboard.sh or the python command.",
] ]
# ---------- parsing ----------
def parse_eval_series(path): def parse_eval_series(path):
"""Return {opponent: [win% per eval, in file order]}.""" """Return {opponent: [win% per eval, in file order]}."""
series = {} series = {}
@@ -81,6 +109,39 @@ def parse_metrics(path):
return rows return rows
def parse_games(path):
"""Return [(opponent, won_bool)] for type=='game' rows, skipping junk."""
out = []
if not path.is_file():
print(f"[skip] games log not found: {path}")
return out
for line in path.read_text(errors="replace").splitlines():
try:
r = json.loads(line)
except json.JSONDecodeError:
continue
if r.get("type") != "game":
continue
opp, win = r.get("opponent"), r.get("win")
if isinstance(opp, str) and isinstance(win, bool):
out.append((opp, win))
return out
def metric_col(rows, key, positive=False):
"""[(index, value)] for float-parseable rows; abs() applied; optional >0 filter."""
out = []
for i, r in enumerate(rows):
try:
v = abs(float(r[key]))
except (KeyError, TypeError, ValueError):
continue
if positive and v <= 0:
continue
out.append((i, v))
return out
def rolling(vals, w=TREND_WINDOW): def rolling(vals, w=TREND_WINDOW):
out, s = [], 0.0 out, s = [], 0.0
for i, v in enumerate(vals): for i, v in enumerate(vals):
@@ -91,6 +152,20 @@ def rolling(vals, w=TREND_WINDOW):
return out return out
def bucket_means(vals, n):
"""Split vals into <=n contiguous buckets of near-equal size; per-bucket mean."""
if not vals:
return []
n = min(n, len(vals))
k, rem = divmod(len(vals), n)
out, i = [], 0
for b in range(n):
size = k + (1 if b < rem else 0)
out.append(sum(vals[i:i + size]) / size)
i += size
return out
# ---------- tiny SVG helpers ---------- # ---------- tiny SVG helpers ----------
def esc(s): def esc(s):
@@ -113,14 +188,6 @@ def write_svg(path, text):
return True return True
def svg_open(w, h, title):
return (f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" '
f'viewBox="0 0 {w} {h}" font-family="sans-serif">\n'
f'<rect width="{w}" height="{h}" fill="white"/>\n'
f'<text x="{w // 2}" y="22" text-anchor="middle" font-size="17" font-weight="bold">'
f"{esc(title)}</text>\n")
def polyline(pts, color, width=1.5, dash=None, opacity=1.0): def polyline(pts, color, width=1.5, dash=None, opacity=1.0):
if len(pts) < 2: if len(pts) < 2:
return "" return ""
@@ -140,20 +207,6 @@ def hgrid(x0, x1, ys):
f'stroke="#dddddd"/>\n' for y in ys) f'stroke="#dddddd"/>\n' for y in ys)
def bucket_means(vals, n):
"""Split vals into <=n contiguous buckets of near-equal size; per-bucket mean."""
if not vals:
return []
n = min(n, len(vals))
k, rem = divmod(len(vals), n)
out, i = [], 0
for b in range(n):
size = k + (1 if b < rem else 0)
out.append(sum(vals[i:i + size]) / size)
i += size
return out
def axis(x0, y0, x1, y1, xt, yt, xlabel, ylabel, ylog=False): def axis(x0, y0, x1, y1, xt, yt, xlabel, ylabel, ylog=False):
"""Draw axes + ticks + labels. xt/yt are (value, px) tick lists.""" """Draw axes + ticks + labels. xt/yt are (value, px) tick lists."""
s = (f'<line x1="{x0}" y1="{y0}" x2="{x1}" y2="{y0}" stroke="black"/>\n' s = (f'<line x1="{x0}" y1="{y0}" x2="{x1}" y2="{y0}" stroke="black"/>\n'
@@ -203,172 +256,228 @@ def map_fn(p0, p1, vmin, vmax, log=False):
def guide_block(w, h, lines): def guide_block(w, h, lines):
"""Plain-English "How to read" band at the bottom of the canvas.""" """Plain-English "How to read" band at the bottom of the canvas."""
y = h - GUIDE_H y = H - GUIDE_H
s = f'<rect x="0" y="{y}" width="{w}" height="{GUIDE_H}" fill="#f2f2f2"/>\n' s = f'<rect x="0" y="{y}" width="{w}" height="{GUIDE_H}" fill="#f2f2f2"/>\n'
s += (f'<text x="16" y="{y + 21}" font-size="15" font-weight="bold">' s += (f'<text x="16" y="{y + 21}" font-size="15" font-weight="bold">'
f"How to read</text>\n" f"How to read</text>\n"
f'<text x="{w - 16}" y="{y + 20}" text-anchor="end" font-size="11" ' f'<text x="{w - 16}" y="{y + 20}" text-anchor="end" font-size="11" '
f'fill="#666">Regenerate anytime: python3 tools/plot_progress.py</text>\n') f'fill="#666">Regenerate anytime: python3 tools/plot_progress.py</text>\n')
for i, ln in enumerate(lines): for i, ln in enumerate(lines):
s += f'<text x="16" y="{y + 41 + i * 17}" font-size="13">{esc(ln)}</text>\n' s += f'<text x="16" y="{y + 43 + i * 17}" font-size="13">{esc(ln)}</text>\n'
return s return s
# ---------- graph 1: eval win rates ---------- def header(x, y, title, sub=None):
s = (f'<text x="{x}" y="{y}" font-size="15" font-weight="bold">'
f"{esc(title)}</text>\n")
if sub:
s += (f'<text x="{x}" y="{y + 15}" font-size="11" fill="#555">'
f"{esc(sub)}</text>\n")
return s
def graph_eval(series_v2, series_v1, out):
if not series_v2 and not series_v1:
print("[skip] no eval data at all -> no win-rate graph")
return 0
# panel geometry (local: graph 2 keeps the module-level constants)
CW, ML, MR = 1200, 64, 24
TOP, PH, GAP, MB = 78, 310, 84, 56
H = TOP + PH + GAP + PH + MB + GUIDE_H
x0, x1 = ML, CW - MR
s = svg_open(CW, H, "SAC-LSTM campaign: eval win rate vs opponents (Run 3 live, Run 1 archived)")
# ---- top panel: run 3 recent trend ---- # ---------- panels ----------
y1t, y0t = TOP, TOP + PH
xmax = max(max((len(v) for v in series_v2.values()), default=1), 2) def panel_test_matches(series, geo):
xm = map_fn(x0, x1, 1, xmax) _, pt, pb, x0, x1 = geo
ymt = map_fn(y0t, y1t, 0, 100) s = ""
s += hgrid(x0, x1, [ymt(v) for v in range(0, 101, 20)]) if not series:
s += (f'<text x="{x0 + 6}" y="{TOP - 34}" font-size="14" font-weight="bold">' s += f'<text x="{x0 + 10}" y="{pt + 40}" font-size="12" fill="#a00">' \
f"Run 3 — recent trend</text>\n" "no eval lines found</text>\n"
f'<text x="{x0 + 6}" y="{TOP - 16}" font-size="11" fill="#555">' return
f"win % per test match — thick line = trend</text>\n") xmax = max(max(len(v) for v in series.values()), 2)
xt = [(str(v), xm(v)) for v in range(10, xmax + 1, 10)] or \ xm, ym = map_fn(x0, x1, 1, xmax), map_fn(pb, pt, 0, 100)
[("1", xm(1)), (str(xmax), xm(xmax))] s += hgrid(x0, x1, [ym(v) for v in range(0, 101, 20)])
s += axis(x0, y0t, x1, y1t, xt, step = max(1, (xmax // 8 // 10) * 10)
ticks_linear(0, 100, y0t, y1t, n=6), xt = [(str(v), xm(v)) for v in range(step, xmax + 1, step)] or [("1", xm(1))]
"eval number", "win rate (%)") s += axis(x0, pb, x1, pt, xt, ticks_linear(0, 100, pb, pt, n=6),
"test match number (each opponent)", "win rate (%)")
items = [] items = []
for name in ("Corners", "Crazy", "Target"): for name in ("Corners", "Crazy", "Target"):
vals = series_v2.get(name, []) vals = series.get(name, [])
if not vals: if not vals:
continue continue
c = COLORS[name] c = COLORS[name]
s += dots([(xm(i + 1), ymt(v)) for i, v in enumerate(vals)], c) s += dots([(xm(i + 1), ym(v)) for i, v in enumerate(vals)], c)
s += polyline([(xm(i + 1), ymt(v)) s += polyline([(xm(i + 1), ym(v))
for i, v in enumerate(rolling(vals, TREND_WINDOW))], c, 3.5) for i, v in enumerate(rolling(vals))], c, 3.5)
items.append((c, f"{name} — {len(vals)} evals")) items.append((c, f"{name} - {len(vals)} evals"))
s += legend(items, x0 + 12, y1t + 14) s += legend(items, x0 + 12, pb + 52)
return s
# ---- bottom panel: run 1 vs run 3 on the same enemy ---- def panel_real_fights(games, geo):
tb = TOP + PH + GAP _, pt, pb, x0, x1 = geo
y1b, y0b = tb, tb + PH s = ""
ymb = map_fn(y0b, y1b, 0, 100) if not games:
s += f'<text x="{x0 + 10}" y="{pt + 40}" font-size="12" fill="#a00">' \
def xf(f): "no game rows found</text>\n"
return x0 + (x1 - x0) * f return
edges = list(range(0, len(games) + 1, GAME_BUCKET))
s += hgrid(x0, x1, [ymb(v) for v in range(0, 101, 20)]) if edges[-1] != len(games):
s += (f'<text x="{x0 + 6}" y="{tb - 22}" font-size="14" font-weight="bold">' edges.append(len(games))
f"Run 1 vs Run 3 (Corners) — same enemy — did learning stick?</text>\n") buckets = list(zip(edges[:-1], edges[1:]))
s += axis(x0, y0b, x1, y1b, xm, ym = map_fn(x0, x1, 1, max(len(games), 2)), map_fn(pb, pt, 0, 100)
[(("0", "0.2", "0.4", "0.6", "0.8", "1")[i], xf(i / 5)) for i in range(6)], s += hgrid(x0, x1, [ym(v) for v in range(0, 101, 20)])
ticks_linear(0, 100, y0b, y1b, n=6), step = max(GAME_BUCKET, GAME_BUCKET * (len(games) // GAME_BUCKET // 8 + 1))
"fraction of run (start → end)", "win rate (%)") xt = [(str(v), xm(v)) for v in range(step, len(games) + 1, step)]
items_b = [] s += axis(x0, pb, x1, pt, xt, ticks_linear(0, 100, pb, pt, n=6),
v1 = series_v1.get("Corners", []) f"game number ({GAME_BUCKET}-game buckets)", "win %")
bm = bucket_means(v1, BUCKETS) opponents = []
if bm: for opp, _ in games:
fb = [xf((i + 0.5) / len(bm)) for i in range(len(bm))] if opp not in opponents:
s += polyline(list(zip(fb, map(ymb, bm))), V1_COLOR, 1.5) opponents.append(opp)
items_b.append((V1_COLOR, f"Run 1 (old) — {len(v1)} evals in {len(bm)} buckets")) items = []
c3 = series_v2.get("Corners", []) for name in opponents:
if c3: c = COLORS.get(name, "#7f7f7f")
rm = rolling(c3, TREND_WINDOW) by_b = {}
fr = [xf((i + 1) / len(rm)) for i in range(len(rm))] for bi, (lo, hi) in enumerate(buckets):
s += polyline(list(zip(fr, map(ymb, rm))), COLORS["Corners"], 3.5) sub = [w for o, w in games[lo:hi] if o == name]
items_b.append((COLORS["Corners"], f"Run 3 trend (rolling-{TREND_WINDOW})")) if sub:
s += legend(items_b, x0 + 12, y1b + 14) by_b[bi] = 100.0 * sum(sub) / len(sub)
s += guide_block(CW, H, WINRATE_GUIDE)
s += "</svg>\n"
return 1 if write_svg(out, s) else 0
# ---------- graph 2: loss curves ----------
def graph_losses(rows, out):
if not rows:
print("[skip] no metric rows -> no loss graph")
return 0
def col(key, log=False):
vals = []
for r in rows:
try:
v = abs(float(r[key])) # abs: log panels plot magnitude
except (KeyError, TypeError, ValueError):
continue
if log and v <= 0:
continue
vals.append(v)
return vals
critic = col("critic_loss", log=True)
actor = col("actor_loss", log=True)
alpha = col("alpha")
if not (critic or actor or alpha):
print("[skip] metric rows lack critic_loss/actor_loss/alpha -> no loss graph")
return 0
PH, GAP = 210, 55
H = M_T + 3 * PH + 2 * GAP + M_B + GUIDE_H
x0, x1 = M_L, W - M_R
n = len(rows)
s = svg_open(W, H, "SAC-LSTM training losses (x = metric line number)")
def panel(top, vals, title, log, ylab, fixed_range=None):
nonlocal s
y0, y1 = top + PH, top
if not vals:
s += (f'<text x="{x0 + 10}" y="{top + 20}" font-size="12" fill="#a00">'
f"{esc(title)}: no valid points</text>\n")
return
if log:
lo = math.floor(math.log10(min(vals)))
hi = math.ceil(math.log10(max(vals)))
if lo == hi:
hi = lo + 1
yt = ticks_log(lo, hi, y0, y1)
else:
lo, hi = fixed_range or (min(vals), max(vals))
if lo == hi:
hi = lo + 1
yt = ticks_linear(lo, hi, y0, y1, n=5, fmt="{:.3g}")
s += (f'<text x="{x0 + 6}" y="{top + 16}" font-size="13" font-weight="bold">'
f"{esc(title)}</text>\n")
s += axis(x0, y0, x1, y1, ticks_linear(1, n, x0, x1, fmt="{:.0f}"), yt,
"", ylab)
ym = map_fn(y0, y1, lo, hi, log=log)
pts = [] pts = []
for i, r in enumerate(rows): segs, prev = [], None
try: for bi in sorted(by_b):
v = abs(float(r[title_key(title)])) if prev is not None and bi != prev + 1:
except (KeyError, TypeError, ValueError): segs.append(pts)
continue pts = []
if log and v <= 0: center = (buckets[bi][0] + buckets[bi][1]) / 2
continue pts.append((xm(center), ym(by_b[bi])))
pts.append((x0 + (x1 - x0) * i / max(n - 1, 1), ym(v))) prev = bi
s += polyline(pts, "#1f77b4", 1.6) if len(pts) >= 2:
segs.append(pts)
for seg in segs:
s += polyline(seg, c, 3.5)
n_played = sum(1 for o, _ in games if o == name)
items.append((c, f"{name} ({n_played} games)"))
s += legend(items, x0 + 12, pb + 52)
return s
def title_key(title): def panel_losses(rows, geo):
return {"critic_loss (log scale)": "critic_loss", _, pt, pb, x0, x1 = geo
"actor_loss |abs| (log scale)": "actor_loss", s = ""
"alpha (temperature)": "alpha"}[title] critic = metric_col(rows, "critic_loss", positive=True)
actor = metric_col(rows, "actor_loss") # abs() applied; sign dropped
actor = [(i, v) for i, v in actor if v > 0]
if not (critic or actor):
s += f'<text x="{x0 + 10}" y="{pt + 40}" font-size="12" fill="#a00">' \
"no valid loss points</text>\n"
return
allv = [v for _, v in critic + actor]
lo, hi = math.floor(math.log10(min(allv))), math.ceil(math.log10(max(allv)))
if lo == hi:
hi = lo + 1
n = len(rows)
xm = lambda i: x0 + (x1 - x0) * i / max(n - 1, 1)
ym = map_fn(pb, pt, lo, hi, log=True)
s += hgrid(x0, x1, [ym(10 ** e) for e in range(lo, hi + 1)])
s += axis(x0, pb, x1, pt, ticks_linear(1, n, x0, x1, n=5),
ticks_log(lo, hi, pb, pt), "metric line number", "loss (log)")
s += polyline([(xm(i), ym(v)) for i, v in critic], CRITIC_C, 1.8)
s += polyline([(xm(i), ym(v)) for i, v in actor], ACTOR_C, 1.8)
s += legend([(CRITIC_C, "critic_loss"), (ACTOR_C, "|actor_loss|")],
x0 + 12, pb + 52)
return s
panel(M_T, critic, "critic_loss (log scale)", True, "critic_loss") def panel_alpha(rows, geo):
panel(M_T + PH + GAP, actor, "actor_loss |abs| (log scale)", True, "|actor_loss|") _, pt, pb, x0, x1 = geo
panel(M_T + 2 * (PH + GAP), alpha, "alpha (temperature)", False, "alpha", s = ""
fixed_range=(0, max(1.0, max(alpha)))) alpha = metric_col(rows, "alpha")
s += (f'<text x="{x0 + (x1 - x0) // 2}" y="{H - GUIDE_H - 8}" text-anchor="middle" ' if not alpha:
f'font-size="12">metric line number ({n} rows)</text>\n') s += f'<text x="{x0 + 10}" y="{pt + 40}" font-size="12" fill="#a00">' \
s += guide_block(W, H, LOSSES_GUIDE) "no alpha points</text>\n"
return
n = len(rows)
hi = max(1.0, max(v for _, v in alpha))
xm = lambda i: x0 + (x1 - x0) * i / max(n - 1, 1)
ym = map_fn(pb, pt, 0, hi)
s += hgrid(x0, x1, [ym(v) for v in
[hi * k / 4 for k in range(5)]])
s += axis(x0, pb, x1, pt, ticks_linear(1, n, x0, x1, n=5),
ticks_linear(0, hi, pb, pt, n=5, fmt="{:.3g}"),
"metric line number", "alpha")
s += polyline([(xm(i), ym(v)) for i, v in alpha], "#9467bd", 1.8)
return s
def panel_throughput(rows, geo):
_, pt, pb, x0, x1 = geo
s = ""
eps = []
for r in rows:
try:
eps.append(float(r["epoch"]))
except (KeyError, TypeError, ValueError):
continue
rates = [] # games/hour per inter-row interval
for a, b in zip(eps, eps[1:]):
dt = b - a
if dt > 0:
rates.append(3600.0 * GAMES_PER_ROW / dt)
if not rates:
s += f'<text x="{x0 + 10}" y="{pt + 40}" font-size="12" fill="#a00">' \
"no usable epoch timestamps</text>\n"
return
bm = bucket_means(rates, RATE_BUCKET)
xm = map_fn(x0, x1, 1, len(rates))
ymax = max(max(rates), max(bm)) * 1.1
ym = map_fn(pb, pt, 0, ymax)
s += hgrid(x0, x1, [ym(ymax * k / 4) for k in range(5)])
step = max(1, len(rates) // 10)
xt = [(str(v), xm(v)) for v in range(step, len(rates) + 1, step)]
s += axis(x0, pb, x1, pt, xt, ticks_linear(0, ymax, pb, pt, n=5, fmt="{:.0f}"),
f"chunk interval ({GAMES_PER_ROW}-game chunks)", "games / hour")
s += dots([(xm(i + 1), ym(v)) for i, v in enumerate(rates)], "#7f7f7f", r=1.6)
if len(bm) >= 2:
ctr = [xm(round((i + 0.5) * len(rates) / len(bm))) for i in range(len(bm))]
s += polyline(list(zip(ctr, map(ym, bm))), "#2ca02c", 3.5)
# ---------- assembly ----------
return s
def build_dashboard(campaign, metrics_f, games_f, out):
series = parse_eval_series(campaign)
print("[info] evals parsed: " +
(", ".join(f"{k}={len(v)}" for k, v in sorted(series.items())) or "(none)"))
rows = parse_metrics(metrics_f)
print(f"[info] metric rows parsed: {len(rows)}")
games = parse_games(games_f)
print(f"[info] game rows parsed: {len(games)}")
s = (f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" '
f'viewBox="0 0 {W} {H}" font-family="sans-serif">\n'
f'<rect width="{W}" height="{H}" fill="white"/>\n'
f'<text x="{W // 2}" y="32" text-anchor="middle" font-size="21" '
f'font-weight="bold">SAC-LSTM campaign dashboard - live run (current only)</text>\n'
f'<text x="{W // 2}" y="56" text-anchor="middle" font-size="12" fill="#555">'
f'generated {datetime.now():%Y-%m-%d %H:%M:%S} - auto-reloads every 60 s '
f'(open this file in Chrome)</text>\n')
drawers = [
(ROWS[1], PANEL_TITLES[0],
"raw dots = single test matches, thick = rolling-mean-%d" % TREND_WINDOW,
lambda: panel_test_matches(series, ROWS[1])),
(ROWS[2], PANEL_TITLES[1],
"training_log.jsonl only - learning in REAL battles, not tests",
lambda: panel_real_fights(games, ROWS[2])),
(ROWS[3], PANEL_TITLES[2],
"training_metrics.jsonl - big early spikes are normal",
lambda: panel_losses(rows, ROWS[3])),
(ROWS[4], PANEL_TITLES[3],
"training_metrics.jsonl - high = exploring, low = exploiting",
lambda: panel_alpha(rows, ROWS[4])),
(ROWS[5], PANEL_TITLES[4],
"method: training_metrics.jsonl 'epoch' deltas (training_log.jsonl has "
"no timestamps); 1 row = one 10-game chunk",
lambda: panel_throughput(rows, ROWS[5])),
]
for geo, title, sub, drawer in drawers:
s += header(geo[3], geo[0], title, sub)
s += drawer()
s += guide_block(W, H, GUIDE_LINES)
s += RELOAD_JS + "\n"
s += "</svg>\n" s += "</svg>\n"
return 1 if write_svg(out, s) else 0 return write_svg(out, s)
# ---------- selftest ---------- # ---------- selftest ----------
@@ -381,33 +490,48 @@ def selftest():
"garbage line\n" "garbage line\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] win rate: broken\n"
">>> [eval] win rate: 5/10 (50%) vs Corners\n") ">>> [eval] win rate: 5/10 (50%) vs Corners\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]}, ser assert ser == {"Corners": [30.0, 50.0], "Crazy": [70.0, 40.0]}, ser
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 bucket_means(list(range(1287)), BUCKETS) is not None assert len(bucket_means(list(range(1287)), RATE_BUCKET)) == RATE_BUCKET
assert len(bucket_means(list(range(1287)), BUCKETS)) == BUCKETS bm = bucket_means([0, 10], RATE_BUCKET)
bm = bucket_means([0, 10], BUCKETS)
assert bm == [0.0, 10.0], bm # fewer points than buckets -> no empty buckets assert bm == [0.0, 10.0], bm # fewer points than buckets -> no empty buckets
(td / "m.jsonl").write_text( (td / "m.jsonl").write_text(
'{"critic_loss": 10, "actor_loss": -2, "alpha": 0.5}\n' '{"epoch": 1000.0, "critic_loss": 10, "actor_loss": -2, "alpha": 0.5}\n'
"not json\n" "not json\n"
'{"critic_loss": 100, "actor_loss": -4, "alpha": 0.25}\n') '{"epoch": 1060.0, "critic_loss": 100, "actor_loss": -4, "alpha": 0.25}\n'
'{"epoch": 1090.0, "critic_loss": 50, "actor_loss": 3, "alpha": 0.2}\n')
rows = parse_metrics(td / "m.jsonl") rows = parse_metrics(td / "m.jsonl")
assert len(rows) == 2 and rows[1]["critic_loss"] == 100 assert len(rows) == 3 and rows[1]["critic_loss"] == 100
ok = graph_losses(rows, td / "g2.svg") and graph_eval(ser, {"Corners": [0, 10]}, td / "g1.svg") glines = []
assert ok and (td / "g1.svg").stat().st_size > 500 for i in range(150): # 2 full GAME_BUCKETs, both opponents in both
g1 = (td / "g1.svg").read_text() glines.append(json.dumps(
g2 = (td / "g2.svg").read_text() {"type": "game", "round": i % 10 + 1, "ticks": 100,
ET.fromstring(g1) # whole doc must parse -> closing tag present "score": i % 3, "total_score": i, "win": i % 3 == 0,
ET.fromstring(g2) "opponent": ("Corners", "Crazy")[i % 2]}))
assert "How to read" in g1 and "How to read" in g2, "reading guide missing" (td / "g.jsonl").write_text("\n".join(glines) + "\n")
assert 'width="1200"' in g1, "canvas must be >=1200 wide" games = parse_games(td / "g.jsonl")
assert g1.count("<circle") == sum(len(v) for v in ser.values()) # one dot per raw eval assert len(games) == 150 and games[0] == ("Corners", True)
n_trend = sum(1 for v in ser.values() if len(v) >= 2) + 2 # trends + v1 buckets + v3 trend assert games[-1] == ("Crazy", False) # i=149: odd -> Crazy; 149%3!=0 -> loss
assert g1.count("<polyline") == n_trend assert metric_col(rows, "actor_loss") == [(0, 2.0), (1, 4.0), (2, 3.0)]
assert graph_eval({}, {}, td / "none.svg") == 0 # missing data handled
dash = td / "dash.svg"
assert build_dashboard(td / "log", td / "m.jsonl", td / "g.jsonl", dash)
text = dash.read_text()
ET.fromstring(text) # whole doc must parse -> closing tag present
assert RELOAD_JS in text, "auto-reload script missing"
for t in PANEL_TITLES:
assert t in text, f"panel title missing: {t}"
assert text.count(PANEL_TITLES[0]) == 1
assert "How to read" in text, "reading guide missing"
assert 'width="1400"' in text and 'height="2000"' in text
# circles: 4 eval dots (panel 1) + 2 throughput rate dots (panel 5)
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")
print("selftest OK") print("selftest OK")
@@ -418,33 +542,19 @@ def main():
args = [a for a in sys.argv[1:] if not a.startswith("-")] args = [a for a in sys.argv[1:] if not a.startswith("-")]
campaign = Path(args[0]) if len(args) > 0 else ROOT / "campaign_v2_stdout.log" campaign = Path(args[0]) if len(args) > 0 else ROOT / "campaign_v2_stdout.log"
metrics = Path(args[1]) if len(args) > 1 else ROOT / "training_metrics.jsonl" metrics = Path(args[1]) if len(args) > 1 else ROOT / "training_metrics.jsonl"
v1log = Path(args[2]) if len(args) > 2 else ROOT / "weights_v1_archive" / "campaign_stdout.log" games = Path(args[2]) if len(args) > 2 else ROOT / "training_log.jsonl"
outdir = Path(args[3]) if len(args) > 3 else ROOT / "docs" outdir = Path(args[3]) if len(args) > 3 else ROOT / "docs"
outdir.mkdir(parents=True, exist_ok=True) outdir.mkdir(parents=True, exist_ok=True)
made = 0
series_v2 = parse_eval_series(campaign)
print(f"[info] v2 evals parsed: " +
", ".join(f"{k}={len(v)}" for k, v in sorted(series_v2.items())) or "(none)")
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)")
try: try:
made += graph_eval(series_v2, series_v1, outdir / "graph_eval_winrates.svg") ok = build_dashboard(campaign, metrics, games, outdir / "campaign_dashboard.svg")
except Exception as e: except Exception as e:
print(f"[error] win-rate graph failed (loss graph still attempted): {e}") print(f"[error] dashboard build failed: {e}")
ok = False
rows = parse_metrics(metrics) if ok:
print(f"[info] metric rows parsed: {len(rows)}") print(f"[done] dashboard written to {outdir / 'campaign_dashboard.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}")
else: else:
print("[error] nothing plotted - check paths above") print("[error] dashboard NOT updated - check paths above")
sys.exit(1) sys.exit(1)
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
# Keep docs/campaign_dashboard.svg fresh: regenerate every 60 s.
# Errors go to stderr and never exit the loop silently.
dir=$(dirname "$0")
while :; do
if ! python3 "$dir/plot_progress.py"; then
echo "[watch_dashboard] $(date '+%F %T') regeneration failed (see error above)" >&2
fi
sleep 60
done