fix(physics): make upright a real state, and actually start ships on the floor

Six rounds of reward shaping (~700M steps) failed to produce upright ground
driving. A critical review of the simulation rather than the reward found
why:

1. The hull was a 1x1x4 box with inertia (1,1,1) and no restoring torque
   anywhere, so belly-down and rolled-90 were geometrically identical
   resting states. "Upright" was not a physically distinguished state at
   all - the reward was paying for a property the simulation did not have.
2. ~65% of episodes spawned ships via _random_position, which samples Y
   uniformly over the full 18m volume (mean ~8.7m). The measured
   airborne_fraction ~0.44 was largely that spawn distribution, and every
   ground-handling term fades out above 3m, so the shaping being tuned
   barely ever applied.
3. air_drill_chance 0.20 spawned deliberately unreachable-without-climbing
   states in the stage meant to teach ground driving, and its own
   air_touch_fraction (0.0002) shows the drills were never solved.

Fixes land in the physics and the task distribution, not the reward:
- ship.tscn: hull 1x1x4 -> 1.6x0.6x4 so it has one stable resting face;
  inertia (1,1,1) -> (7,1,7), physically correct for the hull, making
  tumbling reluctant while keeping yaw snappy.
- ship.gd: new altitude-faded righting torque (spring-damper toward
  belly-down, faded out by 3m so aerials keep full attitude freedom).
  This is the grav-plating analogue of Rocket League's auto-righting and
  helps human pilots land cleanly too.
- training_mode.gd: new ground_start_chance branch spawning ships level and
  resting on the floor with a floor-level ball - the state the handling
  stage's rewards are actually written for.
- generation5.py: ground-start-chance 0.50, air-drill-chance 0.20 -> 0.0.

Reward terms are left exactly as they were; they should finally pull in a
direction the ship can go.
This commit is contained in:
Josh Creek
2026-08-16 08:17:16 +01:00
parent 033a774c92
commit ea756bd5ba
15 changed files with 146 additions and 270 deletions
+6
View File
@@ -334,6 +334,11 @@ def parse_args():
"--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(
"--ground-start-chance", type=float, default=None,
help="Fraction of resets that spawn ships level and resting on the floor with a floor-level "
"ball — the state the handling stage's ground rewards are written for",
)
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",
@@ -406,6 +411,7 @@ def _curriculum_kwargs(args) -> dict:
"ball_near_goal_chance": args.near_goal_chance,
"air_drill_chance": args.air_drill_chance,
"air_intercept_chance": args.air_intercept_chance,
"ground_start_chance": args.ground_start_chance,
"team_size": args.team_size,
"ai_tilt_penalty": args.tilt_penalty,
"ai_ground_tilt_penalty": args.ground_tilt_penalty,