Build _tile_by_coord dictionary from member visible_tiles (covers both test-mode "tiles" key and live-server "visible_tiles" key), then replace the linear scan with a single dict lookup. Net-zero complexity: adds one dict-set per tile in an existing iteration, removes the separate scan loop. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
212 lines
8.0 KiB
GDScript
212 lines
8.0 KiB
GDScript
## Sprint 16 #543: GameState.current_zone_id population tests.
|
|
## Verifies that apply_snapshot() correctly extracts zone_id from the player's
|
|
## current tile after the O(N) → O(1) refactor. Behavior must be identical
|
|
## before and after the change (net-zero behavioral change per ticket spec).
|
|
## Spec refs: D-073, #543, #529.
|
|
class_name TestSnapshotZoneId
|
|
extends GdUnitTestSuite
|
|
|
|
|
|
func before_test() -> void:
|
|
GameState.current_zone_id = ""
|
|
GameState.player_position = Vector2.ZERO
|
|
GameState.visible_tiles = []
|
|
GameState.visible_positions = {}
|
|
GameState.visibility_sectors = {}
|
|
|
|
|
|
# -- Happy path (S16-Z01) ------------------------------------------------------
|
|
|
|
func test_zone_id_populated_when_player_on_zone_tile() -> void:
|
|
# S16-Z01: Player at (5,5), visible_tiles has zone_id "zone_alpha" at (5,5).
|
|
# D-073: current_zone_id must be set to the server-authoritative zone_id.
|
|
GameState.apply_snapshot({
|
|
"tick": 1,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"visible_tiles": [
|
|
{"x": 3, "y": 3, "z": 0, "type": "floor", "zone_id": "zone_foyer"},
|
|
{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_alpha"},
|
|
{"x": 7, "y": 7, "z": 0, "type": "floor", "zone_id": "zone_beta"},
|
|
],
|
|
})
|
|
assert_that(GameState.current_zone_id).override_failure_message(
|
|
"current_zone_id must match zone_id at player tile (5,5)"
|
|
).is_equal("zone_alpha")
|
|
assert_that(GameState.current_zone_id.is_empty()).override_failure_message(
|
|
"current_zone_id must be non-empty when player is on a zone-tagged tile"
|
|
).is_false()
|
|
|
|
|
|
# -- Missing zone_id field (S16-Z02) -------------------------------------------
|
|
|
|
func test_zone_id_empty_when_tile_lacks_zone_field() -> void:
|
|
# S16-Z02: Player's tile exists but has no zone_id key → empty string.
|
|
# Server may send tiles without zone_id when tile is unzoned.
|
|
GameState.apply_snapshot({
|
|
"tick": 1,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"visible_tiles": [
|
|
{"x": 5, "y": 5, "z": 0, "type": "floor"},
|
|
],
|
|
})
|
|
assert_that(GameState.current_zone_id).override_failure_message(
|
|
"Tile without zone_id field → current_zone_id must default to empty string"
|
|
).is_equal("")
|
|
|
|
|
|
# -- Player off-tile (S16-Z03) -------------------------------------------------
|
|
|
|
func test_zone_id_empty_when_player_not_on_any_tile() -> void:
|
|
# S16-Z03: Player at (99,99) but no tile at that position → empty string.
|
|
GameState.apply_snapshot({
|
|
"tick": 1,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 99.0, "y": 99.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"visible_tiles": [
|
|
{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_alpha"},
|
|
],
|
|
})
|
|
assert_that(GameState.current_zone_id).override_failure_message(
|
|
"Player at (99,99) with no matching tile → current_zone_id must be empty"
|
|
).is_equal("")
|
|
|
|
|
|
# -- Empty tiles (S16-Z04) -----------------------------------------------------
|
|
|
|
func test_zone_id_empty_on_empty_visible_tiles() -> void:
|
|
# S16-Z04: No visible tiles at all → empty string, no crash.
|
|
GameState.apply_snapshot({
|
|
"tick": 1,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"visible_tiles": [],
|
|
})
|
|
assert_that(GameState.current_zone_id).override_failure_message(
|
|
"Empty visible_tiles → current_zone_id must be empty string (no crash)"
|
|
).is_equal("")
|
|
|
|
|
|
# -- Correct tile selected among many (S16-Z05) --------------------------------
|
|
|
|
func test_zone_id_selects_correct_tile_among_many() -> void:
|
|
# S16-Z05: 10x10 tile grid, player at (6,4). Only zone_6_4 must be selected.
|
|
# Tests that the lookup doesn't accidentally match a neighboring tile.
|
|
var tiles: Array = []
|
|
for tx in range(10):
|
|
for ty in range(10):
|
|
tiles.append({"x": tx, "y": ty, "z": 0, "type": "floor",
|
|
"zone_id": "zone_%d_%d" % [tx, ty]})
|
|
GameState.apply_snapshot({
|
|
"tick": 1,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 6.0, "y": 4.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"visible_tiles": tiles,
|
|
})
|
|
assert_that(GameState.current_zone_id).override_failure_message(
|
|
"Player at (6,4) must get zone_6_4, not a neighboring tile"
|
|
).is_equal("zone_6_4")
|
|
|
|
|
|
# -- Zone transition (S16-Z06) -------------------------------------------------
|
|
|
|
func test_zone_id_updates_on_zone_transition() -> void:
|
|
# S16-Z06: Player moves from zone_alpha (5,5) to zone_beta (6,5).
|
|
# current_zone_id must update on each snapshot.
|
|
GameState.apply_snapshot({
|
|
"tick": 1,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"visible_tiles": [
|
|
{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_alpha"},
|
|
{"x": 6, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_beta"},
|
|
],
|
|
})
|
|
assert_that(GameState.current_zone_id).is_equal("zone_alpha")
|
|
|
|
GameState.apply_snapshot({
|
|
"tick": 2,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 6.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"visible_tiles": [
|
|
{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_alpha"},
|
|
{"x": 6, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_beta"},
|
|
],
|
|
})
|
|
assert_that(GameState.current_zone_id).override_failure_message(
|
|
"After moving to (6,5), current_zone_id must update to zone_beta"
|
|
).is_equal("zone_beta")
|
|
|
|
|
|
# -- Regression: visible_positions unaffected (S16-Z07) -----------------------
|
|
|
|
func test_visible_positions_unaffected_by_zone_id_refactor() -> void:
|
|
# S16-Z07: The O(1) refactor must not break visible_positions population.
|
|
# Both zone_id and visible_positions derive from the same visible_tiles loop —
|
|
# verify both are correctly populated after a single apply_snapshot().
|
|
GameState.apply_snapshot({
|
|
"tick": 1,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"visible_tiles": [
|
|
{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_alpha", "visibility": "Forward"},
|
|
{"x": 6, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_beta", "visibility": "Peripheral"},
|
|
],
|
|
})
|
|
assert_that(GameState.visible_positions.has(Vector2i(5, 5))).override_failure_message(
|
|
"visible_positions must still contain (5,5) after zone_id refactor"
|
|
).is_true()
|
|
assert_that(GameState.visible_positions.has(Vector2i(6, 5))).is_true()
|
|
assert_that(GameState.current_zone_id).override_failure_message(
|
|
"current_zone_id must be zone_alpha after same apply_snapshot call"
|
|
).is_equal("zone_alpha")
|
|
|
|
|
|
# -- Fractional player position (S16-Z08) --------------------------------------
|
|
|
|
func test_zone_id_uses_int_truncation_of_player_position() -> void:
|
|
# S16-Z08: Player at (5.7, 5.9) → int(5.7)=5, int(5.9)=5 → matches tile (5,5).
|
|
# Server sends player coords as floats; zone lookup must truncate to tile index.
|
|
GameState.apply_snapshot({
|
|
"tick": 1,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 5.7, "y": 5.9, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"visible_tiles": [
|
|
{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_alpha"},
|
|
],
|
|
})
|
|
assert_that(GameState.current_zone_id).override_failure_message(
|
|
"Player at (5.7,5.9) must match tile (5,5) — int() truncation applies"
|
|
).is_equal("zone_alpha")
|
|
|
|
|
|
# -- Test-mode tiles key (S16-Z09) --------------------------------------------
|
|
|
|
func test_zone_id_works_with_test_mode_tiles_key() -> void:
|
|
# S16-Z09: Test mode sends "tiles" key, not "visible_tiles".
|
|
# The O(1) refactor uses member visible_tiles (covers both paths).
|
|
# Regression guard: if _tile_by_coord is moved into the snapshot.visible_tiles
|
|
# block only, this test fails — catching the exact regression Tyre flagged.
|
|
GameState.apply_snapshot({
|
|
"tick": 1,
|
|
"entities": [
|
|
{"entity_id": 1, "x": 5.0, "y": 5.0, "z": 0, "kind": {"variant": "Player", "data": null}},
|
|
],
|
|
"tiles": [
|
|
{"x": 5, "y": 5, "z": 0, "type": "floor", "zone_id": "zone_alpha"},
|
|
],
|
|
})
|
|
assert_that(GameState.current_zone_id).override_failure_message(
|
|
"Test-mode 'tiles' key must populate zone_id via member visible_tiles"
|
|
).is_equal("zone_alpha")
|