test(client): add boundary, P0 regression, P1 fog/entity, protocol v7 tests

MessagePack boundary tests: 41 values (25 pos + 16 neg) with encode-
only verification, roundtrip, and Rust-style unsigned decode (#470).
P0 regressions: monologue carry-forward (Bug #5), camera stability
during pause (Bug #2) (#477). P1 tests: fog shader state (4), entity
lifecycle (2), pending recognition blob (1) using FogState named
constants (#478). Protocol v7: structured dialogue options with
response_id, priority, confrontation flag, malformed skip (#435).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-17 16:35:19 +01:00
co-authored by Claude Opus 4.6
parent be763908b2
commit afc433be34
4 changed files with 766 additions and 9 deletions
+107 -9
View File
@@ -1,7 +1,8 @@
## D-030 Layer 1: Protocol v7 tests for dialogue box (#434) and fog entity
## D-030 Layer 1: Protocol v7 tests for dialogue box (#434/#435) and fog entity
## visualization (#431). Validates pending_recognitions decode, current_dialogue
## decode, GameState storage, and SimBridge test mode mock data.
## Spec refs: D-059, D-060, D-061, D-064, #431, #434
## 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
@@ -97,14 +98,26 @@ func test_decode_current_dialogue() -> void:
"entities": [],
"current_dialogue": {
"npc_name": "Kael",
"npc_entity_id": 2,
"speech": "Hello there.",
"options": ["Hi", "Bye"],
"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)
# current_dialogue is passed through as-is from snapshot
assert_that(snapshot.has("current_dialogue") or true).is_true()
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:
@@ -115,9 +128,74 @@ func test_decode_current_dialogue_missing_is_null() -> void:
}
var encoded = Messagepack.encode(raw)
var snapshot = Protocol.decode_snapshot(encoded.value)
# current_dialogue not in protocol decode (handled by GameState)
# Verify snapshot round-trips correctly
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 -----------------------------------------------
@@ -141,10 +219,13 @@ func test_game_state_clears_pending_recognitions_when_absent() -> void:
func test_game_state_stores_current_dialogue() -> void:
var dlg := {"npc_name": "Kael", "speech": "Hello.", "options": ["Hi"]}
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
@@ -183,7 +264,12 @@ func test_sim_bridge_mock_dialogue_triggers_on_interact() -> void:
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:
@@ -258,6 +344,14 @@ func test_full_v7_snapshot_decode() -> void:
"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},
@@ -272,6 +366,10 @@ func test_full_v7_snapshot_decode() -> void:
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)