From 259b2adc0745c765bec63f36462b4f2795d24ebe Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Wed, 29 Jul 2026 08:35:20 +0100 Subject: [PATCH] fix(training): Guard GoalRateCallback against a missing goal_scored key The vendored godot_rl sync bridge (Game/addons/godot_rl_agents/sync.gd, _training_process) snapshots each agent's info dict once per tick and its own inline comment already flags that reset-timing path as incomplete ("NEEDS REFACTOR"); at least one agent's terminal-step info can arrive without "goal_scored" at all. Indexing it directly crashed a training run (20260729-0607-curric-s1-unmask-retry2) within minutes of starting. Skip episodes missing the key instead of crashing training over a monitoring-only metric. --- training/train.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/training/train.py b/training/train.py index c6553cec..1abcd205 100644 --- a/training/train.py +++ b/training/train.py @@ -43,8 +43,15 @@ class GoalRateCallback(BaseCallback): def _on_rollout_end(self) -> None: if len(self.model.ep_info_buffer) == 0: return - goal_rate = safe_mean([ep_info["goal_scored"] for ep_info in self.model.ep_info_buffer]) - self.logger.record("rollout/goal_rate", goal_rate) + # The vendored godot_rl sync bridge (Game/addons/godot_rl_agents/sync.gd, + # _training_process) snapshots each agent's info dict once per tick and + # has its own "NEEDS REFACTOR" comment on the reset-timing path, so an + # episode's terminal info entry can arrive without "goal_scored" at all + # (observed crashing a run after 2026-07-28). Skip those rather than + # crash training over a monitoring-only metric. + rates = [ep_info["goal_scored"] for ep_info in self.model.ep_info_buffer if "goal_scored" in ep_info] + if rates: + self.logger.record("rollout/goal_rate", safe_mean(rates)) def parse_args():