feat(training): add generation 5 curriculum

This commit is contained in:
Josh Creek
2026-08-08 14:56:17 +01:00
parent 33952b3cd0
commit 341a67f6da
10 changed files with 832 additions and 19 deletions
+46 -5
View File
@@ -62,8 +62,7 @@ class GoalRateCallback(BaseCallback):
class FlightTelemetryCallback(BaseCallback):
"""Logs rollout/{airborne_fraction,mean_altitude,air_touch_fraction,
vertical_thrust_mean} — leading indicators for curriculum generation 4's
"""Logs flight and handling telemetry — leading indicators for curriculum generation 4's
core hypothesis (a discrete action space lets the policy actually hold a
sustained vertical set-point, e.g. hovering), visible from the very
first rollout instead of only in a win-rate number measured a full
@@ -72,7 +71,15 @@ class FlightTelemetryCallback(BaseCallback):
"airborne_fraction", "mean_altitude", "air_touch_fraction",
"vertical_thrust_mean")) — see ShipAIController.get_info."""
_KEYS = ("airborne_fraction", "mean_altitude", "air_touch_fraction", "vertical_thrust_mean")
_KEYS = (
"airborne_fraction",
"mean_altitude",
"air_touch_fraction",
"vertical_thrust_mean",
"productive_air_touch_fraction",
"upright_fraction",
"forward_motion_fraction",
)
def _on_step(self) -> bool:
return True
@@ -287,13 +294,18 @@ def parse_args():
)
curriculum.add_argument(
"--opponent-mode",
choices=["self_play", "inert", "frozen"],
choices=["self_play", "inert", "frozen", "league"],
default=None,
help="self_play (default): both ships are live trainees. inert: team 1 is a "
"do-nothing placeholder (isolated scoring practice). frozen: team 1 runs a "
"fixed exported policy (--opponent-model)",
"fixed exported policy (--opponent-model); league: sample a fixed policy per episode "
"from --opponent-pool",
)
curriculum.add_argument("--opponent-model", default=None, help="Exported policy .json for --opponent-mode=frozen")
curriculum.add_argument(
"--opponent-pool", default=None,
help="Comma-separated exported policy paths for --opponent-mode=league; one is sampled per episode",
)
curriculum.add_argument(
"--draw-penalty", type=float, default=None, help="One-time penalty when an episode times out with no goal"
)
@@ -310,6 +322,14 @@ def parse_args():
help="Overrides air_drill_chance: ball spawned high, both ships spawned low and lateral — "
"unsolvable without climbing (curriculum generation 4's state-setter aerial curriculum)",
)
curriculum.add_argument(
"--air-intercept-chance", type=float, default=None,
help="Moving high-ball interception starts aimed at a real goal (generation-5 aerial stage)",
)
curriculum.add_argument(
"--team-size", type=int, choices=range(1, 6), default=None,
help="Ships per team (1-5); generation-5 automated stages remain 1v1 until 2v2 evaluation exists",
)
curriculum.add_argument(
"--tilt-penalty", type=float, default=None,
help="Overrides ShipAIController.tilt_penalty (dense per-tick cost scaled by non-upright tilt)",
@@ -318,6 +338,10 @@ def parse_args():
"--velocity-to-ball-weight", type=float, default=None,
help="Overrides ShipAIController.velocity_to_ball_weight (dense reward for closing speed toward the ball)",
)
curriculum.add_argument(
"--forward-velocity-to-ball-weight", type=float, default=None,
help="Low-altitude dense reward for nose-led planar approach toward the ball",
)
curriculum.add_argument(
"--ball-distance-penalty", type=float, default=None,
help="Overrides ShipAIController.ball_distance_penalty (dense per-tick cost scaled by distance to the ball)",
@@ -330,6 +354,14 @@ def parse_args():
"--airborne-penalty", type=float, default=None,
help="Overrides ShipAIController.airborne_penalty (dense per-tick cost scaled by height above the floor)",
)
curriculum.add_argument(
"--ground-tilt-penalty", type=float, default=None,
help="Low-altitude-only tilt cost that fades to zero by the handling-height threshold",
)
curriculum.add_argument(
"--speed-reward-weight", type=float, default=None,
help="Overrides the orientation-agnostic own-speed reward (generation 5 handling sets it to zero)",
)
curriculum.add_argument(
"--ball-velocity-to-goal-weight", type=float, default=None,
help="Overrides ShipAIController.ball_velocity_to_goal_weight (dense reward for the ball's velocity toward the attack goal)",
@@ -349,16 +381,22 @@ def _curriculum_kwargs(args) -> dict:
mapping = {
"opponent_mode": args.opponent_mode,
"opponent_model": args.opponent_model,
"opponent_model_pool": args.opponent_pool,
"draw_penalty": args.draw_penalty,
"attack_goal_bias": args.attack_goal_bias,
"kickoff_state_chance": args.kickoff_chance,
"ball_near_goal_chance": args.near_goal_chance,
"air_drill_chance": args.air_drill_chance,
"air_intercept_chance": args.air_intercept_chance,
"team_size": args.team_size,
"ai_tilt_penalty": args.tilt_penalty,
"ai_ground_tilt_penalty": args.ground_tilt_penalty,
"ai_velocity_to_ball_weight": args.velocity_to_ball_weight,
"ai_forward_velocity_to_ball_weight": args.forward_velocity_to_ball_weight,
"ai_ball_distance_penalty": args.ball_distance_penalty,
"ai_ball_touch_reward": args.ball_touch_reward,
"ai_airborne_penalty": args.airborne_penalty,
"ai_speed_reward_weight": args.speed_reward_weight,
"ai_ball_velocity_to_goal_weight": args.ball_velocity_to_goal_weight,
"goal_reward": args.goal_reward,
}
@@ -395,6 +433,9 @@ def main():
"mean_altitude",
"air_touch_fraction",
"vertical_thrust_mean",
"productive_air_touch_fraction",
"upright_fraction",
"forward_motion_fraction",
),
)