Files
settled-reach/client/tests/test_game_state_sprint20.gd
T
jpmschweitzerandClaude Opus 4.6 260eefcdd1 refactor(client): decompose main.gd and game_state.gd god objects
Extract SnapshotHandler (static snapshot parsing), SnapshotConsumers
(non-dialogue consumers + audio handlers), and DialogueCoordinator
(dialogue consumers + signal handlers) as class_name scripts.

main.gd: 28KB → 13KB. game_state.gd: 20KB → 8.5KB.
Autoload parse-order safety maintained via load() inline pattern.

Ticket: #775

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:59:34 +02:00

165 lines
6.1 KiB
GDScript

## Sprint 20 #557: GameState.apply_snapshot() refactor tests.
##
## Verifies D-020 compliance: apply_snapshot() reads server-authoritative values
## for stationary_ticks and zone_id directly from the snapshot when present,
## and degrades gracefully when the server has not yet added these fields.
##
## Does NOT replace test_game_state.gd or test_snapshot_zone_id.gd — those cover
## existing behaviour. This file covers the new code paths added in Sprint 20.
##
## D-030: fixture-based, server-free, no subprocess required.
class_name TestGameStateSprint20
extends GdUnitTestSuite
func before_each() -> void:
GameState.stationary_ticks = 0
SnapshotHandler._prev_player_position = Vector2(-1e9, -1e9)
GameState.current_zone_id = ""
GameState.player_position = Vector2.ZERO
GameState.visible_tiles = []
GameState.visible_positions = {}
GameState.visibility_sectors = {}
# -- stationary_ticks: server-authoritative path (D-020) ----------------------
func test_stationary_ticks_reads_server_value_when_present() -> void:
# When snapshot includes stationary_ticks, apply_snapshot() must use the
# server value directly without client-side accumulation (D-020).
var snapshot := {
"tick": 5,
"entities": [
{"entity_id": 1, "x": 10.0, "y": 10.0, "z": 0, "kind": {"variant": "Player", "data": null}},
],
"stationary_ticks": 42,
}
GameState.apply_snapshot(snapshot)
assert_int(GameState.stationary_ticks).override_failure_message(
"apply_snapshot() must read stationary_ticks=42 from snapshot (D-020)"
).is_equal(42)
func test_stationary_ticks_server_value_does_not_accumulate() -> void:
# Server-sent value must be assigned directly — NOT added to existing value.
# Two calls with stationary_ticks=10 must yield 10, not 20.
var snapshot := {
"tick": 1,
"entities": [
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
],
"stationary_ticks": 10,
}
GameState.apply_snapshot(snapshot)
GameState.apply_snapshot(snapshot)
assert_int(GameState.stationary_ticks).override_failure_message(
"Server value must be assigned directly, not accumulated (D-020)"
).is_equal(10)
func test_stationary_ticks_server_can_reset_to_zero() -> void:
# Server sends 0 when player moves — client must accept this reset.
GameState.stationary_ticks = 50
var snapshot := {
"tick": 2,
"entities": [
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
],
"stationary_ticks": 0,
}
GameState.apply_snapshot(snapshot)
assert_int(GameState.stationary_ticks).override_failure_message(
"Server reset to 0 must override client-held value"
).is_equal(0)
# -- stationary_ticks: DEPRECATED fallback (graceful degradation) --------------
func test_stationary_ticks_fallback_when_field_absent() -> void:
# When snapshot has no stationary_ticks, client-side accumulation must still
# run (backward compat until server ships field). No crash.
var snapshot := {
"tick": 1,
"entities": [
{"entity_id": 1, "x": 10.0, "y": 10.0, "z": 0, "kind": {"variant": "Player", "data": null}},
],
}
GameState.apply_snapshot(snapshot) # position changes from ZERO → resets to 0
GameState.apply_snapshot(snapshot) # position unchanged → increments
assert_int(GameState.stationary_ticks).override_failure_message(
"Client-side fallback must increment stationary_ticks when field absent"
).is_greater(0)
func test_apply_snapshot_missing_stationary_ticks_no_crash() -> void:
# Snapshots lacking stationary_ticks must not crash apply_snapshot().
var snapshot := {"tick": 1, "entities": []}
# No assertion needed beyond confirming no exception is raised.
GameState.apply_snapshot(snapshot)
assert_bool(true).is_true()
# -- zone_id: server-authoritative path (D-020/D-073) -------------------------
func test_current_zone_id_reads_server_value_when_present() -> void:
# When snapshot includes top-level zone_id, apply_snapshot() must use it
# directly without tile lookup (D-020).
var snapshot := {
"tick": 1,
"entities": [
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
],
"zone_id": "zone_server_direct",
}
GameState.apply_snapshot(snapshot)
assert_str(GameState.current_zone_id).override_failure_message(
"apply_snapshot() must read zone_id='zone_server_direct' from snapshot (D-020)"
).is_equal("zone_server_direct")
func test_current_zone_id_server_value_overrides_tile_data() -> void:
# When snapshot has both zone_id and visible_tiles with a different zone,
# the top-level zone_id field takes priority.
var snapshot := {
"tick": 1,
"entities": [
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
],
"zone_id": "zone_from_server",
"visible_tiles": [
{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_from_tile"},
],
}
GameState.apply_snapshot(snapshot)
assert_str(GameState.current_zone_id).override_failure_message(
"Top-level zone_id must override tile-derived zone when both present"
).is_equal("zone_from_server")
# -- zone_id: DEPRECATED fallback (graceful degradation) ----------------------
func test_current_zone_id_fallback_to_tile_lookup_when_absent() -> void:
# When snapshot lacks top-level zone_id, tile lookup fallback must run.
# Existing test_snapshot_zone_id.gd covers detailed scenarios; this is a
# smoke test confirming the fallback path still works after Sprint 20 refactor.
var snapshot := {
"tick": 1,
"entities": [
{"entity_id": 1, "x": 3.0, "y": 3.0, "z": 0, "kind": {"variant": "Player", "data": null}},
],
"visible_tiles": [
{"x": 3, "y": 3, "z": 0, "type": "floor", "zone_id": "zone_tile_fallback"},
],
}
GameState.apply_snapshot(snapshot)
assert_str(GameState.current_zone_id).override_failure_message(
"Tile lookup fallback must populate zone_id when top-level field absent"
).is_equal("zone_tile_fallback")
func test_apply_snapshot_missing_zone_id_no_crash() -> void:
# Snapshots lacking both zone_id and visible_tiles must not crash.
var snapshot := {"tick": 1, "entities": []}
GameState.apply_snapshot(snapshot)
assert_str(GameState.current_zone_id).is_equal("")