diff --git a/Game/scripts/arena_boundary.gd b/Game/scripts/arena_boundary.gd index 32de28ce..de7446d4 100644 --- a/Game/scripts/arena_boundary.gd +++ b/Game/scripts/arena_boundary.gd @@ -98,6 +98,9 @@ const DECK_SHADER_PATH := "res://shaders/arena_deck.gdshader" # _process() drives. Both stay null in headless runs, which never render. var _shell: MeshInstance3D var _field_material: ShaderMaterial +# Cached active camera for _process(), mirroring ship_camera.gd's _get_ball() +# pattern so the viewport lookup isn't repeated every frame. +var _camera: Camera3D func _ready() -> void: @@ -153,12 +156,26 @@ func _process(_delta: float) -> void: # individually. Collision is untouched. if _field_material == null: return - var camera := get_viewport().get_camera_3d() + var camera := _get_camera() if camera == null: return # headless (RL/CI) has no camera _field_material.set_shader_parameter("camera_local_pos", to_local(camera.global_position)) +# Caches the viewport's active camera; a plain is_instance_valid revalidation +# is enough because exactly one ship_camera_rig (Camera3D) is spawned per +# game-mode run (GameMode.spawn_camera_rig, called once each from +# free_play.gd/match_mode.gd/spectate_mode.gd) and never re-spawned mid-match +# — there's no active-camera-switch scenario today. ArenaBoundary itself is +# destroyed/recreated per scene change, so the cache naturally resets with it; +# there's no stale-cache-across-scenes concern. Revisit this if split-screen +# or multiplayer camera-switching lands (see TODO.md's multiplayer section). +func _get_camera() -> Camera3D: + if not is_instance_valid(_camera): + _camera = get_viewport().get_camera_3d() + return _camera + + # --- shared layout ----------------------------------------------------------- # Consumed by both the collider rings and the visual shell, so the two can # never end up describing different geometry. diff --git a/TODO.md b/TODO.md index 36ce1c23..1ba511f8 100644 --- a/TODO.md +++ b/TODO.md @@ -28,7 +28,6 @@ Bugs found in an adversarial review. None are gameplay- or physics-affecting, so - [ ] Shared per-team materials instead of `Ship._apply_team_color()` allocating a fresh `StandardMaterial3D` and assigning it as `material_override` (and running at least twice per ship — once from `_ready`, once from the `team` setter). Removes the allocation and lets same-team ships batch. - [ ] Merge each goal's visuals into one `ArrayMesh` with a hull surface and a net surface — currently 11 `MeshInstance3D`s and 4 materials per goal, ~22 draw calls for a static prop. `arena_boundary.gd:333` already demonstrates the `SurfaceTool` technique in this codebase. - [ ] Merge the four non-tinted ship meshes (Hull/Canopy/EngineGlowL/R) into one — 6 draw calls per ship down to 3. Irrelevant at 1v1; 36 calls before VFX at 3v3. -- [ ] Cache the camera in `ArenaBoundary._process` instead of a `get_viewport().get_camera_3d()` tree lookup every frame (`ship_camera.gd` already caches the ball this way). - [ ] Name collision layers in `project.godot` and assign them — nothing configures `collision_layer`/`collision_mask` today, so every body tests against every other. - [ ] Measure `nebula_dust.gdshader`'s per-fragment depth-texture sample across 500 large soft billboards before adding more particle work. - [ ] Bake `ArenaBoundary`'s ~160 runtime-generated `CollisionShape3D` nodes into the scene. Costs a load hitch on every arena entry and repeats in every parallel headless training env. **Blocked on the trained-bot decision below** — `arena_boundary.gd:235` notes this geometry is what the shipped policies were fitted against, so the bake must be verified byte-identical.