client/scripts/autoloads/game_state.gd — added lattice_profile field (parsed from snapshot)
client/tests/test_monologue_display.gd — 34 tests (new file)
Server dependency note
lattice_profile field ("lattice_augmented" or "lattice_baseline") should be added to ObserverSnapshot. Until then, defaults to "lattice_baseline" (smuggler palette) — no client error.
Test plan
Queue: FIFO drain, depth cap at 5, priority eviction
No-overwrite: mid-display lines not replaced (P0 #477)
Character colours: detective cool blue, smuggler warm amber, unknown fallback
Italic BBCode wrapping on all lines
Empty text guard: no ghost slots, no queue pollution, stagger untouched
GameState: null monologue = no display, round-trip preservation, non-dict rejection
D-049: monologue on CanvasLayer 20 (CANVAS_UI)
## Summary
- **#122 Monologue display — client**: Full implementation of the internal monologue HUD display per D-016, D-032, D-055
- Multi-line architecture: up to 3 simultaneous visible lines in a VBoxContainer, 5-deep priority queue with eviction
- Character-specific text colours via `GameState.lattice_profile` (`lattice_augmented` = detective, `lattice_baseline` = smuggler)
- Italic BBCode rendering, 0.15s stagger between fade-ins, standard/urgent opacity levels
- `main.gd` passes `priority` + `is_urgent` from MonologueEvent; confrontation monologue uses priority 3 + urgent
- Empty-text guard prevents ghost slots or queue pollution
- Bottom-left positioning (420px wide, 80px above verb list)
- **34 gdUnit4 tests**: queue management, priority eviction, stagger timing, character colours, BBCode output, no-overwrite contract, empty-text guard, GameState integration (null/stale clearing, round-trip, non-dict rejection), D-049 canvas layer compliance
## Files changed
- `client/ui/monologue_display.gd` — rewritten: multi-line VBox, priority queue, lattice colour palette
- `client/ui/monologue_display.tscn` — repositioned bottom-left, font size 13px
- `client/scripts/main.gd` — `_consume_monologue()` passes priority/is_urgent; confrontation uses priority 3
- `client/scripts/autoloads/game_state.gd` — added `lattice_profile` field (parsed from snapshot)
- `client/tests/test_monologue_display.gd` — 34 tests (new file)
## Server dependency note
`lattice_profile` field ("lattice_augmented" or "lattice_baseline") should be added to ObserverSnapshot. Until then, defaults to "lattice_baseline" (smuggler palette) — no client error.
## Test plan
- [x] Queue: FIFO drain, depth cap at 5, priority eviction
- [x] No-overwrite: mid-display lines not replaced (P0 #477)
- [x] Character colours: detective cool blue, smuggler warm amber, unknown fallback
- [x] Italic BBCode wrapping on all lines
- [x] Empty text guard: no ghost slots, no queue pollution, stagger untouched
- [x] GameState: null monologue = no display, round-trip preservation, non-dict rejection
- [x] D-049: monologue on CanvasLayer 20 (CANVAS_UI)
show_monologue() now returns early when text.is_empty(), preventing:
- ghost visible slots with blank labels
- stagger timer advancing on empty calls
- empty strings queuing when slots are full
Tests: add test_show_monologue_with_empty_text_does_not_set_displaying and
two companion cases (stagger timer unchanged, no enqueue when full) anticipating
Hoshe's QA additions to the test suite.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Warnings fixed:
- BBCode injection (line 153): escape [ → [lb] in server text before interpolation
- sort_custom on silent-drop (line 125): sort now only runs on actual insertion/replacement
- clip_contents: add clip_contents=true to MonologueDisplay Control (overflow guard)
- confrontation tick guard (main.gd): _last_confrontation_tick deduplicates same-tick signals
- GameState decoupling: show_monologue() reads lattice_profile once and passes it through
_show_line() → _build_line_node(); renderer no longer reaches into autoload (D-020)
- Equal-priority eviction: >= tiebreak (was >); FIFO for equal-priority queue overflow
Suggestions fixed:
- Minimum duration clamp: maxf(duration, FADE_IN_SEC + 0.1) — line survives own fade-in
- _label_text bounds check: guard against empty _visible before indexing [0]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
GameState.lattice_profile(lattice_augmented= detective,lattice_baseline= smuggler)main.gdpassespriority+is_urgentfrom MonologueEvent; confrontation monologue uses priority 3 + urgentFiles changed
client/ui/monologue_display.gd— rewritten: multi-line VBox, priority queue, lattice colour paletteclient/ui/monologue_display.tscn— repositioned bottom-left, font size 13pxclient/scripts/main.gd—_consume_monologue()passes priority/is_urgent; confrontation uses priority 3client/scripts/autoloads/game_state.gd— addedlattice_profilefield (parsed from snapshot)client/tests/test_monologue_display.gd— 34 tests (new file)Server dependency note
lattice_profilefield ("lattice_augmented" or "lattice_baseline") should be added to ObserverSnapshot. Until then, defaults to "lattice_baseline" (smuggler palette) — no client error.Test plan
PR Review: client -> main (type: code)
Hoshe (Code Quality): REQUEST_CHANGES
Well-structured queue/priority/stagger architecture with solid test coverage. Several issues found.
monologue_display.gd:~155[i][color=...]without escaping[/]monologue_display.gd:125sort_custom()runs unconditionally including on the silent-drop pathmonologue_display.tscnclip_contentson container — wrapped long lines could overflow into dialogue boxmain.gd:~195_on_confrontation_monologuebypasses tick guard — no rate limitingtest_monologue_display.gd:~460monologue_display.gdtest_monologue_display.gd:~20_label_texthelper has no bounds check on empty_visibleTyre (Architecture): REQUEST_CHANGES
Core architecture is sound. Z-layer, lattice palette, and D-032 compliance correct. Two architectural concerns.
monologue_display.gd:96-107_build_line_nodereadsGameState.lattice_profiledirectly — hard autoload coupling violates D-020 renderer contract. Pass as parameter instead.monologue_display.gd:126-141>in eviction means default priority-2 lines get lost when queue is full. Should be>=or FIFO tiebreak.test_monologue_display.gd:~460monologue_display.gd:104-107Verdict: CHANGES REQUESTED