feat(client): upgrade protocol decoder and GameState for v2 snapshot

Server shipped ObserverSnapshot v2 with game_time, player_facing,
visible_tiles (with visibility sectors), and per-entity visibility.
Protocol decoder was silently ignoring these fields. Now extracts
all v2 data with null defaults for backward compatibility.

GameState gains game_time, player_facing, visibility_sectors vars.
Derives visible_positions from visible_tiles when present (for real
server mode). Test snapshot updated with v2 fields.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 00:53:18 +01:00
co-authored by Claude Opus 4.6
parent a9f1cbd865
commit 57da75c509
4 changed files with 152 additions and 1 deletions
+27
View File
@@ -10,6 +10,11 @@ var visible_entities: Array = []
var visible_tiles: Array = []
var visible_positions: Dictionary = {} # Vector2i -> true, for fast fog lookups
# v2 fields (D-015, D-031)
var game_time: Dictionary = {} # {day, time_of_day, day_phase, paused} or empty
var player_facing: String = "North" # 8-directional facing direction
var visibility_sectors: Dictionary = {} # Vector2i -> "Forward"/"Peripheral"
# Player entity ID — the first entity is assumed to be the player (will be
# refined when the server assigns explicit player entity IDs).
var player_entity_id: int = 1
@@ -40,3 +45,25 @@ func apply_snapshot(snapshot: Dictionary) -> void:
visible_positions.clear()
for pos in snapshot.visible_positions:
visible_positions[Vector2i(pos.x, pos.y)] = true
# v2: game_time (D-031)
if snapshot.has("game_time") and snapshot.game_time is Dictionary:
game_time = snapshot.game_time
# v2: player_facing (D-015)
if snapshot.has("player_facing") and snapshot.player_facing is String:
player_facing = snapshot.player_facing
# v2: visible_tiles with visibility sectors
# Derives visible_positions when not explicitly provided (real server mode)
if snapshot.has("visible_tiles") and snapshot.visible_tiles is Array and snapshot.visible_tiles.size() > 0:
visibility_sectors.clear()
var has_explicit_positions := snapshot.has("visible_positions")
if not has_explicit_positions:
visible_positions.clear()
for vtile in snapshot.visible_tiles:
var pos := Vector2i(vtile.x, vtile.y)
if vtile.has("visibility"):
visibility_sectors[pos] = vtile.visibility
if not has_explicit_positions:
visible_positions[pos] = true
+32
View File
@@ -240,6 +240,14 @@ func _test_snapshot() -> Dictionary:
_test_tick += 1
return {
"tick": _test_tick,
"version": 2,
"game_time": {
"day": 0,
"time_of_day": 0,
"day_phase": "Morning",
"paused": false,
},
"player_facing": "North",
"entities": [
{
"entity_id": 1,
@@ -247,6 +255,7 @@ func _test_snapshot() -> Dictionary:
"y": 10.0,
"z": 0,
"kind": { "variant": "Player", "data": null },
"visibility": "Forward",
},
{
"entity_id": 2,
@@ -254,9 +263,11 @@ func _test_snapshot() -> Dictionary:
"y": 10.0,
"z": 0,
"kind": { "variant": "Npc", "data": null },
"visibility": "Peripheral",
},
],
"tiles": _test_tiles(),
"visible_tiles": _test_visible_tiles(),
"visible_positions": _test_visible_positions(),
}
@@ -292,6 +303,27 @@ func _test_tiles() -> Array:
return tiles
# Test visible tiles with visibility sectors (v2 format)
# Tiles ahead of the player (y <= player_y) are Forward, others Peripheral.
func _test_visible_tiles() -> Array:
var vtiles: Array = []
var player_x := 10
var player_y := 10
var radius := 4
var room_x := 7
var room_y := 7
var room_w := 8
var room_h := 8
for x in range(player_x - radius, player_x + radius + 1):
for y in range(player_y - radius, player_y + radius + 1):
var dist := absf(x - player_x) + absf(y - player_y)
if dist <= radius:
if x >= room_x and x < room_x + room_w and y >= room_y and y < room_y + room_h:
var sector: String = "Forward" if y <= player_y else "Peripheral"
vtiles.append({"x": x, "y": y, "z": 0, "visibility": sector})
return vtiles
# Test visibility: player at (10,10) can see tiles within radius 4, blocked by walls
func _test_visible_positions() -> Array:
var positions: Array = []