feat(bots): promote gen5 stage-5 policy to the Hard tier

Hard has been a label-only duplicate of medium.json since medium was promoted
on 2026-08-17. Promote 20260823-1734-gen5-s5-intercepts-retry2 into
hard.json so the tier is a genuinely distinct policy, and so the strongest bot
the curriculum has produced survives the next round's checkpoint pruning —
promoted files are never touched by training scripts.

Stage 5 blocked after three attempts, so like medium.json this comes from a run
recorded as decision: "fail". Both failing floors are covered in TRAINING.md:
goal_rate 0.7369 vs 0.75 is marginal, and productive_air_touch_fraction 0.0001
vs 0.005 is a bar no policy in the lineage has approached, against a metric
quantised at 0.01 per ~100-episode window. On every other axis it is the best
yet: upright_fraction 0.757 against a 0.40 floor that the pre-Round-6 lineage
never pushed past 0.331, and forward_motion_fraction 0.479 against 0.20.

Chosen over attempt 2 (retry1) on a tiebreak, not a margin. retry1 posts a much
wider indirect result against medium.json (63-23-14 vs 47-32-21), but a direct
100-episode head-to-head between the two finished 36-39 with 25 draws, so that
gap does not reflect a real strength difference. Attempt 3 is the later
checkpoint (it resumed from attempt 2) and edges every telemetry metric.

Verified: hard.json is byte-identical to its source export, matches easy/medium
on input_size 83, 3 layers and action space, and beats medium.json 19-7-4 in a
fresh 30-episode paired run. Tiers stay monotonic: hard > medium > easy.

That head-to-head also showed a 17% physical side imbalance (physical teams
0-1 = 29-46), reproduced at 13% in the 30-episode check. Inside the 20% bar
used elsewhere and equal across both models, but noted in TRAINING.md as worth
investigating rather than assuming variance.
This commit is contained in:
Josh Creek
2026-08-24 08:46:06 +01:00
parent 6320b982a8
commit e1f512c94e
4 changed files with 62 additions and 11 deletions
File diff suppressed because one or more lines are too long
+4 -5
View File
@@ -13,16 +13,15 @@ const BOTS_DIR := "res://bots"
# Every tier runs its promoted checkpoint at full trained capability —
# difficulty is a genuinely different policy, never the same policy
# handicapped with reaction delay or action noise. Easy and Medium are now
# distinct models (medium.json beats easy.json 65-22-13 head-to-head); Hard
# still points at medium.json, the strongest promoted policy, and stays a
# label-only duplicate until a stronger one earns hard.json. Keep the tiers
# handicapped with reaction delay or action noise. All three tiers are now
# distinct models: medium.json beats easy.json 65-22-13, and hard.json (the
# generation-5 Stage-5 policy) beats medium.json 47-32-21. Keep the tiers
# monotonic: never leave a lower tier pointing at a stronger model than the
# one above it.
const DIFFICULTIES := [
{"name": "Easy", "model": "res://bots/promoted/easy.json", "reaction_ticks": 8, "action_noise": 0.0},
{"name": "Medium", "model": "res://bots/promoted/medium.json", "reaction_ticks": 8, "action_noise": 0.0},
{"name": "Hard", "model": "res://bots/promoted/medium.json", "reaction_ticks": 8, "action_noise": 0.0},
{"name": "Hard", "model": "res://bots/promoted/hard.json", "reaction_ticks": 8, "action_noise": 0.0},
]
@onready var difficulty_dropdown: OptionButton = %DifficultyDropdown
+30 -6
View File
@@ -181,12 +181,36 @@ promoted file is never touched by training scripts, never overwritten by a
same-named future export, and never disturbed by pruning old experiment
files from the flat dump.
Easy and Medium are now genuinely different policies. Hard still points at
`medium.json` and remains a label-only duplicate until a stronger policy earns
`hard.json`. Every tier runs at full trained cadence (`reaction_ticks=8`,
`action_noise=0`) — the game does not manufacture difficulty gaps by
handicapping a model. When promoting, keep the tiers monotonic: a lower tier
must never point at a policy that beats the tier above it.
`hard.json` was promoted 2026-08-24 from generation 5's
`20260823-1734-gen5-s5-intercepts-retry2` — Stage 5 attempt 3, and like
`medium.json` above it is recorded as a *fail* in `generation5_state.json`.
Stage 5 blocked after three attempts on two telemetry floors: `goal_rate`
0.7369 against a 0.75 bar (marginal), and `productive_air_touch_fraction`
0.0001 against 0.005. The second is not a judgement about this policy — see
"Generation 5" below — the metric is quantised at 0.01 (one touch per ~100
episode logging window), so a 0.005 floor demands a productive air touch in
half of all windows, and no policy in the lineage has ever come close. On
everything else it is the strongest bot produced so far: `upright_fraction`
0.757 against a 0.40 floor (the pre-Round-6 lineage never exceeded 0.331),
`forward_motion_fraction` 0.479 against 0.20, and it beats `medium.json`
47-32-21 over 100 paired episodes.
Attempt 2 (`20260823-0258-gen5-s5-intercepts-retry1`) posts a much wider
margin against `medium.json` (63-23-14) and was the obvious alternative, but a
direct 100-episode head-to-head between the two finished 36-39 with 25 draws —
a dead heat, so the wider indirect margin does not reflect a real strength
difference. Attempt 3 was taken on the tiebreakers: it is the later checkpoint
(it resumed from attempt 2) and edges every telemetry metric. That head-to-head
also measured a 17% physical side imbalance (physical teams 0-1 = 29-46, with
`retry1` going 15-25 as team 0 but 21-14 as team 1) — inside the 20% bar used
elsewhere, but large enough to be worth understanding rather than assuming it
is noise, since it affects both models equally and may point at an asymmetry in
the arena or in `ship_observations.gd`'s team-1 mirroring.
Every tier runs at full trained cadence (`reaction_ticks=8`, `action_noise=0`)
— the game does not manufacture difficulty gaps by handicapping a model. When
promoting, keep the tiers monotonic: a lower tier must never point at a policy
that beats the tier above it.
To promote a new bot into a tier: copy the chosen `Game/bots/<experiment>.json`
to `Game/bots/promoted/<tier>.json` (overwriting the old one), and note the
+27
View File
@@ -1062,5 +1062,32 @@
"team_1": 43
},
"win_rate_a": 0.47
},
{
"timestamp": "2026-08-24T07:43:21+00:00",
"model_a": "/Users/jcreek/Documents/repos/GitHub/CosmicClash/Game/bots/20260823-0258-gen5-s5-intercepts-retry1.json",
"model_b": "/Users/jcreek/Documents/repos/GitHub/CosmicClash/Game/bots/20260823-1734-gen5-s5-intercepts-retry2.json",
"seed": 1,
"episodes": 100,
"wins_a": 36,
"wins_b": 39,
"draws": 25,
"side_results": {
"a_team_0": {
"wins_a": 15,
"wins_b": 25,
"draws": 10
},
"a_team_1": {
"wins_a": 21,
"wins_b": 14,
"draws": 15
}
},
"physical_team_wins": {
"team_0": 29,
"team_1": 46
},
"win_rate_a": 0.36
}
]