From 80064196f77534b1a8f16836ab00a95b0134416c Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Wed, 25 Feb 2026 02:29:37 +0100 Subject: [PATCH] feat(client): protocol v14 decoders and game state fields Decode poi_list, examine_result, and player_knowledge from ObserverSnapshot. Add GameState.discovered_pois, current_examine_result, and player_knowledge fields populated from snapshot apply handlers. Co-Authored-By: Claude Opus 4.6 --- client/scripts/autoloads/game_state.gd | 34 +++++++++ client/scripts/protocol/protocol.gd | 69 +++++++++++++++++- .../msgpack/snapshot_boundary_tick_0.msgpack | Bin 343 -> 353 bytes .../snapshot_boundary_tick_127.msgpack | Bin 343 -> 353 bytes .../snapshot_boundary_tick_2b31m1.msgpack | Bin 347 -> 357 bytes .../snapshot_boundary_tick_2b32.msgpack | Bin 351 -> 361 bytes .../snapshot_boundary_tick_32767.msgpack | Bin 345 -> 355 bytes .../fixtures/msgpack/snapshot_empty.msgpack | Bin 343 -> 353 bytes .../msgpack/snapshot_multi_entity.msgpack | Bin 748 -> 758 bytes .../fixtures/msgpack/snapshot_one_npc.msgpack | Bin 441 -> 451 bytes .../fixtures/msgpack/snapshot_player.msgpack | Bin 444 -> 454 bytes .../fixtures/msgpack/snapshot_v2_full.msgpack | Bin 608 -> 618 bytes 12 files changed, 102 insertions(+), 1 deletion(-) diff --git a/client/scripts/autoloads/game_state.gd b/client/scripts/autoloads/game_state.gd index 979008f4a..881be4bd3 100644 --- a/client/scripts/autoloads/game_state.gd +++ b/client/scripts/autoloads/game_state.gd @@ -70,6 +70,21 @@ var dialogue_response: Variant = null # {line_id, text, speaker_entity_id} var conversation_events: Array = [] # [{speaker_id, target_id, speaker_name, target_name, occluded_line}] var conversation_ended: Array = [] # [{speaker_id, target_id}] +# v10 fields (#151, D-013): Discovered POIs from server (#148/#149). +# Format: [{poi_id, name, x, y, z, category}]. Persists between snapshots unless +# server explicitly sends an empty array (cleared locations are not typical in v0.1). +# Populated from snapshot "poi_list" field — only updated when field present. +var discovered_pois: Array = [] + +# v14 fields (#174, #242): Character-filtered examine result. +# {entity_id, text, confidence} or null. Auto-dismisses on client after 4-6 seconds. +var current_examine_result: Variant = null + +# v14 fields (#264, D-041): Player knowledge graph dump for journal panel. +# {entities: [{entity_id, name, confidence, source, state, relationship, last_observed_tick}], +# facts: [{fact_id, confidence, source, state, acquired_tick}]} +var player_knowledge: Variant = null + # #126, D-018: Medium-range sound events for fog-edge directional indicators. # Format: [{x, y, event_type, range_category}] — server sends current medium events per tick. var medium_sound_events: Array = [] @@ -240,6 +255,25 @@ func apply_snapshot(snapshot: Dictionary) -> void: medium_sound_events = [] close_sound_events = [] + # v10: discovered_pois (#151, D-013) — server sends POIs discovered by the player. + # Accepts "discovered_pois" or "poi_list" key — both map to the same client field. + # Only update if the field is present — absence means "no change since last tick". + if snapshot.has("discovered_pois") and snapshot.discovered_pois is Array: + discovered_pois = snapshot.discovered_pois + elif snapshot.has("poi_list") and snapshot.poi_list is Array: + discovered_pois = snapshot.poi_list + + # v14: examine_result (#174, #242) — character-filtered observation from Examine verb. + if snapshot.has("examine_result") and snapshot.examine_result is Dictionary: + current_examine_result = snapshot.examine_result + else: + current_examine_result = null + + # v14: player_knowledge (#264, D-041) — partial KG dump for journal panel. + # Only update when field is present (null means no change, server sends when KG changes). + if snapshot.has("player_knowledge") and snapshot.player_knowledge is Dictionary: + player_knowledge = snapshot.player_knowledge + # D-073 (#529): O(1) zone_id lookup. Build coord→tile dict from member visible_tiles # (populated above from either "tiles" test-mode key or "visible_tiles" live key). # Must use the member var, not snapshot.visible_tiles, so test mode is covered. diff --git a/client/scripts/protocol/protocol.gd b/client/scripts/protocol/protocol.gd index 492dc219d..c18b3c274 100644 --- a/client/scripts/protocol/protocol.gd +++ b/client/scripts/protocol/protocol.gd @@ -11,7 +11,7 @@ class_name Protocol ## Protocol version — must match server PROTOCOL_VERSION in bridge/types.rs. ## Reject snapshots where version != this value. -const PROTOCOL_VERSION: int = 13 +const PROTOCOL_VERSION: int = 14 # -- Decode: bytes from server → GDScript types -------------------------------- @@ -206,6 +206,70 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant: "target_id": int(raw_end.get("target_id", 0)), }) + # v14: poi_list (#151) — discovered POIs for minimap rendering. + # Each entry: {poi_id, name, x, y, z, category}. Positions in sim tile coords. + var poi_list: Array = [] + var raw_pois: Variant = raw.get("poi_list") + if raw_pois is Array: + for raw_poi in raw_pois: + if raw_poi is Dictionary and raw_poi.has("poi_id") and raw_poi.has("x") and raw_poi.has("y"): + poi_list.append({ + "poi_id": str(raw_poi["poi_id"]), + "name": str(raw_poi.get("name", "")), + "x": int(raw_poi["x"]), + "y": int(raw_poi["y"]), + "z": int(raw_poi.get("z", 0)), + "category": str(raw_poi.get("category", "Location")), + }) + + # v14: examine_result (#174, #242) — character-filtered observation text. + # {entity_id, text, confidence} or null. Auto-dismisses on client after 4-6 seconds. + var examine_result: Variant = null + var raw_examine: Variant = raw.get("examine_result") + if raw_examine is Dictionary and raw_examine.has("text"): + examine_result = { + "entity_id": int(raw_examine.get("entity_id", 0)), + "text": str(raw_examine["text"]), + "confidence": str(raw_examine.get("confidence", "KnowsOf")), + } + + # v14: player_knowledge (#264, D-041) — partial KG dump for journal panel. + # {entities: [{entity_id, name, confidence, source, state, relationship, last_observed_tick}], + # facts: [{fact_id, confidence, source, state, acquired_tick}]} + var player_knowledge: Variant = null + var raw_pk: Variant = raw.get("player_knowledge") + if raw_pk is Dictionary: + var kg_entities: Array = [] + var raw_kg_entities: Variant = raw_pk.get("entities") + if raw_kg_entities is Array: + for raw_ke in raw_kg_entities: + if raw_ke is Dictionary and raw_ke.has("entity_id"): + kg_entities.append({ + "entity_id": int(raw_ke["entity_id"]), + "name": str(raw_ke.get("name", "Unknown")), + "confidence": str(raw_ke.get("confidence", "Suspects")), + "source": str(raw_ke.get("source", "")), + "state": str(raw_ke.get("state", "Active")), + "relationship": str(raw_ke.get("relationship", "Unknown")), + "last_observed_tick": int(raw_ke.get("last_observed_tick", 0)), + }) + var kg_facts: Array = [] + var raw_kg_facts: Variant = raw_pk.get("facts") + if raw_kg_facts is Array: + for raw_kf in raw_kg_facts: + if raw_kf is Dictionary and raw_kf.has("fact_id"): + kg_facts.append({ + "fact_id": str(raw_kf["fact_id"]), + "confidence": str(raw_kf.get("confidence", "Suspects")), + "source": str(raw_kf.get("source", "")), + "state": str(raw_kf.get("state", "Active")), + "acquired_tick": int(raw_kf.get("acquired_tick", 0)), + }) + player_knowledge = { + "entities": kg_entities, + "facts": kg_facts, + } + return { "tick": tick, "entities": entities, @@ -223,6 +287,9 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant: "pending_recognitions": pending_recognitions, "conversation_events": conversation_events, "conversation_ended": conversation_ended, + "poi_list": poi_list, + "examine_result": examine_result, + "player_knowledge": player_knowledge, } diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_0.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_0.msgpack index 0a0876c58923e10f551abe3adce508c9bbc2abff..56a60bcd163ea48ae77c4bfaa241c1d912803880 100644 GIT binary patch delta 32 ocmcc4^pJ_?9)s}mvecsD%=|pQjXd6rTq_FlGvjkIi%TW|0LE$zBLDyZ delta 21 ccmaFJbe)Ok9)r;GvecsD%=|pwjXd6r09o+|d;kCd diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_127.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_127.msgpack index 6aafb8c2c49dc5c2a3cb38eeff5acaed48b2893e..e0784d8f30f7f53985fb14b983b0beb5fd9bda7f 100644 GIT binary patch delta 32 ocmcc4^pJ_?9)s}mvecsD%=|pQjXd6rTq_FlGvjkIi%TW|0LE$zBLDyZ delta 21 ccmaFJbe)Ok9)r;GvecsD%=|pwjXd6r09o+|d;kCd diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_2b31m1.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_2b31m1.msgpack index 7d6d91200e308b87c8d5384a98ed51fd9544dda5..b9228db570372b8afde55497031a8ac21fdd55b2 100644 GIT binary patch delta 32 ocmcc3^puI`9)s}mvecsD%=|pQjXeI0Tq_FlGvjkIi%TW|0LPXLF8}}l delta 21 ccmaFLbeoCi9)r;GvecsD%=|pwjXeI009u&`hyVZp diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_2b32.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_2b32.msgpack index cd39ea41fe151544ea434d43dee0aa84f1453bd7..a286d7833abf0a6c0a616a33253a6b92f915d4a7 100644 GIT binary patch delta 32 ocmcc5^pc6^9)s}mvecsD%=|pQjXc4OTq_FlGvjkIi%TW|0La1&I{*Lx delta 21 ccmaFKbf1am9)r;GvecsD%=|pwjXc4O09!!^lmGw# diff --git a/client/tests/fixtures/msgpack/snapshot_boundary_tick_32767.msgpack b/client/tests/fixtures/msgpack/snapshot_boundary_tick_32767.msgpack index a195fdf6b34198db586496e8a51314b105cd1b3d..84ef216198c66cf1c4e47e7a175094a164d7532d 100644 GIT binary patch delta 32 ocmcb~^q7g~9)s}mvecsD%=|pQjXb`LTq_FlGvjkIi%TW|0LK6fDF6Tf delta 21 ccmaFNbd!na9)r;GvecsD%=|pwjXb`L09r){f&c&j diff --git a/client/tests/fixtures/msgpack/snapshot_empty.msgpack b/client/tests/fixtures/msgpack/snapshot_empty.msgpack index 0a0876c58923e10f551abe3adce508c9bbc2abff..56a60bcd163ea48ae77c4bfaa241c1d912803880 100644 GIT binary patch delta 32 ocmcc4^pJ_?9)s}mvecsD%=|pQjXd6rTq_FlGvjkIi%TW|0LE$zBLDyZ delta 21 ccmaFJbe)Ok9)r;GvecsD%=|pwjXd6r09o+|d;kCd diff --git a/client/tests/fixtures/msgpack/snapshot_multi_entity.msgpack b/client/tests/fixtures/msgpack/snapshot_multi_entity.msgpack index a332c6c50f3f2dec5a9cd542e565ba4b70111c32..3ebbb60943255d728d7fd66fd22bcb5d190c6106 100644 GIT binary patch delta 32 ocmaFE`i+(69)s}mvecsD%=|pQjXV#SxKPx# delta 21 ccmeyy`i7O~9)r;GvecsD%=|pwjXV#S0AAJzLI3~& diff --git a/client/tests/fixtures/msgpack/snapshot_one_npc.msgpack b/client/tests/fixtures/msgpack/snapshot_one_npc.msgpack index 0093a3ac1113edf035ac5e1f43d303c08e13257f..17c2845580758dd94c8f0e55a6d75ba108993c8e 100644 GIT binary patch delta 32 ocmdnVe3+T%9)s}mvecsD%=|pQjXY}^xmFb9XU6Ab7MDx_0K{AjO8@`> delta 21 ccmX@iypx&d9)r;GvecsD%=|pwjXY}^0ahyqqyPW_ diff --git a/client/tests/fixtures/msgpack/snapshot_player.msgpack b/client/tests/fixtures/msgpack/snapshot_player.msgpack index e919024ce2741717339937281d08328ece1ce061..9908361a78c6f93c292f73cec42fc54132f33d82 100644 GIT binary patch delta 32 ocmdnPe2kgr9)s}mvecsD%=|pQjXWC|xmFb9XU6Ab7MDx_0L48FQ~&?~ delta 21 ccmX@cyoZ_R9)r;GvecsD%=|pwjXWC|0amF8tpET3 diff --git a/client/tests/fixtures/msgpack/snapshot_v2_full.msgpack b/client/tests/fixtures/msgpack/snapshot_v2_full.msgpack index b7d86d7de686713dcc6d8ddea1c7e7bc362252fe..c2faadd097655abcd2b587f15b51df5c15eb6c15 100644 GIT binary patch delta 32 ocmaFB@`{D$9)s}mvecsD%=|pQjXWVtTq_FlGvjkIi%TW|0LhUILjV8( delta 21 ccmaFG@_>cs9)r;GvecsD%=|pwjXWVt09&{RoB#j-