feat(ui): monologue display — queue, character colours, italic BBCode (#122)

- Queue management: enqueue on mid-display arrival, drain in order, MAX_QUEUE_DEPTH=8 cap, no-overwrite contract (P0 #477)
- Character colours: detective=#c8e0ff (cool blue), smuggler=#f0c870 (warm amber), fallback to neutral insert text
- Typography: italic via BBCode [i] tags; font size 13px (smaller than dialogue)
- Positioning: bottom-left of viewport, 420×120px, 80px bottom margin above verb list
- main.gd: _get_active_character_type() reads player entity kind.data.character_type; passed to show_monologue() and _on_confrontation_monologue()
- gdUnit4 tests: queue order, depth cap, no-overwrite, BBCode output, colour mapping, fade timer integration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 18:31:13 +01:00
co-authored by Claude Sonnet 4.6
parent 4e53a2e979
commit 4365871846
4 changed files with 360 additions and 41 deletions
+22 -2
View File
@@ -288,7 +288,11 @@ func _consume_monologue() -> void:
return
_last_monologue_tick = GameState.current_tick
var mono: Dictionary = GameState.current_monologue
monologue_display.show_monologue(mono.get("text", ""), mono.get("duration_seconds", 5.0))
monologue_display.show_monologue(
mono.get("text", ""),
mono.get("duration_seconds", 5.0),
_get_active_character_type()
)
# #502: Amber flash on room reset
var mono_id: String = mono.get("id", "")
if mono_id.begins_with("room_reset"):
@@ -296,6 +300,22 @@ func _consume_monologue() -> void:
GameState.current_monologue = null
# Derive the active character type from the player entity kind data.
# Server populates kind.data.character_type ("detective" or "smuggler") per D-032.
# Returns "" when the field is absent (display falls back to neutral colour).
func _get_active_character_type() -> String:
for entity in GameState.visible_entities:
if not entity is Dictionary:
continue
var kind = entity.get("kind", {})
if not kind is Dictionary or kind.get("variant") != "Player":
continue
var data = kind.get("data", {})
if data is Dictionary:
return data.get("character_type", "")
return ""
# Consume-once per tick with ID tracking: show dialogue, then clear.
# Tick guard + is_dialogue_active check prevent re-triggering.
func _consume_dialogue() -> void:
@@ -333,7 +353,7 @@ func _on_dialogue_option_selected(response_id: String, text: String) -> void:
# D-063: Handle confrontation beat monologue → show on monologue display (layer 7)
func _on_confrontation_monologue(text: String, duration: float) -> void:
if monologue_display:
monologue_display.show_monologue(text, duration)
monologue_display.show_monologue(text, duration, _get_active_character_type())
# D-064: Handle walk-away → send WalkAway{npc_id} to server