chore(training): retune stage-4 handling penalties and restart from Stage-3 foundation

Stage 4's upright/forward-motion telemetry plateaued flat across all three
blocked attempts because ground_tilt_penalty (0.003) was too weak to matter
and nothing penalized sideways/reverse motion at all. Raise
ground_tilt_penalty to 0.05 and add a new non_forward_penalty term
(ship_ai_controller.gd) that directly costs non-forward planar velocity near
the floor, independent of the ball. Delete the three blocked attempts'
checkpoints/logs/exports and reset generation5_state.json so the next run
starts fresh from the Stage-3 foundation checkpoint instead of continuing
from the drifted retry2 weights.
This commit is contained in:
Josh Creek
2026-08-09 13:09:12 +01:00
parent 005cd0c66e
commit c56f5ed1a3
14 changed files with 41 additions and 263 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+19
View File
@@ -74,6 +74,11 @@ extends AIController3D
# pitch/roll during a real aerial. Generation 5 uses this instead of raising
# the global tilt_penalty back to its pre-flight value.
@export var ground_tilt_penalty := 0.0
# Per-tick penalty on the planar-velocity component not pointed along the
# nose (sideways or reverse), independent of the ball — the mirror image of
# forward_velocity_to_ball_weight's ball-conditioned bonus. Same
# GROUND_HANDLING_HEIGHT altitude fade as ground_tilt_penalty.
@export var non_forward_penalty := 0.0
# Per-tick bonus for own speed: 0 stationary, full value (+0.24/s) at
# max_speed. Run07 lesson: after the kickoff flurry both ships parked next to
# a cornered ball — with every other dense term near zero there, standing
@@ -324,6 +329,20 @@ func _physics_process(delta):
var tilt_ground_factor: float = 1.0 - clampf(ship.global_position.y / GROUND_HANDLING_HEIGHT, 0.0, 1.0)
reward -= ground_tilt_penalty * (1.0 - ground_uprightness) * 0.5 * tilt_ground_factor
# Dense penalty: any planar velocity component not pointed along the nose
# (sideways or reverse), independent of the ball — the mirror image of
# forward_velocity_to_ball_weight's ball-conditioned bonus. Fades out with
# altitude via the same GROUND_HANDLING_HEIGHT ramp as ground_tilt_penalty.
if non_forward_penalty > 0.0 and ship.global_position.y < GROUND_HANDLING_HEIGHT:
var non_forward_planar_velocity := Vector3(ship.linear_velocity.x, 0.0, ship.linear_velocity.z)
var non_forward_planar_speed := non_forward_planar_velocity.length()
var non_forward_planar_forward := Vector3(-ship.global_transform.basis.z.x, 0.0, -ship.global_transform.basis.z.z)
if non_forward_planar_speed > 0.0001 and non_forward_planar_forward.length_squared() > 0.0001:
var forward_component: float = maxf(non_forward_planar_velocity.dot(non_forward_planar_forward.normalized()), 0.0)
var non_forward_speed: float = non_forward_planar_speed - forward_component
var non_forward_ground_factor: float = 1.0 - clampf(ship.global_position.y / GROUND_HANDLING_HEIGHT, 0.0, 1.0)
reward -= non_forward_penalty * (non_forward_speed / ship.max_speed) * non_forward_ground_factor
# Dense penalty: height above the floor (see airborne_penalty). The
# floor sits at world y = 0 (see training_mode.gd's FIELD_MIN_Y/
# _escaped bounds); normalized so the worst case is pinned at the
+2 -1
View File
@@ -251,7 +251,7 @@ const SHIP_AI_OVERRIDES := [
"ball_touch_reward", "ball_touch_cooldown_ticks", "ball_touch_direction_floor",
"velocity_to_ball_weight", "ball_velocity_to_goal_weight", "ball_distance_penalty",
"forward_velocity_to_ball_weight", "wall_contact_penalty", "tilt_penalty",
"ground_tilt_penalty", "speed_reward_weight", "time_penalty",
"ground_tilt_penalty", "non_forward_penalty", "speed_reward_weight", "time_penalty",
"airborne_penalty",
]
@@ -312,6 +312,7 @@ func _ai_default(name: String) -> Variant:
"wall_contact_penalty": return 0.0025
"tilt_penalty": return 0.0005
"ground_tilt_penalty": return 0.0
"non_forward_penalty": return 0.0
"speed_reward_weight": return 0.004
"time_penalty": return 0.001
"airborne_penalty": return 0.0
+11 -2
View File
@@ -43,7 +43,15 @@ STANDING_ARGS = ["--ent-coef", "0.01", "--entropy-floor"]
# Scoring/ball-direction shaping inherited from generation 4. Handling
# replaces half the orientation-agnostic closing reward and all generic speed
# reward with nose-led ground approach, while keeping global tilt pressure
# small enough for flight and adding a stronger floor-local term.
# small enough for flight. The first three Stage-4 attempts (2026-08-08/09)
# plateaued with upright_fraction/forward_motion_fraction flat at ~0.22-0.26
# against 0.45/0.25 floors for 120M cumulative timesteps: ground_tilt_penalty
# at 0.003 only cost a fully-sideways episode ~2.7 reward, trivial next to a
# goal (80) or a touch (0.7). ground_tilt_penalty is raised ~17x to 0.05 (a
# full sideways episode now costs ~45, comparable to a goal) and
# non_forward_penalty is a new term (ship_ai_controller.gd) directly costing
# sideways/reverse planar velocity near the floor, independent of the ball,
# since nothing previously penalized that at all.
HANDLING_REWARD_FLAGS = [
"--velocity-to-ball-weight", "0.04",
"--forward-velocity-to-ball-weight", "0.06",
@@ -53,7 +61,8 @@ HANDLING_REWARD_FLAGS = [
"--goal-reward", "80",
"--speed-reward-weight", "0.0",
"--tilt-penalty", "0.0002",
"--ground-tilt-penalty", "0.003",
"--ground-tilt-penalty", "0.05",
"--non-forward-penalty", "0.04",
]
STAGES = [
+3 -257
View File
@@ -1,260 +1,6 @@
{
"stage_index": 0,
"attempt": 2,
"status": "blocked",
"log": [
{
"stage_index": 0,
"stage_number": 4,
"stage_name": "handling",
"experiment": "20260808-1508-gen5-s4-handling",
"attempt": 0,
"telemetry_tail": {
"rollout/air_touch_fraction": 0.0004599999897181988,
"rollout/airborne_fraction": 0.42232042902708056,
"rollout/ep_len_mean": 137.51244003295898,
"rollout/ep_rew_mean": 6.987018242835998,
"rollout/forward_motion_fraction": 0.22800671431422234,
"rollout/goal_rate": 0.5818800025582314,
"rollout/mean_altitude": 4.261052157402038,
"rollout/productive_air_touch_fraction": 7.999999821186065e-05,
"rollout/upright_fraction": 0.22364509524405002,
"rollout/vertical_thrust_mean": 0.09885899936710485
},
"telemetry_failures": [
"rollout/goal_rate=0.5819 < 0.8000",
"rollout/upright_fraction=0.2236 < 0.4500",
"rollout/forward_motion_fraction=0.2280 < 0.2500"
],
"evaluation_goal_failures": [
"easy.json: goal_rate=0.750 < 0.800"
],
"side_balance_failures": [],
"eval": {
"timestamp": "2026-08-08T20:20:09+00:00",
"model_a": "/home/jcreek/ai-training/CosmicClash/Game/bots/20260808-1508-gen5-s4-handling.json",
"model_b": "/home/jcreek/ai-training/CosmicClash/Game/bots/promoted/easy.json",
"seed": 1,
"episodes": 100,
"wins_a": 45,
"wins_b": 30,
"draws": 25,
"side_results": {
"a_team_0": {
"wins_a": 18,
"wins_b": 19,
"draws": 13
},
"a_team_1": {
"wins_a": 27,
"wins_b": 11,
"draws": 12
}
},
"physical_team_wins": {
"team_0": 29,
"team_1": 46
},
"win_rate_a": 0.45
},
"evals": [
{
"timestamp": "2026-08-08T20:20:09+00:00",
"model_a": "/home/jcreek/ai-training/CosmicClash/Game/bots/20260808-1508-gen5-s4-handling.json",
"model_b": "/home/jcreek/ai-training/CosmicClash/Game/bots/promoted/easy.json",
"seed": 1,
"episodes": 100,
"wins_a": 45,
"wins_b": 30,
"draws": 25,
"side_results": {
"a_team_0": {
"wins_a": 18,
"wins_b": 19,
"draws": 13
},
"a_team_1": {
"wins_a": 27,
"wins_b": 11,
"draws": 12
}
},
"physical_team_wins": {
"team_0": 29,
"team_1": 46
},
"win_rate_a": 0.45
}
],
"decision": "fail"
},
{
"stage_index": 0,
"stage_number": 4,
"stage_name": "handling",
"experiment": "20260808-2120-gen5-s4-handling-retry1",
"attempt": 1,
"telemetry_tail": {
"rollout/air_touch_fraction": 0.00023999999463558198,
"rollout/airborne_fraction": 0.4264982384443283,
"rollout/ep_len_mean": 135.5532396697998,
"rollout/ep_rew_mean": 7.420562901496887,
"rollout/forward_motion_fraction": 0.2443552376627922,
"rollout/goal_rate": 0.5880400028824806,
"rollout/mean_altitude": 4.2706854009628294,
"rollout/productive_air_touch_fraction": 0.0,
"rollout/upright_fraction": 0.2381512857079506,
"rollout/vertical_thrust_mean": 0.07293799882452004
},
"telemetry_failures": [
"rollout/goal_rate=0.5880 < 0.8000",
"rollout/upright_fraction=0.2382 < 0.4500",
"rollout/forward_motion_fraction=0.2444 < 0.2500"
],
"evaluation_goal_failures": [],
"side_balance_failures": [],
"eval": {
"timestamp": "2026-08-09T02:28:48+00:00",
"model_a": "/home/jcreek/ai-training/CosmicClash/Game/bots/20260808-2120-gen5-s4-handling-retry1.json",
"model_b": "/home/jcreek/ai-training/CosmicClash/Game/bots/promoted/easy.json",
"seed": 1,
"episodes": 100,
"wins_a": 43,
"wins_b": 38,
"draws": 19,
"side_results": {
"a_team_0": {
"wins_a": 18,
"wins_b": 21,
"draws": 11
},
"a_team_1": {
"wins_a": 25,
"wins_b": 17,
"draws": 8
}
},
"physical_team_wins": {
"team_0": 35,
"team_1": 46
},
"win_rate_a": 0.43
},
"evals": [
{
"timestamp": "2026-08-09T02:28:48+00:00",
"model_a": "/home/jcreek/ai-training/CosmicClash/Game/bots/20260808-2120-gen5-s4-handling-retry1.json",
"model_b": "/home/jcreek/ai-training/CosmicClash/Game/bots/promoted/easy.json",
"seed": 1,
"episodes": 100,
"wins_a": 43,
"wins_b": 38,
"draws": 19,
"side_results": {
"a_team_0": {
"wins_a": 18,
"wins_b": 21,
"draws": 11
},
"a_team_1": {
"wins_a": 25,
"wins_b": 17,
"draws": 8
}
},
"physical_team_wins": {
"team_0": 35,
"team_1": 46
},
"win_rate_a": 0.43
}
],
"decision": "fail"
},
{
"stage_index": 0,
"stage_number": 4,
"stage_name": "handling",
"experiment": "20260809-0328-gen5-s4-handling-retry2",
"attempt": 2,
"telemetry_tail": {
"rollout/air_touch_fraction": 0.0002799999937415123,
"rollout/airborne_fraction": 0.4334809045791626,
"rollout/ep_len_mean": 136.65584008789062,
"rollout/ep_rew_mean": 7.696315350532532,
"rollout/forward_motion_fraction": 0.23557147654891014,
"rollout/goal_rate": 0.5825200036764145,
"rollout/mean_altitude": 4.346557500362397,
"rollout/productive_air_touch_fraction": 3.999999910593033e-05,
"rollout/upright_fraction": 0.2288729049563408,
"rollout/vertical_thrust_mean": 0.06853299860764295
},
"telemetry_failures": [
"rollout/goal_rate=0.5825 < 0.8000",
"rollout/upright_fraction=0.2289 < 0.4500",
"rollout/forward_motion_fraction=0.2356 < 0.2500"
],
"evaluation_goal_failures": [
"easy.json: goal_rate=0.750 < 0.800"
],
"side_balance_failures": [],
"eval": {
"timestamp": "2026-08-09T08:36:44+00:00",
"model_a": "/home/jcreek/ai-training/CosmicClash/Game/bots/20260809-0328-gen5-s4-handling-retry2.json",
"model_b": "/home/jcreek/ai-training/CosmicClash/Game/bots/promoted/easy.json",
"seed": 1,
"episodes": 100,
"wins_a": 46,
"wins_b": 29,
"draws": 25,
"side_results": {
"a_team_0": {
"wins_a": 21,
"wins_b": 18,
"draws": 11
},
"a_team_1": {
"wins_a": 25,
"wins_b": 11,
"draws": 14
}
},
"physical_team_wins": {
"team_0": 32,
"team_1": 43
},
"win_rate_a": 0.46
},
"evals": [
{
"timestamp": "2026-08-09T08:36:44+00:00",
"model_a": "/home/jcreek/ai-training/CosmicClash/Game/bots/20260809-0328-gen5-s4-handling-retry2.json",
"model_b": "/home/jcreek/ai-training/CosmicClash/Game/bots/promoted/easy.json",
"seed": 1,
"episodes": 100,
"wins_a": 46,
"wins_b": 29,
"draws": 25,
"side_results": {
"a_team_0": {
"wins_a": 21,
"wins_b": 18,
"draws": 11
},
"a_team_1": {
"wins_a": 25,
"wins_b": 11,
"draws": 14
}
},
"physical_team_wins": {
"team_0": 32,
"team_1": 43
},
"win_rate_a": 0.46
}
],
"decision": "fail"
}
]
"attempt": 0,
"status": "in_progress",
"log": []
}
+6
View File
@@ -358,6 +358,11 @@ def parse_args():
"--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(
"--non-forward-penalty", type=float, default=None,
help="Overrides ShipAIController.non_forward_penalty (low-altitude dense cost on sideways/reverse "
"planar velocity, independent of the ball)",
)
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)",
@@ -391,6 +396,7 @@ def _curriculum_kwargs(args) -> dict:
"team_size": args.team_size,
"ai_tilt_penalty": args.tilt_penalty,
"ai_ground_tilt_penalty": args.ground_tilt_penalty,
"ai_non_forward_penalty": args.non_forward_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,