14 tests covering _octant_to_direction (all 8 octants + 2 fallbacks)
and _entity_direction (NPC default south, player facing 3 cases).
Closes review warning on zero test coverage for direction system.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add push_error on null texture at create time, keep previous texture
on null at update time (entity stays visible mid-game). Add push_warning
on unrecognised octant in _octant_to_direction fallback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Migrate entity rendering from ColorRect placeholders to Sprite2D with
rendered PNGs at -72.5° from horizontal. Key changes:
- Sprite2D.centered=false, scale=0.5 for 64px source → 32px runtime
- self_modulate for D-033 relationship tinting (modulate.a reserved
for D-015 peripheral dimming)
- 8-octant to 4-cardinal direction mapping for sprite selection
- Feet-anchored ENTITY_OFFSET_Y for correct y-sort with tilted sprites
- Facing indicator repositioned to sprite local center (32,32)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Build _tile_by_coord dictionary from member visible_tiles (covers both
test-mode "tiles" key and live-server "visible_tiles" key), then replace
the linear scan with a single dict lookup. Net-zero complexity: adds one
dict-set per tile in an existing iteration, removes the separate scan loop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tyre #1: Extract run_dialogue_pipeline() shared helper — eliminates ~60
lines of duplication between process_talk_interaction and
process_dialogue_response (L1-L4 pipeline).
Hoshe #1: process_dialogue_response now updates ActiveDialogue with
current tick on follow-up selection — prevents stale started_tick.
Tyre #4: process_dialogue_response now updates InteractionMemory on
follow-up — multi-turn conversations are visible in history.
Hoshe #6 / Tyre #6: handle_dialogue_response adds server-side range
check (CLOSE_RANGE), matching Talk/Confront pattern (D-010 info
boundary).
Hoshe #2: Weighted selection fallback replaced with unreachable!() —
score_line always returns >= 1, so the fallback was dead code.
Hoshe #3: assert!(false, ...) → panic!() in serialization.rs (clippy).
Hoshe #4: SetFacing and TeleportToHub added to roundtrip test.
Hoshe #5: setup_dialogue_response_world inlined (trivial pass-through).
Tyre #2: Doc comment on DialogueCooldownTracker explains per-player-global
design choice (line IDs are NPC-scoped per D-035, no collision risk).
Tyre #3: CONFRONTATION_LINES comment updated with TODO for D-028/D-035
migration.
Tyre #5: DialogueResponse fixture added for cross-language GDScript
testing (input_dialogue_response.msgpack).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Runtime 64px sprites rendered through the new pipeline: generic NPC
(4 directions) and structural wall (4 directions). These unblock
client ticket #540 for sprite camera angle integration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- dialogue_box.gd: panel is always visible as permanent insert UI
element per D-061 — content fades but frame stays on screen
- protocol.gd: bump PROTOCOL_VERSION to 13
- Makefile: add check-protocol target that verifies server/client
protocol versions match, runs automatically before build
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The 1200px value was a deliberate readability decision, not a spec
violation. Reverts the incorrect 640px change from round 1. Updates
comment and regression test to match.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove stale smoothing re-enable comments from main.gd (Hoshe #1).
Add DIALOGUE_MAX_WIDTH=640 regression test (Hoshe #2).
Extract GROUND_FLOOR const in tile_renderer (Tyre #3).
Clean up entity_renderer migration comment (Hoshe #3).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Regenerate all msgpack test fixtures with tell_state and follow_state
fields. Update golden proof_room snapshot. Adjust serialization and
bridge tests for new ObserverSnapshot fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical: DIALOGUE_MAX_WIDTH 1200 → 640 to match D-076 spec.
tile_renderer: clarify z = server floor level, not scene z_index.
Add z-filter unit test (tiles at z!=0 must be skipped).
Camera test: is_equal → distance check for float safety, convergence
test frames 40 → 120 for robustness at lower smoothing speeds.
Teleport: remove redundant first snap in _teleport_transition (the
camera block in _process handles it via _teleport_in_progress flag).
entity_renderer: document y-sort bottom-anchor migration path.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Godot built-in Camera2D smoothing with manual lerp using
CAMERA_SMOOTHING_SPEED (8.0) in constants.gd — same exponential
smoothing pattern as entity_renderer.gd. Teleport snap preserved
via _teleport_in_progress flag. D-015 fixed-north camera lock intact.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
tile_renderer: only render z=0 tiles on FloorTiles layer (D-049 z-stack).
entity_renderer: fix footprint from 24x24 to 24x32 per D-044, split
ENTITY_SIZE into ENTITY_WIDTH/ENTITY_HEIGHT with separate offsets.
game_state: add follow_target_id stub for server ticket #241.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New .uid files from Godot 4 and updated .import metadata for
audio assets and fonts added during Sprint 14.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Regenerated all cross-language test fixtures to include new
conversation_events wire fields (speaker/target name, color index).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- bug_report_dialog: release_focus() before queue_free() prevents
stale focus on ModalLayer blocking input to lower layers
- dialogue_box: clear dialogue_active immediately on conversation
end instead of holding for entry_lifetime
- main.gd: permanent entity ID tracking per room stops recognition
chime re-triggering when NPCs oscillate in/out of fog queue
- audio_manager: correct path res://audio/ to res://assets/audio/
- world_radial.tscn: anchors_preset 15 to 0 (script sets size)
- bug_report_dialog: push_warning to print (informational)
- debug_overlay: max() to maxi() for integer line count
- monologue_display: remove unnecessary Tween cast
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Critical: bump PROTOCOL_VERSION 8→9 for conversation_events/ended fields.
Hoshe review:
- Dirty flag (_log_dirty) prevents per-frame O(n) BBCode rebuild
- BBCode injection: _escape_bbcode() replaces [ with [lb] on server text
- D-064 regression: dialogue_active cleared in fade callback, not before
- YAML quoting: remove unnecessary quotes from numeric values
Tyre review:
- Carry-forward for dialogue_response, conversation_events, conversation_ended
in receive_bytes() — arrays merge, scalar falls through
- pause_requested/unpause_requested signals route through main.gd input
recording (_pending_record_inputs) for #507 replay determinism
- Fix version comments: dialogue_response is v8 (#305), not v9
- Remove dead _active_overheard dictionary
Araminta review:
- Passive lines: ┃ glyph prefix + _desaturate() for name colours
- Active conversation entries pinned (no timeout), unpinned with timestamp
reset on conversation end
- _enforce_contrast(): minimum luminance floor for name colour readability
- Simplified 1-on-1 attribution: "Speaker:" instead of "Speaker → You:"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactors dialogue box into a scrolling conversation log. All dialogue
(player-NPC and overheard NPC-NPC) flows chronologically, oldest at top.
Player response options at the bottom during active conversations.
- Entries expire after configurable timeout (equal for all message types)
- Walk-away clears options but preserves log entries (fair information)
- Per-character name colors from dialogue-theme.yaml (hash-indexed palette)
- Overheard lines render at 90% opacity (D-078)
- Protocol decode for conversation_events + conversation_ended
- GameState fields for conversation_events, conversation_ended, dialogue_response
- Mock Mira/Soren NPC-NPC conversation in test snapshot
- Also wires #511 debug overlay into main.gd and main.tscn
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical: bump PROTOCOL_VERSION 8→9 for conversation_events/ended fields.
Hoshe review:
- Dirty flag (_log_dirty) prevents per-frame O(n) BBCode rebuild
- BBCode injection: _escape_bbcode() replaces [ with [lb] on server text
- D-064 regression: dialogue_active cleared in fade callback, not before
- YAML quoting: remove unnecessary quotes from numeric values
Tyre review:
- Carry-forward for dialogue_response, conversation_events, conversation_ended
in receive_bytes() — arrays merge, scalar falls through
- pause_requested/unpause_requested signals route through main.gd input
recording (_pending_record_inputs) for #507 replay determinism
- Fix version comments: dialogue_response is v8 (#305), not v9
- Remove dead _active_overheard dictionary
Araminta review:
- Passive lines: ┃ glyph prefix + _desaturate() for name colours
- Active conversation entries pinned (no timeout), unpinned with timestamp
reset on conversation end
- _enforce_contrast(): minimum luminance floor for name colour readability
- Simplified 1-on-1 attribution: "Speaker:" instead of "Speaker → You:"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Refactors dialogue box into a scrolling conversation log. All dialogue
(player-NPC and overheard NPC-NPC) flows chronologically, oldest at top.
Player response options at the bottom during active conversations.
- Entries expire after configurable timeout (equal for all message types)
- Walk-away clears options but preserves log entries (fair information)
- Per-character name colors from dialogue-theme.yaml (hash-indexed palette)
- Overheard lines render at 90% opacity (D-078)
- Protocol decode for conversation_events + conversation_ended
- GameState fields for conversation_events, conversation_ended, dialogue_response
- Mock Mira/Soren NPC-NPC conversation in test snapshot
- Also wires #511 debug overlay into main.gd and main.tscn
Co-Authored-By: Claude Opus 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>
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>
Godot requires .import files for stable uid:// references across
editor sessions. All other audio assets have one — this was missed
in the audio branch PR.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove premature Voice/VoiceConversation from SOUND_EVENT_ASSETS (D-072
defers murmur to end-to-end sprint). Extract current_zone_id in
GameState.apply_snapshot() as first-class field, eliminating O(N) tile
scan in main.gd (D-020 server-authoritative). Add dir.list_dir_end()
after registry scan. Add rapid zone-crossing + _load_prefs() roundtrip
tests. Enhance comments on hub/workplace same-asset pattern, station
base hum, and confrontation dip replacement semantics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
57 new tests in test_audio_sprint13.gd covering bus setup, dip profiles,
zone crossfade, recognition chime, and murmur wiring. Fix lambda capture
bug in 3 signal tests (String → Array wrapper for reliable closure
capture in Godot 4). Add bus volume reset to before_test() for test
isolation against prefs-loaded state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AudioManager: volume slider persistence (ConfigFile), settings UI with
5 teal-bordered sliders, default_bus_layout.tres for editor. Zone
crossfade: defensive zone_id read from snapshot tiles, 1.5-2s ambient
tween, auto-activates when server ships OQ-09. Dip profiles: dialogue
dip in show/hide_dialogue, ListeningFocus 30-tick gate via
stationary_ticks in game_state.gd. NPC murmur: client plumbing for
event-driven World SFX playback, no-ops until audio asset arrives.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server-tracked zone_id on VisibleTile for D-073 zone crossfade and
D-059 deep fog temperature tint. ZoneMap resource backed by BTreeMap,
observer enrichment in snapshot assembly. Backwards-compatible: v10
payloads deserialize with zone_id: None.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Produces sfx_npc_murmur.ogg via Stable Audio Open for D-072 universal
event-driven NPC proximity murmur. Organic sonic family (D-074): warm
mid-freq 200-800Hz vocal texture, human voices in adjacent room with
direction and cadence distinguishable, words not. World SFX bus.
Generated 3 candidates (5s/CFG8, 6s/CFG7, 4s/CFG9), selected candidate
1 for richest texture and clearest spatial framing. Unblocks client #533.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix SOUND_EVENT_ASSETS walk-speed keys to match actual filename
(sfx_footstep_metal_walk), add play_loop null guard, source indicator
colors from Constants, extract CAMERA_DEFAULT_ZOOM, document
consume-once semantics on close_sound_events.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- main.gd: add has("entity_id") guard to _play_recognition_chimes()
(matches defensive pattern in _play_close_sound_events and update_sound_events)
- sound_indicator_renderer.gd: rename _color_for_type → color_for_type
(public testable API, not an internal-only method)
- test_rendering.gd: update test calls to match rename
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds rng_seed: Option<u64> to ObserverSnapshot. The WRONG button (#507) captures
inputs.jsonl and seed.txt for replay, but seed.txt was writing "unavailable"
because the server did not include the RNG seed in ObserverSnapshot.
Changes:
- bridge/types.rs: PROTOCOL_VERSION 9→10, rng_seed field with serde(default,
skip_serializing_if = "Option::is_none") for backward compatibility
- perception/observer/mod.rs: inject Res<SimRng> into compute_observer_snapshot,
populate rng_seed: Some(rng.seed()) each tick
- All test files: add rng_seed: None to ObserverSnapshot constructors
- tests/serialization.rs: bump protocol_version_constant assertion 9→10
- Regenerate msgpack fixtures and golden file for protocol v10
Completes the WRONG button capture loop: replays can now fully reproduce
observed bugs with the exact RNG seed from the capture.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DIALOGUE_MAX_WIDTH: correct to 640px (20 × TILE_SIZE) per Tyre architecture
review. D-076 updated in decisions/perception.md with amendment note.
Initial 1920px was D-061 "max-width" but readability wins at 640px.
D-067 recognition chime: wire sfx_monologue_chime to fog entity recognition
onset. AudioManager.CHIME_RECOGNITION constant added. main.gd tracks seen
entity IDs in _known_recognition_ids; fires chime on first appearance in
pending_recognitions, expires when entity leaves the queue. UISounds bus
(not WorldSFX) per D-038 "monologue chime is a UI sound." Tests added
to test_audio_bus_routing.gd (Layer 2b).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
#345: entity_renderer.gd already used entity_id; added regression tests
confirming old "id" field is rejected and "entity_id" is accepted.
#447 (OQ-29): DIALOGUE_MAX_WIDTH = 1920 added to constants.gd. Full
viewport width at target resolution (60 × TILE_SIZE), per D-061 Lead
directive "max-width". Recorded as D-076 in decisions/perception.md.
#126: SoundIndicatorRenderer — fog-edge directional arrows for medium-range
sound events (D-018). Node2D at z:951 in World scene. Color-coded per
D-018/D-069 (neutral/voice/danger). GameState.medium_sound_events
partitions Medium events from snapshot sound_events field. Tests added
to test_rendering.gd; Hoshe's test_sound_indicators.gd stubs updated.
#125: Close-range stereo audio pipeline wired. AudioManager.play_sound_event()
maps event_type to D-038 asset key (Footstep/FootstepSprint → sfx_footstep_*).
GameState.close_sound_events partitions Close events. main.gd calls
_play_close_sound_events() each snapshot tick. test_audio_bus_routing.gd
Layer 4 stubs upgraded to real tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>