## D-030 Layer 1: Protocol v7 tests for dialogue box (#434/#435) and fog entity ## visualization (#431). Validates pending_recognitions decode, current_dialogue ## decode with structured options {text, response_id, priority}, GameState storage, ## and SimBridge test mode mock data. ## Spec refs: D-059, D-060, D-061, D-062, D-064, #431, #434, #435 class_name TestProtocolV7 extends GdUnitTestSuite # -- pending_recognitions decode (D-059/D-060) --------------------------------- func test_decode_pending_recognitions_basic() -> void: var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], "pending_recognitions": [ {"entity_id": 100, "x": 13.5, "y": 12.5, "z": 0, "remaining_ticks": 4, "total_delay_ticks": 6}, ], } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot).is_not_null() assert_that(snapshot.pending_recognitions.size()).is_equal(1) var pr: Dictionary = snapshot.pending_recognitions[0] assert_that(pr.entity_id).is_equal(100) assert_that(pr.x).is_equal(13.5) assert_that(pr.y).is_equal(12.5) assert_that(pr.remaining_ticks).is_equal(4) assert_that(pr.total_delay_ticks).is_equal(6) func test_decode_pending_recognitions_empty() -> void: var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], "pending_recognitions": [], } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot.pending_recognitions.size()).is_equal(0) func test_decode_pending_recognitions_missing_defaults_empty() -> void: var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot.pending_recognitions.size()).is_equal(0) func test_decode_pending_recognitions_skips_malformed() -> void: var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], "pending_recognitions": [ {"entity_id": 100, "x": 13.5, "y": 12.5, "z": 0, "remaining_ticks": 4, "total_delay_ticks": 6}, {"broken": true}, # Missing entity_id, x, y {"entity_id": 101}, # Missing x, y {"entity_id": 102, "x": 10.0, "y": 11.0, "z": 0, "remaining_ticks": 2, "total_delay_ticks": 6}, ], } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot.pending_recognitions.size()).is_equal(2) assert_that(snapshot.pending_recognitions[0].entity_id).is_equal(100) assert_that(snapshot.pending_recognitions[1].entity_id).is_equal(102) func test_decode_pending_recognitions_defaults() -> void: # remaining_ticks and total_delay_ticks default to 0 and 1 var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], "pending_recognitions": [ {"entity_id": 100, "x": 5.0, "y": 5.0}, ], } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot.pending_recognitions[0].remaining_ticks).is_equal(0) assert_that(snapshot.pending_recognitions[0].total_delay_ticks).is_equal(1) assert_that(snapshot.pending_recognitions[0].z).is_equal(0) # -- current_dialogue decode (D-061) ------------------------------------------- func test_decode_current_dialogue() -> void: var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], "current_dialogue": { "npc_name": "Kael", "npc_entity_id": 2, "speech": "Hello there.", "options": [ {"text": "Hi", "response_id": "opt_hi", "priority": 1}, {"text": "Bye", "response_id": "opt_bye", "priority": 2}, ], }, } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot).is_not_null() assert_that(snapshot.current_dialogue).is_not_null() assert_that(snapshot.current_dialogue.npc_name).is_equal("Kael") assert_that(snapshot.current_dialogue.npc_entity_id).is_equal(2) assert_that(snapshot.current_dialogue.speech).is_equal("Hello there.") assert_that(snapshot.current_dialogue.options.size()).is_equal(2) assert_that(snapshot.current_dialogue.options[0].text).is_equal("Hi") assert_that(snapshot.current_dialogue.options[0].response_id).is_equal("opt_hi") assert_that(snapshot.current_dialogue.options[0].priority).is_equal(1) assert_that(snapshot.current_dialogue.options[0].confrontation).is_false() func test_decode_current_dialogue_missing_is_null() -> void: var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot).is_not_null() assert_that(snapshot.current_dialogue == null).is_true() func test_decode_current_dialogue_options_default_fields() -> void: # response_id and priority default when absent var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], "current_dialogue": { "speech": "Just speech.", "options": [ {"text": "OK"}, ], }, } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot.current_dialogue).is_not_null() assert_that(snapshot.current_dialogue.npc_name).is_equal("") assert_that(snapshot.current_dialogue.npc_entity_id).is_equal(-1) assert_that(snapshot.current_dialogue.options[0].response_id).is_equal("") assert_that(snapshot.current_dialogue.options[0].priority).is_equal(0) assert_that(snapshot.current_dialogue.options[0].confrontation).is_false() func test_decode_current_dialogue_confrontation_option() -> void: var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], "current_dialogue": { "npc_name": "Sera", "speech": "What's on your mind?", "options": [ {"text": "Just passing time.", "response_id": "sera_01", "priority": 1, "confrontation": false}, {"text": "I saw you avoiding Torek.", "response_id": "sera_02", "priority": 2, "confrontation": true}, ], }, } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot.current_dialogue.options[0].confrontation).is_false() assert_that(snapshot.current_dialogue.options[1].confrontation).is_true() assert_that(snapshot.current_dialogue.options[1].text).is_equal("I saw you avoiding Torek.") func test_decode_current_dialogue_skips_malformed_options() -> void: var raw := { "tick": 1, "version": Protocol.PROTOCOL_VERSION, "entities": [], "current_dialogue": { "npc_name": "Sera", "speech": "What do you want?", "options": [ {"text": "Talk", "response_id": "opt_talk", "priority": 1}, {"broken": true}, # Missing text {"text": "Leave", "response_id": "opt_leave", "priority": 3}, ], }, } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot.current_dialogue.options.size()).is_equal(2) assert_that(snapshot.current_dialogue.options[0].response_id).is_equal("opt_talk") assert_that(snapshot.current_dialogue.options[1].response_id).is_equal("opt_leave") # -- GameState: v7 field storage ----------------------------------------------- func test_game_state_stores_pending_recognitions() -> void: var recs := [ {"entity_id": 100, "x": 13.5, "y": 12.5, "z": 0, "remaining_ticks": 4, "total_delay_ticks": 6}, ] GameState.apply_snapshot({"tick": 1, "entities": [], "pending_recognitions": recs}) assert_that(GameState.pending_recognitions.size()).is_equal(1) assert_that(GameState.pending_recognitions[0].entity_id).is_equal(100) GameState.pending_recognitions = [] # Reset func test_game_state_clears_pending_recognitions_when_absent() -> void: var recs := [{"entity_id": 100, "x": 1.0, "y": 2.0, "z": 0, "remaining_ticks": 1, "total_delay_ticks": 3}] GameState.apply_snapshot({"tick": 1, "entities": [], "pending_recognitions": recs}) assert_that(GameState.pending_recognitions.size()).is_equal(1) GameState.apply_snapshot({"tick": 2, "entities": []}) assert_that(GameState.pending_recognitions.size()).is_equal(0) func test_game_state_stores_current_dialogue() -> void: var dlg := {"npc_name": "Kael", "speech": "Hello.", "options": [ {"text": "Hi", "response_id": "opt_hi", "priority": 1}, ]} GameState.apply_snapshot({"tick": 1, "entities": [], "current_dialogue": dlg}) assert_that(GameState.current_dialogue).is_not_null() assert_that(GameState.current_dialogue.npc_name).is_equal("Kael") assert_that(GameState.current_dialogue.options[0].response_id).is_equal("opt_hi") GameState.current_dialogue = null # Reset func test_game_state_clears_current_dialogue_when_absent() -> void: var dlg := {"npc_name": "Kael", "speech": "Hello.", "options": []} GameState.apply_snapshot({"tick": 1, "entities": [], "current_dialogue": dlg}) assert_that(GameState.current_dialogue).is_not_null() GameState.apply_snapshot({"tick": 2, "entities": []}) assert_that(GameState.current_dialogue == null).is_true() # -- SimBridge test mode: v7 fields ------------------------------------------- func test_sim_bridge_test_snapshot_has_pending_recognitions() -> void: SimBridge.reset_test_state() var snap = SimBridge._test_snapshot() assert_that(snap.has("pending_recognitions")).is_true() assert_that(snap.pending_recognitions is Array).is_true() func test_sim_bridge_test_snapshot_has_current_dialogue_field() -> void: SimBridge.reset_test_state() var snap = SimBridge._test_snapshot() assert_that(snap.has("current_dialogue")).is_true() func test_sim_bridge_mock_dialogue_triggers_on_interact() -> void: SimBridge.reset_test_state() # Move near NPC at (12, 9) — start at (10, 10), move to (11, 9) SimBridge._test_input_queue.append("MoveEast") SimBridge._test_snapshot() # tick 1: move to (11, 10) SimBridge._test_input_queue.append("MoveNorth") SimBridge._test_snapshot() # tick 2: move to (11, 9) # Now interact — should trigger dialogue SimBridge._test_input_queue.append("Interact") var snap = SimBridge._test_snapshot() # tick 3 assert_that(snap.current_dialogue).is_not_null() assert_that(snap.current_dialogue.npc_name).is_equal("Kael") assert_that(snap.current_dialogue.npc_entity_id).is_equal(2) assert_that(snap.current_dialogue.options.size()).is_equal(3) assert_that(snap.current_dialogue.options[0].response_id).is_equal("kael_greet_01") assert_that(snap.current_dialogue.options[0].confrontation).is_false() assert_that(snap.current_dialogue.options[2].response_id).is_equal("kael_confront_01") assert_that(snap.current_dialogue.options[2].confrontation).is_true() func test_sim_bridge_mock_dialogue_sustained() -> void: SimBridge.reset_test_state() # Move near NPC and interact SimBridge._test_input_queue.append("MoveEast") SimBridge._test_snapshot() SimBridge._test_input_queue.append("MoveNorth") SimBridge._test_snapshot() SimBridge._test_input_queue.append("Interact") var snap1 = SimBridge._test_snapshot() assert_that(snap1.current_dialogue).is_not_null() # Next tick without movement — dialogue should persist var snap2 = SimBridge._test_snapshot() assert_that(snap2.current_dialogue).is_not_null() func test_sim_bridge_mock_dialogue_walk_away() -> void: SimBridge.reset_test_state() # Move near NPC and interact SimBridge._test_input_queue.append("MoveEast") SimBridge._test_snapshot() SimBridge._test_input_queue.append("MoveNorth") SimBridge._test_snapshot() SimBridge._test_input_queue.append("Interact") var snap1 = SimBridge._test_snapshot() assert_that(snap1.current_dialogue).is_not_null() # Walk away — dialogue should clear SimBridge._test_input_queue.append("MoveSouth") var snap2 = SimBridge._test_snapshot() assert_that(snap2.current_dialogue == null).is_true() func test_sim_bridge_mock_cognitive_delay_cycle() -> void: SimBridge.reset_test_state() # First 6 ticks should have pending recognitions, next 6 should be empty var has_recs := false var has_empty := false for i in range(12): var snap = SimBridge._test_snapshot() if snap.pending_recognitions.size() > 0: has_recs = true assert_that(snap.pending_recognitions[0].entity_id).is_equal(100) else: has_empty = true assert_that(has_recs).is_true() assert_that(has_empty).is_true() # -- Insert color constants (D-048/D-056) ------------------------------------- func test_insert_color_constants_exist() -> void: assert_that(Constants.INSERT_COLOR_TEXT).is_equal(Color("#c8d0e0")) assert_that(Constants.INSERT_COLOR_HOVER).is_equal(Color("#e8c547")) assert_that(Constants.INSERT_COLOR_ACTIVE).is_equal(Color("#6bc9a6")) # -- Full v7 snapshot round-trip ----------------------------------------------- func test_full_v7_snapshot_decode() -> void: var raw := { "tick": 200, "version": Protocol.PROTOCOL_VERSION, "game_time": {"day": 2, "time_of_day": 1000, "day_phase": "Evening", "tick_rate": "Full"}, "player_facing": "West", "player_stance": "Careful", "player_inventory": [{"item_id": 100, "name": "Access Token", "slot": 0}], "entities": [ {"entity_id": 1, "x": 10.0, "y": 10.0, "z": 0, "kind": "Player", "visibility": "Forward", "relationship": "Unknown", "observation": "Visible"}, ], "visible_tiles": [], "nearby_interactions": [], "current_monologue": null, "current_dialogue": { "npc_name": "Sera", "speech": "What brings you here?", "options": [ {"text": "Just looking around.", "response_id": "sera_01", "priority": 2}, {"text": "I have questions.", "response_id": "sera_02", "priority": 1}, ], }, "pending_recognitions": [ {"entity_id": 50, "x": 14.0, "y": 11.0, "z": 0, "remaining_ticks": 3, "total_delay_ticks": 6}, {"entity_id": 51, "x": 8.0, "y": 13.0, "z": 0, "remaining_ticks": 0, "total_delay_ticks": 6}, ], } var encoded = Messagepack.encode(raw) var snapshot = Protocol.decode_snapshot(encoded.value) assert_that(snapshot).is_not_null() assert_that(snapshot.tick).is_equal(200) assert_that(snapshot.version).is_equal(Protocol.PROTOCOL_VERSION) assert_that(snapshot.player_facing).is_equal("West") assert_that(snapshot.player_stance).is_equal("Careful") assert_that(snapshot.player_inventory.size()).is_equal(1) assert_that(snapshot.current_dialogue).is_not_null() assert_that(snapshot.current_dialogue.npc_name).is_equal("Sera") assert_that(snapshot.current_dialogue.options.size()).is_equal(2) assert_that(snapshot.current_dialogue.options[0].response_id).is_equal("sera_01") assert_that(snapshot.pending_recognitions.size()).is_equal(2) assert_that(snapshot.pending_recognitions[0].remaining_ticks).is_equal(3) assert_that(snapshot.pending_recognitions[1].remaining_ticks).is_equal(0)