From d229bccd19efbb199ccad4ee6408678379448ff0 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Sat, 8 Aug 2026 15:03:46 +0100 Subject: [PATCH] fix(arena): contain goal pockets --- Game/scripts/goal.gd | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Game/scripts/goal.gd b/Game/scripts/goal.gd index 71363942..23cd3057 100644 --- a/Game/scripts/goal.gd +++ b/Game/scripts/goal.gd @@ -14,6 +14,14 @@ signal goal_scored(team: int) # wall is thick or it pokes out the back of the arena. ArenaBoundary cuts the # matching aperture in the hull (see its GOAL_APERTURE_* constants). const POCKET_DEPTH := ArenaBoundary.SURFACE_THICKNESS +# Physical back wall for the goal pocket. The arena end wall has a real hole +# at the scoring aperture; without this, the visual-only net lets ships and +# the ball leave the enclosure after crossing the sensor. A small overlap +# into the surrounding end-wall panels closes numerical seams at the rim. +const BACKSTOP_THICKNESS := 0.4 +const BACKSTOP_EDGE_OVERLAP := 0.25 +const ARENA_COLLISION_LAYER := 1 << 2 +const ARENA_COLLISION_MASK := (1 << 0) | (1 << 1) # Clearance between the mouth and the pocket shell, so the pocket's side walls # stay hidden behind the hull rather than showing at the aperture edge. const POCKET_CLEARANCE := 0.2 @@ -32,6 +40,7 @@ var _goal_burst: GPUParticles3D func _ready(): # Group lets AI controllers and game modes discover goals add_to_group("goal") + _build_backstop() # Everything below _on_body_entered is decoration, and training spawns # headless arenas that never render it. The sensor is unaffected. if DisplayServer.get_name() != "headless": @@ -46,6 +55,26 @@ func _on_body_entered(body): goal_scored.emit(team) +func _build_backstop() -> void: + var mouth := ($CollisionShape3D.shape as BoxShape3D).size + var body := StaticBody3D.new() + body.name = "PocketBackstop" + body.collision_layer = ARENA_COLLISION_LAYER + body.collision_mask = ARENA_COLLISION_MASK + var collision := CollisionShape3D.new() + collision.name = "CollisionShape3D" + var box := BoxShape3D.new() + box.size = Vector3( + mouth.x + BACKSTOP_EDGE_OVERLAP * 2.0, + mouth.y + BACKSTOP_EDGE_OVERLAP * 2.0, + BACKSTOP_THICKNESS + ) + collision.shape = box + collision.position = Vector3(0.0, 0.0, POCKET_DEPTH) + body.add_child(collision) + add_child(body) + + # --- visuals ----------------------------------------------------------------- # Cosmetic only. The mouth is measured off the sensor's own collision shape, so # the frame can never drift from the volume that actually scores.