diff --git a/client/scripts/autoloads/input_mapper.gd b/client/scripts/autoloads/input_mapper.gd index 74cbdda5d..10e90b8b8 100644 --- a/client/scripts/autoloads/input_mapper.gd +++ b/client/scripts/autoloads/input_mapper.gd @@ -120,6 +120,13 @@ func flush_queue() -> Array[Dictionary]: return queue +## Reset facing state to default (North). Use in tests per D-030 testability. +func reset_facing_state() -> void: + facing_angle = -PI / 2.0 + facing_octant = "North" + _last_sent_octant = "North" + + # D-054: Compute facing angle from mouse position relative to player screen position. # Uses viewport canvas transform to convert world coords to screen coords. # Intentional coupling: reads GameState.player_position directly — InputMapper is an diff --git a/client/scripts/checklist/checklist_evaluator.gd b/client/scripts/checklist/checklist_evaluator.gd index ca4e8fe71..2f51187ca 100644 --- a/client/scripts/checklist/checklist_evaluator.gd +++ b/client/scripts/checklist/checklist_evaluator.gd @@ -160,6 +160,9 @@ func _evaluate_condition(cond: Dictionary) -> bool: return false +## x/y and radius are in tile coordinates (matching GameState.player_position), +## not pixels. D-066 dual-scale: YAML authors write tile coords, pixel conversion +## happens only at render time. func _eval_player_near(cond: Dictionary) -> bool: var tx: float = float(cond.get("x", 0)) var ty: float = float(cond.get("y", 0)) diff --git a/client/tests/test_client_p3.gd b/client/tests/test_client_p3.gd index 7fc79a39c..faffb3dd8 100644 --- a/client/tests/test_client_p3.gd +++ b/client/tests/test_client_p3.gd @@ -267,7 +267,7 @@ func test_facing_indicator_rotation_matches_input_mapper_angle() -> void: -PI / 2.0: 0.0, # North 0.0: PI / 2.0, # East PI / 2.0: PI, # South - PI: 3.0 * PI / 2.0, # West + PI: -PI / 2.0, # West (3PI/2 normalized to -PI/2 by Godot) } for angle in angles: InputMapper.facing_angle = angle diff --git a/client/tests/test_input_mapper_facing.gd b/client/tests/test_input_mapper_facing.gd index 9aea61b72..24b3b3e56 100644 --- a/client/tests/test_input_mapper_facing.gd +++ b/client/tests/test_input_mapper_facing.gd @@ -116,7 +116,4 @@ func test_wasd_strafe_right_facing_west() -> void: func after_test() -> void: - # Reset facing to default - InputMapper.facing_angle = -PI / 2.0 - InputMapper.facing_octant = "North" - InputMapper._last_sent_octant = "North" + InputMapper.reset_facing_state() diff --git a/client/tests/test_rendering.gd b/client/tests/test_rendering.gd index a0751dc60..f19d9ff96 100644 --- a/client/tests/test_rendering.gd +++ b/client/tests/test_rendering.gd @@ -310,9 +310,9 @@ func test_entity_renderer_facing_indicator_rotation_accuracy() -> void: 0.0: PI / 2.0, # East PI / 4.0: 3.0 * PI / 4.0, # Southeast PI / 2.0: PI, # South - 3.0 * PI / 4.0: 5.0 * PI / 4.0, # Southwest - PI: 3.0 * PI / 2.0, # West - -3.0 * PI / 4.0: 7.0 * PI / 4.0, # Northwest (note: -PI/2 wraps) + 3.0 * PI / 4.0: -3.0 * PI / 4.0, # Southwest (Godot normalizes to (-PI, PI]) + PI: -PI / 2.0, # West (3PI/2 normalized to -PI/2) + -3.0 * PI / 4.0: -PI / 4.0, # Northwest (-3PI/4 + PI/2 = -PI/4) } renderer.update_entities(_test_entities_v2) var player_node = renderer.entity_nodes[1] diff --git a/client/ui/world_radial.gd b/client/ui/world_radial.gd index b7193c5e1..5451efc95 100644 --- a/client/ui/world_radial.gd +++ b/client/ui/world_radial.gd @@ -5,8 +5,8 @@ extends Control ## Renders on InsertOverlay (CanvasLayer 10). ## Drag-release for power users, click-click for newcomers. ## Insert spoke sends PauseSimulation on activate (#518/D-058). -## Selecting any other spoke (or re-opening the radial) calls deactivate_insert() -## which sends ResumeSimulation. Pause/resume are idempotent. +## Selecting any non-Insert spoke, cancelling, or pressing Escape calls +## deactivate_insert() which sends ResumeSimulation. Pause/resume are idempotent. signal spoke_selected(spoke_name: String) @@ -37,6 +37,7 @@ var _origin: Vector2 = Vector2.ZERO var _hovered_spoke: int = Spoke.NONE var _drag_mode: bool = false var _insert_active: bool = false +var _cached_font: Font = null func _ready() -> void: @@ -45,6 +46,7 @@ func _ready() -> void: size = custom_minimum_size visible = false mouse_filter = Control.MOUSE_FILTER_STOP + _cached_font = get_theme_default_font() func _input(event: InputEvent) -> void: @@ -192,10 +194,9 @@ func _draw() -> void: # Label var label: String = SPOKE_NAMES.get(spoke, "") - var font := get_theme_default_font() - var text_size := font.get_string_size(label, HORIZONTAL_ALIGNMENT_CENTER, -1, 11) + var text_size := _cached_font.get_string_size(label, HORIZONTAL_ALIGNMENT_CENTER, -1, 11) var label_pos := icon_center + Vector2(-text_size.x / 2.0, ICON_SIZE + 14.0) - draw_string(font, label_pos, label, HORIZONTAL_ALIGNMENT_LEFT, -1, 11, color) + draw_string(_cached_font, label_pos, label, HORIZONTAL_ALIGNMENT_LEFT, -1, 11, color) func _draw_spoke_icon(spoke: int, center: Vector2, color: Color) -> void: