feat(arena): draw the play volume as one non-overlapping surface shell

Every visible surface of the enclosure shared one alpha-blended material and
was drawn as several overlapping layers: floor, ceiling and four walls as
solid BoxMeshes, plus corner curves and fillets generated on top of the box
faces they eased into. Alpha-blended surfaces don't write depth and sort per
object, so the perimeter composited that tint twice and the corners three
times, giving hard-edged trapezoidal patches that re-sorted as the camera
moved. The floor box also overhung the walls by 1 m and showed through them.

Replace all of it with a single generated mesh covering the inner surface
exactly once, every piece cut to meet its neighbours edge-on and only
inward-facing triangles emitted. It carries two surfaces: an opaque hull
(deck, base fillets, goal bulkheads) and the translucent containment field
(walls, corners, ceiling fillets, ceiling).

The field shader is additive and unshaded rather than alpha-blended: additive
cannot double-darken and composites order-independently, so overlap is
structurally invisible, and being unshaded it no longer picks up per-arena
light and GI gradients across a 28x40 m panel. Fresnel replaces the old
StandardMaterial3D rim, which was a lit effect and the wrong tool. The deck
shader draws plating and field markings procedurally from the boundary's own
constants, so markings cannot drift from the collision geometry.

Per-face MeshInstance3D visibility toggling is gone; the field shader fades
facets the camera has crossed outside of per-pixel from one uniform, which is
what allows a single merged mesh.

Also adds ELEVATED goal mode and the ceiling fillets, and skips the mesh build
entirely under --headless, where training spawns instances that never render.

Collision is untouched: 166 collider shapes in FLOOR mode and 158 in ELEVATED,
verified byte-identical to before, so trained policies in Game/bots/ are
unaffected.
This commit is contained in:
Josh Creek
2026-08-04 13:35:55 +01:00
parent 6f5ce488a9
commit 02df09e40d
6 changed files with 674 additions and 257 deletions
+1 -59
View File
@@ -1,36 +1,7 @@
[gd_scene load_steps=11 format=3]
[gd_scene load_steps=5 format=3]
[ext_resource type="Script" path="res://scripts/arena_boundary.gd" id="1_bndry"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_floor"]
albedo_color = Color(0.08, 0.09, 0.12, 1)
metallic = 0.3
roughness = 0.6
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_field"]
transparency = 1
albedo_color = Color(0.6, 0.75, 1, 0.035)
roughness = 0.05
rim_enabled = true
rim = 1.0
rim_tint = 0.6
[sub_resource type="BoxMesh" id="BoxMesh_floor"]
material = SubResource("StandardMaterial3D_floor")
size = Vector3(28, 1, 40)
[sub_resource type="BoxMesh" id="BoxMesh_ceiling"]
material = SubResource("StandardMaterial3D_field")
size = Vector3(28, 1, 40)
[sub_resource type="BoxMesh" id="BoxMesh_side_wall"]
material = SubResource("StandardMaterial3D_field")
size = Vector3(1, 13, 38)
[sub_resource type="BoxMesh" id="BoxMesh_end_wall"]
material = SubResource("StandardMaterial3D_field")
size = Vector3(26, 13, 1)
[sub_resource type="BoxShape3D" id="BoxShape3D_slab"]
size = Vector3(28, 1, 40)
@@ -47,51 +18,22 @@ script = ExtResource("1_bndry")
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)
shape = SubResource("BoxShape3D_slab")
[node name="FloorMesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.5, 0)
mesh = SubResource("BoxMesh_floor")
[node name="WallPosXShape" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.5, 5.5, 0)
shape = SubResource("BoxShape3D_side_wall")
[node name="WallPosXMesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 12.5, 5.5, 0)
cast_shadow = 0
mesh = SubResource("BoxMesh_side_wall")
[node name="WallNegXShape" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -12.5, 5.5, 0)
shape = SubResource("BoxShape3D_side_wall")
[node name="WallNegXMesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -12.5, 5.5, 0)
cast_shadow = 0
mesh = SubResource("BoxMesh_side_wall")
[node name="WallPosZShape" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.5, 18.5)
shape = SubResource("BoxShape3D_end_wall")
[node name="WallPosZMesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.5, 18.5)
cast_shadow = 0
mesh = SubResource("BoxMesh_end_wall")
[node name="WallNegZShape" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.5, -18.5)
shape = SubResource("BoxShape3D_end_wall")
[node name="WallNegZMesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5.5, -18.5)
cast_shadow = 0
mesh = SubResource("BoxMesh_end_wall")
[node name="CeilingShape" type="CollisionShape3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 12.5, 0)
shape = SubResource("BoxShape3D_slab")
[node name="CeilingMesh" type="MeshInstance3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 12.5, 0)
cast_shadow = 0
mesh = SubResource("BoxMesh_ceiling")