diff --git a/Game/assets/blender_models/nebula_decoration.blend b/Game/assets/blender_models/nebula_decoration.blend new file mode 100644 index 00000000..8a60a814 Binary files /dev/null and b/Game/assets/blender_models/nebula_decoration.blend differ diff --git a/Game/assets/blender_models/nebula_decoration.blend.import b/Game/assets/blender_models/nebula_decoration.blend.import new file mode 100644 index 00000000..cdf4d88c --- /dev/null +++ b/Game/assets/blender_models/nebula_decoration.blend.import @@ -0,0 +1,63 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://d2mrvrt1h305x" +path="res://.godot/imported/nebula_decoration.blend-2f6fe244eedad258eac5faa9b1684142.scn" + +[deps] + +source_file="res://assets/blender_models/nebula_decoration.blend" +dest_files=["res://.godot/imported/nebula_decoration.blend-2f6fe244eedad258eac5faa9b1684142.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +blender/nodes/visible=0 +blender/nodes/active_collection_only=false +blender/nodes/punctual_lights=true +blender/nodes/cameras=true +blender/nodes/custom_properties=true +blender/nodes/modifiers=1 +blender/meshes/vertex_colors=1 +blender/meshes/uvs=true +blender/meshes/normals=true +blender/meshes/export_geometry_nodes_instances=false +blender/meshes/gpu_instances=false +blender/meshes/tangents=true +blender/meshes/skins=2 +blender/meshes/export_bones_deforming_mesh_only=false +blender/materials/unpack_enabled=true +blender/materials/export_materials=1 +blender/animation/limit_playback=true +blender/animation/always_sample=true +blender/animation/group_tracks=true +gltf/naming_version=2 +gltf/texture_map_mode=1 diff --git a/Game/assets/models/nebula_debris.glb b/Game/assets/models/nebula_debris.glb new file mode 100644 index 00000000..fc1b19b2 Binary files /dev/null and b/Game/assets/models/nebula_debris.glb differ diff --git a/Game/assets/models/nebula_debris.glb.import b/Game/assets/models/nebula_debris.glb.import new file mode 100644 index 00000000..50d858ab --- /dev/null +++ b/Game/assets/models/nebula_debris.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://fc85v6374c1" +path="res://.godot/imported/nebula_debris.glb-39af77a17c0998b5b73172577d906cb9.scn" + +[deps] + +source_file="res://assets/models/nebula_debris.glb" +dest_files=["res://.godot/imported/nebula_debris.glb-39af77a17c0998b5b73172577d906cb9.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/Game/assets/models/nebula_planet.glb b/Game/assets/models/nebula_planet.glb new file mode 100644 index 00000000..cfdb1a8c Binary files /dev/null and b/Game/assets/models/nebula_planet.glb differ diff --git a/Game/assets/models/nebula_planet.glb.import b/Game/assets/models/nebula_planet.glb.import new file mode 100644 index 00000000..e39b246e --- /dev/null +++ b/Game/assets/models/nebula_planet.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://bpfwv7fse77wk" +path="res://.godot/imported/nebula_planet.glb-2431590907ff85cf1057e7d6ad614ed7.scn" + +[deps] + +source_file="res://assets/models/nebula_planet.glb" +dest_files=["res://.godot/imported/nebula_planet.glb-2431590907ff85cf1057e7d6ad614ed7.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/Game/assets/models/nebula_planet_planet_surface.png b/Game/assets/models/nebula_planet_planet_surface.png new file mode 100644 index 00000000..caf4dc94 Binary files /dev/null and b/Game/assets/models/nebula_planet_planet_surface.png differ diff --git a/Game/assets/models/nebula_planet_planet_surface.png.import b/Game/assets/models/nebula_planet_planet_surface.png.import new file mode 100644 index 00000000..67e36a7b --- /dev/null +++ b/Game/assets/models/nebula_planet_planet_surface.png.import @@ -0,0 +1,43 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cr3feyv7yldjc" +path="res://.godot/imported/nebula_planet_planet_surface.png-4b68cca75d2d22d25510c7eb277c0b85.ctex" +metadata={ +"vram_texture": false +} +generator_parameters={ +"md5": "44a2e26010b529339e264deecde957e0" +} + +[deps] + +source_file="res://assets/models/nebula_planet_planet_surface.png" +dest_files=["res://.godot/imported/nebula_planet_planet_surface.png-4b68cca75d2d22d25510c7eb277c0b85.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=true +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Game/assets/models/nebula_station.glb b/Game/assets/models/nebula_station.glb new file mode 100644 index 00000000..5d43fb93 Binary files /dev/null and b/Game/assets/models/nebula_station.glb differ diff --git a/Game/assets/models/nebula_station.glb.import b/Game/assets/models/nebula_station.glb.import new file mode 100644 index 00000000..23f57346 --- /dev/null +++ b/Game/assets/models/nebula_station.glb.import @@ -0,0 +1,45 @@ +[remap] + +importer="scene" +importer_version=1 +type="PackedScene" +uid="uid://dn8tj3iw0io7e" +path="res://.godot/imported/nebula_station.glb-fa9a6dd87ae3789d04205b52215e2e76.scn" + +[deps] + +source_file="res://assets/models/nebula_station.glb" +dest_files=["res://.godot/imported/nebula_station.glb-fa9a6dd87ae3789d04205b52215e2e76.scn"] + +[params] + +nodes/root_type="" +nodes/root_name="" +nodes/root_script=null +mesh_library/use_node_names_as_mesh_names=false +array_mesh/deduplicate_surfaces=true +nodes/apply_root_scale=true +nodes/root_scale=1.0 +nodes/import_as_skeleton_bones=false +nodes/use_name_suffixes=true +nodes/use_node_type_suffixes=true +meshes/ensure_tangents=true +meshes/generate_lods=true +meshes/create_shadow_meshes=true +meshes/light_baking=1 +meshes/lightmap_texel_size=0.2 +meshes/force_disable_compression=false +skins/use_named_skins=true +animation/import=true +animation/fps=30 +animation/trimming=false +animation/remove_immutable_tracks=true +animation/import_rest_as_RESET=false +import_script/path="" +materials/extract=0 +materials/extract_format=0 +materials/extract_path="" +_subresources={} +gltf/naming_version=2 +gltf/embedded_image_handling=1 +gltf/texture_map_mode=1 diff --git a/Game/assets/textures/particle_glow.png b/Game/assets/textures/particle_glow.png new file mode 100644 index 00000000..63ea48a4 Binary files /dev/null and b/Game/assets/textures/particle_glow.png differ diff --git a/Game/assets/textures/particle_glow.png.import b/Game/assets/textures/particle_glow.png.import new file mode 100644 index 00000000..bae1cb8e --- /dev/null +++ b/Game/assets/textures/particle_glow.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://damajpvo00prm" +path="res://.godot/imported/particle_glow.png-e6c07ae4c700896e29ebe96adf51fa5c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/particle_glow.png" +dest_files=["res://.godot/imported/particle_glow.png-e6c07ae4c700896e29ebe96adf51fa5c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Game/assets/textures/planet_surface.png b/Game/assets/textures/planet_surface.png new file mode 100644 index 00000000..caf4dc94 Binary files /dev/null and b/Game/assets/textures/planet_surface.png differ diff --git a/Game/assets/textures/planet_surface.png.import b/Game/assets/textures/planet_surface.png.import new file mode 100644 index 00000000..becb5fde --- /dev/null +++ b/Game/assets/textures/planet_surface.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bxsadfd04v6r2" +path="res://.godot/imported/planet_surface.png-d3aaf8310508e4c85b26a3d093bcf9c8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/planet_surface.png" +dest_files=["res://.godot/imported/planet_surface.png-d3aaf8310508e4c85b26a3d093bcf9c8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Game/assets/textures/sky_nebula.png b/Game/assets/textures/sky_nebula.png new file mode 100644 index 00000000..6c5e9f61 Binary files /dev/null and b/Game/assets/textures/sky_nebula.png differ diff --git a/Game/assets/textures/sky_nebula.png.import b/Game/assets/textures/sky_nebula.png.import new file mode 100644 index 00000000..86f491ab --- /dev/null +++ b/Game/assets/textures/sky_nebula.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dc445qysyxwwv" +path="res://.godot/imported/sky_nebula.png-70f974ab520aaa5206eef67a936b662e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/textures/sky_nebula.png" +dest_files=["res://.godot/imported/sky_nebula.png-70f974ab520aaa5206eef67a936b662e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/Game/objects/arena_boundary.tscn b/Game/objects/arena_boundary.tscn index 5248954f..d0005580 100644 --- a/Game/objects/arena_boundary.tscn +++ b/Game/objects/arena_boundary.tscn @@ -10,7 +10,7 @@ roughness = 0.6 [sub_resource type="StandardMaterial3D" id="StandardMaterial3D_field"] transparency = 1 shading_mode = 0 -albedo_color = Color(0, 0.368627, 1, 0.08) +albedo_color = Color(0.6, 0.75, 1, 0.035) [sub_resource type="BoxMesh" id="BoxMesh_floor"] material = SubResource("StandardMaterial3D_floor") diff --git a/Game/project.godot b/Game/project.godot index e631417b..d41adac3 100644 --- a/Game/project.godot +++ b/Game/project.godot @@ -118,4 +118,5 @@ common/physics_interpolation=true [autoload] + GameSettings="*res://scripts/game_settings.gd" diff --git a/Game/scenes/arena_02.tscn b/Game/scenes/arena_02.tscn new file mode 100644 index 00000000..b28f5554 --- /dev/null +++ b/Game/scenes/arena_02.tscn @@ -0,0 +1,142 @@ +[gd_scene load_steps=15 format=3] + +[ext_resource type="Script" path="res://scripts/arena.gd" id="1_iywne"] +[ext_resource type="PackedScene" path="res://objects/arena_boundary.tscn" id="2_bndry"] +[ext_resource type="PackedScene" uid="uid://cofdcxo5170rs" path="res://objects/goal.tscn" id="6_p57ef"] +[ext_resource type="Texture2D" path="res://assets/textures/sky_nebula.png" id="7_nebula"] +[ext_resource type="PackedScene" path="res://assets/models/nebula_station.glb" id="8_station"] +[ext_resource type="PackedScene" path="res://assets/models/nebula_debris.glb" id="9_debris"] +[ext_resource type="Texture2D" path="res://assets/textures/particle_glow.png" id="10_dust"] +[ext_resource type="PackedScene" path="res://assets/models/nebula_planet.glb" id="11_planet"] + +[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_nebula"] +panorama = ExtResource("7_nebula") + +[sub_resource type="Sky" id="Sky_nebula"] +sky_material = SubResource("PanoramaSkyMaterial_nebula") + +[sub_resource type="Environment" id="Environment_nebula"] +background_mode = 2 +sky = SubResource("Sky_nebula") +ambient_light_source = 2 +ambient_light_color = Color(0.65, 0.35, 0.75, 1) +ambient_light_energy = 0.45 + +[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_dust"] +transparency = 1 +shading_mode = 0 +billboard_mode = 1 +vertex_color_use_as_albedo = true +albedo_color = Color(1, 0.75, 0.9, 1) +albedo_texture = ExtResource("10_dust") +emission_enabled = true +emission = Color(1, 0.6, 0.85, 1) +emission_energy_multiplier = 2.5 + +[sub_resource type="QuadMesh" id="QuadMesh_dust"] +material = SubResource("StandardMaterial3D_dust") +size = Vector2(0.4, 0.4) + +[sub_resource type="Gradient" id="Gradient_dust"] +offsets = PackedFloat32Array(0, 0.15, 0.85, 1) +colors = PackedColorArray(1, 1, 1, 0, 1, 1, 1, 0.55, 1, 1, 1, 0.55, 1, 1, 1, 0) + +[sub_resource type="GradientTexture1D" id="GradientTexture1D_dust"] +gradient = SubResource("Gradient_dust") + +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_dust"] +emission_shape = 3 +emission_box_extents = Vector3(26, 11, 28) +direction = Vector3(0, 1, 0) +spread = 180.0 +initial_velocity_min = 0.1 +initial_velocity_max = 0.45 +gravity = Vector3(0, 0, 0) +turbulence_enabled = true +turbulence_noise_strength = 0.7 +turbulence_noise_scale = 0.6 +turbulence_influence_min = 0.35 +turbulence_influence_max = 0.65 +scale_min = 0.5 +scale_max = 1.6 +color_ramp = SubResource("GradientTexture1D_dust") + +[node name="Arena" type="Node3D"] +script = ExtResource("1_iywne") + +[node name="Boundary" parent="." instance=ExtResource("2_bndry")] + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.866025, -0.383022, 0.321394, 0, 0.642788, 0.766044, -0.5, -0.663414, 0.55667, 0, 11, 0) +light_color = Color(0.85, 0.75, 1, 1) +shadow_enabled = true + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_nebula") + +[node name="GoalTeam0" parent="." instance=ExtResource("6_p57ef")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.79, 18) + +[node name="GoalTeam1" parent="." instance=ExtResource("6_p57ef")] +transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.79, -18) +team = 1 + +[node name="BallSpawn" type="Marker3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) + +[node name="SpawnsTeam0" type="Node3D" parent="."] + +[node name="Spawn1" type="Marker3D" parent="SpawnsTeam0"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.8, 13.5) + +[node name="SpawnsTeam1" type="Node3D" parent="."] + +[node name="Spawn1" type="Marker3D" parent="SpawnsTeam1"] +transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 2.8, -13.5) + +[node name="NebulaDust" type="GPUParticles3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 5, 0) +amount = 500 +lifetime = 22.0 +preprocess = 8.0 +randomness = 1.0 +visibility_aabb = AABB(-30, -14, -32, 60, 28, 64) +process_material = SubResource("ParticleProcessMaterial_dust") +draw_pass_1 = SubResource("QuadMesh_dust") + +[node name="Decoration" type="Node3D" parent="."] + +[node name="NebulaStationA" parent="Decoration" instance=ExtResource("8_station")] +transform = Transform3D(1.8, 0, 0, 0, 1.8, 0, 0, 0, 1.8, 28, 5, -6) +rotation = Vector3(0, -0.436332, 0) + +[node name="NebulaDebrisA" parent="Decoration" instance=ExtResource("9_debris")] +transform = Transform3D(1.2, 0, 0, 0, 1.2, 0, 0, 0, 1.2, -22, 9, 11) +rotation = Vector3(0.261799, 0.698132, 0.087266) + +[node name="NebulaDebrisB" parent="Decoration" instance=ExtResource("9_debris")] +transform = Transform3D(0.8, 0, 0, 0, 0.8, 0, 0, 0, 0.8, 34, 3, 15) +rotation = Vector3(-0.174533, 1.919862, 0.349066) + +[node name="NebulaDebrisC" parent="Decoration" instance=ExtResource("9_debris")] +transform = Transform3D(1.5, 0, 0, 0, 1.5, 0, 0, 0, 1.5, -30, 11, -21) +rotation = Vector3(0.087266, -1.047198, -0.261799) + +[node name="NebulaStationB" parent="Decoration" instance=ExtResource("8_station")] +transform = Transform3D(1.5, 0, 0, 0, 1.5, 0, 0, 0, 1.5, -32, 7, 9) +rotation = Vector3(0.05, 1.134464, -0.02) + +[node name="NebulaStationC" parent="Decoration" instance=ExtResource("8_station")] +transform = Transform3D(1.3, 0, 0, 0, 1.3, 0, 0, 0, 1.3, 9, 8, 42) +rotation = Vector3(-0.03, 2.740167, 0.06) + +[node name="NebulaDebrisD" parent="Decoration" instance=ExtResource("9_debris")] +transform = Transform3D(1.0, 0, 0, 0, 1.0, 0, 0, 0, 1.0, 13, 15, -35) +rotation = Vector3(0.4, 1.3, -0.2) + +[node name="NebulaDebrisE" parent="Decoration" instance=ExtResource("9_debris")] +transform = Transform3D(0.9, 0, 0, 0, 0.9, 0, 0, 0, 0.9, -11, 4, 33) +rotation = Vector3(-0.3, -0.9, 0.15) + +[node name="NebulaPlanet" parent="Decoration" instance=ExtResource("11_planet")] +transform = Transform3D(30, 0, 0, 0, 30, 0, 0, 0, 30, -55, 38, -80) diff --git a/Game/scenes/arena_03.tscn b/Game/scenes/arena_03.tscn new file mode 100644 index 00000000..8a18b89a --- /dev/null +++ b/Game/scenes/arena_03.tscn @@ -0,0 +1,54 @@ +[gd_scene load_steps=6 format=3] + +[ext_resource type="Script" path="res://scripts/arena.gd" id="1_iywne"] +[ext_resource type="PackedScene" path="res://objects/arena_boundary.tscn" id="2_bndry"] +[ext_resource type="PackedScene" uid="uid://cofdcxo5170rs" path="res://objects/goal.tscn" id="6_p57ef"] +[ext_resource type="Shader" path="res://shaders/sky_asteroid_field.gdshader" id="7_asteroid"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_asteroid"] +shader = ExtResource("7_asteroid") + +[sub_resource type="Sky" id="Sky_asteroid"] +sky_material = SubResource("ShaderMaterial_asteroid") + +[sub_resource type="Environment" id="Environment_asteroid"] +background_mode = 2 +sky = SubResource("Sky_asteroid") +ambient_light_source = 2 +ambient_light_color = Color(0.6, 0.45, 0.3, 1) +ambient_light_energy = 0.3 + +[node name="Arena" type="Node3D"] +script = ExtResource("1_iywne") + +[node name="Boundary" parent="." instance=ExtResource("2_bndry")] + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(0.866025, -0.383022, 0.321394, 0, 0.642788, 0.766044, -0.5, -0.663414, 0.55667, 0, 11, 0) +light_color = Color(1, 0.85, 0.65, 1) +shadow_enabled = true + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_asteroid") + +[node name="GoalTeam0" parent="." instance=ExtResource("6_p57ef")] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.79, 18) + +[node name="GoalTeam1" parent="." instance=ExtResource("6_p57ef")] +transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 0.79, -18) +team = 1 + +[node name="BallSpawn" type="Marker3D" parent="."] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0) + +[node name="SpawnsTeam0" type="Node3D" parent="."] + +[node name="Spawn1" type="Marker3D" parent="SpawnsTeam0"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2.8, 13.5) + +[node name="SpawnsTeam1" type="Node3D" parent="."] + +[node name="Spawn1" type="Marker3D" parent="SpawnsTeam1"] +transform = Transform3D(-1, 0, 0, 0, 1, 0, 0, 0, -1, 0, 2.8, -13.5) + +[node name="Decoration" type="Node3D" parent="."] diff --git a/Game/scenes/free_play.tscn b/Game/scenes/free_play.tscn index 93b4fc7e..0220cb83 100644 --- a/Game/scenes/free_play.tscn +++ b/Game/scenes/free_play.tscn @@ -1,12 +1,10 @@ -[gd_scene load_steps=4 format=3] +[gd_scene load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/free_play.gd" id="1_fp"] -[ext_resource type="PackedScene" path="res://scenes/arena_01.tscn" id="2_fp"] [ext_resource type="PackedScene" uid="uid://c8kak2l3m4n5" path="res://scenes/HUD.tscn" id="3_fp"] [node name="FreePlay" type="Node3D"] script = ExtResource("1_fp") -[node name="Arena" parent="." instance=ExtResource("2_fp")] [node name="HUD" parent="." instance=ExtResource("3_fp")] diff --git a/Game/scenes/main_menu.tscn b/Game/scenes/main_menu.tscn index dac2804b..708a5044 100644 --- a/Game/scenes/main_menu.tscn +++ b/Game/scenes/main_menu.tscn @@ -53,6 +53,20 @@ theme_override_font_sizes/font_size = 13 text = "Solo practice — no timer, R resets the ball" horizontal_alignment = 1 +[node name="ArenaRow" type="HBoxContainer" parent="CenterContainer/VBoxContainer"] +layout_mode = 2 +theme_override_constants/separation = 10 + +[node name="ArenaLabel" type="Label" parent="CenterContainer/VBoxContainer/ArenaRow"] +layout_mode = 2 +text = "Arena" + +[node name="ArenaDropdown" type="OptionButton" parent="CenterContainer/VBoxContainer/ArenaRow"] +unique_name_in_owner = true +custom_minimum_size = Vector2(0, 40) +layout_mode = 2 +size_flags_horizontal = 3 + [node name="MatchSeparator" type="HSeparator" parent="CenterContainer/VBoxContainer"] layout_mode = 2 diff --git a/Game/scenes/match.tscn b/Game/scenes/match.tscn index a48dee6a..700862d3 100644 --- a/Game/scenes/match.tscn +++ b/Game/scenes/match.tscn @@ -1,13 +1,11 @@ -[gd_scene load_steps=4 format=3] +[gd_scene load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/match_mode.gd" id="1_m"] -[ext_resource type="PackedScene" path="res://scenes/arena_01.tscn" id="2_m"] [ext_resource type="PackedScene" uid="uid://c8kak2l3m4n5" path="res://scenes/HUD.tscn" id="3_m"] [node name="Match" type="Node3D"] script = ExtResource("1_m") bot_model_path = "res://bots/promoted/easy.json" -[node name="Arena" parent="." instance=ExtResource("2_m")] [node name="HUD" parent="." instance=ExtResource("3_m")] diff --git a/Game/scenes/spectate.tscn b/Game/scenes/spectate.tscn index 06e6d2c5..fd4af692 100644 --- a/Game/scenes/spectate.tscn +++ b/Game/scenes/spectate.tscn @@ -1,7 +1,6 @@ -[gd_scene load_steps=4 format=3] +[gd_scene load_steps=3 format=3] [ext_resource type="Script" path="res://scripts/spectate_mode.gd" id="1_s"] -[ext_resource type="PackedScene" path="res://scenes/arena_01.tscn" id="2_s"] [ext_resource type="PackedScene" uid="uid://c8kak2l3m4n5" path="res://scenes/HUD.tscn" id="3_s"] [node name="Spectate" type="Node3D"] @@ -9,6 +8,5 @@ script = ExtResource("1_s") bot_a_model_path = "res://bots/promoted/easy.json" bot_b_model_path = "res://bots/promoted/easy.json" -[node name="Arena" parent="." instance=ExtResource("2_s")] [node name="HUD" parent="." instance=ExtResource("3_s")] diff --git a/Game/scripts/arena_registry.gd b/Game/scripts/arena_registry.gd new file mode 100644 index 00000000..a5047878 --- /dev/null +++ b/Game/scripts/arena_registry.gd @@ -0,0 +1,14 @@ +class_name ArenaRegistry + +# Single source of truth for available arenas: the Free Play menu dropdown +# lists these, and Match/Spectate pick one at random each session. Training +# is exempt — training.tscn keeps its own fixed arena_01 child. +const ARENAS := [ + {"name": "Starfield", "path": "res://scenes/arena_01.tscn"}, + {"name": "Nebula", "path": "res://scenes/arena_02.tscn"}, + {"name": "Asteroid Field", "path": "res://scenes/arena_03.tscn"}, +] + + +static func random_path() -> String: + return ARENAS[randi() % ARENAS.size()]["path"] diff --git a/Game/scripts/arena_registry.gd.uid b/Game/scripts/arena_registry.gd.uid new file mode 100644 index 00000000..b2eb7a43 --- /dev/null +++ b/Game/scripts/arena_registry.gd.uid @@ -0,0 +1 @@ +uid://orkh5g5828cv diff --git a/Game/scripts/free_play.gd b/Game/scripts/free_play.gd index a6534cdd..40c3e338 100644 --- a/Game/scripts/free_play.gd +++ b/Game/scripts/free_play.gd @@ -4,6 +4,12 @@ extends GameMode # Rocket League's free play. R resets the ball, Esc returns to the menu. +func _get_arena_scene_path() -> String: + if not GameSettings.selected_arena_path.is_empty(): + return GameSettings.selected_arena_path + return ArenaRegistry.ARENAS[0]["path"] + + func _start() -> void: spawn_ball() var player_ship := spawn_ship(0, 0, PlayerShipController.new()) diff --git a/Game/scripts/game_mode.gd b/Game/scripts/game_mode.gd index 103617d9..9b11f4db 100644 --- a/Game/scripts/game_mode.gd +++ b/Game/scripts/game_mode.gd @@ -26,6 +26,11 @@ func _ready(): if child is Arena: arena = child break + if not arena: + var path := _get_arena_scene_path() + if not path.is_empty(): + arena = (load(path) as PackedScene).instantiate() + add_child(arena) if not arena: push_error("GameMode: scene has no Arena child") return @@ -34,6 +39,13 @@ func _ready(): _start() +# Virtual: subclasses whose scene has no fixed Arena child override this to +# pick which arena scene to instantiate in code (see ArenaRegistry). Modes +# that keep a fixed Arena child (training.tscn) never call this. +func _get_arena_scene_path() -> String: + return "" + + # Virtual: subclasses spawn their ball/ships/camera here. func _start() -> void: pass diff --git a/Game/scripts/game_settings.gd b/Game/scripts/game_settings.gd index c33f9d9f..3615687b 100644 --- a/Game/scripts/game_settings.gd +++ b/Game/scripts/game_settings.gd @@ -21,3 +21,7 @@ var dev_bot_override_path: String = "" # Spectate (bot vs bot) policies chosen in the main menu; empty = mode export. var spectate_bot_a_path: String = "" var spectate_bot_b_path: String = "" + +# Arena chosen in the main menu for Free Play; empty = ArenaRegistry's default. +# Match/Spectate ignore this and pick a random arena each session instead. +var selected_arena_path: String = "" diff --git a/Game/scripts/main_menu.gd b/Game/scripts/main_menu.gd index c0263323..394f495b 100644 --- a/Game/scripts/main_menu.gd +++ b/Game/scripts/main_menu.gd @@ -18,6 +18,7 @@ const DIFFICULTIES := [ ] @onready var difficulty_dropdown: OptionButton = %DifficultyDropdown +@onready var arena_dropdown: OptionButton = %ArenaDropdown @onready var dev_section: Control = %DevSection @onready var dev_bot_dropdown: OptionButton = %DevBotDropdown @onready var bot_a_dropdown: OptionButton = %BotADropdown @@ -26,6 +27,7 @@ const DIFFICULTIES := [ func _ready() -> void: _populate_difficulty_dropdown() + _populate_arena_dropdown() dev_section.visible = OS.is_debug_build() if dev_section.visible: var bots := _list_bots() @@ -47,6 +49,19 @@ func _populate_difficulty_dropdown() -> void: difficulty_dropdown.select(selected) +# Free Play only — Match/Spectate pick a random arena in code instead. +func _populate_arena_dropdown() -> void: + arena_dropdown.clear() + for tier in ArenaRegistry.ARENAS: + arena_dropdown.add_item(tier["name"]) + var selected := 0 + for i in ArenaRegistry.ARENAS.size(): + if ArenaRegistry.ARENAS[i]["path"] == GameSettings.selected_arena_path: + selected = i + break + arena_dropdown.select(selected) + + # Lists bot checkpoints as paths relative to BOTS_DIR, including the # top-level curriculum checkpoints and anything under promoted/. func _list_bots() -> Array[String]: @@ -93,6 +108,8 @@ func _selected_path(dropdown: OptionButton) -> String: func _on_free_play_pressed() -> void: + var chosen: Dictionary = ArenaRegistry.ARENAS[0] if arena_dropdown.selected < 0 else ArenaRegistry.ARENAS[arena_dropdown.selected] + GameSettings.selected_arena_path = chosen["path"] get_tree().change_scene_to_file("res://scenes/free_play.tscn") diff --git a/Game/scripts/match_mode.gd b/Game/scripts/match_mode.gd index c6aa5b49..3e401ac9 100644 --- a/Game/scripts/match_mode.gd +++ b/Game/scripts/match_mode.gd @@ -32,6 +32,10 @@ var match_timer: Timer var _in_overtime := false +func _get_arena_scene_path() -> String: + return ArenaRegistry.random_path() + + func _start() -> void: spawn_ball() var player_ship := spawn_ship(0, 0, PlayerShipController.new()) diff --git a/Game/scripts/spectate_mode.gd b/Game/scripts/spectate_mode.gd index a7882044..6f6fdd5c 100644 --- a/Game/scripts/spectate_mode.gd +++ b/Game/scripts/spectate_mode.gd @@ -19,6 +19,10 @@ signal score_changed(score: Dictionary) var score := {0: 0, 1: 0} +func _get_arena_scene_path() -> String: + return ArenaRegistry.random_path() + + func _start() -> void: spawn_ball() # The main menu's dropdown selections (GameSettings autoload) win over the diff --git a/Game/shaders/sky_asteroid_field.gdshader b/Game/shaders/sky_asteroid_field.gdshader new file mode 100644 index 00000000..5fda5f98 --- /dev/null +++ b/Game/shaders/sky_asteroid_field.gdshader @@ -0,0 +1,17 @@ +shader_type sky; + +// Procedural dusty void: dim rocky-belt haze with a warm horizon glow, sparser +// stars than arena_01 for a grittier, more enclosed feel. +void sky() { + vec3 dir = EYEDIR; + vec3 col = vec3(0.015, 0.012, 0.01); + col += vec3(0.09, 0.05, 0.02) * pow(1.0 - abs(dir.y), 4.0); + vec3 cell = floor(dir * 220.0); + float h = fract(sin(dot(cell, vec3(12.9898, 78.233, 45.164))) * 43758.5453); + if (h > 0.9985) { + vec3 in_cell = fract(dir * 220.0) - 0.5; + float star = smoothstep(0.35, 0.0, length(in_cell)); + col += vec3(star * (0.4 + fract(h * 999.0)) * 0.8); + } + COLOR = col; +} diff --git a/Game/shaders/sky_asteroid_field.gdshader.uid b/Game/shaders/sky_asteroid_field.gdshader.uid new file mode 100644 index 00000000..eabeae91 --- /dev/null +++ b/Game/shaders/sky_asteroid_field.gdshader.uid @@ -0,0 +1 @@ +uid://bg7diy2ywqkbd diff --git a/TODO.md b/TODO.md index 0e5f9a15..bddf3d35 100644 --- a/TODO.md +++ b/TODO.md @@ -22,5 +22,30 @@ The training pipeline is built — see `TRAINING.md` (self-play PPO via the vend ## General -- [x] `GameSettings` autoload (`scripts/game_settings.gd`) added for exactly the cross-scene case this item anticipated — passing menu selections (bot/difficulty, spectate matchup) into match/spectate modes. Still the only autoload; keep it that way unless another cross-scene need shows up. -- [ ] More arenas: `arena_01.tscn` is the template — an arena is a setting (sky, lighting, decoration) + two team-tagged goals on the standard goal lines + spawn markers + an instance of the shared `objects/arena_boundary.tscn`, with no rules or state. All arenas share the standard play volume defined by `ArenaBoundary`'s constants. +- [x] More arenas: `arena_02.tscn` (Nebula) and `arena_03.tscn` (Asteroid Field) added alongside `arena_01.tscn`, all following the same template (goals/spawns/`arena_boundary.tscn` unchanged) and listed in `scripts/arena_registry.gd`. Free Play lets the player pick an arena from the main menu; Match/Spectate each pick one at random per session; Training keeps its own fixed `arena_01.tscn` untouched. +- [x] arena_02 (Nebula) redone RL-style: `arena_boundary.tscn`'s field material is now near-invisible glass (shared by all arenas), the sky is a baked photoreal-style equirect nebula texture (`assets/textures/sky_nebula.png`, procedurally generated offline — no copied astrophotography) instead of a procedural cell-shader, a `NebulaDust` `GPUParticles3D` gives a drifting-motes "weather" effect, and the `Decoration` node holds a station/debris/planet environment (Blender-modeled, `assets/blender_models/nebula_decoration.blend` → `assets/models/nebula_*.glb`) visible in multiple directions. `arena_01`/`arena_03` still use the older embedded-shader sky — same treatment for those is unstarted follow-up work. + +## Visual quality pass (arena_02 critique follow-up) + +A subagent ran the game and critiqued arena_02 head-on against Rocket League 2 in Unreal Engine 5: verdict was "a flat-shaded, unlit-looking blockout dressed up with two nice noise textures" — not fundamentally unfixable in Godot (SDFGI/Nanite-tier GI and hero-sculpted geometry aside), but several concrete gaps. Work through these one at a time, in this order (cheapest/highest-impact first). Each item has a ready-to-use prompt — paste it into a fresh session to tackle just that piece. The texture-generation Python scripts used for `sky_nebula.png`/`planet_surface.png` currently only exist in an ephemeral scratchpad, not the repo — the first item that touches them should commit a copy into the repo (e.g. `tools/textures/`) so they're reproducible. + +- [ ] **Post-processing pipeline** (no models needed — cheapest, highest-impact item on the list). Nothing currently sits on top of the raw render: no bloom/glow, no AA, no color grading, no vignette beyond the floor's baked fade. + Prompt: "In Cosmic Clash (Godot 4.7), the arena Environment resources (start with `Game/scenes/arena_02.tscn`'s `Environment_nebula`, then apply consistently across `arena_01`/`arena_03` too) currently have no post-processing. Enable and tune Glow (bloom) so the nebula core, emissive accent strips on the station model, and bright stars actually bleed light; enable Adjustments (`adjustment_enabled`) for a subtle contrast/saturation grade. Godot's `Environment` has no built-in vignette property — if that's still wanted, it needs a small custom full-screen shader (a `CompositorEffect` or a screen-space `ColorRect` overlay), not a toggle; treat it as a separate, smaller sub-task rather than assuming it's free. Also enable FXAA and/or TAA via `project.godot`'s `rendering/anti_aliasing/quality/*` settings project-wide to fix the aliased hard edges visible on ship geometry. Motion blur and depth-of-field are deliberately out of scope here — Godot has no built-in equivalent, and faking either well needs a custom `CompositorEffect`, which is a much bigger task than this pass. Verify with before/after screenshots via godot-mcp (`run_project` on `free_play.tscn` with the Nebula arena selected) — don't just eyeball the editor, actually run the game." + +- [ ] **De-duplicate the nebula sky's star sprites**. Every bright star in `sky_nebula.png` is the exact same diffraction-spike stamp at the same size/brightness, just relocated — called out as "the single most amateur-looking tell in the whole scene" once you look for more than a second. + Prompt: "Commit the nebula sky texture generator (currently only in an ephemeral scratchpad — recreate it if needed: numpy/Pillow script generating a 4096x2048 equirect nebula via layered FFT/domain-warped noise, color-graded, with drawn hero stars) into the repo at `tools/textures/gen_nebula_sky.py`. Fix the hero-star drawing loop so each star's diffraction-spike stamp gets randomized rotation, spike length, and brightness instead of reusing one identical stamp at every location. Regenerate `Game/assets/textures/sky_nebula.png`, reimport, and confirm via an in-game screenshot that the repeated-stamp tell is gone." + +- [ ] **Real PBR lighting + materials, fresnel glass boundary** (no new models — material/lighting setup only). Everything is flat-shaded/unshaded with a single directional light: no GI, no reflections, no bounce/AO, and the "glass" boundary is a flat unshaded tinted overlay with no fresnel rim or reflection. + Prompt: "In Cosmic Clash, enable SDFGI and add reflection probes to the arena scenes (start with `arena_02.tscn`) so surfaces get real bounce lighting/reflections instead of flat ambient. Convert `arena_boundary.tscn`'s `StandardMaterial3D_field` from unshaded to a proper shaded material with a fresnel-based rim highlight (bright at grazing angles, near-invisible face-on) so it reads as glass rather than a tinted overlay — check the performance impact of moving it off unshaded, given it's a large always-visible surface. Verify visually via godot-mcp screenshots, and confirm headless runs (`free_play`/`match`/`spectate`/`training`) still show zero errors." + +- [ ] **Greeble/detail pass on the station + debris models** (Blender remodel — explicit models to redo). `nebula_station.glb`/`nebula_debris.glb` are unmistakably Blender primitive kitbashes up close: plain boxes/cylinders with bevel modifiers, no panel lines, damage decals, or surface detail, and debris chunks look undifferentiated from the station. + Prompt: "Using the blender MCP, rebuild `nebula_station` (source in `Game/assets/blender_models/nebula_decoration.blend`) with actual surface detail: greebled panel-line insets via bmesh inset-and-extrude on selected faces (more scriptable and robust than chained boolean cuts — prefer this over booleans for the main detailing pass), and 2-3 emissive window/light strips distinct from the existing single accent strip. A full normal-map/AO bake pipeline (low-poly + high-poly pair, UV unwrap, bake settings) is the ideal AAA-style finish but is fragile to script end-to-end in one pass — attempt it, but if it proves too unreliable, fall back to the inset/extrude geometric detail alone plus a simple tiled trim-sheet-style texture rather than forcing a bake that doesn't work. Give `nebula_debris` chunks a rockier/damaged material distinct from the station's clean hull (darker, rougher, maybe scorch-mark variation) so they read as separate debris rather than clones of the station's material. Re-export both to `Game/assets/models/nebula_station.glb`/`nebula_debris.glb`, re-save the shared `.blend`, and re-verify placement/transforms in `arena_02.tscn` still look right (screenshot check)." + +- [ ] **Richer nebula sky + planet surface textures** (extends the existing procedural generation — no new asset types). `sky_nebula.png` is essentially one noise-filter pass over a bright core; `planet_surface.png` is a flat gradient with a single vortex swirl and no bands, craters, or day/night terminator. + Prompt: "Extend the nebula/planet texture generators (see `tools/textures/` once committed, per the star-sprite TODO item above) with more detail layers: for the sky, add a second/third dust-lane layer at a different scale plus subtler color variation within the bright core (real nebulae aren't one flat color); for the planet, add a proper lit/unlit terminator gradient (the side facing the arena's directional light should read brighter), more band variation at different latitudes, and a couple more storm-vortex features so it doesn't read as a single gradient with one twist. Regenerate both textures, reimport, and screenshot-verify." + +- [ ] **Particle lighting response for the nebula dust ("weather") effect** (no models — particle material/shader only). The `NebulaDust` `GPUParticles3D` motes are generic soft glow sprites with no lighting interaction, no depth-based fade, and no secondary motion (no sparkle/color shift). + Prompt: "In `arena_02.tscn`'s `NebulaDust` particle system, replace the current unshaded glow-sprite material with a custom shader that fakes lighting response — note the sprites are camera-facing billboards, so they have no fixed world-space normal and true per-pixel PBR lighting won't behave like it would on a solid mesh. A practical approach: derive a fake per-pixel normal from the quad's local UV (as if each sprite were a small sphere/puff, same trick used for 2D lit particle effects) and light that against the directional light + a fixed 'glow color' bias toward the nebula core direction, rather than relying on the mesh's real (camera-facing) normal. Add soft-particle depth fade (compare particle depth to the depth buffer) so motes don't hard-clip through the boundary/decoration geometry, and add subtle per-particle brightness flicker via the color ramp or shader for sparkle. Verify visually via godot-mcp screenshots that the dust reads as lit rather than flat-glowing, from a couple of different camera angles (billboard lighting tricks can look wrong from some angles even when right from others)." + +- [ ] **Ship + ball visual pass** (Blender remodel — explicit models to make). The ship (`Game/objects/ship.tscn`) is *not* a single placeholder box — it's already 5 separate primitive `MeshInstance3D` parts (`Hull` a `BoxMesh`, `Nose` a `PrismMesh`, `Canopy` and `EngineGlow` boxes, `TailFin` a thin box), several with emission already (the cyan canopy, the orange `EngineGlow`, team-tinted `Nose`/`TailFin`). It's the same underlying critique as everything else, though: plain primitives, no greebling/panel detail, no imported model — just built further already than the write-up above implied. + Prompt: "Using the blender MCP, model an actual low-poly-but-detailed ship hull (greebled fuselage panel lines, a proper cockpit canopy shape, 1-2 engine nacelles with emissive exhaust glow) to replace `Game/objects/ship.tscn`'s current 5 primitive `MeshInstance3D` parts (`Hull`/`Nose`/`Canopy`/`TailFin`/`EngineGlow`), built the same procedural way as the arena decoration (bpy/bmesh primitives + bevels + inset/extrude detail — kept original, no borrowed/asset-store art). Two hard constraints this must respect: (1) **`scripts/ship.gd`'s `_apply_team_color()` recolors two child nodes by exact name, `get_node_or_null(\"Nose\")` and `get_node_or_null(\"TailFin\")`, at runtime per-team** (team 0 blue, team 1 orange, set via `game_mode.gd`'s `ship.team = team`) — either keep two `MeshInstance3D` parts of the new model literally named `Nose` and `TailFin` so this keeps working untouched, or update `_apply_team_color()`'s node-name list in the same change if the new model's parts are named differently; don't let this silently break. (2) **Do not touch `ship.tscn`'s single `CollisionShape3D` (`BoxShape3D`, size `1 x 1 x 4`) or the `RigidBody3D`'s `mass = 5.0`/`inertia = (1,1,1)`/`physics_material_override`** — those are exactly what the trained RL bots and flight feel are tuned against (per CLAUDE.md). Keep the new visual mesh's silhouette reasonably close to that `1 x 1 x 4` box so the ship doesn't visually stick out of or float inside its own collision bounds. Separately, audit the existing gold ball's material (`Game/assets/models/gold_ball.glb`/`.res`, referenced from `Game/objects/ball.tscn`'s `MeshInstance3D` with no scene-level material override, so its look currently comes entirely from whatever's embedded in the imported mesh) and upgrade it to a proper metallic/emissive-accent PBR look if it turns out flat/unshaded once the lighting pass above lands — don't assume it needs work without checking first. Verify both visually (screenshot, including a look at team-color recoloring in Match/Spectate) and via a headless run of `free_play.tscn`/`training.tscn` to confirm physics/RL behavior is unaffected."