fix(training): correct non-forward penalty math and add a grounding incentive

Adversarial review of the previous stage-4 retune found two problems:
non_forward_speed used planar_speed - forward_component, which under-charges
diagonal motion relative to true lateral speed (e.g. ~29% penalty at 45
degrees off the nose instead of the correct ~71%); fixed to the Pythagorean
magnitude for forward-facing angles, full speed for backward-facing ones.

Also, ground_tilt_penalty and non_forward_penalty only ever cost reward near
the floor with nothing offsetting them above it, which could teach a policy
that's still bad at ground handling to just avoid the floor rather than get
better at it. Added grounded_upright_reward (ship_ai_controller.gd) plus a
new ShipObservations.is_floor_contact helper for genuine belly-on-floor
contact detection, so grounding well while upright is the locally profitable
choice, not just the least-punished one.
This commit is contained in:
Josh Creek
2026-08-09 13:23:00 +01:00
parent c56f5ed1a3
commit 6f7536f03c
5 changed files with 71 additions and 5 deletions
+8 -1
View File
@@ -51,7 +51,13 @@ STANDING_ARGS = ["--ent-coef", "0.01", "--entropy-floor"]
# 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.
# since nothing previously penalized that at all. Both are floor-proximity
# penalties only, with nothing equivalent above GROUND_HANDLING_HEIGHT — on
# its own that risks teaching "avoid the floor" instead of "handle well on
# it", worsening Stage 3's already-airborne-heavy baseline. grounded_upright_
# reward is the positive counterpart: a bonus for genuine floor contact
# (not just low altitude) while upright, so grounding well is the locally
# profitable choice rather than merely the least-punished one.
HANDLING_REWARD_FLAGS = [
"--velocity-to-ball-weight", "0.04",
"--forward-velocity-to-ball-weight", "0.06",
@@ -63,6 +69,7 @@ HANDLING_REWARD_FLAGS = [
"--tilt-penalty", "0.0002",
"--ground-tilt-penalty", "0.05",
"--non-forward-penalty", "0.04",
"--grounded-upright-reward", "0.015",
]
STAGES = [
+6
View File
@@ -363,6 +363,11 @@ def parse_args():
help="Overrides ShipAIController.non_forward_penalty (low-altitude dense cost on sideways/reverse "
"planar velocity, independent of the ball)",
)
curriculum.add_argument(
"--grounded-upright-reward", type=float, default=None,
help="Overrides ShipAIController.grounded_upright_reward (dense bonus for genuine floor contact "
"while upright, countering an incentive to just avoid the floor)",
)
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)",
@@ -397,6 +402,7 @@ def _curriculum_kwargs(args) -> dict:
"ai_tilt_penalty": args.tilt_penalty,
"ai_ground_tilt_penalty": args.ground_tilt_penalty,
"ai_non_forward_penalty": args.non_forward_penalty,
"ai_grounded_upright_reward": args.grounded_upright_reward,
"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,