test(training): require multi-seed curriculum evaluation

This commit is contained in:
Josh Creek
2026-09-01 17:26:44 +01:00
parent 533ac1afab
commit 9004800326
4 changed files with 32 additions and 4 deletions
+22 -2
View File
@@ -38,6 +38,10 @@ PROMOTED_EASY = REPO_ROOT / "Game" / "bots" / "promoted" / "easy.json"
MAX_RETRIES = 4
EVAL_EPISODES = 100
REGRESSION_MARGIN = 0.15
# A single paired seed can produce a large physical-side swing even for a
# policy playing itself. Keep the first historical seed for continuity, but
# require two independent deterministic sequences before a stage can pass.
DEFAULT_EVALUATION_SEEDS = (1, 19, 43)
# --min-head-entropy-frac / --ent-coef-max added 2026-08-24. The aggregate
# entropy target is a SUM and read healthy (21% of h_max, on target) through
# all nine Stage-5 attempts while thrust_y alone sat at 14% of its own ceiling
@@ -582,11 +586,12 @@ def run_training(state: dict, stage_index: int, attempt: int, args) -> str:
return experiment
def evaluate(experiment: str, reference: pathlib.Path, args) -> dict:
def evaluate(experiment: str, reference: pathlib.Path, args, seed: int) -> dict:
candidate = REPO_ROOT / "Game" / "bots" / f"{experiment}.json"
cmd = [
".venv/bin/python", "evaluate.py", str(candidate), str(reference),
"--episodes", str(EVAL_EPISODES), "--speedup", str(args.speedup),
"--seed", str(seed),
]
if args.godot_bin:
cmd += ["--godot_bin", args.godot_bin]
@@ -628,11 +633,22 @@ def main() -> None:
parser.add_argument("--n-parallel", type=int, default=14)
parser.add_argument("--speedup", type=int, default=16)
parser.add_argument("--godot-bin", default=None, help="Godot binary for post-stage evaluation")
parser.add_argument(
"--evaluation-seeds",
default=",".join(str(seed) for seed in DEFAULT_EVALUATION_SEEDS),
help="Comma-separated independent paired seeds required for every reference evaluation",
)
parser.add_argument("--foundation-checkpoint", default=str(FOUNDATION_CHECKPOINT))
parser.add_argument("--force-retry", action="store_true")
parser.add_argument("--skip-to-next-stage", action="store_true")
parser.add_argument("--dry-run", action="store_true", help="Print the next run command without executing it")
args = parser.parse_args()
try:
evaluation_seeds = tuple(dict.fromkeys(int(value) for value in args.evaluation_seeds.split(",") if value.strip()))
except ValueError as error:
parser.error(f"--evaluation-seeds must be comma-separated integers: {error}")
if not evaluation_seeds:
parser.error("--evaluation-seeds requires at least one seed")
state = load_state()
if state["status"] == "done":
@@ -670,7 +686,11 @@ def main() -> None:
# Preserve order while avoiding a duplicate Stage-5 evaluation in
# the league stage (its predecessor is also in the pool).
references = list(dict.fromkeys(references))
records = [evaluate(experiment, reference, args) for reference in references]
records = [
evaluate(experiment, reference, args, seed)
for reference in references
for seed in evaluation_seeds
]
match_ok = all(match_passes(record) for record in records)
evaluation_goal_floor = stage.get("evaluation_goal_rate_floor", 0.0)
evaluation_goal_failures = [