diff --git a/client/scripts/autoloads/audio_manager.gd b/client/scripts/autoloads/audio_manager.gd index 59fccf883..3fc0f64be 100644 --- a/client/scripts/autoloads/audio_manager.gd +++ b/client/scripts/autoloads/audio_manager.gd @@ -142,6 +142,8 @@ func play_loop(asset_key: String, bus: String = BUS_AMBIENT) -> AudioStreamPlaye return null stop_loop(asset_key) var loop_stream := stream.duplicate() as AudioStream + if loop_stream == null: + return null _enable_loop(loop_stream) var player := AudioStreamPlayer.new() player.stream = loop_stream @@ -175,10 +177,10 @@ func stop_all_loops() -> void: # No asset for Voice events in v0.1 — play_sound_event no-ops gracefully # (D-038: "renders as audio if asset exists, or visual indicator + monologue if not"). const SOUND_EVENT_ASSETS: Dictionary = { - "Footstep": "sfx_footstep_metal", - "FootstepWalk": "sfx_footstep_metal", - "FootstepCareful":"sfx_footstep_metal", - "FootstepCrouch": "sfx_footstep_metal", + "Footstep": "sfx_footstep_metal_walk", + "FootstepWalk": "sfx_footstep_metal_walk", + "FootstepCareful":"sfx_footstep_metal_walk", # D-053: same asset until stance-differentiated audio lands + "FootstepCrouch": "sfx_footstep_metal_walk", # D-053: same asset until stance-differentiated audio lands "FootstepSprint": "sfx_footstep_metal_run", "FootstepRun": "sfx_footstep_metal_run", } diff --git a/client/scripts/constants.gd b/client/scripts/constants.gd index 3cb7bc01e..1fe78761e 100644 --- a/client/scripts/constants.gd +++ b/client/scripts/constants.gd @@ -96,6 +96,9 @@ const FACING_INDICATOR_OFFSET: float = 14.0 # two columns of text comfortably, leaves world game visible alongside. const DIALOGUE_MAX_WIDTH: int = 640 +# Default camera zoom — used as fallback when get_camera_2d() returns null +const CAMERA_DEFAULT_ZOOM: Vector2 = Vector2(2.0, 2.0) + # #517: Implant UI font color grading — avoid pure white, project through a lens const IMPLANT_TEXT_COLOR: Color = Color("#E0F7FA") # Cyan-white — primary text const IMPLANT_TEXT_DIM: Color = Color("#9EBFC4") # Dimmed variant — secondary text diff --git a/client/scripts/main.gd b/client/scripts/main.gd index a68af9dd5..677c7ebf4 100644 --- a/client/scripts/main.gd +++ b/client/scripts/main.gd @@ -201,6 +201,8 @@ func _process(_delta: float) -> void: # D-018 #125: Play close-range sound events — fired once per snapshot tick. # Each event is passed to AudioManager.play_sound_event() for 2D positional playback # on the WorldSFX bus. Events with no registered asset are silently skipped (D-038). +# Consume-once: events are cleared after processing so they don't replay if +# _process runs again before the next server tick (D-009 multiplayer-safe pattern). func _play_close_sound_events() -> void: for evt in GameState.close_sound_events: if not evt is Dictionary or not evt.has("x") or not evt.has("y"): diff --git a/client/scripts/rendering/sound_indicator_renderer.gd b/client/scripts/rendering/sound_indicator_renderer.gd index 1ed633cbc..7d00f32b0 100644 --- a/client/scripts/rendering/sound_indicator_renderer.gd +++ b/client/scripts/rendering/sound_indicator_renderer.gd @@ -26,10 +26,10 @@ const EDGE_INSET: float = 20.0 # Pixels inward from viewport edge const ARROW_HALF: float = 7.0 # Half-width of arrowhead base const ARROW_LEN: float = 12.0 # Length from tip to base -# D-018/D-069 colors -const COLOR_NEUTRAL: Color = Color("#c8d0e0") # Generic / footstep -const COLOR_VOICE: Color = Color("#e8c547") # Speech / conversation -const COLOR_DANGER: Color = Color("#d45d5d") # Alert / threat / gunshot +# D-018/D-069 colors — sourced from Constants to prevent palette drift +const COLOR_NEUTRAL: Color = Constants.INSERT_COLOR_TEXT # Generic / footstep +const COLOR_VOICE: Color = Constants.ENTITY_COLOR_POI # Speech / conversation +const COLOR_DANGER: Color = Constants.ENTITY_COLOR_HOSTILE # Alert / threat / gunshot # Indicators: [{x, y, event_type, elapsed}] var _indicators: Array = [] @@ -89,7 +89,7 @@ func _draw() -> void: # the fog edge and don't clip to the physical screen border. var vp_size := get_viewport().get_visible_rect().size var cam := get_viewport().get_camera_2d() - var zoom := cam.zoom if cam else Vector2(2.0, 2.0) + var zoom := cam.zoom if cam else Constants.CAMERA_DEFAULT_ZOOM var half_extents: Vector2 = vp_size / (2.0 * zoom) for ind in _indicators: diff --git a/client/tests/test_audio_bus_routing.gd b/client/tests/test_audio_bus_routing.gd index fe930e083..f0df0eb2b 100644 --- a/client/tests/test_audio_bus_routing.gd +++ b/client/tests/test_audio_bus_routing.gd @@ -272,9 +272,9 @@ func test_audio_manager_sound_event_assets_registry_exists() -> void: assert_that(AudioManager.SOUND_EVENT_ASSETS.size()).is_greater(0) func test_audio_manager_footstep_maps_to_sfx_footstep_metal() -> void: - ## #125: "Footstep" event type → sfx_footstep_metal (D-038 asset). + ## #125: "Footstep" event type → sfx_footstep_metal_walk (D-038 asset). assert_that(AudioManager.SOUND_EVENT_ASSETS.has("Footstep")).is_true() - assert_that(AudioManager.SOUND_EVENT_ASSETS["Footstep"]).is_equal("sfx_footstep_metal") + assert_that(AudioManager.SOUND_EVENT_ASSETS["Footstep"]).is_equal("sfx_footstep_metal_walk") func test_audio_manager_footstep_sprint_maps_to_sfx_footstep_metal_run() -> void: ## #125: "FootstepSprint" → sfx_footstep_metal_run (D-038 faster footstep).