feat(ui): entity-anchored dialogue log with keyboard selection

Log entries store entity IDs and resolve display names at render
time from _entity_display lookup — enables retroactive name update
when player learns an NPC's real name. Color index from server
replaces name-hash coloring for stable NPC colors.

Dialogue options: switched RichTextLabel to Label (fixes stacking
bug), added 1/2/3 number key selection, numbered option labels.

Interaction list: added background panel, mouse hover highlighting,
click-to-interact, pointing hand cursor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-21 13:43:08 +01:00
co-authored by Claude Opus 4.6
parent 28e7e6d9ad
commit 03ff3c5fef
6 changed files with 204 additions and 45 deletions
+1 -1
View File
@@ -94,7 +94,7 @@ const FACING_INDICATOR_OFFSET: float = 14.0
# 640px = 20 × TILE_SIZE (32px) — grid-aligned, ~33% of 1920px viewport.
# Tyre architecture review 2026-02-19: readability over max-width; fits
# two columns of text comfortably, leaves world game visible alongside.
const DIALOGUE_MAX_WIDTH: int = 640
const DIALOGUE_MAX_WIDTH: int = 1200
# Default camera zoom — used as fallback when get_camera_2d() returns null
const CAMERA_DEFAULT_ZOOM: Vector2 = Vector2(2.0, 2.0)
+9 -9
View File
@@ -250,22 +250,16 @@ func _play_close_sound_events() -> void:
# D-067: Recognition chime — fires sfx_monologue_chime when a fog entity
# enters the cognitive delay recognition queue for the first time.
# "The chime marks the character's attention shifting" (D-067).
# Entities that complete recognition (leave pending_recognitions) are removed
# from _known_recognition_ids so they can chime again if re-encountered.
# IDs persist for the session — one chime per entity, no re-trigger on
# fog oscillation or server re-send. Cleared on room change (teleport).
func _play_recognition_chimes() -> void:
var active_ids: Dictionary = {}
for rec in GameState.pending_recognitions:
if not rec is Dictionary or not rec.has("entity_id"):
continue
var eid: int = rec.entity_id
active_ids[eid] = true
if not _known_recognition_ids.has(eid):
_known_recognition_ids[eid] = true
AudioManager.play(AudioManager.CHIME_RECOGNITION)
# Expire IDs no longer in the recognition queue
for eid in _known_recognition_ids.keys():
if not active_ids.has(eid):
_known_recognition_ids.erase(eid)
# D-073 (#529): Zone ambient crossfade — reads zone_id from GameState.current_zone_id
@@ -358,11 +352,16 @@ func _consume_conversation_ended() -> void:
# #535: Consume dialogue_response — NPC follow-up line after player picks an option.
# Updates dialogue_box entity display registry with speaker identity from the wire.
func _consume_dialogue_response() -> void:
if GameState.dialogue_response == null or not dialogue_box:
return
var dr: Dictionary = GameState.dialogue_response
dialogue_box.append_dialogue_response(_last_dialogue_npc_name, dr.get("text", ""))
var speaker_entity_id: int = dr.get("speaker_entity_id", _last_dialogue_npc_id)
var speaker_color_index: int = dr.get("speaker_color_index", -1)
var speaker_name: String = dr.get("speaker_name", _last_dialogue_npc_name)
dialogue_box.update_entity_display(speaker_entity_id, speaker_name, speaker_color_index)
dialogue_box.append_dialogue_response(speaker_name, dr.get("text", ""))
GameState.dialogue_response = null
@@ -446,6 +445,7 @@ func _teleport_transition() -> void:
GameState.current_monologue = null
GameState.current_dialogue = null
GameState.dialogue_active = false
_known_recognition_ids.clear() # D-067: reset chimes for new room
if dialogue_box and dialogue_box.is_dialogue_active():
dialogue_box.hide_dialogue()
+1 -1
View File
@@ -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 = 9
const PROTOCOL_VERSION: int = 12
# -- Decode: bytes from server → GDScript types --------------------------------