diff --git a/Game/scripts/player_ship_controller.gd b/Game/scripts/player_ship_controller.gd index ea1fce7c..4a3c45ca 100644 --- a/Game/scripts/player_ship_controller.gd +++ b/Game/scripts/player_ship_controller.gd @@ -4,46 +4,37 @@ extends ShipController # Drives a Ship from the local player's input actions (see project.godot # [input] and FLIGHT_MANUAL.md). +var _action := ShipAction.new() + func get_action() -> ShipAction: - var action := ShipAction.new() + # Full overwrite per axis (not +=/-=): _action is reused across ticks, so + # fields must not depend on starting from a fresh Vector3.ZERO each call. # Forward/Backward thrust (main engines) - if Input.is_action_pressed("move_forward"): - action.thrust.z += 1.0 - if Input.is_action_pressed("move_back"): - action.thrust.z -= 1.0 + _action.thrust.z = (1.0 if Input.is_action_pressed("move_forward") else 0.0) \ + - (1.0 if Input.is_action_pressed("move_back") else 0.0) # Strafe thrusters (left/right) - if Input.is_action_pressed("move_left"): - action.thrust.x -= 1.0 - if Input.is_action_pressed("move_right"): - action.thrust.x += 1.0 + _action.thrust.x = (1.0 if Input.is_action_pressed("move_right") else 0.0) \ + - (1.0 if Input.is_action_pressed("move_left") else 0.0) # Vertical thrusters (up/down) - if Input.is_action_pressed("move_up"): - action.thrust.y += 1.0 - if Input.is_action_pressed("move_down"): - action.thrust.y -= 1.0 + _action.thrust.y = (1.0 if Input.is_action_pressed("move_up") else 0.0) \ + - (1.0 if Input.is_action_pressed("move_down") else 0.0) # Yaw (turn left/right around Y axis) - if Input.is_action_pressed("turn_left"): - action.rotation.y += 1.0 - if Input.is_action_pressed("turn_right"): - action.rotation.y -= 1.0 + _action.rotation.y = (1.0 if Input.is_action_pressed("turn_left") else 0.0) \ + - (1.0 if Input.is_action_pressed("turn_right") else 0.0) # Pitch (nose up/down around X axis) - if Input.is_action_pressed("pitch_up"): - action.rotation.x -= 1.0 - if Input.is_action_pressed("pitch_down"): - action.rotation.x += 1.0 + _action.rotation.x = (1.0 if Input.is_action_pressed("pitch_down") else 0.0) \ + - (1.0 if Input.is_action_pressed("pitch_up") else 0.0) # Roll (bank left/right around Z axis) - if Input.is_action_pressed("roll_left"): - action.rotation.z += 1.0 - if Input.is_action_pressed("roll_right"): - action.rotation.z -= 1.0 + _action.rotation.z = (1.0 if Input.is_action_pressed("roll_left") else 0.0) \ + - (1.0 if Input.is_action_pressed("roll_right") else 0.0) - action.turbo = Input.is_action_pressed("turbo") + _action.turbo = Input.is_action_pressed("turbo") - return action + return _action diff --git a/Game/scripts/ship.gd b/Game/scripts/ship.gd index 7cbc0cd1..891f6528 100644 --- a/Game/scripts/ship.gd +++ b/Game/scripts/ship.gd @@ -79,6 +79,7 @@ static func _get_team_material(team: int) -> StandardMaterial3D: var controller: ShipController var _current_action: ShipAction = ShipAction.new() +var _inert_action: ShipAction = ShipAction.new() var _boundary: ArenaBoundary # Instrument signals for efficient data distribution @@ -194,7 +195,7 @@ func _has_telemetry_listeners() -> bool: func _integrate_forces(state): # One action per physics tick, pulled from the controller (deterministic) - _current_action = controller.get_action() if controller else ShipAction.new() + _current_action = controller.get_action() if controller else _inert_action # === TRANSLATION (Movement) === apply_thruster_forces(state, _current_action) diff --git a/TODO.md b/TODO.md index 3cc0717b..c10eb588 100644 --- a/TODO.md +++ b/TODO.md @@ -14,13 +14,7 @@ The training pipeline is built — see `TRAINING.md` (self-play PPO via the vend Bugs found in an adversarial review. None are gameplay- or physics-affecting, so all are safe to land against the current `Game/bots/` checkpoints. -- [x] `VideoSettings.apply_to_environment()` (`scripts/video_settings.gd`) compounds on every arena load: `env.glow_intensity *= glow_scale` mutates an `Environment` that is a `[sub_resource]` of the arena scene, and Godot shares sub-resources across instantiations of a cached `PackedScene`. Glow at 50% becomes 25% then 12.5% across repeat entries. Fix by duplicating the Environment in `Arena._ready()` (`scripts/arena.gd`). Regression test: set glow to 50%, enter/leave Free Play three times, confirm it's still 50%. -- [x] Collapse the four disagreeing team palettes into one source of truth — `ship.gd`, `HUDController.gd` and `goal.gd` each declare `TEAM_COLORS`, `arena_boundary.gd` exports `team0_tint`/`team1_tint`, and `arena_deck.gdshader` defaults to a fifth pair. Three of them disagree, so nose, goal rim, end zone and scoreboard are all different blues. - [ ] Goal scoring volume (3.5 x 1.5, `objects/goal.tscn`) is smaller than the drawn mouth (3.7 x 1.65, `ArenaBoundary.GOAL_APERTURE_*`) — a ball crossing the visible edge doesn't score. Derive the aperture constants from the goal's collision shape, the way `goal.gd:53` already measures its own visuals. -- [x] `match_mode.gd`: full time can fire mid-kickoff-countdown, and the stalled coroutine resumes into the dying scene (can re-emit `kickoff_countdown` / unfreeze bodies for a frame). Guard `_run_kickoff_countdown` with a match-over flag. -- [x] `match_mode.gd` emits `timer_updated` every frame for a value that changes once a second; the HUD re-formats and re-shapes the label each time. Emit only on change, matching `ship.gd`'s threshold-gated telemetry discipline. -- [x] `HUDController` binds to `get_first_node_in_group("ship")` in a group that always has 2+ members — works only because the player ship happens to spawn first. Have the game mode hand the HUD its target ship. -- [ ] Reuse a member `ShipAction` in `ship.gd` (controllerless path) and `player_ship_controller.gd` instead of allocating one per physics tick; `ai_ship_controller.gd` already does this correctly. - [ ] Delete the duplicate 1 MB texture — `assets/textures/planet_surface.png` and `assets/models/nebula_planet_planet_surface.png` are byte-identical. ## Performance