diff --git a/Game/scripts/ship_ai_controller.gd b/Game/scripts/ship_ai_controller.gd index 283859b6..aeea7aea 100644 --- a/Game/scripts/ship_ai_controller.gd +++ b/Game/scripts/ship_ai_controller.gd @@ -180,16 +180,22 @@ func get_reward() -> float: # TRAINING.md). Flight telemetry fields are leading indicators for # generation 4's core hypothesis (see train.py's FlightTelemetryCallback). func get_info() -> Dictionary: + # The four telemetry keys must ALWAYS be present (not just when their + # denominator is nonzero) — VecMonitor's info_keywords does a bare + # info[key] lookup on whatever info dict is attached to a completed + # episode's terminal step (see train.py's VecMonitor(..., + # info_keywords=(...))) and raises KeyError, crashing the whole training + # run, if a key is ever missing. 0.0 is a reasonable default for "no + # touches/no ticks yet" (in practice _telemetry_ticks is >0 by the time + # any episode ends; _touches often legitimately is 0). var info := {"goal_scored": goal_scored_this_episode} if truncated_this_episode: info["truncated"] = true info["terminal_obs"] = terminal_obs - if _telemetry_ticks > 0: - info["airborne_fraction"] = float(_airborne_ticks) / _telemetry_ticks - info["mean_altitude"] = _altitude_sum / _telemetry_ticks - info["vertical_thrust_mean"] = _thrust_y_sum / _telemetry_ticks - if _touches > 0: - info["air_touch_fraction"] = float(_air_touches) / _touches + info["airborne_fraction"] = float(_airborne_ticks) / _telemetry_ticks if _telemetry_ticks > 0 else 0.0 + info["mean_altitude"] = _altitude_sum / _telemetry_ticks if _telemetry_ticks > 0 else 0.0 + info["vertical_thrust_mean"] = _thrust_y_sum / _telemetry_ticks if _telemetry_ticks > 0 else 0.0 + info["air_touch_fraction"] = float(_air_touches) / _touches if _touches > 0 else 0.0 return info