feat(client): add interaction prompt system (#405)

Server-driven interaction prompt that displays "E - Talk" when near
an interactable NPC. Decodes v4 nearby_interactions from snapshot,
stores in GameState, renders via InteractionPrompt UI with fade
animation. Extensible interface (get_interaction_target/get_selected_verb)
for future radial verb menu (v0.2).

- Protocol: decode nearby_interactions array with nested VerbOption structs,
  entity relationship/observation fields, tick_rate in GameTime
- GameState: store/clear nearby_interactions per snapshot
- SimBridge: test mode generates v4 format with structured verbs
- InteractionPrompt: PanelContainer with fade in/out, polls GameState
- Tests: 19 new test cases covering protocol, state, sim bridge, UI, encoding
- Fixture assertions updated for v4 protocol version

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-12 23:43:37 +01:00
co-authored by Claude Opus 4.6
parent c970d2eba7
commit 6bebbd9933
17 changed files with 447 additions and 8 deletions
+3 -4
View File
@@ -279,13 +279,12 @@ func test_decode_snapshot_v2_full() -> void:
assert_that(snapshot).is_not_null()
assert_that(snapshot.tick).is_equal(500)
assert_that(snapshot.version).is_equal(2)
assert_that(snapshot.version).is_equal(4)
# game_time
assert_that(snapshot.game_time).is_not_null()
assert_that(snapshot.game_time.day).is_equal(1)
assert_that(snapshot.game_time.time_of_day).is_equal(720)
assert_that(snapshot.game_time.paused).is_false()
# player_facing (unit enum → bare string)
assert_that(snapshot.player_facing).is_equal("Southeast")
@@ -302,12 +301,12 @@ func test_decode_snapshot_v2_full() -> void:
func test_existing_fixtures_have_v2_fields() -> void:
# All fixtures are generated by v2 fixture_snapshot() — verify decoder extracts v2 fields
# All fixtures are generated by fixture_snapshot() — verify decoder extracts v2+ fields
for fixture_name in ["snapshot_one_npc", "snapshot_empty", "snapshot_player", "snapshot_multi_entity"]:
var bytes = _load_fixture(fixture_name)
var snapshot = Protocol.decode_snapshot(bytes)
assert_that(snapshot).is_not_null()
assert_that(snapshot.version).is_equal(2)
assert_that(snapshot.version).is_equal(4)
assert_that(snapshot.player_facing).is_equal("North")
assert_that(snapshot.game_time).is_not_null()