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
+9
View File
@@ -19,6 +19,9 @@ var visibility_sectors: Dictionary = {} # Vector2i -> "Forward"/"Peripheral"
# refined when the server assigns explicit player entity IDs).
var player_entity_id: int = 1
# v4 fields (#404/#405)
var nearby_interactions: Array = [] # [{entity_id, entity_type, distance, verbs: [{kind, label, priority, available}]}]
func apply_snapshot(snapshot: Dictionary) -> void:
current_snapshot = snapshot
@@ -54,6 +57,12 @@ func apply_snapshot(snapshot: Dictionary) -> void:
if snapshot.has("player_facing") and snapshot.player_facing is String:
player_facing = snapshot.player_facing
# v4: nearby_interactions (#404/#405)
if snapshot.has("nearby_interactions") and snapshot.nearby_interactions is Array:
nearby_interactions = snapshot.nearby_interactions
else:
nearby_interactions = []
# 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:
+16 -2
View File
@@ -294,20 +294,34 @@ func _test_snapshot() -> Dictionary:
"visibility": sector,
})
# v4: nearby_interactions when NPC is nearby and visible (#404/#405)
var nearby: Array = []
if npc_dist <= 2 and _test_has_los(Vector2i(px, py), npc_pos):
nearby.append({
"entity_id": 2,
"entity_type": "Npc",
"distance": npc_dist,
"verbs": [
{"kind": "Talk", "label": "Talk", "priority": 0, "available": true},
{"kind": "ExamineNpc", "label": "Examine", "priority": 1, "available": true},
],
})
return {
"tick": _test_tick,
"version": 2,
"version": 4,
"game_time": {
"day": 0,
"time_of_day": _test_tick * 10,
"day_phase": "Morning",
"paused": false,
"tick_rate": "Full",
},
"player_facing": _test_facing,
"entities": entities,
"tiles": _test_tiles(),
"visible_tiles": _test_visible_tiles(),
"visible_positions": _test_visible_positions(),
"nearby_interactions": nearby,
}
# Generate a small test room: 8x6 room with walls, a door, and floor