11 Commits

Author SHA1 Message Date
SirStone 8eb7c53dd0 fix(dashboard): remove broken unused win%-per-opponent panel 2026-08-24 09:05:41 +02:00
SirStone 51e7f33ecb fix(dashboard): plot live campaign-4 logs; derive ROOT from script location 2026-08-24 08:30:04 +02:00
SirStone d2d205e6f6 Merge branch 'research/ga-parameters' into research/goto-controller 2026-08-24 08:23:25 +02:00
SirStone 3851f28cc5 Merge branch 'worktree-agent-a556517b' into research/goto-controller 2026-08-24 08:23:22 +02:00
SirStone 6241c41e52 Merge branch 'worktree-agent-a4c06ae3' into research/goto-controller 2026-08-24 08:23:19 +02:00
SirStone 156b4ae8db Merge branch 'worktree-agent-af671e69' into research/goto-controller 2026-08-24 08:23:12 +02:00
SirStone eee48dec38 prototype: GA evolution spike -- sin(x) prediction validates pipeline (#66)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 08:14:36 +02:00
SirStone eb8faa7c99 prototype: GA evolution spike -- sin(x) prediction validates pipeline (#66)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 08:14:32 +02:00
SirStone cb1bbc35dc docs(adr): Evo_Bot neuroevolution gun architecture (#63)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 00:02:47 +02:00
SirStone b7b10af811 feat: OscillatorBot sparring partner for GA gun testing (#64)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 00:01:38 +02:00
SirStone e455566c8e docs: CONTEXT.md — Evo_Bot domain vocabulary (#62)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-08-24 00:00:21 +02:00
9 changed files with 466 additions and 779 deletions
+59
View File
@@ -0,0 +1,59 @@
# Evo_Bot
Robocode Tank Royale bot with a modular gun system where evolved neural networks learn to predict enemy dodge behavior.
## Language
**Evo_Bot**:
The bot itself — handles movement and firing discipline. Guns are pluggable modules.
_Avoid_: robot, tank
**Guess Factor (GF)**:
A value from -1 to +1 representing where on the maximum escape angle arc the enemy is. 0 = directly ahead, -1 = full left dodge, +1 = full right dodge. The gun's prediction target.
_Avoid_: aim offset, dodge index
**Max Escape Angle (MEA)**:
The widest angle the enemy can reach before a bullet arrives, computed from distance and bullet speed. Guess factor is multiplied by MEA to get the aim offset.
**Lateral Velocity**:
Enemy speed projected perpendicular to the line between you and them. The primary signal for guess factor prediction.
_Avoid_: tangential speed, sideways velocity
**Sliding Window**:
The last N ticks (default 30) of enemy state fed as input to the network. Each tick contains lateral velocity, heading delta, and wall distance ahead.
_Avoid_: observation buffer, input history
**Replay Tape**:
Rolling buffer of recorded enemy states (~2000 ticks). The evolution thread evaluates gun fitness against this tape.
_Avoid_: experience buffer, replay buffer
**Virtual Gun**:
A gun that runs in parallel without actually firing. It tracks where it would have aimed and whether a simulated bullet would have hit. Used to compare gun variants.
**Virtual Bullet**:
A simulated bullet fired by a virtual gun. Never actually sent to the game engine.
**TOPO_Gun**:
Fixed-topology ANN gun evolved by GA. Network shape is predetermined (e.g., 91-8-1), only weights are evolved.
_Avoid_: static gun, fixed gun
**NEAT_Gun**:
Variable-topology ANN gun where evolution can add/remove neurons and connections (NEAT algorithm). Deferred — only built if TOPO_Gun hits a ceiling.
**Population**:
The set of candidate networks (default 64-200) being evolved. Each member is a complete set of ANN weights.
**Champion**:
The best-performing network in the current GA population. The champion's weights are pushed to the inference side when it beats the current best.
_Avoid_: best, winner, elite
**Fitness**:
Hit count when a network's aim predictions are evaluated as virtual bullets against sampled ticks from the replay tape.
_Avoid_: score, reward
**Cold Start**:
The first-ever battle with no saved weights. The gun does not fire until the evolution thread produces its first champion. Subsequent battles load persisted weights.
**Weight Persistence**:
Saving evolved weights to disk. Load order: per-opponent file, then global fallback, then random initialization.
_Avoid_: model saving, checkpointing
+11
View File
@@ -0,0 +1,11 @@
{
"name": "OscillatorBot",
"version": "0.1.0",
"authors": ["Davide Cappellini"],
"description": "Predictable zigzag sparring partner for gun testing",
"homepage": "",
"countryCodes": ["IT"],
"gameTypes": ["classic", "1v1"],
"platform": "Nim",
"programmingLang": "Nim"
}
+48
View File
@@ -0,0 +1,48 @@
# OscillatorBot — predictable zigzag sparring partner for GA gun testing.
# Reverses direction + turn every PERIOD ticks. Head-on targeting only.
import std/[math, os]
import tankroyale_botapi
const botJsonPath = currentSourcePath().parentDir / "OscillatorBot.json"
const
SPEED = 7.0 # forward/backward speed
PERIOD = 25 # ticks between direction reversals
type OscillatorBot = ref object of Bot
tickCount: int
moveSign: float # +1 forward, -1 backward
turnSign: float # +1 right, -1 left
method onRoundStarted*(bot: OscillatorBot, e: RoundStartedEvent) =
setAdjustGunForBodyTurn(true)
setAdjustRadarForBodyTurn(true)
setAdjustRadarForGunTurn(true)
bot.tickCount = 0
bot.moveSign = 1.0
bot.turnSign = 1.0
method onScannedBot*(bot: OscillatorBot, e: ScannedBotEvent) =
# Head-on targeting: aim gun directly at enemy, fire medium power
let bearing = directionTo(getX(), getY(), e.x, e.y)
let gunDelta = normalizeRelativeAngle(bearing - getGunDirection())
setGunTurnRate(gunDelta.clamp(-MAX_GUN_TURN_RATE, MAX_GUN_TURN_RATE))
if abs(gunDelta) < 10.0 and getGunHeat() <= 0.0:
discard setFire(2.0)
method run*(bot: OscillatorBot) =
while isRunning():
inc bot.tickCount
if bot.tickCount mod PERIOD == 0:
bot.moveSign *= -1.0
bot.turnSign *= -1.0
setTargetSpeed(bot.moveSign * SPEED)
setTurnRate(bot.turnSign * 4.0)
setRadarTurnRate(45.0) # spin radar to keep scanning
go()
when isMainModule:
var bot = OscillatorBot(moveSign: 1.0, turnSign: 1.0)
start(bot, botJsonPath)
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
cd "$(dirname "$0")"
exec ./OscillatorBot 2>> /tmp/oscillatorbot_stderr.log
+1
View File
@@ -0,0 +1 @@
--path:"../libs"
+162 -664
View File
@@ -1,7 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1400" height="2000" viewBox="0 0 1400 2000" font-family="sans-serif"> <svg xmlns="http://www.w3.org/2000/svg" width="1400" height="1720" viewBox="0 0 1400 1720" font-family="sans-serif">
<rect width="1400" height="2000" fill="white"/> <rect width="1400" height="1720" fill="white"/>
<text x="700" y="32" text-anchor="middle" font-size="21" font-weight="bold">SAC-LSTM campaign dashboard - live run (current only)</text> <text x="700" y="32" text-anchor="middle" font-size="21" font-weight="bold">SAC-LSTM campaign dashboard - live run (current only)</text>
<text x="700" y="56" text-anchor="middle" font-size="12" fill="#555">generated 2026-08-23 10:46:52 - auto-reloads every 60 s (open this file in Chrome)</text> <text x="700" y="56" text-anchor="middle" font-size="12" fill="#555">generated 2026-08-24 09:05:32 - auto-reloads every 60 s (open this file in Chrome)</text>
<text x="70" y="100" font-size="15" font-weight="bold">Test matches - win % vs opponents</text> <text x="70" y="100" font-size="15" font-weight="bold">Test matches - win % vs opponents</text>
<text x="70" y="115" font-size="11" fill="#555">raw dots = single test matches, thick = rolling-mean-10</text> <text x="70" y="115" font-size="11" fill="#555">raw dots = single test matches, thick = rolling-mean-10</text>
<line x1="70" y1="720.0" x2="697" y2="720.0" stroke="#dddddd"/> <line x1="70" y1="720.0" x2="697" y2="720.0" stroke="#dddddd"/>
@@ -12,24 +12,24 @@
<line x1="70" y1="120.0" x2="697" y2="120.0" stroke="#dddddd"/> <line x1="70" y1="120.0" x2="697" y2="120.0" stroke="#dddddd"/>
<line x1="70" y1="720" x2="697" y2="720" stroke="black"/> <line x1="70" y1="720" x2="697" y2="720" stroke="black"/>
<line x1="70" y1="720" x2="70" y2="120" stroke="black"/> <line x1="70" y1="720" x2="70" y2="120" stroke="black"/>
<line x1="128.2" y1="720" x2="128.2" y2="724" stroke="black"/> <line x1="70.0" y1="720" x2="70.0" y2="724" stroke="black"/>
<text x="128.2" y="737" text-anchor="middle" font-size="11">10</text> <text x="70.0" y="737" text-anchor="middle" font-size="11">1</text>
<line x1="192.8" y1="720" x2="192.8" y2="724" stroke="black"/> <line x1="148.4" y1="720" x2="148.4" y2="724" stroke="black"/>
<text x="192.8" y="737" text-anchor="middle" font-size="11">20</text> <text x="148.4" y="737" text-anchor="middle" font-size="11">2</text>
<line x1="257.5" y1="720" x2="257.5" y2="724" stroke="black"/> <line x1="226.8" y1="720" x2="226.8" y2="724" stroke="black"/>
<text x="257.5" y="737" text-anchor="middle" font-size="11">30</text> <text x="226.8" y="737" text-anchor="middle" font-size="11">3</text>
<line x1="322.1" y1="720" x2="322.1" y2="724" stroke="black"/> <line x1="305.1" y1="720" x2="305.1" y2="724" stroke="black"/>
<text x="322.1" y="737" text-anchor="middle" font-size="11">40</text> <text x="305.1" y="737" text-anchor="middle" font-size="11">4</text>
<line x1="386.7" y1="720" x2="386.7" y2="724" stroke="black"/> <line x1="383.5" y1="720" x2="383.5" y2="724" stroke="black"/>
<text x="386.7" y="737" text-anchor="middle" font-size="11">50</text> <text x="383.5" y="737" text-anchor="middle" font-size="11">5</text>
<line x1="451.4" y1="720" x2="451.4" y2="724" stroke="black"/> <line x1="461.9" y1="720" x2="461.9" y2="724" stroke="black"/>
<text x="451.4" y="737" text-anchor="middle" font-size="11">60</text> <text x="461.9" y="737" text-anchor="middle" font-size="11">6</text>
<line x1="516.0" y1="720" x2="516.0" y2="724" stroke="black"/> <line x1="540.2" y1="720" x2="540.2" y2="724" stroke="black"/>
<text x="516.0" y="737" text-anchor="middle" font-size="11">70</text> <text x="540.2" y="737" text-anchor="middle" font-size="11">7</text>
<line x1="580.6" y1="720" x2="580.6" y2="724" stroke="black"/> <line x1="618.6" y1="720" x2="618.6" y2="724" stroke="black"/>
<text x="580.6" y="737" text-anchor="middle" font-size="11">80</text> <text x="618.6" y="737" text-anchor="middle" font-size="11">8</text>
<line x1="645.3" y1="720" x2="645.3" y2="724" stroke="black"/> <line x1="697.0" y1="720" x2="697.0" y2="724" stroke="black"/>
<text x="645.3" y="737" text-anchor="middle" font-size="11">90</text> <text x="697.0" y="737" text-anchor="middle" font-size="11">9</text>
<line x1="66" y1="720.0" x2="70" y2="720.0" stroke="black"/> <line x1="66" y1="720.0" x2="70" y2="720.0" stroke="black"/>
<text x="63" y="724.0" text-anchor="end" font-size="11">0</text> <text x="63" y="724.0" text-anchor="end" font-size="11">0</text>
<line x1="66" y1="600.0" x2="70" y2="600.0" stroke="black"/> <line x1="66" y1="600.0" x2="70" y2="600.0" stroke="black"/>
@@ -45,418 +45,112 @@
<text x="383" y="753" text-anchor="middle" font-size="12">test match number (each opponent)</text> <text x="383" y="753" text-anchor="middle" font-size="12">test match number (each opponent)</text>
<text x="16" y="420" text-anchor="middle" font-size="12" transform="rotate(-90 16 420)">win rate (%)</text> <text x="16" y="420" text-anchor="middle" font-size="12" transform="rotate(-90 16 420)">win rate (%)</text>
<circle cx="70.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="70.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="76.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="148.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="82.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="226.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="89.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="305.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="95.9" cy="540.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="383.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="102.3" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="461.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="108.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="540.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="115.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="618.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="121.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="128.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="134.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="141.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="147.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="154.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="160.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="167.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="173.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="179.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="186.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="192.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="199.3" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="205.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="212.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="218.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="225.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="231.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="238.1" cy="660.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="244.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="251.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="257.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="263.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="270.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="276.8" cy="660.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="283.3" cy="660.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="289.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="296.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="302.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="309.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="315.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="322.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="328.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="335.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="341.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="347.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="354.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="360.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="367.3" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="373.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="380.3" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="386.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="393.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="399.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="406.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="412.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="419.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="425.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="432.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="438.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="444.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="451.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="457.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="464.3" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="470.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="477.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="483.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="490.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="496.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="503.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="509.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="516.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="522.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="528.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="535.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="541.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="548.3" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="554.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="561.3" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="567.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="574.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="580.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="587.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="593.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="600.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="606.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="613.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="619.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="625.9" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="632.4" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="638.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="645.3" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="651.8" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="658.2" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="664.7" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="671.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="677.6" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="684.1" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="690.5" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<circle cx="697.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/> <circle cx="697.0" cy="720.0" r="2" fill="#d62728" opacity="0.25"/>
<polyline points="70.0,720.0 76.5,720.0 82.9,720.0 89.4,720.0 95.9,684.0 102.3,690.0 108.8,694.3 115.2,697.5 121.7,700.0 128.2,702.0 134.6,702.0 141.1,702.0 147.6,702.0 154.0,702.0 160.5,720.0 167.0,720.0 173.4,720.0 179.9,720.0 186.4,720.0 192.8,720.0 199.3,720.0 205.7,720.0 212.2,720.0 218.7,720.0 225.1,720.0 231.6,720.0 238.1,714.0 244.5,714.0 251.0,714.0 257.5,714.0 263.9,714.0 270.4,714.0 276.8,708.0 283.3,702.0 289.8,702.0 296.2,702.0 302.7,708.0 309.2,708.0 315.6,708.0 322.1,708.0 328.6,708.0 335.0,708.0 341.5,714.0 347.9,720.0 354.4,720.0 360.9,720.0 367.3,720.0 373.8,720.0 380.3,720.0 386.7,720.0 393.2,720.0 399.7,720.0 406.1,720.0 412.6,720.0 419.1,720.0 425.5,720.0 432.0,720.0 438.4,720.0 444.9,720.0 451.4,720.0 457.8,720.0 464.3,720.0 470.8,720.0 477.2,720.0 483.7,720.0 490.2,720.0 496.6,720.0 503.1,720.0 509.5,720.0 516.0,720.0 522.5,720.0 528.9,720.0 535.4,720.0 541.9,720.0 548.3,720.0 554.8,720.0 561.3,720.0 567.7,720.0 574.2,720.0 580.6,720.0 587.1,720.0 593.6,720.0 600.0,720.0 606.5,720.0 613.0,720.0 619.4,720.0 625.9,720.0 632.4,720.0 638.8,720.0 645.3,720.0 651.8,720.0 658.2,720.0 664.7,720.0 671.1,720.0 677.6,720.0 684.1,720.0 690.5,720.0 697.0,720.0" fill="none" stroke="#d62728" stroke-width="3.5" opacity="1.0"/> <polyline points="70.0,720.0 148.4,720.0 226.8,720.0 305.1,720.0 383.5,720.0 461.9,720.0 540.2,720.0 618.6,720.0 697.0,720.0" fill="none" stroke="#d62728" stroke-width="3.5" opacity="1.0"/>
<circle cx="70.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="70.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="76.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="148.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="82.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="226.8" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="89.4" cy="180.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="305.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="95.9" cy="480.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="383.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="102.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="461.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="108.8" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="540.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="115.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="618.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="121.7" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="128.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="134.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="141.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="147.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="154.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="160.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="167.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="173.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="179.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="186.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="192.8" cy="360.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="199.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="205.7" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="212.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="218.7" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="225.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="231.6" cy="600.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="238.1" cy="600.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="244.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="251.0" cy="120.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="257.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="263.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="270.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="276.8" cy="540.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="283.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="289.8" cy="600.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="296.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="302.7" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="309.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="315.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="322.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="328.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="335.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="341.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="347.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="354.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="360.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="367.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="373.8" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="380.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="386.7" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="393.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="399.7" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="406.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="412.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="419.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="425.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="432.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="438.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="444.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="451.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="457.8" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="464.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="470.8" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="477.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="483.7" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="490.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="496.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="503.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="509.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="516.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="522.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="528.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="535.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="541.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="548.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="554.8" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="561.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="567.7" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="574.2" cy="540.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="580.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="587.1" cy="600.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="593.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="600.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="606.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="613.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="619.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="625.9" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="632.4" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="638.8" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="645.3" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="651.8" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="658.2" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="664.7" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="671.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="677.6" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="684.1" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="690.5" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<circle cx="697.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/> <circle cx="697.0" cy="720.0" r="2" fill="#1f77b4" opacity="0.25"/>
<polyline points="70.0,720.0 76.5,720.0 82.9,720.0 89.4,585.0 95.9,564.0 102.3,590.0 108.8,608.6 115.2,622.5 121.7,633.3 128.2,642.0 134.6,642.0 141.1,642.0 147.6,642.0 154.0,696.0 160.5,720.0 167.0,720.0 173.4,720.0 179.9,720.0 186.4,720.0 192.8,684.0 199.3,684.0 205.7,684.0 212.2,684.0 218.7,624.0 225.1,624.0 231.6,612.0 238.1,600.0 244.5,600.0 251.0,540.0 257.5,576.0 263.9,576.0 270.4,576.0 276.8,558.0 283.3,618.0 289.8,606.0 296.2,618.0 302.7,630.0 309.2,630.0 315.6,690.0 322.1,690.0 328.6,690.0 335.0,690.0 341.5,708.0 347.9,708.0 354.4,720.0 360.9,720.0 367.3,720.0 373.8,720.0 380.3,720.0 386.7,720.0 393.2,720.0 399.7,720.0 406.1,720.0 412.6,720.0 419.1,720.0 425.5,720.0 432.0,720.0 438.4,720.0 444.9,720.0 451.4,720.0 457.8,720.0 464.3,720.0 470.8,720.0 477.2,720.0 483.7,720.0 490.2,720.0 496.6,720.0 503.1,720.0 509.5,720.0 516.0,720.0 522.5,720.0 528.9,720.0 535.4,720.0 541.9,720.0 548.3,720.0 554.8,720.0 561.3,720.0 567.7,720.0 574.2,702.0 580.6,702.0 587.1,690.0 593.6,690.0 600.0,690.0 606.5,690.0 613.0,690.0 619.4,690.0 625.9,690.0 632.4,690.0 638.8,708.0 645.3,708.0 651.8,720.0 658.2,720.0 664.7,720.0 671.1,720.0 677.6,720.0 684.1,720.0 690.5,720.0 697.0,720.0" fill="none" stroke="#1f77b4" stroke-width="3.5" opacity="1.0"/> <polyline points="70.0,720.0 148.4,720.0 226.8,720.0 305.1,720.0 383.5,720.0 461.9,720.0 540.2,720.0 618.6,720.0 697.0,720.0" fill="none" stroke="#1f77b4" stroke-width="3.5" opacity="1.0"/>
<circle cx="70.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="70.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="76.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="148.4" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="82.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="226.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="89.4" cy="600.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="305.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="95.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="383.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="102.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="461.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="108.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="540.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="115.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="618.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="121.7" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="128.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="134.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="141.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="147.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="154.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="160.5" cy="660.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="167.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="173.4" cy="240.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="179.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="186.4" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="192.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="199.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="205.7" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="212.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="218.7" cy="660.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="225.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="231.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="238.1" cy="360.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="244.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="251.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="257.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="263.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="270.4" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="276.8" cy="660.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="283.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="289.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="296.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="302.7" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="309.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="315.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="322.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="328.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="335.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="341.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="347.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="354.4" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="360.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="367.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="373.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="380.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="386.7" cy="660.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="393.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="399.7" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="406.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="412.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="419.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="425.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="432.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="438.4" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="444.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="451.4" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="457.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="464.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="470.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="477.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="483.7" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="490.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="496.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="503.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="509.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="516.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="522.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="528.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="535.4" cy="660.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="541.9" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="548.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="554.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="561.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="567.7" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="574.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="580.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="587.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="593.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="600.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="606.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="613.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="619.4" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="625.9" cy="660.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="632.4" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="638.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="645.3" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="651.8" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="658.2" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="664.7" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="671.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="677.6" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="684.1" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="690.5" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<circle cx="697.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/> <circle cx="697.0" cy="720.0" r="2" fill="#2ca02c" opacity="0.25"/>
<polyline points="70.0,720.0 76.5,720.0 82.9,720.0 89.4,690.0 95.9,696.0 102.3,700.0 108.8,702.9 115.2,705.0 121.7,706.7 128.2,708.0 134.6,708.0 141.1,708.0 147.6,708.0 154.0,720.0 160.5,714.0 167.0,714.0 173.4,666.0 179.9,666.0 186.4,666.0 192.8,666.0 199.3,666.0 205.7,666.0 212.2,666.0 218.7,660.0 225.1,666.0 231.6,666.0 238.1,678.0 244.5,678.0 251.0,678.0 257.5,678.0 263.9,678.0 270.4,678.0 276.8,672.0 283.3,678.0 289.8,678.0 296.2,678.0 302.7,714.0 309.2,714.0 315.6,714.0 322.1,714.0 328.6,714.0 335.0,714.0 341.5,720.0 347.9,720.0 354.4,720.0 360.9,720.0 367.3,720.0 373.8,720.0 380.3,720.0 386.7,714.0 393.2,714.0 399.7,714.0 406.1,714.0 412.6,714.0 419.1,714.0 425.5,714.0 432.0,714.0 438.4,714.0 444.9,714.0 451.4,720.0 457.8,720.0 464.3,720.0 470.8,720.0 477.2,720.0 483.7,720.0 490.2,720.0 496.6,720.0 503.1,720.0 509.5,720.0 516.0,720.0 522.5,720.0 528.9,720.0 535.4,714.0 541.9,714.0 548.3,714.0 554.8,714.0 561.3,714.0 567.7,714.0 574.2,714.0 580.6,714.0 587.1,714.0 593.6,714.0 600.0,720.0 606.5,720.0 613.0,720.0 619.4,720.0 625.9,714.0 632.4,714.0 638.8,714.0 645.3,714.0 651.8,714.0 658.2,714.0 664.7,714.0 671.1,714.0 677.6,714.0 684.1,714.0 690.5,720.0 697.0,720.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/> <polyline points="70.0,720.0 148.4,720.0 226.8,720.0 305.1,720.0 383.5,720.0 461.9,720.0 540.2,720.0 618.6,720.0 697.0,720.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<line x1="82" y1="772" x2="110" y2="772" stroke="#d62728" stroke-width="3"/> <line x1="82" y1="772" x2="110" y2="772" stroke="#d62728" stroke-width="3"/>
<text x="116" y="776" font-size="12">Corners - 98 evals</text> <text x="116" y="776" font-size="12">Corners - 9 evals</text>
<line x1="82" y1="790" x2="110" y2="790" stroke="#1f77b4" stroke-width="3"/> <line x1="82" y1="790" x2="110" y2="790" stroke="#1f77b4" stroke-width="3"/>
<text x="116" y="794" font-size="12">Crazy - 98 evals</text> <text x="116" y="794" font-size="12">Crazy - 9 evals</text>
<line x1="82" y1="808" x2="110" y2="808" stroke="#2ca02c" stroke-width="3"/> <line x1="82" y1="808" x2="110" y2="808" stroke="#2ca02c" stroke-width="3"/>
<text x="116" y="812" font-size="12">Target - 98 evals</text> <text x="116" y="812" font-size="12">Target - 9 evals</text>
<text x="747" y="100" font-size="15" font-weight="bold">Real fights - win % per opponent</text> <text x="747" y="100" font-size="15" font-weight="bold">Training losses (log scale)</text>
<text x="747" y="115" font-size="11" fill="#555">training_log.jsonl only - learning in REAL battles, not tests</text> <text x="747" y="115" font-size="11" fill="#555">training_metrics.jsonl - big early spikes are normal</text>
<line x1="747" y1="720.0" x2="1375" y2="720.0" stroke="#dddddd"/> <line x1="747" y1="720.0" x2="1375" y2="720.0" stroke="#dddddd"/>
<line x1="747" y1="600.0" x2="1375" y2="600.0" stroke="#dddddd"/> <line x1="747" y1="570.0" x2="1375" y2="570.0" stroke="#dddddd"/>
<line x1="747" y1="480.0" x2="1375" y2="480.0" stroke="#dddddd"/> <line x1="747" y1="420.0" x2="1375" y2="420.0" stroke="#dddddd"/>
<line x1="747" y1="360.0" x2="1375" y2="360.0" stroke="#dddddd"/> <line x1="747" y1="270.0" x2="1375" y2="270.0" stroke="#dddddd"/>
<line x1="747" y1="240.0" x2="1375" y2="240.0" stroke="#dddddd"/>
<line x1="747" y1="120.0" x2="1375" y2="120.0" stroke="#dddddd"/> <line x1="747" y1="120.0" x2="1375" y2="120.0" stroke="#dddddd"/>
<line x1="747" y1="720" x2="1375" y2="720" stroke="black"/> <line x1="747" y1="720" x2="1375" y2="720" stroke="black"/>
<line x1="747" y1="720" x2="747" y2="120" stroke="black"/> <line x1="747" y1="720" x2="747" y2="120" stroke="black"/>
<line x1="840.9" y1="720" x2="840.9" y2="724" stroke="black"/> <line x1="747.0" y1="720" x2="747.0" y2="724" stroke="black"/>
<text x="840.9" y="737" text-anchor="middle" font-size="11">300</text> <text x="747.0" y="737" text-anchor="middle" font-size="11">1</text>
<line x1="935.2" y1="720" x2="935.2" y2="724" stroke="black"/> <line x1="904.0" y1="720" x2="904.0" y2="724" stroke="black"/>
<text x="935.2" y="737" text-anchor="middle" font-size="11">600</text> <text x="904.0" y="737" text-anchor="middle" font-size="11">6</text>
<line x1="1029.4" y1="720" x2="1029.4" y2="724" stroke="black"/> <line x1="1061.0" y1="720" x2="1061.0" y2="724" stroke="black"/>
<text x="1029.4" y="737" text-anchor="middle" font-size="11">900</text> <text x="1061.0" y="737" text-anchor="middle" font-size="11">10</text>
<line x1="1123.7" y1="720" x2="1123.7" y2="724" stroke="black"/> <line x1="1218.0" y1="720" x2="1218.0" y2="724" stroke="black"/>
<text x="1123.7" y="737" text-anchor="middle" font-size="11">1200</text> <text x="1218.0" y="737" text-anchor="middle" font-size="11">15</text>
<line x1="1217.9" y1="720" x2="1217.9" y2="724" stroke="black"/> <line x1="1375.0" y1="720" x2="1375.0" y2="724" stroke="black"/>
<text x="1217.9" y="737" text-anchor="middle" font-size="11">1500</text> <text x="1375.0" y="737" text-anchor="middle" font-size="11">20</text>
<line x1="1312.2" y1="720" x2="1312.2" y2="724" stroke="black"/>
<text x="1312.2" y="737" text-anchor="middle" font-size="11">1800</text>
<line x1="743" y1="720.0" x2="747" y2="720.0" stroke="black"/> <line x1="743" y1="720.0" x2="747" y2="720.0" stroke="black"/>
<text x="740" y="724.0" text-anchor="end" font-size="11">0</text> <text x="740" y="724.0" text-anchor="end" font-size="11">0.01</text>
<line x1="743" y1="600.0" x2="747" y2="600.0" stroke="black"/> <line x1="743" y1="570.0" x2="747" y2="570.0" stroke="black"/>
<text x="740" y="604.0" text-anchor="end" font-size="11">20</text> <text x="740" y="574.0" text-anchor="end" font-size="11">0.1</text>
<line x1="743" y1="480.0" x2="747" y2="480.0" stroke="black"/> <line x1="743" y1="420.0" x2="747" y2="420.0" stroke="black"/>
<text x="740" y="484.0" text-anchor="end" font-size="11">40</text> <text x="740" y="424.0" text-anchor="end" font-size="11">1</text>
<line x1="743" y1="360.0" x2="747" y2="360.0" stroke="black"/> <line x1="743" y1="270.0" x2="747" y2="270.0" stroke="black"/>
<text x="740" y="364.0" text-anchor="end" font-size="11">60</text> <text x="740" y="274.0" text-anchor="end" font-size="11">10</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"/> <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="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="1061" y="753" text-anchor="middle" font-size="12">metric line number</text>
<text x="16" y="420" text-anchor="middle" font-size="12" transform="rotate(-90 16 420)">win %</text> <text x="16" y="420" text-anchor="middle" font-size="12" transform="rotate(-90 16 420)">loss (log)</text>
<polyline points="762.4,720.0 793.8,720.0 825.2,720.0 856.6,720.0 888.1,720.0 919.5,720.0 950.9,720.0 982.3,720.0 1013.7,720.0 1045.1,720.0 1076.6,720.0 1108.0,720.0 1139.4,720.0 1170.8,720.0 1202.2,720.0 1233.6,720.0 1265.0,720.0 1296.5,720.0 1327.9,700.0 1359.3,720.0" fill="none" stroke="#1f77b4" stroke-width="3.5" opacity="1.0"/> <polyline points="747.0,281.9 780.1,253.6 813.1,243.9 846.2,213.5 879.2,207.6 912.3,386.8 945.3,424.4 978.4,307.1 1011.4,293.6 1044.5,282.6 1077.5,242.7 1110.6,171.4 1143.6,158.6 1176.7,169.7 1209.7,191.2 1242.8,253.6 1275.8,327.8 1308.9,625.2 1341.9,506.4 1375.0,374.2" fill="none" stroke="#1f77b4" stroke-width="1.8" opacity="1.0"/>
<polyline points="825.2,720.0 856.6,720.0 888.1,720.0 919.5,720.0 950.9,720.0 982.3,720.0 1013.7,720.0 1045.1,720.0" fill="none" stroke="#ff7f0e" stroke-width="3.5" opacity="1.0"/> <polyline points="747.0,348.9 780.1,307.9 813.1,284.5 846.2,261.3 879.2,244.2 912.3,235.2 945.3,236.5 978.4,241.4 1011.4,270.7 1044.5,292.4 1077.5,378.9 1110.6,302.9 1143.6,277.7 1176.7,264.4 1209.7,254.8 1242.8,246.5 1275.8,242.0 1308.9,251.8 1341.9,262.0 1375.0,273.4" fill="none" stroke="#ff7f0e" stroke-width="1.8" opacity="1.0"/>
<polyline points="1108.0,720.0 1139.4,720.0 1170.8,720.0" fill="none" stroke="#ff7f0e" stroke-width="3.5" opacity="1.0"/>
<polyline points="1233.6,720.0 1265.0,720.0 1296.5,720.0 1327.9,720.0 1359.3,720.0" fill="none" stroke="#ff7f0e" stroke-width="3.5" opacity="1.0"/>
<polyline points="762.4,720.0 793.8,720.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<polyline points="888.1,720.0 919.5,720.0 950.9,720.0 982.3,720.0 1013.7,720.0 1045.1,720.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<polyline points="1202.2,690.0 1233.6,720.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<polyline points="1296.5,720.0 1327.9,720.0 1359.3,720.0" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<polyline points="762.4,720.0 793.8,720.0 825.2,720.0 856.6,720.0 888.1,720.0 919.5,720.0 950.9,660.0 982.3,720.0 1013.7,720.0" fill="none" stroke="#9467bd" stroke-width="3.5" opacity="1.0"/>
<polyline points="1139.4,720.0 1170.8,720.0" fill="none" stroke="#9467bd" stroke-width="3.5" opacity="1.0"/>
<polyline points="762.4,720.0 793.8,720.0 825.2,720.0 856.6,720.0 888.1,720.0 919.5,720.0 950.9,720.0 982.3,720.0 1013.7,720.0 1045.1,720.0 1076.6,720.0 1108.0,720.0 1139.4,720.0 1170.8,720.0 1202.2,720.0 1233.6,720.0 1265.0,720.0 1296.5,720.0 1327.9,720.0 1359.3,720.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"/> <line x1="759" y1="772" x2="787" y2="772" stroke="#1f77b4" stroke-width="3"/>
<text x="793" y="776" font-size="12">Crazy (470 games)</text> <text x="793" y="776" font-size="12">critic_loss</text>
<line x1="759" y1="790" x2="787" y2="790" stroke="#ff7f0e" stroke-width="3"/> <line x1="759" y1="790" x2="787" y2="790" stroke="#ff7f0e" stroke-width="3"/>
<text x="793" y="794" font-size="12">RamFire (450 games)</text> <text x="793" y="794" font-size="12">|actor_loss|</text>
<line x1="759" y1="808" x2="787" y2="808" stroke="#2ca02c" stroke-width="3"/> <text x="70" y="940" font-size="15" font-weight="bold">Alpha temperature</text>
<text x="793" y="812" font-size="12">Target (220 games)</text> <text x="70" y="955" font-size="11" fill="#555">training_metrics.jsonl - high = exploring, low = exploiting</text>
<line x1="759" y1="826" x2="787" y2="826" stroke="#9467bd" stroke-width="3"/>
<text x="793" y="830" font-size="12">SacTwin (190 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 (670 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="1428.0" x2="697" y2="1428.0" stroke="#dddddd"/> <line x1="70" y1="1428.0" x2="697" y2="1428.0" stroke="#dddddd"/>
<line x1="70" y1="1401.9" x2="697" y2="1401.9" stroke="#dddddd"/> <line x1="70" y1="1310.5" x2="697" y2="1310.5" stroke="#dddddd"/>
<line x1="70" y1="1375.8" x2="697" y2="1375.8" stroke="#dddddd"/>
<line x1="70" y1="1349.7" x2="697" y2="1349.7" stroke="#dddddd"/>
<line x1="70" y1="1323.6" x2="697" y2="1323.6" stroke="#dddddd"/>
<line x1="70" y1="1297.4" x2="697" y2="1297.4" stroke="#dddddd"/>
<line x1="70" y1="1271.3" x2="697" y2="1271.3" stroke="#dddddd"/>
<line x1="70" y1="1245.2" x2="697" y2="1245.2" stroke="#dddddd"/>
<line x1="70" y1="1219.1" x2="697" y2="1219.1" stroke="#dddddd"/>
<line x1="70" y1="1193.0" x2="697" y2="1193.0" stroke="#dddddd"/> <line x1="70" y1="1193.0" x2="697" y2="1193.0" stroke="#dddddd"/>
<line x1="70" y1="1166.9" x2="697" y2="1166.9" stroke="#dddddd"/> <line x1="70" y1="1075.5" x2="697" y2="1075.5" stroke="#dddddd"/>
<line x1="70" y1="1140.8" x2="697" y2="1140.8" stroke="#dddddd"/>
<line x1="70" y1="1114.7" x2="697" y2="1114.7" stroke="#dddddd"/>
<line x1="70" y1="1088.6" x2="697" y2="1088.6" stroke="#dddddd"/>
<line x1="70" y1="1062.4" x2="697" y2="1062.4" stroke="#dddddd"/>
<line x1="70" y1="1036.3" x2="697" y2="1036.3" stroke="#dddddd"/>
<line x1="70" y1="1010.2" x2="697" y2="1010.2" stroke="#dddddd"/>
<line x1="70" y1="984.1" x2="697" y2="984.1" stroke="#dddddd"/>
<line x1="70" y1="958.0" x2="697" y2="958.0" stroke="#dddddd"/> <line x1="70" y1="958.0" x2="697" y2="958.0" stroke="#dddddd"/>
<line x1="70" y1="1428" x2="697" y2="1428" stroke="black"/> <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" y1="1428" x2="70" y2="958" stroke="black"/>
<line x1="70.0" y1="1428" x2="70.0" y2="1432" 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> <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"/> <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">51</text> <text x="226.8" y="1445" text-anchor="middle" font-size="11">6</text>
<line x1="383.5" y1="1428" x2="383.5" y2="1432" stroke="black"/> <line x1="383.5" y1="1428" x2="383.5" y2="1432" stroke="black"/>
<text x="383.5" y="1445" text-anchor="middle" font-size="11">101</text> <text x="383.5" y="1445" text-anchor="middle" font-size="11">10</text>
<line x1="540.2" y1="1428" x2="540.2" y2="1432" stroke="black"/> <line x1="540.2" y1="1428" x2="540.2" y2="1432" stroke="black"/>
<text x="540.2" y="1445" text-anchor="middle" font-size="11">151</text> <text x="540.2" y="1445" text-anchor="middle" font-size="11">15</text>
<line x1="697.0" y1="1428" x2="697.0" y2="1432" stroke="black"/> <line x1="697.0" y1="1428" x2="697.0" y2="1432" stroke="black"/>
<text x="697.0" y="1445" text-anchor="middle" font-size="11">201</text> <text x="697.0" y="1445" text-anchor="middle" font-size="11">20</text>
<line x1="66" y1="1428.0" x2="70" y2="1428.0" stroke="black"/> <line x1="66" y1="1428.0" x2="70" y2="1428.0" stroke="black"/>
<text x="63" y="1432.0" text-anchor="end" font-size="11">0.1</text> <text x="63" y="1432.0" text-anchor="end" font-size="11">0</text>
<line x1="66" y1="1310.5" x2="70" y2="1310.5" stroke="black"/> <line x1="66" y1="1310.5" x2="70" y2="1310.5" stroke="black"/>
<text x="63" y="1314.5" text-anchor="end" font-size="11">3.16e+03</text> <text x="63" y="1314.5" text-anchor="end" font-size="11">0.25</text>
<line x1="66" y1="1193.0" x2="70" y2="1193.0" stroke="black"/> <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> <text x="63" y="1197.0" text-anchor="end" font-size="11">0.5</text>
<line x1="66" y1="1075.5" x2="70" y2="1075.5" stroke="black"/> <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> <text x="63" y="1079.5" text-anchor="end" font-size="11">0.75</text>
<line x1="66" y1="958.0" x2="70" y2="958.0" stroke="black"/> <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="63" y="962.0" text-anchor="end" font-size="11">1</text>
<text x="383" y="1461" text-anchor="middle" font-size="12">metric line number</text> <text x="383" y="1461" text-anchor="middle" font-size="12">metric line number</text>
<text x="16" y="1193" text-anchor="middle" font-size="12" transform="rotate(-90 16 1193)">loss (log)</text> <text x="16" y="1193" text-anchor="middle" font-size="12" transform="rotate(-90 16 1193)">alpha</text>
<polyline points="70.0,1366.3 73.1,1368.7 76.3,1365.5 79.4,1360.9 82.5,1355.0 85.7,1351.6 88.8,1353.9 91.9,1357.7 95.1,979.1 98.2,1370.9 101.3,1393.2 104.5,1379.4 107.6,1366.7 110.8,1357.6 113.9,1351.5 117.0,1349.9 120.2,1348.1 123.3,1347.8 126.4,1345.5 129.6,1346.3 132.7,1348.6 135.8,1350.0 139.0,1103.1 142.1,1359.7 145.2,1364.0 148.4,1366.7 151.5,1064.5 154.6,1382.5 157.8,1382.4 160.9,1385.5 164.1,1383.9 167.2,1386.3 170.3,1390.4 173.5,1392.9 176.6,1392.1 179.7,1392.5 182.9,1404.7 186.0,1403.6 189.1,1397.8 192.3,1409.3 195.4,1415.2 198.5,979.1 201.7,979.1 204.8,1391.7 207.9,1385.3 211.1,1375.4 214.2,1372.1 217.3,979.1 220.5,1364.5 223.6,1359.7 226.8,1101.7 229.9,1357.2 233.0,1354.6 236.2,1363.0 239.3,1377.5 242.4,1390.2 245.6,1375.3 248.7,979.1 251.8,979.1 255.0,1353.1 258.1,1350.3 261.2,1352.5 264.4,1352.6 267.5,979.1 270.6,1352.6 273.8,1350.5 276.9,1354.6 280.0,1359.8 283.2,1355.3 286.3,1359.3 289.4,1354.0 292.6,1362.1 295.7,1365.6 298.9,1367.3 302.0,1374.4 305.1,1397.8 308.3,1104.4 311.4,1375.4 314.5,1261.1 317.7,1156.2 320.8,1076.6 323.9,1059.3 327.1,1058.7 330.2,1058.0 333.3,1057.7 336.5,1057.4 339.6,1057.2 342.7,1057.4 345.9,1056.5 349.0,1056.0 352.1,1055.7 355.3,1055.3 358.4,1054.6 361.6,1054.1 364.7,979.2 367.8,979.2 371.0,979.2 374.1,979.3 377.2,1052.4 380.4,1051.9 383.5,1051.6 386.6,1051.2 389.8,1050.7 392.9,1050.2 396.0,1049.7 399.2,1049.3 402.3,1048.8 405.4,1048.3 408.6,1047.8 411.7,1046.9 414.9,1046.4 418.0,1045.9 421.1,1045.5 424.3,1045.0 427.4,1044.6 430.5,1044.1 433.7,1043.6 436.8,1043.1 439.9,979.4 443.1,1042.3 446.2,1042.0 449.3,1041.6 452.5,1041.3 455.6,1040.9 458.7,1040.2 461.9,1039.9 465.0,1039.6 468.1,1039.2 471.3,1038.9 474.4,1038.5 477.6,1038.9 480.7,1037.9 483.8,1037.4 487.0,979.4 490.1,1036.9 493.2,1036.5 496.4,1036.0 499.5,979.4 502.6,1035.4 505.8,1035.1 508.9,1034.7 512.0,1034.4 515.2,1034.0 518.3,1033.7 521.4,1033.2 524.6,1032.8 527.7,979.5 530.8,1032.0 534.0,979.5 537.1,1031.3 540.2,1030.9 543.4,1030.5 546.5,1030.2 549.7,1029.9 552.8,1029.6 555.9,1029.4 559.1,1029.1 562.2,1028.7 565.3,1028.3 568.5,1028.0 571.6,1028.1 574.7,1027.3 577.9,1026.9 581.0,979.6 584.1,1026.2 587.3,1025.6 590.4,1025.3 593.5,1025.0 596.7,979.6 599.8,979.6 603.0,1023.8 606.1,1023.2 609.2,1022.9 612.4,1022.6 615.5,1022.3 618.6,1022.0 621.8,1021.7 624.9,1021.4 628.0,1021.1 631.2,1020.8 634.3,1020.5 637.4,1020.2 640.6,1019.9 643.7,1019.3 646.8,1019.1 650.0,1018.8 653.1,1018.6 656.2,1018.5 659.4,1018.0 662.5,1017.5 665.6,1017.3 668.8,1017.0 671.9,1016.7 675.1,1016.5 678.2,1016.2 681.3,1016.0 684.5,1015.8 687.6,1015.5 690.7,1015.3 693.9,979.8 697.0,1014.8" fill="none" stroke="#1f77b4" stroke-width="1.8" opacity="1.0"/> <polyline points="70.0,958.1 103.0,958.7 136.0,959.3 169.0,959.8 202.0,960.4 235.0,961.0 268.0,961.2 301.0,961.5 334.0,962.1 367.0,962.4 400.0,962.9 433.0,963.4 466.0,962.8 499.0,962.3 532.0,961.7 565.0,961.1 598.0,960.6 631.0,960.0 664.0,959.5 697.0,958.9" fill="none" stroke="#9467bd" stroke-width="1.8" opacity="1.0"/>
<polyline points="70.0,1388.6 73.1,1384.3 76.3,1382.4 79.4,1380.1 82.5,1377.2 85.7,1375.8 88.8,1376.7 91.9,1378.2 95.1,1381.7 98.2,1384.2 101.3,1390.4 104.5,1394.2 107.6,1384.3 110.8,1379.2 113.9,1376.0 117.0,1373.9 120.2,1373.1 123.3,1372.6 126.4,1371.7 129.6,1371.0 132.7,1371.2 135.8,1371.3 139.0,1371.8 142.1,1372.0 145.2,1373.0 148.4,1373.4 151.5,1375.0 154.6,1375.2 157.8,1377.9 160.9,1379.3 164.1,1380.5 167.2,1381.0 170.3,1380.8 173.5,1380.4 176.6,1382.1 179.7,1382.3 182.9,1380.9 186.0,1380.5 189.1,1382.1 192.3,1379.9 195.4,1379.2 198.5,1378.0 201.7,1375.1 204.8,1372.7 207.9,1371.7 211.1,1370.2 214.2,1369.3 217.3,1367.8 220.5,1366.8 223.6,1365.7 226.8,1364.8 229.9,1364.0 233.0,1363.3 236.2,1363.7 239.3,1363.9 242.4,1364.5 245.6,1365.6 248.7,1367.1 251.8,1369.1 255.0,1370.0 258.1,1371.0 261.2,1371.8 264.4,1371.9 267.5,1371.2 270.6,1370.3 273.8,1371.1 276.9,1370.3 280.0,1369.3 283.2,1370.3 286.3,1370.0 289.4,1371.0 292.6,1369.7 295.7,1369.3 298.9,1369.5 302.0,1369.1 305.1,1367.5 308.3,1365.0 311.4,1364.7 314.5,1332.0 317.7,1278.9 320.8,1239.1 323.9,1230.5 327.1,1230.2 330.2,1229.8 333.3,1229.7 336.5,1229.5 339.6,1229.4 342.7,1229.2 345.9,1229.1 349.0,1228.8 352.1,1228.7 355.3,1228.5 358.4,1228.1 361.6,1227.9 364.7,1227.8 367.8,1227.6 371.0,1227.5 374.1,1227.3 377.2,1227.0 380.4,1226.8 383.5,1226.6 386.6,1226.4 389.8,1226.2 392.9,1225.9 396.0,1225.7 399.2,1225.5 402.3,1225.2 405.4,1225.0 408.6,1224.7 411.7,1224.3 414.9,1224.0 418.0,1223.8 421.1,1223.6 424.3,1223.3 427.4,1223.1 430.5,1222.9 433.7,1222.6 436.8,1222.4 439.9,1222.2 443.1,1222.0 446.2,1221.8 449.3,1221.7 452.5,1221.5 455.6,1221.3 458.7,1220.9 461.9,1220.8 465.0,1220.6 468.1,1220.5 471.3,1220.3 474.4,1220.1 477.6,1219.9 480.7,1219.8 483.8,1219.5 487.0,1219.4 490.1,1219.3 493.2,1219.1 496.4,1218.8 499.5,1218.6 502.6,1218.5 505.8,1218.4 508.9,1218.2 512.0,1218.0 515.2,1217.8 518.3,1217.7 521.4,1217.5 524.6,1217.3 527.7,1217.0 530.8,1216.8 534.0,1216.7 537.1,1216.5 540.2,1216.3 543.4,1216.1 546.5,1215.9 549.7,1215.8 552.8,1215.6 555.9,1215.5 559.1,1215.4 562.2,1215.2 565.3,1215.0 568.5,1214.8 571.6,1214.6 574.7,1214.5 577.9,1214.3 581.0,1214.1 584.1,1213.8 587.3,1213.6 590.4,1213.5 593.5,1213.3 596.7,1213.1 599.8,1213.0 603.0,1212.8 606.1,1212.4 609.2,1212.3 612.4,1212.1 615.5,1212.0 618.6,1211.8 621.8,1211.7 624.9,1211.5 628.0,1211.4 631.2,1211.2 634.3,1211.1 637.4,1210.9 640.6,1210.8 643.7,1210.5 646.8,1210.4 650.0,1210.2 653.1,1210.1 656.2,1210.0 659.4,1209.8 662.5,1209.6 665.6,1209.5 668.8,1209.3 671.9,1209.2 675.1,1209.1 678.2,1208.9 681.3,1208.8 684.5,1208.7 687.6,1208.6 690.7,1208.5 693.9,1208.3 697.0,1208.2" fill="none" stroke="#ff7f0e" stroke-width="1.8" opacity="1.0"/> <text x="747" y="940" font-size="15" font-weight="bold">Throughput - games per hour</text>
<line x1="82" y1="1480" x2="110" y2="1480" stroke="#1f77b4" stroke-width="3"/> <text x="747" y="955" font-size="11" fill="#555">method: training_metrics.jsonl 'epoch' deltas; 1 row = one 10-game chunk</text>
<text x="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="1428.0" x2="1375" y2="1428.0" stroke="#dddddd"/> <line x1="747" y1="1428.0" x2="1375" y2="1428.0" stroke="#dddddd"/>
<line x1="747" y1="1310.5" x2="1375" y2="1310.5" stroke="#dddddd"/> <line x1="747" y1="1310.5" x2="1375" y2="1310.5" stroke="#dddddd"/>
<line x1="747" y1="1193.0" x2="1375" y2="1193.0" stroke="#dddddd"/> <line x1="747" y1="1193.0" x2="1375" y2="1193.0" stroke="#dddddd"/>
@@ -466,278 +160,82 @@
<line x1="747" y1="1428" x2="747" y2="958" 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"/> <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> <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"/> <line x1="781.9" y1="1428" x2="781.9" y2="1432" stroke="black"/>
<text x="904.0" y="1445" text-anchor="middle" font-size="11">51</text> <text x="781.9" y="1445" text-anchor="middle" font-size="11">2</text>
<line x1="816.8" y1="1428" x2="816.8" y2="1432" stroke="black"/>
<text x="816.8" y="1445" text-anchor="middle" font-size="11">3</text>
<line x1="851.7" y1="1428" x2="851.7" y2="1432" stroke="black"/>
<text x="851.7" y="1445" text-anchor="middle" font-size="11">4</text>
<line x1="886.6" y1="1428" x2="886.6" y2="1432" stroke="black"/>
<text x="886.6" y="1445" text-anchor="middle" font-size="11">5</text>
<line x1="921.4" y1="1428" x2="921.4" y2="1432" stroke="black"/>
<text x="921.4" y="1445" text-anchor="middle" font-size="11">6</text>
<line x1="956.3" y1="1428" x2="956.3" y2="1432" stroke="black"/>
<text x="956.3" y="1445" text-anchor="middle" font-size="11">7</text>
<line x1="991.2" y1="1428" x2="991.2" y2="1432" stroke="black"/>
<text x="991.2" y="1445" text-anchor="middle" font-size="11">8</text>
<line x1="1026.1" y1="1428" x2="1026.1" y2="1432" stroke="black"/>
<text x="1026.1" y="1445" text-anchor="middle" font-size="11">9</text>
<line x1="1061.0" y1="1428" x2="1061.0" y2="1432" stroke="black"/> <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">101</text> <text x="1061.0" y="1445" text-anchor="middle" font-size="11">10</text>
<line x1="1218.0" y1="1428" x2="1218.0" y2="1432" stroke="black"/> <line x1="1095.9" y1="1428" x2="1095.9" y2="1432" stroke="black"/>
<text x="1218.0" y="1445" text-anchor="middle" font-size="11">151</text> <text x="1095.9" y="1445" text-anchor="middle" font-size="11">11</text>
<line x1="1130.8" y1="1428" x2="1130.8" y2="1432" stroke="black"/>
<text x="1130.8" y="1445" text-anchor="middle" font-size="11">12</text>
<line x1="1165.7" y1="1428" x2="1165.7" y2="1432" stroke="black"/>
<text x="1165.7" y="1445" text-anchor="middle" font-size="11">13</text>
<line x1="1200.6" y1="1428" x2="1200.6" y2="1432" stroke="black"/>
<text x="1200.6" y="1445" text-anchor="middle" font-size="11">14</text>
<line x1="1235.4" y1="1428" x2="1235.4" y2="1432" stroke="black"/>
<text x="1235.4" y="1445" text-anchor="middle" font-size="11">15</text>
<line x1="1270.3" y1="1428" x2="1270.3" y2="1432" stroke="black"/>
<text x="1270.3" y="1445" text-anchor="middle" font-size="11">16</text>
<line x1="1305.2" y1="1428" x2="1305.2" y2="1432" stroke="black"/>
<text x="1305.2" y="1445" text-anchor="middle" font-size="11">17</text>
<line x1="1340.1" y1="1428" x2="1340.1" y2="1432" stroke="black"/>
<text x="1340.1" y="1445" text-anchor="middle" font-size="11">18</text>
<line x1="1375.0" y1="1428" x2="1375.0" y2="1432" stroke="black"/> <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">201</text> <text x="1375.0" y="1445" text-anchor="middle" font-size="11">19</text>
<line x1="743" y1="1428.0" x2="747" y2="1428.0" stroke="black"/> <line x1="743" y1="1428.0" x2="747" y2="1428.0" stroke="black"/>
<text x="740" y="1432.0" text-anchor="end" font-size="11">0</text> <text x="740" y="1432.0" text-anchor="end" font-size="11">0</text>
<line x1="743" y1="1310.5" x2="747" y2="1310.5" stroke="black"/> <line x1="743" y1="1310.5" x2="747" y2="1310.5" stroke="black"/>
<text x="740" y="1314.5" text-anchor="end" font-size="11">0.252</text> <text x="740" y="1314.5" text-anchor="end" font-size="11">1653</text>
<line x1="743" y1="1193.0" x2="747" y2="1193.0" stroke="black"/> <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> <text x="740" y="1197.0" text-anchor="end" font-size="11">3305</text>
<line x1="743" y1="1075.5" x2="747" y2="1075.5" stroke="black"/> <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> <text x="740" y="1079.5" text-anchor="end" font-size="11">4958</text>
<line x1="743" y1="958.0" x2="747" y2="958.0" stroke="black"/> <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="740" y="962.0" text-anchor="end" font-size="11">6611</text>
<text x="1061" y="1461" text-anchor="middle" font-size="12">metric line number</text> <text x="1061" y="1461" text-anchor="middle" font-size="12">chunk interval (10-game chunks)</text>
<text x="16" y="1193" text-anchor="middle" font-size="12" transform="rotate(-90 16 1193)">alpha</text> <text x="16" y="1193" text-anchor="middle" font-size="12" transform="rotate(-90 16 1193)">games / hour</text>
<polyline points="747.0,961.7 750.1,962.3 753.3,962.6 756.4,963.0 759.6,963.6 762.7,964.5 765.8,964.8 769.0,965.1 772.1,965.6 775.3,965.9 778.4,966.5 781.5,967.4 784.7,967.6 787.8,967.0 791.0,966.5 794.1,965.9 797.2,965.4 800.4,964.8 803.5,964.1 806.7,963.6 809.8,963.0 812.9,962.5 816.1,961.9 819.2,961.3 822.4,960.8 825.5,960.5 828.6,959.6 831.8,959.3 834.9,959.8 838.1,960.1 841.2,960.5 844.3,960.8 847.5,961.1 850.6,961.5 853.8,962.0 856.9,962.5 860.0,963.3 863.2,963.9 866.3,964.4 869.5,965.0 872.6,965.5 875.7,966.0 878.9,966.3 882.0,965.9 885.2,965.5 888.3,965.2 891.4,964.9 894.6,964.4 897.7,964.0 900.9,963.5 904.0,963.0 907.1,962.4 910.3,962.0 913.4,961.6 916.6,960.6 919.7,960.1 922.8,959.5 926.0,958.5 929.1,958.0 932.3,958.4 935.4,958.8 938.5,959.4 941.7,960.0 944.8,960.5 948.0,961.5 951.1,962.1 954.2,962.6 957.4,963.2 960.5,963.7 963.7,964.0 966.8,964.6 969.9,965.1 973.1,965.7 976.2,965.9 979.4,965.9 982.5,965.4 985.6,964.3 988.8,963.7 991.9,963.5 995.1,964.1 998.2,964.6 1001.3,964.9 1004.5,965.6 1007.6,966.4 1010.8,966.7 1013.9,967.0 1017.0,967.2 1020.2,967.8 1023.3,968.1 1026.5,968.6 1029.6,969.2 1032.7,969.7 1035.9,970.4 1039.0,971.0 1042.2,971.2 1045.3,971.5 1048.4,971.8 1051.6,972.3 1054.7,972.9 1057.9,973.4 1061.0,973.7 1064.1,974.2 1067.3,974.8 1070.4,975.3 1073.6,975.9 1076.7,976.4 1079.8,977.0 1083.0,977.5 1086.1,978.0 1089.3,979.0 1092.4,979.5 1095.5,980.1 1098.7,980.5 1101.8,981.0 1105.0,981.4 1108.1,981.9 1111.2,982.5 1114.4,983.0 1117.5,983.5 1120.7,983.9 1123.8,984.3 1126.9,984.7 1130.1,985.3 1133.2,985.8 1136.4,986.7 1139.5,987.3 1142.6,987.7 1145.8,988.2 1148.9,988.7 1152.1,989.2 1155.2,989.8 1158.3,990.2 1161.5,990.9 1164.6,991.3 1167.8,991.7 1170.9,992.3 1174.0,993.0 1177.2,993.6 1180.3,994.0 1183.5,994.3 1186.6,994.9 1189.7,995.4 1192.9,995.9 1196.0,996.4 1199.2,996.9 1202.3,997.5 1205.4,998.0 1208.6,998.5 1211.7,999.0 1214.9,999.5 1218.0,1000.0 1221.1,1000.5 1224.3,1000.9 1227.4,1001.3 1230.6,1001.7 1233.7,1002.1 1236.8,1002.5 1240.0,1003.0 1243.1,1003.5 1246.3,1004.0 1249.4,1004.5 1252.5,1005.0 1255.7,1005.5 1258.8,1006.0 1262.0,1006.9 1265.1,1007.4 1268.2,1007.9 1271.4,1008.4 1274.5,1008.9 1277.7,1009.4 1280.8,1010.1 1283.9,1011.1 1287.1,1011.6 1290.2,1012.1 1293.4,1012.6 1296.5,1013.1 1299.6,1013.6 1302.8,1014.1 1305.9,1014.5 1309.1,1015.0 1312.2,1015.7 1315.3,1016.2 1318.5,1016.7 1321.6,1017.6 1324.8,1018.0 1327.9,1018.5 1331.0,1019.0 1334.2,1019.5 1337.3,1020.0 1340.5,1020.8 1343.6,1021.3 1346.7,1021.8 1349.9,1022.3 1353.0,1022.8 1356.2,1023.3 1359.3,1023.6 1362.4,1024.1 1365.6,1024.6 1368.7,1025.1 1371.9,1025.6 1375.0,1026.0" fill="none" stroke="#9467bd" stroke-width="1.8" opacity="1.0"/> <circle cx="747.0" cy="1159.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<text x="70" y="1600" font-size="15" font-weight="bold">Throughput - games per hour</text> <circle cx="781.9" cy="1413.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<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> <circle cx="816.8" cy="1148.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="70" y1="1780.0" x2="1375" y2="1780.0" stroke="#dddddd"/> <circle cx="851.7" cy="1404.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="70" y1="1739.5" x2="1375" y2="1739.5" stroke="#dddddd"/> <circle cx="886.6" cy="1153.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="70" y1="1699.0" x2="1375" y2="1699.0" stroke="#dddddd"/> <circle cx="921.4" cy="1399.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="70" y1="1658.5" x2="1375" y2="1658.5" stroke="#dddddd"/> <circle cx="956.3" cy="1000.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="70" y1="1618.0" x2="1375" y2="1618.0" stroke="#dddddd"/> <circle cx="991.2" cy="1399.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="70" y1="1780" x2="1375" y2="1780" stroke="black"/> <circle cx="1026.1" cy="1021.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="70" y1="1780" x2="70" y2="1618" stroke="black"/> <circle cx="1061.0" cy="1394.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="194.6" y1="1780" x2="194.6" y2="1784" stroke="black"/> <circle cx="1095.9" cy="1376.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<text x="194.6" y="1797" text-anchor="middle" font-size="11">20</text> <circle cx="1130.8" cy="1394.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="325.8" y1="1780" x2="325.8" y2="1784" stroke="black"/> <circle cx="1165.7" cy="1130.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<text x="325.8" y="1797" text-anchor="middle" font-size="11">40</text> <circle cx="1200.6" cy="1399.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="456.9" y1="1780" x2="456.9" y2="1784" stroke="black"/> <circle cx="1235.4" cy="1176.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<text x="456.9" y="1797" text-anchor="middle" font-size="11">60</text> <circle cx="1270.3" cy="1402.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="588.1" y1="1780" x2="588.1" y2="1784" stroke="black"/> <circle cx="1305.2" cy="1158.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<text x="588.1" y="1797" text-anchor="middle" font-size="11">80</text> <circle cx="1340.1" cy="1400.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<line x1="719.2" y1="1780" x2="719.2" y2="1784" stroke="black"/> <circle cx="1375.0" cy="1149.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<text x="719.2" y="1797" text-anchor="middle" font-size="11">100</text> <polyline points="747.0,1159.8 781.9,1413.4 781.9,1148.0 851.7,1404.7 851.7,1153.6 921.4,1399.6 921.4,1000.7 991.2,1399.5 991.2,1021.4 1061.0,1394.8 1061.0,1376.2 1130.8,1394.4 1130.8,1130.1 1200.6,1399.7 1200.6,1176.5 1270.3,1402.0 1270.3,1158.0 1340.1,1400.0 1340.1,1149.2" fill="none" stroke="#2ca02c" stroke-width="3.5" opacity="1.0"/>
<line x1="850.4" y1="1780" x2="850.4" y2="1784" stroke="black"/> <rect x="0" y="1540" width="1400" height="180" fill="#f2f2f2"/>
<text x="850.4" y="1797" text-anchor="middle" font-size="11">120</text> <text x="16" y="1561" font-size="15" font-weight="bold">How to read</text>
<line x1="981.5" y1="1780" x2="981.5" y2="1784" stroke="black"/> <text x="1384" y="1560" text-anchor="end" font-size="11" fill="#666">Regenerate anytime: python3 tools/plot_progress.py</text>
<text x="981.5" y="1797" text-anchor="middle" font-size="11">140</text> <text x="16" y="1583" font-size="13">Test matches: dots are single fights, thick line shows trend.</text>
<line x1="1112.7" y1="1780" x2="1112.7" y2="1784" stroke="black"/> <text x="16" y="1600" font-size="13">Loss spikes are normal early; endless growth is bad.</text>
<text x="1112.7" y="1797" text-anchor="middle" font-size="11">160</text> <text x="16" y="1617" font-size="13">Alpha high means experimenting; falling too fast freezes habits.</text>
<line x1="1243.8" y1="1780" x2="1243.8" y2="1784" stroke="black"/> <text x="16" y="1634" font-size="13">Throughput flat is healthy; dips mean something slowed.</text>
<text x="1243.8" y="1797" text-anchor="middle" font-size="11">180</text> <text x="16" y="1651" font-size="13">This file reloads itself in Chrome every sixty seconds.</text>
<line x1="1375.0" y1="1780" x2="1375.0" y2="1784" stroke="black"/> <text x="16" y="1668" font-size="13">Regenerate anytime with tools/watch_dashboard.sh or the python command.</text>
<text x="1375.0" y="1797" text-anchor="middle" font-size="11">200</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="70.0" cy="1698.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="76.6" cy="1774.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="83.1" cy="1658.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="89.7" cy="1776.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="96.2" cy="1762.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="102.8" cy="1776.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="109.3" cy="1655.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="115.9" cy="1775.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="122.5" cy="1654.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="129.0" cy="1776.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="135.6" cy="1762.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="142.1" cy="1773.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="148.7" cy="1689.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="155.3" cy="1772.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="161.8" cy="1698.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="168.4" cy="1771.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="174.9" cy="1693.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="181.5" cy="1772.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="188.0" cy="1696.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="194.6" cy="1771.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="201.2" cy="1692.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="207.7" cy="1771.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="214.3" cy="1689.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="220.8" cy="1771.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="227.4" cy="1653.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="233.9" cy="1773.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="240.5" cy="1687.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="247.1" cy="1772.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="253.6" cy="1653.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="260.2" cy="1775.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="266.7" cy="1650.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="273.3" cy="1773.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="279.8" cy="1682.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="286.4" cy="1774.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="293.0" cy="1687.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="299.5" cy="1774.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="306.1" cy="1691.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="312.6" cy="1774.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="319.2" cy="1690.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="325.8" cy="1775.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="332.3" cy="1690.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="338.9" cy="1776.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="345.4" cy="1659.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="352.0" cy="1772.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="358.5" cy="1645.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="365.1" cy="1773.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="371.7" cy="1680.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="378.2" cy="1772.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="384.8" cy="1666.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="391.3" cy="1775.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="397.9" cy="1690.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="404.4" cy="1772.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="411.0" cy="1679.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="417.6" cy="1775.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="424.1" cy="1686.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="430.7" cy="1775.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="437.2" cy="1762.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="443.8" cy="1775.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="450.4" cy="1694.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="456.9" cy="1775.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="463.5" cy="1680.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="470.0" cy="1775.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="476.6" cy="1681.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="483.1" cy="1775.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="489.7" cy="1691.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="496.3" cy="1772.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="502.8" cy="1683.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="509.4" cy="1774.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="515.9" cy="1642.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="522.5" cy="1775.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="529.0" cy="1683.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="535.6" cy="1774.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="542.2" cy="1639.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="548.7" cy="1775.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="555.3" cy="1696.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="561.8" cy="1775.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="568.4" cy="1691.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="574.9" cy="1773.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="581.5" cy="1696.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="588.1" cy="1771.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="594.6" cy="1632.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="601.2" cy="1757.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="607.7" cy="1771.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="614.3" cy="1641.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="620.9" cy="1770.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="627.4" cy="1650.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="634.0" cy="1771.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="640.5" cy="1639.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="647.1" cy="1770.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="653.6" cy="1689.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="660.2" cy="1770.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="666.8" cy="1705.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="673.3" cy="1771.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="679.9" cy="1652.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="686.4" cy="1771.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="693.0" cy="1660.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="699.5" cy="1771.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="706.1" cy="1681.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="712.7" cy="1770.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="719.2" cy="1643.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="725.8" cy="1770.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="732.3" cy="1690.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="738.9" cy="1771.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="745.5" cy="1684.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="752.0" cy="1769.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="758.6" cy="1688.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="765.1" cy="1769.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="771.7" cy="1695.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="778.2" cy="1773.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="784.8" cy="1694.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="791.4" cy="1770.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="797.9" cy="1673.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="804.5" cy="1770.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="811.0" cy="1676.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="817.6" cy="1771.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="824.1" cy="1691.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="830.7" cy="1769.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="837.3" cy="1692.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="843.8" cy="1770.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="850.4" cy="1670.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="856.9" cy="1770.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="863.5" cy="1698.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="870.1" cy="1770.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="876.6" cy="1762.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="883.2" cy="1770.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="889.7" cy="1688.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="896.3" cy="1770.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="902.8" cy="1690.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="909.4" cy="1771.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="916.0" cy="1693.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="922.5" cy="1769.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="929.1" cy="1760.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="935.6" cy="1769.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="942.2" cy="1682.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="948.7" cy="1770.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="955.3" cy="1761.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="961.9" cy="1770.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="968.4" cy="1680.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="975.0" cy="1769.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="981.5" cy="1681.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="988.1" cy="1769.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="994.6" cy="1692.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1001.2" cy="1771.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1007.8" cy="1685.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1014.3" cy="1771.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1020.9" cy="1690.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1027.4" cy="1772.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1034.0" cy="1696.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1040.6" cy="1770.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1047.1" cy="1682.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1053.7" cy="1771.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1060.2" cy="1673.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1066.8" cy="1770.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1073.3" cy="1676.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1079.9" cy="1769.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1086.5" cy="1668.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1093.0" cy="1771.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1099.6" cy="1694.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1106.1" cy="1771.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1112.7" cy="1695.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1119.2" cy="1771.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1125.8" cy="1689.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1132.4" cy="1771.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1138.9" cy="1761.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1145.5" cy="1771.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1152.0" cy="1688.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1158.6" cy="1771.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1165.2" cy="1684.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1171.7" cy="1772.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1178.3" cy="1702.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1184.8" cy="1774.6" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1191.4" cy="1702.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1197.9" cy="1772.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1204.5" cy="1688.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1211.1" cy="1772.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1217.6" cy="1696.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1224.2" cy="1772.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1230.7" cy="1695.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1237.3" cy="1771.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1243.8" cy="1707.2" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1250.4" cy="1772.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1257.0" cy="1699.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1263.5" cy="1774.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1270.1" cy="1672.7" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1276.6" cy="1771.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1283.2" cy="1689.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1289.7" cy="1771.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1296.3" cy="1696.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1302.9" cy="1774.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1309.4" cy="1689.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1316.0" cy="1772.3" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1322.5" cy="1687.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1329.1" cy="1771.8" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1335.7" cy="1690.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1342.2" cy="1772.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1348.8" cy="1691.0" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1355.3" cy="1772.5" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1361.9" cy="1701.1" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1368.4" cy="1770.4" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<circle cx="1375.0" cy="1682.9" r="1.6" fill="#7f7f7f" opacity="0.25"/>
<polyline points="96.2,1730.7 161.8,1740.2 227.4,1724.1 293.0,1727.4 358.5,1721.3 424.1,1738.6 489.7,1725.4 555.3,1727.8 620.9,1709.3 686.4,1720.0 752.0,1730.7 817.6,1725.8 883.2,1738.6 948.7,1741.6 1014.3,1730.2 1079.9,1726.2 1145.5,1738.5 1211.1,1735.3 1276.6,1731.1 1342.2,1731.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> <script type="text/javascript"><![CDATA[ setTimeout(function(){ location.reload(); }, 60000); ]]></script>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 18 KiB

+25 -115
View File
@@ -3,21 +3,18 @@
Pure-stdlib SVG output (matplotlib not available on this box). Pure-stdlib SVG output (matplotlib not available on this box).
Generates ONE file: Generates ONE file:
docs/campaign_dashboard.svg - five panels, current (v2) run only: docs/campaign_dashboard.svg - four panels, current (v2) run only:
1. test-match win % vs opponents (campaign_v2_stdout.log eval lines) 1. test-match win % vs opponents (campaign_v4_stdout.log eval lines)
2. real-fight win % per opponent (training_log.jsonl, ~100-game buckets) 2. critic_loss / |actor_loss| (training_metrics.jsonl, shared log-y)
3. critic_loss / |actor_loss| (training_metrics.jsonl, shared log-y) 3. alpha temperature (training_metrics.jsonl, linear)
4. alpha temperature (training_metrics.jsonl, linear) 4. throughput, games/hour buckets (training_metrics.jsonl 'epoch' deltas;
5. throughput, games/hour buckets (training_metrics.jsonl 'epoch' deltas; 1 metrics row == one 10-game chunk,
training_log.jsonl has NO timestamps - counts match stdout chunk markers)
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 plus an embedded JS snippet that reloads the page every 60 s when the SVG is
opened as a top-level document in Chrome. opened as a top-level document in Chrome.
Usage: Usage:
python3 tools/plot_progress.py [campaign_log] [metrics_jsonl] [games_jsonl] [outdir] python3 tools/plot_progress.py [campaign_log] [metrics_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
""" """
@@ -34,7 +31,6 @@ 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 (panel 1) TREND_WINDOW = 10 # rolling mean shown as the thick trend line (panel 1)
GAME_BUCKET = 100 # games per bucket, real-fight panel
RATE_BUCKET = 20 # metric intervals per throughput bucket (~200 games) RATE_BUCKET = 20 # metric intervals per throughput bucket (~200 games)
GAMES_PER_ROW = 10 # one training_metrics.jsonl row per 10-round chunk GAMES_PER_ROW = 10 # one training_metrics.jsonl row per 10-round chunk
COLORS = {"Corners": "#d62728", "Crazy": "#1f77b4", "Target": "#2ca02c", COLORS = {"Corners": "#d62728", "Crazy": "#1f77b4", "Target": "#2ca02c",
@@ -42,7 +38,7 @@ COLORS = {"Corners": "#d62728", "Crazy": "#1f77b4", "Target": "#2ca02c",
CRITIC_C, ACTOR_C = "#1f77b4", "#ff7f0e" CRITIC_C, ACTOR_C = "#1f77b4", "#ff7f0e"
RELOAD_JS = ('<script type="text/javascript"><![CDATA[ ' RELOAD_JS = ('<script type="text/javascript"><![CDATA[ '
'setTimeout(function(){ location.reload(); }, 60000); ]]></script>') 'setTimeout(function(){ location.reload(); }, 60000); ]]></script>')
W, H = 1400, 2000 W, H = 1400, 1720
TITLE_H, GUIDE_H = 80, 180 TITLE_H, GUIDE_H = 80, 180
# rows: (header_y, panel_top_y, panel_bottom_y, x_left, x_right) # rows: (header_y, panel_top_y, panel_bottom_y, x_left, x_right)
C1_L, C1_R = 70, 697 C1_L, C1_R = 70, 697
@@ -52,18 +48,15 @@ ROWS = {
2: (100, 120, 720, C2_L, C2_R), 2: (100, 120, 720, C2_L, C2_R),
3: (940, 958, 1428, C1_L, C1_R), 3: (940, 958, 1428, C1_L, C1_R),
4: (940, 958, 1428, C2_L, C2_R), 4: (940, 958, 1428, C2_L, C2_R),
5: (1600, 1618, 1780, C1_L, C2_R),
} }
PANEL_TITLES = [ PANEL_TITLES = [
"Test matches - win % vs opponents", "Test matches - win % vs opponents",
"Real fights - win % per opponent",
"Training losses (log scale)", "Training losses (log scale)",
"Alpha temperature", "Alpha temperature",
"Throughput - games per hour", "Throughput - games per hour",
] ]
GUIDE_LINES = [ GUIDE_LINES = [
"Test matches: dots are single fights, thick line shows trend.", "Test matches: dots are single fights, thick line shows trend.",
"Real battles only. Rising lines mean the bot improves.",
"Loss spikes are normal early; endless growth is bad.", "Loss spikes are normal early; endless growth is bad.",
"Alpha high means experimenting; falling too fast freezes habits.", "Alpha high means experimenting; falling too fast freezes habits.",
"Throughput flat is healthy; dips mean something slowed.", "Throughput flat is healthy; dips mean something slowed.",
@@ -109,25 +102,6 @@ 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): def metric_col(rows, key, positive=False):
"""[(index, value)] for float-parseable rows; abs() applied; optional >0 filter.""" """[(index, value)] for float-parseable rows; abs() applied; optional >0 filter."""
out = [] out = []
@@ -305,53 +279,6 @@ def panel_test_matches(series, geo):
s += legend(items, x0 + 12, pb + 52) s += legend(items, x0 + 12, pb + 52)
return s return s
def panel_real_fights(games, geo):
_, pt, pb, x0, x1 = geo
s = ""
if not games:
s += f'<text x="{x0 + 10}" y="{pt + 40}" font-size="12" fill="#a00">' \
"no game rows found</text>\n"
return
edges = list(range(0, len(games) + 1, GAME_BUCKET))
if edges[-1] != len(games):
edges.append(len(games))
buckets = list(zip(edges[:-1], edges[1:]))
xm, ym = map_fn(x0, x1, 1, max(len(games), 2)), map_fn(pb, pt, 0, 100)
s += hgrid(x0, x1, [ym(v) for v in range(0, 101, 20)])
step = max(GAME_BUCKET, GAME_BUCKET * (len(games) // GAME_BUCKET // 8 + 1))
xt = [(str(v), xm(v)) for v in range(step, len(games) + 1, step)]
s += axis(x0, pb, x1, pt, xt, ticks_linear(0, 100, pb, pt, n=6),
f"game number ({GAME_BUCKET}-game buckets)", "win %")
opponents = []
for opp, _ in games:
if opp not in opponents:
opponents.append(opp)
items = []
for name in opponents:
c = COLORS.get(name, "#7f7f7f")
by_b = {}
for bi, (lo, hi) in enumerate(buckets):
sub = [w for o, w in games[lo:hi] if o == name]
if sub:
by_b[bi] = 100.0 * sum(sub) / len(sub)
pts = []
segs, prev = [], None
for bi in sorted(by_b):
if prev is not None and bi != prev + 1:
segs.append(pts)
pts = []
center = (buckets[bi][0] + buckets[bi][1]) / 2
pts.append((xm(center), ym(by_b[bi])))
prev = bi
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 panel_losses(rows, geo): def panel_losses(rows, geo):
_, pt, pb, x0, x1 = geo _, pt, pb, x0, x1 = geo
s = "" s = ""
@@ -434,14 +361,12 @@ def panel_throughput(rows, geo):
# ---------- assembly ---------- # ---------- assembly ----------
def build_dashboard(campaign, metrics_f, games_f, out): def build_dashboard(campaign, metrics_f, out):
series = parse_eval_series(campaign) series = parse_eval_series(campaign)
print("[info] evals parsed: " + print("[info] evals parsed: " +
(", ".join(f"{k}={len(v)}" for k, v in sorted(series.items())) or "(none)")) (", ".join(f"{k}={len(v)}" for k, v in sorted(series.items())) or "(none)"))
rows = parse_metrics(metrics_f) rows = parse_metrics(metrics_f)
print(f"[info] metric rows parsed: {len(rows)}") print(f"[info] metric rows parsed: {len(rows)}")
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}" ' 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'viewBox="0 0 {W} {H}" font-family="sans-serif">\n'
@@ -457,18 +382,14 @@ def build_dashboard(campaign, metrics_f, games_f, out):
"raw dots = single test matches, thick = rolling-mean-%d" % TREND_WINDOW, "raw dots = single test matches, thick = rolling-mean-%d" % TREND_WINDOW,
lambda: panel_test_matches(series, ROWS[1])), lambda: panel_test_matches(series, ROWS[1])),
(ROWS[2], PANEL_TITLES[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", "training_metrics.jsonl - big early spikes are normal",
lambda: panel_losses(rows, ROWS[3])), lambda: panel_losses(rows, ROWS[2])),
(ROWS[4], PANEL_TITLES[3], (ROWS[3], PANEL_TITLES[2],
"training_metrics.jsonl - high = exploring, low = exploiting", "training_metrics.jsonl - high = exploring, low = exploiting",
lambda: panel_alpha(rows, ROWS[4])), lambda: panel_alpha(rows, ROWS[3])),
(ROWS[5], PANEL_TITLES[4], (ROWS[4], PANEL_TITLES[3],
"method: training_metrics.jsonl 'epoch' deltas (training_log.jsonl has " "method: training_metrics.jsonl 'epoch' deltas; 1 row = one 10-game chunk",
"no timestamps); 1 row = one 10-game chunk", lambda: panel_throughput(rows, ROWS[4])),
lambda: panel_throughput(rows, ROWS[5])),
] ]
for geo, title, sub, drawer in drawers: for geo, title, sub, drawer in drawers:
s += header(geo[3], geo[0], title, sub) s += header(geo[3], geo[0], title, sub)
@@ -506,20 +427,10 @@ def selftest():
'{"epoch": 1090.0, "critic_loss": 50, "actor_loss": 3, "alpha": 0.2}\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) == 3 and rows[1]["critic_loss"] == 100 assert len(rows) == 3 and rows[1]["critic_loss"] == 100
glines = []
for i in range(150): # 2 full GAME_BUCKETs, both opponents in both
glines.append(json.dumps(
{"type": "game", "round": i % 10 + 1, "ticks": 100,
"score": i % 3, "total_score": i, "win": i % 3 == 0,
"opponent": ("Corners", "Crazy")[i % 2]}))
(td / "g.jsonl").write_text("\n".join(glines) + "\n")
games = parse_games(td / "g.jsonl")
assert len(games) == 150 and games[0] == ("Corners", True)
assert games[-1] == ("Crazy", False) # i=149: odd -> Crazy; 149%3!=0 -> loss
assert metric_col(rows, "actor_loss") == [(0, 2.0), (1, 4.0), (2, 3.0)] assert metric_col(rows, "actor_loss") == [(0, 2.0), (1, 4.0), (2, 3.0)]
dash = td / "dash.svg" dash = td / "dash.svg"
assert build_dashboard(td / "log", td / "m.jsonl", td / "g.jsonl", dash) assert build_dashboard(td / "log", td / "m.jsonl", dash)
text = dash.read_text() text = dash.read_text()
ET.fromstring(text) # whole doc must parse -> closing tag present ET.fromstring(text) # whole doc must parse -> closing tag present
assert RELOAD_JS in text, "auto-reload script missing" assert RELOAD_JS in text, "auto-reload script missing"
@@ -527,11 +438,11 @@ def selftest():
assert t in text, f"panel title missing: {t}" assert t in text, f"panel title missing: {t}"
assert text.count(PANEL_TITLES[0]) == 1 assert text.count(PANEL_TITLES[0]) == 1
assert "How to read" in text, "reading guide missing" assert "How to read" in text, "reading guide missing"
assert 'width="1400"' in text and 'height="2000"' in text assert 'width="1400"' in text and 'height="1720"' in text
# circles: 4 eval dots (panel 1) + 2 throughput rate dots (panel 5) # circles: 4 eval dots (panel 1) + 2 throughput rate dots (panel 4)
assert text.count("<circle") == 6, text.count("<circle") assert text.count("<circle") == 6, text.count("<circle")
# 2 trends + 2 real-fight opp lines + 2 losses + 1 alpha + 1 throughput # 2 trends + 2 losses + 1 alpha + 1 throughput
assert text.count("<polyline") == 8, text.count("<polyline") assert text.count("<polyline") == 6, text.count("<polyline")
# orientation guard: a known rising series (10% -> 90%) rendered through # orientation guard: a known rising series (10% -> 90%) rendered through
# the FULL build path must plot upward (smaller SVG y) and forward in # the FULL build path must plot upward (smaller SVG y) and forward in
@@ -541,7 +452,7 @@ def selftest():
">>> [eval] win rate: 1/10 (10%) vs Corners\n" ">>> [eval] win rate: 1/10 (10%) vs Corners\n"
">>> [eval] win rate: 9/10 (90%) vs Corners\n") ">>> [eval] win rate: 9/10 (90%) vs Corners\n")
ori_dash = td / "ori.svg" ori_dash = td / "ori.svg"
assert build_dashboard(ori_log, td / "m.jsonl", td / "g.jsonl", ori_dash) assert build_dashboard(ori_log, td / "m.jsonl", ori_dash)
m = re.search(r'<polyline points="([^"]+)"', ori_dash.read_text()) m = re.search(r'<polyline points="([^"]+)"', ori_dash.read_text())
pts = [tuple(map(float, p.split(","))) for p in m.group(1).split()] pts = [tuple(map(float, p.split(","))) for p in m.group(1).split()]
assert len(pts) == 2, pts assert len(pts) == 2, pts
@@ -556,14 +467,13 @@ def main():
selftest() selftest()
return return
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_v4_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"
games = Path(args[2]) if len(args) > 2 else ROOT / "training_log.jsonl" outdir = Path(args[2]) if len(args) > 2 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)
try: try:
ok = build_dashboard(campaign, metrics, games, outdir / "campaign_dashboard.svg") ok = build_dashboard(campaign, metrics, outdir / "campaign_dashboard.svg")
except Exception as e: except Exception as e:
print(f"[error] dashboard build failed: {e}") print(f"[error] dashboard build failed: {e}")
ok = False ok = False
@@ -0,0 +1,46 @@
# Neuroevolution gun with fixed-topology ANN evolved by GA
Evo_Bot needs a gun that adapts to each opponent's dodge patterns during a match, finds nonlinear movement patterns that histograms miss, and is original. We chose a fixed-topology feedforward ANN (91->8->1, 745 weights) whose weights are evolved by a mutation-only GA running on a parallel thread. This beats the alternatives (RL too slow to adapt in-match, Q-learning collapses to histogram for single-shot decisions, guess-factor histograms are unoriginal, transformer/LLM-style prediction is data-starved at ~14k ticks per match) while keeping implementation risk low by deferring topology evolution (NEAT) until the fixed network hits its ceiling.
## Considered Options
- **PPO / SAC (end-to-end RL):** Too slow -- thousands of rounds to converge, cannot adapt mid-match. Explored in other bots in this repo.
- **Q-learning for aiming:** Collapses to a histogram. Single-shot aiming has no sequential decision structure for Q-learning to exploit.
- **Guess-factor histogram:** Proven and fast to converge (~15 ticks), but unoriginal -- 20 years of community tuning.
- **Transformer / LLM-style sequence prediction:** Data-starved. ~14k ticks per match vs billions needed for attention-based models.
- **GA with crossover:** Literature uniformly shows crossover is harmful for ANN weight evolution -- it breaks co-adapted weight configurations. Every modern neuroevolution paper (Uber Deep GA, NRA, OpenAI ES) drops it.
- **CMA-ES:** Ideal at d=750 weights (self-adapts sigma and covariance). More complex to implement; upgrade path from simple GA when needed.
## Decision
**Architecture:**
- Evo_Bot (1v1) with modular gun interface: `feed(state)` / `aim() -> (angle, power)`
- Gun owns its evolution thread (parallel, never blocks inference)
**TOPO_Gun (first gun implementation):**
- Network: 91->8->1 (hidden size configurable), 745 weights
- Input: 30 ticks x (lateral_vel, delta_heading, wall_distance_ahead) + current distance = 91
- Output: guess factor (-1 to +1)
- Bullet power: deterministic distance-based formula (not learned)
- Evolution: population 300, clone loaded champion + small Gaussian mutations (ALL weights, sigma=0.005-0.01), NO crossover, single elite preserved unchanged
- Fitness: virtual bullet hits on 100 randomly sampled replay tape ticks, using real distance-based power
- Replay tape: rolling window ~2000 ticks (configurable)
- Weight persistence: per-opponent -> global fallback -> random init (load order)
- Cold start: first-ever run, don't fire until champion emerges; subsequent runs load weights, fire from tick 1
- Push new champion weights to inference when it hits better than current
**Deferred:**
- NEAT_Gun: deferred until TOPO_Gun hits its ceiling
- Virtual Guns: run multiple guns in parallel, fire whichever has best virtual hit rate
**Boundaries:**
- Bot controls firing discipline (when to shoot, energy management); gun always returns best aim
## Consequences
- GA+ANN finds nonlinear patterns histograms miss, but needs more data (~50+ ticks vs ~15 for guess-factor histogram) before it outperforms
- Fixed topology before NEAT reduces implementation risk
- Mutation-only evolution simplifies implementation (no crossover logic)
- Parallel evolution thread reuses the pattern from SAC_LSTM_Bot (#48)
- Per-opponent weight persistence eliminates cold start after first encounter
- CMA-ES is the natural upgrade path if simple GA convergence is too slow (d=750 is CMA-ES sweet spot)
+111
View File
@@ -0,0 +1,111 @@
## ga_spike.nim — throwaway GA neuroevolution spike: evolve ANN to predict sin(x)
## Validates: population init, fitness eval, truncation selection, gaussian
## mutation on all weights, single-elite preservation, champion tracking.
## Issue #66.
import std/[math, random, algorithm, strformat]
const
InputDim = 10 # sliding window of 10 past sin values
HiddenDim = 4
OutputDim = 1
NumWeights = (InputDim * HiddenDim + HiddenDim) + # W1 + b1
(HiddenDim * OutputDim + OutputDim) # W2 + b2 = 49
PopSize = 200
Generations = 200
TopFrac = 0.2 # keep top 20%
Sigma = 0.01 # gaussian mutation sigma on ALL weights
EvalPoints = 50 # fitness eval sample size
type Individual = object
weights: seq[float64]
fitness: float64
proc forward(w: seq[float64]; input: array[InputDim, float64]): float64 =
## 10->4->1 tanh ANN, flat weight layout: W1[40], b1[4], W2[4], b2[1]
var hidden: array[HiddenDim, float64]
for h in 0..<HiddenDim:
var s = w[InputDim * HiddenDim + h] # b1[h]
for i in 0..<InputDim:
s += w[h * InputDim + i] * input[i] # W1[h,i]
hidden[h] = tanh(s)
var s = w[InputDim * HiddenDim + HiddenDim + HiddenDim] # b2[0]
for h in 0..<HiddenDim:
s += w[InputDim * HiddenDim + HiddenDim + h] * hidden[h] # W2[h]
result = tanh(s)
proc evaluate(ind: var Individual; rng: var Rand) =
## Fitness = -MSE on EvalPoints random samples of sin prediction.
var mse = 0.0
for _ in 0..<EvalPoints:
let x = rng.rand(0.0 .. 20.0 * PI)
var input: array[InputDim, float64]
for i in 0..<InputDim:
input[i] = sin(x - float64(InputDim - 1 - i))
let target = sin(x)
let pred = forward(ind.weights, input)
mse += (pred - target) * (pred - target)
ind.fitness = -mse / EvalPoints.float64
proc mutate(ind: var Individual; rng: var Rand) =
## Additive gaussian on ALL weights, per GA-parameters research.
for i in 0..<ind.weights.len:
ind.weights[i] += rng.gauss(0.0, Sigma)
when isMainModule:
var rng = initRand(42)
# Init population: random weights in [-0.5, 0.5]
var pop = newSeq[Individual](PopSize)
for i in 0..<PopSize:
pop[i].weights = newSeq[float64](NumWeights)
for j in 0..<NumWeights:
pop[i].weights[j] = rng.rand(-0.5 .. 0.5)
echo &"GA spike: {PopSize} individuals, {NumWeights} weights, {Generations} gens, sigma={Sigma}"
for gen in 0..<Generations:
# Evaluate
for i in 0..<PopSize:
pop[i].evaluate(rng)
# Sort descending by fitness (higher = better)
pop.sort(proc(a, b: Individual): int = cmp(b.fitness, a.fitness))
if gen mod 20 == 0 or gen == Generations - 1:
echo &" gen {gen:3d} best_fitness={pop[0].fitness:.6f} (MSE={-pop[0].fitness:.6f})"
# Selection: keep top 20%
let nParents = max(1, int(PopSize.float64 * TopFrac))
# Next generation: elite(1) + mutated children from parents
var next = newSeq[Individual](PopSize)
next[0] = pop[0] # single elite, unchanged
for i in 1..<PopSize:
let parent = rng.rand(0..<nParents)
next[i].weights = pop[parent].weights # clone parent
next[i].mutate(rng)
pop = next
# Final evaluation of champion
pop[0].evaluate(rng)
echo &"\nChampion fitness: {pop[0].fitness:.6f} (MSE={-pop[0].fitness:.6f})"
# Print predictions vs actual
echo "\nPredictions (sample):"
var totalErr = 0.0
let nSamples = 20
for i in 0..<nSamples:
let x = float64(i) * 1.0
var input: array[InputDim, float64]
for j in 0..<InputDim:
input[j] = sin(x - float64(InputDim - 1 - j))
let target = sin(x)
let pred = forward(pop[0].weights, input)
totalErr += abs(pred - target)
echo &" x={x:5.1f} sin(x)={target:+.4f} pred={pred:+.4f} err={abs(pred-target):.4f}"
let avgErr = totalErr / nSamples.float64
echo &"\nAverage absolute error: {avgErr:.4f}"
assert avgErr < 0.1, &"Champion avg error {avgErr:.4f} >= 0.1 — evolution did not converge"
echo "PASS: assert avgErr < 0.1"