Files
settled-reach/client/tests/test_camera_anchor.gd
T
jpmschweitzerandClaude Opus 4.6 e616af6eca test(client): sprint 15 validation tests — camera, UI framework, entities
Add test_smooth_camera_sprint15.gd (11 tests): lerp convergence,
teleport snap, D-015 fixed-north, constant range validation.
Add test_ui_framework_sprint15.gd (20 tests): D-049 z-layer hierarchy,
OQ-07 insert_active, #241 follow stub, Sprint 14 regression checks.
Update test_camera_anchor.gd and test_client_p3.gd for ENTITY_OFFSET_X/Y
split and manual lerp camera behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 14:10:06 +01:00

149 lines
4.7 KiB
GDScript

## Tests for camera anchor behavior — the camera must show the player's
## position from the very first rendered frame, with no visible drift from (0,0).
##
## These tests verify the full pipeline: SimBridge → GameState → Camera2D.
## The player starts at (10,10) in test mode, so the expected camera position
## is (10,10) * TILE_SIZE = (320, 320).
class_name TestCameraAnchor
extends GdUnitTestSuite
const EXPECTED_PLAYER_POS := Vector2(10, 10)
const EXPECTED_CAMERA_POS := Vector2(320, 320) # 10 * 32, 10 * 32
var _instance: Node = null
func before_test() -> void:
SimBridge.reset_test_state()
GameState.current_tick = 0
GameState.player_position = Vector2.ZERO
GameState.visible_entities = []
GameState.visible_tiles = []
GameState.visible_positions = {}
GameState.current_monologue = null
GameState.current_dialogue = null
func after_test() -> void:
if _instance and is_instance_valid(_instance):
_instance.queue_free()
_instance = null
# --- Pipeline precondition tests ---
func test_simbridge_connected_in_test_mode() -> void:
SimBridge.state = SimBridge.ConnectionState.DISCONNECTED
SimBridge.connect_to_sim()
assert_that(SimBridge.state).is_equal(SimBridge.ConnectionState.CONNECTED)
func test_poll_snapshot_returns_data_when_connected() -> void:
SimBridge.connect_to_sim()
var snapshot: Variant = SimBridge.poll_snapshot()
assert_that(snapshot).is_not_null()
assert_that(snapshot is Dictionary).is_true()
func test_snapshot_has_player_at_10_10() -> void:
var snap := SimBridge._test_snapshot()
var player_x: float = -1.0
var player_y: float = -1.0
for entity in snap.entities:
if entity.kind.variant == "Player":
player_x = entity.x
player_y = entity.y
break
assert_that(player_x).is_equal(10.0)
assert_that(player_y).is_equal(10.0)
func test_apply_snapshot_sets_player_position() -> void:
var snap := SimBridge._test_snapshot()
GameState.apply_snapshot(snap)
assert_that(GameState.player_position).is_equal(EXPECTED_PLAYER_POS)
# --- Camera anchor after _ready() ---
func test_camera_position_after_ready() -> void:
var scene := load("res://scenes/main.tscn")
_instance = scene.instantiate()
auto_free(_instance)
add_child(_instance)
var camera: Camera2D = _instance.get_node("Camera2D")
assert_that(camera.global_position).is_equal(EXPECTED_CAMERA_POS)
func test_camera_anchored_flag_after_ready() -> void:
var scene := load("res://scenes/main.tscn")
_instance = scene.instantiate()
auto_free(_instance)
add_child(_instance)
assert_that(_instance._camera_anchored).is_true()
func test_camera_smoothing_off_after_ready() -> void:
# Camera smoothing must be disabled during init to prevent lerp from (0,0).
# If this test fails, the camera will visibly drift from origin to player.
var scene := load("res://scenes/main.tscn")
_instance = scene.instantiate()
auto_free(_instance)
add_child(_instance)
var camera: Camera2D = _instance.get_node("Camera2D")
assert_that(camera.position_smoothing_enabled).is_false()
func test_camera_smoothing_stays_off_with_manual_lerp() -> void:
# #117: Manual lerp approach — Godot's built-in smoothing must stay OFF always.
# CAMERA_SMOOTHING_SPEED is used as the lerp weight, not Godot's position_smoothing_speed.
var scene := load("res://scenes/main.tscn")
_instance = scene.instantiate()
auto_free(_instance)
add_child(_instance)
_instance._process(0.016)
var camera: Camera2D = _instance.get_node("Camera2D")
assert_that(camera.position_smoothing_enabled).is_false()
# --- Camera behavior across frames ---
func test_camera_tracks_player_after_process() -> void:
var scene := load("res://scenes/main.tscn")
_instance = scene.instantiate()
auto_free(_instance)
add_child(_instance)
# Simulate one game frame
_instance._process(0.016)
var camera: Camera2D = _instance.get_node("Camera2D")
var expected := GameState.player_position * Constants.TILE_SIZE
assert_that(camera.global_position).is_equal(expected)
func test_camera_lerps_toward_player_movement() -> void:
# #117: With manual lerp, camera moves TOWARD player position (not snapping).
# After one 16ms frame the camera should be partway between old and new position.
var scene := load("res://scenes/main.tscn")
_instance = scene.instantiate()
auto_free(_instance)
add_child(_instance)
var camera: Camera2D = _instance.get_node("Camera2D")
var initial_pos := camera.global_position # anchored at (320, 320)
# Move player north via SimBridge test mode
SimBridge._test_input_queue.append("MoveNorth")
_instance._process(0.016)
var new_target := GameState.player_position * Constants.TILE_SIZE # (320, 288)
# Camera should have moved north (lower y) but NOT reached the target yet
assert_that(camera.global_position.y).is_less(initial_pos.y)
assert_that(camera.global_position.y).is_greater(new_target.y)