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.
This commit is contained in:
Josh Creek
2026-07-29 08:35:20 +01:00
parent 8bc16ee048
commit 259b2adc07
+9 -2
View File
@@ -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():