mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-12 16:53:44 +00:00
chore(training): add air_approach_weight and restart stage-5 intercepts
Stage 5 blocked all three attempts on productive_air_touch_fraction stuck exactly at 0.0 across a continuous 180M-step lineage, while goal_rate/upright_fraction/forward_motion_fraction kept improving on the same budget. forward_velocity_to_ball_weight (the term that solved Stage 4's ground pursuit) is hard-gated below GROUND_HANDLING_HEIGHT and does nothing in the air, so Stage 5's air_intercept_chance had no matching aerial incentive to learn from. air_approach_weight adds the airborne mirror (nose-first 3D closing speed, no uprightness multiplier) and folds into HANDLING_REWARD_FLAGS so Stage 6 inherits it too. Deleted the three blocked attempts and reset state to resume Stage 5 from the Stage-4 checkpoint with the new term.
This commit is contained in:
@@ -46,6 +46,20 @@ extends AIController3D
|
||||
# frozen checkpoints keep their original objective; generation 5 handling
|
||||
# turns it on while reducing the orientation-agnostic term.
|
||||
@export var forward_velocity_to_ball_weight := 0.0
|
||||
# Aerial mirror of forward_velocity_to_ball_weight: nose-first closing speed
|
||||
# on the ball, active above GROUND_HANDLING_HEIGHT instead of below it (the
|
||||
# two are mutually exclusive by altitude, never both active on the same
|
||||
# tick). Generation 5's intercepts stage added air_intercept_chance without
|
||||
# an airborne equivalent of the term that actually solved ground handling;
|
||||
# above 3m the only remaining approach incentive was the generic, orientation
|
||||
# -agnostic velocity_to_ball_weight (0.02-0.04), which three consecutive
|
||||
# 60M-step attempts (180M cumulative, resuming each time) showed produces
|
||||
# zero learnable gradient toward touching an aerial ball at all —
|
||||
# productive_air_touch_fraction stayed exactly 0.0 the whole time while every
|
||||
# other metric kept improving on the same budget. Uses the full 3D nose
|
||||
# vector rather than the planar-only one, since a real aerial requires
|
||||
# pitching away from level.
|
||||
@export var air_approach_weight := 0.0
|
||||
@export var ball_velocity_to_goal_weight := 0.004
|
||||
# Per-tick penalty scaled by distance to the ball (full value at the arena's
|
||||
# far diagonal, 0 on top of the ball). Run04 lesson: with idling worth a flat
|
||||
@@ -357,6 +371,21 @@ func _physics_process(delta):
|
||||
reward += forward_velocity_to_ball_weight * forward_speed * facing_ball \
|
||||
* approach_uprightness * handling_ground_factor
|
||||
|
||||
# Aerial shaping: nose-first 3D closing speed on the ball (see
|
||||
# air_approach_weight). Mirrors the ground block above but with the full
|
||||
# nose vector instead of the planar one, and no uprightness multiplier —
|
||||
# a genuine aerial approach requires pitching away from level, so paying
|
||||
# only while upright would oppose the exact behaviour this rewards.
|
||||
if air_approach_weight > 0.0 and ship.global_position.y >= GROUND_HANDLING_HEIGHT \
|
||||
and to_ball.length_squared() > 0.0001:
|
||||
var nose_forward := -ship.global_transform.basis.z
|
||||
if nose_forward.length_squared() > 0.0001:
|
||||
nose_forward = nose_forward.normalized()
|
||||
var to_ball_dir := to_ball.normalized()
|
||||
var air_facing_ball: float = maxf(nose_forward.dot(to_ball_dir), 0.0)
|
||||
var air_closing_speed: float = maxf(ship.linear_velocity.dot(to_ball_dir), 0.0) / ship.max_speed
|
||||
reward += air_approach_weight * air_closing_speed * air_facing_ball
|
||||
|
||||
# Dense penalty: distance to the ball, so idling far away bleeds reward
|
||||
# instead of scoring a safe zero (see ball_distance_penalty).
|
||||
if ball_distance_penalty > 0.0:
|
||||
|
||||
Reference in New Issue
Block a user