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>
This commit is contained in:
@@ -97,6 +97,20 @@ func test_camera_smoothing_off_after_ready() -> void:
|
||||
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:
|
||||
@@ -113,33 +127,22 @@ func test_camera_tracks_player_after_process() -> void:
|
||||
assert_that(camera.global_position).is_equal(expected)
|
||||
|
||||
|
||||
func test_camera_smoothing_reenabled_after_process() -> void:
|
||||
# After the first anchored frame, smoothing should be back on for gameplay.
|
||||
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_true()
|
||||
|
||||
|
||||
func test_camera_follows_player_movement() -> void:
|
||||
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
|
||||
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)
|
||||
|
||||
# Camera should have moved with the player
|
||||
assert_that(camera.global_position.y < initial_pos.y).is_true()
|
||||
assert_that(camera.global_position).is_equal(
|
||||
GameState.player_position * Constants.TILE_SIZE)
|
||||
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)
|
||||
|
||||
@@ -152,8 +152,8 @@ func test_entity_snap_on_first_appear() -> void:
|
||||
renderer.update_entities(entity)
|
||||
var node = renderer.entity_nodes[10]
|
||||
var expected := Vector2(
|
||||
floorf(8.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET,
|
||||
floorf(6.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET
|
||||
floorf(8.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET_X,
|
||||
floorf(6.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET_Y
|
||||
)
|
||||
assert_that(node.position).override_failure_message(
|
||||
"Entity should snap to position on first appear (no lerp)"
|
||||
@@ -179,8 +179,8 @@ func test_entity_lerp_moves_toward_target() -> void:
|
||||
renderer._process(0.016)
|
||||
var after_pos: Vector2 = node.position
|
||||
var target := Vector2(
|
||||
floorf(6.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET,
|
||||
floorf(5.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET
|
||||
floorf(6.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET_X,
|
||||
floorf(5.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET_Y
|
||||
)
|
||||
# Position should have moved toward target (x increased)
|
||||
assert_that(after_pos.x > start_pos.x).override_failure_message(
|
||||
@@ -206,8 +206,8 @@ func test_entity_lerp_converges_within_300ms() -> void:
|
||||
"kind": {"variant": "Npc", "data": null}}]
|
||||
renderer.update_entities(entity_moved)
|
||||
var target := Vector2(
|
||||
floorf(7.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET,
|
||||
floorf(5.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET
|
||||
floorf(7.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET_X,
|
||||
floorf(5.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET_Y
|
||||
)
|
||||
# Simulate 0.3s at 60fps (18 frames × 0.016s ≈ 0.288s)
|
||||
for i in 20:
|
||||
@@ -298,8 +298,8 @@ func test_lerp_weight_increases_with_delta() -> void:
|
||||
var small_progress: float = small_node.position.x - small_start
|
||||
# Reset position for large delta test
|
||||
small_node.position = Vector2(
|
||||
floorf(5.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET,
|
||||
floorf(5.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET
|
||||
floorf(5.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET_X,
|
||||
floorf(5.0) * Constants.TILE_SIZE + EntityRenderer.ENTITY_OFFSET_Y
|
||||
)
|
||||
# Large delta step
|
||||
var large_start: float = small_node.position.x
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
## Sprint 15 — Smooth camera movement tests (#117)
|
||||
## Validates exponential lerp, teleport snap, and configurable smoothing.
|
||||
## Spec: D-015 (camera locked, fixed-north), #117 (interpolated tracking).
|
||||
class_name TestSmoothCameraSprint15
|
||||
extends GdUnitTestSuite
|
||||
|
||||
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
|
||||
|
||||
|
||||
# --- Configurable smoothing constant ---
|
||||
|
||||
func test_camera_smoothing_speed_constant_defined() -> void:
|
||||
# #117: CAMERA_SMOOTHING_SPEED must be declared in Constants (configurable).
|
||||
assert_that(Constants.CAMERA_SMOOTHING_SPEED > 0.0).is_true()
|
||||
|
||||
|
||||
func test_camera_smoothing_speed_constant_reasonable() -> void:
|
||||
# #117: Speed should produce smooth-but-responsive feel (2.0–20.0 range).
|
||||
assert_that(
|
||||
Constants.CAMERA_SMOOTHING_SPEED >= 2.0 and Constants.CAMERA_SMOOTHING_SPEED <= 20.0
|
||||
).is_true()
|
||||
|
||||
|
||||
# --- Manual lerp, no Godot built-in smoothing ---
|
||||
|
||||
func test_godot_smoothing_disabled_at_ready() -> void:
|
||||
# #117: Godot's built-in Camera2D smoothing must be OFF (manual lerp replaces it).
|
||||
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_godot_smoothing_stays_off_after_frames() -> void:
|
||||
# #117: Smoothing must NOT be re-enabled at any point — manual lerp only.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
for i in range(5):
|
||||
_instance._process(0.016)
|
||||
|
||||
var camera: Camera2D = _instance.get_node("Camera2D")
|
||||
assert_that(camera.position_smoothing_enabled).is_false()
|
||||
|
||||
|
||||
# --- Interpolated tracking (no snap) ---
|
||||
|
||||
func test_camera_lerps_not_snaps_on_player_move() -> void:
|
||||
# #117: When player moves, camera should lerp (not snap) to new position.
|
||||
# After 1 frame at ~60fps, camera should be partway there — not at target.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
var camera: Camera2D = _instance.get_node("Camera2D")
|
||||
var start_y := camera.global_position.y # anchored at player (10,10) → 320px
|
||||
|
||||
SimBridge._test_input_queue.append("MoveNorth")
|
||||
_instance._process(0.016)
|
||||
|
||||
# Player moved to (10,9) → target_y = 288. Camera should be between 288 and 320.
|
||||
var target_y: float = GameState.player_position.y * Constants.TILE_SIZE
|
||||
assert_that(camera.global_position.y < start_y).is_true()
|
||||
assert_that(camera.global_position.y > target_y).is_true()
|
||||
|
||||
|
||||
func test_camera_converges_to_player_over_multiple_frames() -> void:
|
||||
# #117: After enough frames the camera should be within 1px of target.
|
||||
# At LERP_SPEED=8: ~95% convergence in 0.25s, >99% in 0.5s.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
SimBridge._test_input_queue.append("MoveNorth")
|
||||
_instance._process(0.016) # trigger the move, get new player position
|
||||
|
||||
var target := GameState.player_position * Constants.TILE_SIZE
|
||||
|
||||
# Run 40 frames (~0.67s at 60fps) — well past convergence for any speed ≥ 2.0
|
||||
for i in range(40):
|
||||
_instance._process(0.016)
|
||||
|
||||
var camera: Camera2D = _instance.get_node("Camera2D")
|
||||
var dist := camera.global_position.distance_to(target)
|
||||
assert_that(dist < 1.0).is_true()
|
||||
|
||||
|
||||
func test_camera_stationary_player_no_drift() -> void:
|
||||
# #117: When player is stationary, camera should not drift (lerp to same point).
|
||||
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
|
||||
|
||||
# Run several frames with no movement
|
||||
for i in range(10):
|
||||
_instance._process(0.016)
|
||||
|
||||
# Camera should still be at anchored position (target = same point)
|
||||
assert_that(camera.global_position).is_equal(initial_pos)
|
||||
|
||||
|
||||
# --- Teleport snap ---
|
||||
|
||||
func test_teleport_snaps_camera_immediately() -> void:
|
||||
# #117: _teleport_in_progress causes camera to snap (not lerp) in the same frame.
|
||||
# Manually displace camera, set the flag, call _process — camera should snap to target.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
var camera: Camera2D = _instance.get_node("Camera2D")
|
||||
|
||||
# Displace camera from its anchored position
|
||||
camera.global_position = Vector2(0, 0)
|
||||
# Set teleport flag — next _process() should snap to player target
|
||||
_instance._camera_anchored = true
|
||||
_instance._teleport_in_progress = true
|
||||
|
||||
_instance._process(0.016)
|
||||
|
||||
# Camera must now be exactly at player position (snapshot puts player at 10,10 → 320,320)
|
||||
var expected := GameState.player_position * Constants.TILE_SIZE
|
||||
assert_that(camera.global_position).is_equal(expected)
|
||||
|
||||
|
||||
func test_teleport_flag_cleared_after_snap() -> void:
|
||||
# #117: _teleport_in_progress must be false after the snap frame.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
_instance._camera_anchored = true
|
||||
_instance._teleport_in_progress = true
|
||||
_instance._process(0.016)
|
||||
|
||||
assert_that(_instance._teleport_in_progress).is_false()
|
||||
|
||||
|
||||
func test_camera_resumes_lerp_after_teleport() -> void:
|
||||
# #117: Frame after teleport snap must resume lerp (not continue snapping).
|
||||
# After teleport flag clears, any position delta produces lerp movement.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
# Frame 1: teleport snap — camera displaced, flag set, expect snap
|
||||
var camera: Camera2D = _instance.get_node("Camera2D")
|
||||
camera.global_position = Vector2(0, 0)
|
||||
_instance._camera_anchored = true
|
||||
_instance._teleport_in_progress = true
|
||||
_instance._process(0.016)
|
||||
# After snap: camera at player position (10,10) = (320, 320)
|
||||
var post_snap_y := camera.global_position.y
|
||||
|
||||
# Frame 2: player moves north — camera should lerp, not snap
|
||||
SimBridge._test_input_queue.append("MoveNorth")
|
||||
_instance._process(0.016)
|
||||
|
||||
var new_target_y: float = GameState.player_position.y * Constants.TILE_SIZE
|
||||
# Camera must be between snap position and new target (lerping, not snapping)
|
||||
assert_that(camera.global_position.y < post_snap_y).is_true()
|
||||
assert_that(camera.global_position.y > new_target_y).is_true()
|
||||
# Teleport flag must not be re-set by normal movement
|
||||
assert_that(_instance._teleport_in_progress).is_false()
|
||||
|
||||
|
||||
# --- D-015: Fixed-north camera ---
|
||||
|
||||
func test_camera_no_rotation() -> void:
|
||||
# D-015: Camera must be fixed-north in v0.1 — no rotation regardless of facing.
|
||||
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.rotation).is_equal(0.0)
|
||||
|
||||
_instance._process(0.016)
|
||||
assert_that(camera.rotation).is_equal(0.0)
|
||||
@@ -0,0 +1 @@
|
||||
uid://s15smoothcam1
|
||||
@@ -0,0 +1,272 @@
|
||||
## Sprint 15 — Basic UI framework validation tests (#74)
|
||||
## Validates HUD structure, z-layer hierarchy, insert_active control,
|
||||
## and monologue display wiring per D-049, D-056, D-057, D-061, OQ-07.
|
||||
class_name TestUIFrameworkSprint15
|
||||
extends GdUnitTestSuite
|
||||
|
||||
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
|
||||
GameState.insert_active = true
|
||||
|
||||
|
||||
func after_test() -> void:
|
||||
if _instance and is_instance_valid(_instance):
|
||||
_instance.queue_free()
|
||||
_instance = null
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# D-049: Z-layer scene hierarchy
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_insert_overlay_is_canvas_layer_10() -> void:
|
||||
# D-049: InsertOverlay = conceptual layer 6 (insert scope) = CanvasLayer 10.
|
||||
# Constants.CANVAS_INSERT must match.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
var insert_overlay: CanvasLayer = _instance.get_node("InsertOverlay")
|
||||
assert_that(insert_overlay).is_not_null()
|
||||
assert_that(insert_overlay.layer).is_equal(Constants.CANVAS_INSERT)
|
||||
|
||||
|
||||
func test_ui_layer_is_canvas_layer_20() -> void:
|
||||
# D-049: UILayer = conceptual layer 7 (UI/monologue scope) = CanvasLayer 20.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
var ui_layer: CanvasLayer = _instance.get_node("UILayer")
|
||||
assert_that(ui_layer).is_not_null()
|
||||
assert_that(ui_layer.layer).is_equal(Constants.CANVAS_UI)
|
||||
|
||||
|
||||
func test_modal_layer_is_canvas_layer_30() -> void:
|
||||
# D-049: ModalLayer = pause/inventory modal scope = CanvasLayer 30.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
var modal_layer: CanvasLayer = _instance.get_node("ModalLayer")
|
||||
assert_that(modal_layer).is_not_null()
|
||||
assert_that(modal_layer.layer).is_equal(Constants.CANVAS_MODAL)
|
||||
|
||||
|
||||
func test_ui_layer_above_insert_overlay() -> void:
|
||||
# D-049: UILayer (20) must render above InsertOverlay (10).
|
||||
assert_that(Constants.CANVAS_UI).is_greater(Constants.CANVAS_INSERT)
|
||||
|
||||
|
||||
func test_modal_layer_above_ui_layer() -> void:
|
||||
# D-049: ModalLayer (30) must render above UILayer (20).
|
||||
assert_that(Constants.CANVAS_MODAL).is_greater(Constants.CANVAS_UI)
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# D-049: Required nodes exist in correct layers
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_monologue_display_exists_in_ui_layer() -> void:
|
||||
# D-049 / #117 / #414: MonologueDisplay must be in UILayer (layer 7).
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("UILayer/MonologueDisplay")).is_not_null()
|
||||
|
||||
|
||||
func test_stance_indicator_exists_in_ui_layer() -> void:
|
||||
# D-053: StanceIndicator must be in UILayer (layer 7), top-right, color-coded.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("UILayer/StanceIndicator")).is_not_null()
|
||||
|
||||
|
||||
func test_minimap_placeholder_exists_in_ui_layer() -> void:
|
||||
# D-013: Minimap/insert placeholder must be in UILayer (not implemented yet).
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("UILayer/Minimap")).is_not_null()
|
||||
|
||||
|
||||
func test_hud_exists_in_ui_layer() -> void:
|
||||
# D-049: HUD must be in UILayer.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("UILayer/HUD")).is_not_null()
|
||||
|
||||
|
||||
func test_interaction_list_exists_in_insert_overlay() -> void:
|
||||
# D-057: InteractionList must be in InsertOverlay (z-layer 6).
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("InsertOverlay/InteractionList")).is_not_null()
|
||||
|
||||
|
||||
func test_dialogue_box_exists_in_insert_overlay() -> void:
|
||||
# D-061: DialogueBox must be in InsertOverlay (z-layer 6).
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("InsertOverlay/DialogueBox")).is_not_null()
|
||||
|
||||
|
||||
func test_world_radial_exists_in_insert_overlay() -> void:
|
||||
# D-058: WorldRadial must be in InsertOverlay (z-layer 6).
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("InsertOverlay/WorldRadial")).is_not_null()
|
||||
|
||||
|
||||
func test_cursor_renderer_exists_in_ui_layer() -> void:
|
||||
# D-056: CursorRenderer must be in UILayer (topmost, z-layer 7).
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("UILayer/CursorRenderer")).is_not_null()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# OQ-07 / D-056: insert_active controls z-layer 6 visibility
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_gamestate_insert_active_defaults_true() -> void:
|
||||
# OQ-07: v0.1 characters all have inserts — default true.
|
||||
assert_that(GameState.insert_active).is_true()
|
||||
|
||||
|
||||
func test_insert_active_propagates_on_process() -> void:
|
||||
# OQ-07 (#522): After apply_snapshot with insert_active=false,
|
||||
# the next _process() call must propagate the state to z-layer-6 nodes.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
# Inject a snapshot with insert_active = false
|
||||
var snap := SimBridge._test_snapshot()
|
||||
snap["insert_active"] = false
|
||||
GameState.apply_snapshot(snap)
|
||||
assert_that(GameState.insert_active).is_false()
|
||||
|
||||
|
||||
func test_insert_active_true_from_snapshot() -> void:
|
||||
# OQ-07: Snapshot with insert_active=true keeps GameState in default-on state.
|
||||
var snap := SimBridge._test_snapshot()
|
||||
snap["insert_active"] = true
|
||||
GameState.apply_snapshot(snap)
|
||||
assert_that(GameState.insert_active).is_true()
|
||||
|
||||
|
||||
func test_insert_active_missing_field_defaults_true() -> void:
|
||||
# OQ-07: Old servers without insert_active field must not disable the insert.
|
||||
var snap := SimBridge._test_snapshot()
|
||||
snap.erase("insert_active")
|
||||
GameState.apply_snapshot(snap)
|
||||
assert_that(GameState.insert_active).is_true()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# #241 stub: follow_target_id for entity sprite system
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_follow_target_id_stub_exists() -> void:
|
||||
# #72 / #241: follow_target_id stub must exist on GameState with default -1.
|
||||
# Populated by server ticket #241 (Follow verb) when it lands.
|
||||
assert_that(GameState.follow_target_id).is_equal(-1)
|
||||
|
||||
|
||||
func test_follow_target_id_is_negative_one_by_default() -> void:
|
||||
# #72: -1 means "not following" — client #72 checks this for entity highlight.
|
||||
SimBridge.reset_test_state()
|
||||
GameState.apply_snapshot(SimBridge._test_snapshot())
|
||||
# Server doesn't send follow_target_id yet — must stay -1 after snapshot
|
||||
assert_that(GameState.follow_target_id).is_equal(-1)
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Monologue display wiring (#414)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_monologue_display_receives_first_tick_monologue() -> void:
|
||||
# #414 / #74: MonologueDisplay must show monologue from tick 1 test snapshot.
|
||||
# Verifies the wiring: GameState.current_monologue → main.gd → MonologueDisplay.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
# Tick 1 snapshot has a monologue (SimBridge test mode)
|
||||
# _ready() consumes tick 0 (no monologue). _process() here gets tick 1.
|
||||
_instance._process(0.016)
|
||||
|
||||
# If monologue_display received it, current_monologue is cleared (consume-once)
|
||||
assert_that(GameState.current_monologue).is_null()
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Regression: Sprint 14 integration proofs (D-030 regression markers)
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
func test_fog_group_exists_in_world() -> void:
|
||||
# Sprint 14 regression: fog rendering must still be present after sprint 15 changes.
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("World/FogGroup")).is_not_null()
|
||||
|
||||
|
||||
func test_floor_tiles_in_fog_group() -> void:
|
||||
# Sprint 14 regression: FloorTiles must be in FogGroup (D-049 z:0).
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("World/FogGroup/FloorTiles")).is_not_null()
|
||||
|
||||
|
||||
func test_entities_in_ysort_group() -> void:
|
||||
# Sprint 14 regression: Entities must be in YSortGroup for y-sort ordering (D-049 z:100).
|
||||
var scene := load("res://scenes/main.tscn")
|
||||
_instance = scene.instantiate()
|
||||
auto_free(_instance)
|
||||
add_child(_instance)
|
||||
|
||||
assert_that(_instance.get_node_or_null("World/FogGroup/YSortGroup/Entities")).is_not_null()
|
||||
@@ -0,0 +1 @@
|
||||
uid://s15uiframe001
|
||||
Reference in New Issue
Block a user