fix(*): Detect wall contact by contact normal so floor contact anywhere on the pitch is exempt from the wall penalty

This commit is contained in:
Josh Creek
2026-07-19 15:53:30 +01:00
parent 4e2d406aa2
commit c8f052854c
+16 -15
View File
@@ -29,9 +29,9 @@ extends AIController3D
# 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
# Contact normals with y above this are floor contact (exempt from the wall
# penalty); below it they read as wall (sideways) or ceiling (downward).
const FLOOR_NORMAL_MIN_Y := 0.7
var ship: Ship
var rl_controller: RLShipController
@@ -101,8 +101,9 @@ func _physics_process(delta):
# 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, 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():
# body, so the contact normal tells us which surface: floor contact
# pushes the ship up (+Y), walls push sideways, the ceiling down.
if wall_contact_penalty > 0.0 and _wall_or_ceiling_contact():
reward -= wall_contact_penalty
# Dense penalty: tilt away from upright (0 flat, max when inverted) —
@@ -112,20 +113,20 @@ func _physics_process(delta):
reward -= tilt_penalty * (1.0 - uprightness) * 0.5
func _touching_boundary() -> bool:
for body in ship.get_colliding_bodies():
if body is ArenaBoundary:
func _wall_or_ceiling_contact() -> bool:
var state := PhysicsServer3D.body_get_direct_state(ship.get_rid())
if state == null:
return false
for i in state.get_contact_count():
if not state.get_contact_collider_object(i) is ArenaBoundary:
continue
# Normal points from the surface into the ship: floor ≈ +Y (exempt),
# anything flatter or downward is a wall or the ceiling.
if state.get_contact_local_normal(i).y < FLOOR_NORMAL_MIN_Y:
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:
if body.is_in_group("ball"):
reward += ball_touch_reward