From 7777280062c70f4d3d5aa32a92e1127940e956dd Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:34:34 +0100 Subject: [PATCH] feat(*): Exempt the floor from the wall-contact penalty, add a tilt penalty for non-upright flight, and double velocity-to-ball shaping --- Game/scenes/match.tscn | 2 +- Game/scenes/spectate.tscn | 4 +-- Game/scripts/ship_ai_controller.gd | 51 +++++++++++++++++++++++------- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/Game/scenes/match.tscn b/Game/scenes/match.tscn index 1dd84273..cd62ba01 100644 --- a/Game/scenes/match.tscn +++ b/Game/scenes/match.tscn @@ -6,7 +6,7 @@ [node name="Match" type="Node3D"] script = ExtResource("1_m") -bot_model_path = "res://bots/rookie.json" +bot_model_path = "res://bots/run03.json" [node name="Arena" parent="." instance=ExtResource("2_m")] diff --git a/Game/scenes/spectate.tscn b/Game/scenes/spectate.tscn index 3b19254a..48134de2 100644 --- a/Game/scenes/spectate.tscn +++ b/Game/scenes/spectate.tscn @@ -6,8 +6,8 @@ [node name="Spectate" type="Node3D"] script = ExtResource("1_s") -bot_a_model_path = "res://bots/run02.json" -bot_b_model_path = "res://bots/run01.json" +bot_a_model_path = "res://bots/run03.json" +bot_b_model_path = "res://bots/run03.json" [node name="Arena" parent="." instance=ExtResource("2_s")] diff --git a/Game/scripts/ship_ai_controller.gd b/Game/scripts/ship_ai_controller.gd index 3c2718b4..f7ae0326 100644 --- a/Game/scripts/ship_ai_controller.gd +++ b/Game/scripts/ship_ai_controller.gd @@ -16,13 +16,22 @@ extends AIController3D # per sim-second); event terms fire once. Exported so tuning needs no code # edits. Goal rewards are added by TrainingMode, which owns goal events. @export var ball_touch_reward := 0.25 -@export var velocity_to_ball_weight := 0.001 +@export var velocity_to_ball_weight := 0.002 @export var ball_velocity_to_goal_weight := 0.004 -# Per-tick penalty while touching the arena enclosure (walls/floor/ceiling). -# At 60 ticks/sim-second this is -0.3/s: a ship parked on a wall for a full -# 30 s episode loses ~9 — comparable to conceding — while a brief graze -# costs almost nothing. +# Per-tick penalty while pressed against a side wall, end wall, or the +# ceiling — NOT the floor (run03 lesson: taxing floor contact punishes the +# ship's natural low flight and drowns every other signal). At 60 ticks per +# sim-second this is -0.3/s: parked on a wall for a full 30 s episode loses +# ~9 — comparable to conceding — while a brief graze costs almost nothing. @export var wall_contact_penalty := 0.005 +# Per-tick penalty for not being upright, scaled by tilt: 0 when flat, full +# value (-0.12/s) when inverted. A penalty rather than an upright bonus so a +# flat, idle ship farms nothing. +@export var tilt_penalty := 0.002 + +# A ship (1x1x4 box) touching a wall has its centre within ~2.05 of it; +# 2.5 adds slack for contact jitter without misreading mid-field contact. +const WALL_PROXIMITY_MARGIN := 2.5 var ship: Ship var rl_controller: RLShipController @@ -89,14 +98,32 @@ func _physics_process(delta): var ball_progress := ball.linear_velocity.dot(ball_to_goal.normalized()) reward += ball_velocity_to_goal_weight * ball_progress / ShipObservations.BALL_SPEED_SCALE - # Dense penalty: every tick spent in contact with the arena enclosure + # Dense penalty: every tick spent pressed against a wall or the ceiling # (contact monitoring is already on for the ball-touch reward). Ships - # bumping each other or the ball is fine — only the boundary counts. - if wall_contact_penalty > 0.0: - for body in ship.get_colliding_bodies(): - if body is ArenaBoundary: - reward -= wall_contact_penalty - break + # bumping each other, the ball, or the floor is fine. The boundary is one + # body, so position tells us which surface the contact is. + if wall_contact_penalty > 0.0 and _touching_boundary() and _near_wall_or_ceiling(): + reward -= wall_contact_penalty + + # Dense penalty: tilt away from upright (0 flat, max when inverted) — + # discourages ending up on a side or roof without rewarding idleness. + if tilt_penalty > 0.0: + var uprightness: float = ship.global_transform.basis.y.dot(Vector3.UP) + reward -= tilt_penalty * (1.0 - uprightness) * 0.5 + + +func _touching_boundary() -> bool: + for body in ship.get_colliding_bodies(): + if body is ArenaBoundary: + return true + return false + + +func _near_wall_or_ceiling() -> bool: + var p := ship.global_position + return absf(p.x) > ArenaBoundary.INNER_HALF_X - WALL_PROXIMITY_MARGIN \ + or absf(p.z) > ArenaBoundary.INNER_HALF_Z - WALL_PROXIMITY_MARGIN \ + or p.y > ArenaBoundary.INNER_HEIGHT - WALL_PROXIMITY_MARGIN func _on_ship_body_entered(body: Node) -> void: