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>
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>
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>
Option (a): cursor shape still changes (body orients to targets), but
verb labels and interaction prompts are suppressed when insert_active
is false. Amends D-056 and D-057 with resolution note.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hoshe:
- COLOR_FADE_DURATION 0.7 → 0.5 to match D-033 spec ("0.5s fade")
- Gauntlet guard tests now exercise InputMapper._unhandled_input()
with synthesized InputEventKey instead of asserting a bool
- Buffer clearing tests use SimBridge pipeline instead of manual nulls
- Add mid-transition re-trigger test (rapid relationship changes)
- Add relationship field to test snapshot NPC
Tyre:
- Add _teleport_in_progress flag to defer smoothing re-enable by one
frame after teleport (prevents same-_process() re-enable race)
- Add _test_gauntlet_mode to SimBridge test snapshot
- Extract TELEPORT_DISTANCE_THRESHOLD constant, mirror in tests
- Add comments: flash preemption, modulate/color independence
- Rename "hub teleport" → "Gauntlet dev teleport" in code comments
to clarify this is not production fast-travel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Home key sends TeleportToHub in Gauntlet mode. Camera snaps to hub
spawn with 0.3s fade-from-black. Clears dialogue/monologue buffers
on teleport. Teleport detection uses distance threshold (>5 tiles)
so future teleport types get the transition for free.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Warnings: facing indicator tests use Godot-normalized rotation range
(-PI, PI] instead of raw addition (SW/W/NW in test_rendering,
West in test_client_p3). Suggestions: cache font in world_radial
_draw(), fix docstring on deactivate_insert() trigger, document
tile-coordinate system on _eval_player_near (D-066), add public
reset_facing_state() to InputMapper (D-030 testability).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical:
- deactivate_insert() now called when selecting non-Insert spoke,
cancelling with no selection, or pressing Escape while insert is
active. Fixes simulation staying paused permanently after Insert.
Warnings:
- Checklist conditions with empty id excluded from get_results() and
get_total_count() — prevents impossible-to-complete checklists.
Warns at load time when empty-id conditions are found.
- _content_base now checks res://content/ first (exported builds),
falls back to ../content for editor/dev mode.
- 26 new tests for D-054 functions: _angle_to_octant (8 octants),
_snap_to_octant_dir (9 cases incl. zero/tiny), _wasd_to_world_dir
(8 facing/movement combos). New test file: test_input_mapper_facing.gd.
Suggestions:
- Cached get_theme_default_font() in checklist overlay _ready().
- Documented InputMapper → GameState coupling as intentional.
- Documented YAML parser # truncation limitation.
- _insert_active reset on Escape dismiss (Tyre #3).
- SimBridge test mode SetFacing reads action_data.facing instead of
InputMapper global (Tyre #4).
- Removed dead _facing_to_rotation() from entity_renderer.gd (Tyre #5).
- Fixed 2 failing facing indicator tests to use InputMapper.facing_angle
instead of GameState.player_facing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mouse position now determines facing direction as a client-side float.
WASD remapped: W=toward cursor, S=away, A/D=strafe. Facing octant
derived from mouse angle and sent to server via SET_FACING action only
when it changes. EntityRenderer facing indicator uses continuous angle
for smooth rotation. SimBridge test mode updated to handle SetFacing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generated by headless --import after audio merge. These are needed
for stable UID references across editor sessions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Cast Variant to String via str() before passing to _on_room_change
- Clear _current_room_id on null room transition (fixes re-entry skip)
- Add push_error for failed dir creation and file writes in _save_report
- Fix docstring: tests/gauntlet-stats.json → user://dev/gauntlet-stats.json
- Namespace stats path to user://dev/ to avoid save data collision
- Replace print() with push_warning in _save_report (codebase consistency)
- Downgrade client-only wire guard from push_warning to silent return
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
F12 pauses simulation, shows modal LineEdit prompt, saves three files
to user://bug-reports/gauntlet-t{tick}-{timestamp}/: snapshot.json
(full ObserverSnapshot), render.txt (simplified client-side text
render), description.txt (tester notes + tick/room/stance metadata).
Esc cancels without saving. Double-activation guard prevents stacking.
BUG_REPORT action added to InputMapper with wire guard in SimBridge
(client-only, never sent to server). Dialog on ModalLayer (CL 30).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GauntletHUD in UILayer shows TIMER: MM:SS (PB: MM:SS). Timer starts on
room entry, resets on room change, records personal bests to
user://gauntlet-stats.json. Session summary printed on disconnect.
Hidden in non-gauntlet mode (anti-tedium guard).
Adds room_id and gauntlet_mode fields to GameState, parsed from
ObserverSnapshot. Main.gd wires update_from_state() and finalize().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical fixes:
- hide_dialogue() sent PAUSE instead of UNPAUSE, permanently freezing
simulation after every dialogue (both reviewers)
- Confrontation monologue hardcoded in GDScript constant, violating
D-042/D-020 — moved to ui-strings.yaml as dialogue.confrontation_beat
- Walk-away WASD didn't call set_input_as_handled(), letting movement
event propagate and potentially stepping on the same frame
- is_dialogue_active() returned _is_showing only — interaction list
could flash during 300ms fade gap. Now includes dialogue_active state
- Removed dead _last_dialogue_id / get_dialogue_id() state (never read)
Warnings addressed:
- Audio registry now scans res://audio/ recursively (subdirs registered)
- add_bus_effect guarded against duplicate calls in tests
- int64 encoder dead code tagged KNOWN-DEFECT, filed as ticket #516
- D-073 zone crossfade stub comment clarifies Sprint 9+ deferral
- listening_focus dip documents caller tick-gate responsibility (D-069/D-071)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Define VIS_HIDDEN/PERIPHERAL/FORWARD and EXP_UNEXPLORED/EXPLORED/
VISIBLE in FogState. Replaces 7 magic number usages in _resize() and
update_from_state(). Provides stable assertion targets for fog tests.
Implements #476.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three client input/bridge fixes:
1. Pause toggle: add UNPAUSE action and toggle logic based on
GameState.game_time.tick_rate. Wire UNPAUSE in sim_bridge.gd.
2. Hold-to-move: replace press-event movement with polled _process()
direction sampling. Composite diagonals via simultaneous keys
(W+D → northeast). Client-side throttle per stance (D-053):
Sprint=200ms, Walk=400ms, Careful=600ms, Crouch=800ms.
3. Snapshot carry-forward: when a newer snapshot overwrites an
unconsumed one, carry forward current_monologue and current_dialogue
so one-shot events aren't silently dropped.
Fixes bugs #5 (monologue lost on overwrite) and #6 (overwrite spam).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fog entity cognitive delay rendering (D-059/D-060): sonar-style sound
pings (3 concentric rings, 1.5s fade), unrecognized grey blobs with
breathing pulse, D-033 color transition at 50% recognition progress,
±0.5 tile position drift. FogEntities node at z:950 between fog
shader and InsertOverlay.
Protocol v7 bump to match server PR #23 (pending_recognitions field).
Wires dialogue box and fog entities into game loop with mock test data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Upgrade client protocol bridge from v5 to v6 to match server.
Adds player_stance (4 variants) and player_inventory decode to
ObserverSnapshot. Adds TOGGLE_STANCE_UP/DOWN to InputMapper.
Includes 25 gdUnit4 tests for v6 decode + server serialization
test gap fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
These are auto-generated by Godot 4 to provide stable resource
references across renames. Tracking them prevents UID divergence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three fixes to make the gameplay loop functional end-to-end:
- Add Interactable component to NPC spawn so E-prompt detection works
- Build monologue trigger system (enter_location + time_idle) with
MonologueBuffer/MonologueState components, wire through ObserverSnapshot
as current_monologue field, decode on client and display via HUD
- Change PlayerAction::Interact from unit to struct variant carrying
optional target_entity_id and verb fields
Bumps protocol version from 4 to 5. Regenerates MessagePack fixtures.
All 200 tests pass (170 unit + 30 integration).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The copy team rewrote ui-strings.yaml with multi-level nesting,
renamed sections, and inline comments. Update the YAML parser to
use a stack-based approach for arbitrary nesting depth, update HUD
and interaction prompt to reference the new key names, and align
tests with the new structure.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds UIStrings autoload that loads display text from a YAML file,
replacing hardcoded strings in HUD and interaction prompt. Copy team
can now author UI microcopy in client/data/ui-strings.yaml without
touching GDScript. Includes 38 strings across 5 categories and 16
gdUnit4 tests for the parser and lookup API.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Client was sending Time.get_ticks_msec() (e.g. 12345) as the input
tick, but the server's drain_for_tick only processes inputs where
tick <= current_tick (a small frame counter). Inputs accumulated in
the queue and were never processed, making movement keys unresponsive
in live mode. Use GameState.current_tick from the latest snapshot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Protocol decoder now reads tile_kind from VisibleTile and maps it to
the client's tile type string (floor/wall/door/object). GameState
falls back to visible_tiles when the test-mode tiles array is absent,
enabling live server tile rendering.
Fix entity-to-tile alignment: server sends tile-center render coords
(tile 16 -> 16.5) but entity renderer was using raw floats, placing
entities half a tile off. Now floors the coords to get the tile index.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add SR_LIVE=1 environment variable to switch SimBridge from test mode
to TCP connection. Default behavior unchanged (test mode).
- sim_bridge.gd: read SR_LIVE env var instead of hardcoded test_mode
- game_state.gd: find player entity by kind.variant == "Player" instead
of hardcoded entity_id 1 (real server assigns different IDs)
- Makefile: add 'make game' (builds server, starts it, launches client
with SR_LIVE=1, kills server on exit) and 'make stop' helper
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server team shipped strict PROTOCOL_VERSION enforcement (c05ff7b),
1-indexed verb priorities, and "Observe" label for ExamineNpc.
Updates all test snapshots to include version: 4, fixes sim_bridge
test mode priorities (0-indexed → 1-indexed) and labels, replaces
v1 backward-compat tests with strict version rejection tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Client now rejects snapshots where version != PROTOCOL_VERSION (4).
Returns null with error log on mismatch. Test snapshot updated to
use Protocol.PROTOCOL_VERSION and v4 game_time format (tick_rate
replaces paused field).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three E2E proof tests verify all acceptance criteria through the real
server pipeline: player movement, v2 snapshot with visible_tiles,
wall hiding (NPC behind wall invisible), corner reveal (move around
wall to see NPC). Dynamic test snapshot tracks player position from
queued inputs with simple LOS and Manhattan-distance visibility for
standalone demo mode. 88 client + 112 server tests passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server shipped ObserverSnapshot v2 with game_time, player_facing,
visible_tiles (with visibility sectors), and per-entity visibility.
Protocol decoder was silently ignoring these fields. Now extracts
all v2 data with null defaults for backward compatibility.
GameState gains game_time, player_facing, visibility_sectors vars.
Derives visible_positions from visible_tiles when present (for real
server mode). Test snapshot updated with v2 fields.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sprint 2 rendering pipeline: tiles (#129), fog (#131), camera (#116).
- tile_renderer.gd: programmatic TileSet with floor/wall/door/object
placeholder tiles, renders from snapshot tile data
- fog_renderer.gd: TileMapLayer overlay with three visibility states
(visible/fog-edge/hidden), computed from visible_positions data
- Camera2D: smoothing enabled (speed 6.0), 2x zoom, locked to player
- game_state.gd: stores visible_tiles and visible_positions from snapshots
- sim_bridge.gd: test data with 8x8 room, corridor, and Manhattan
distance visibility for development without server
- Scene render order: Tiles -> FogOverlay -> Entities
- Background clear color set to near-black for unexplored areas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hoshe #1: E2E test now uses random ephemeral port (49152-65535) with
port rotation on bind failure, avoiding conflicts in parallel CI.
Hoshe #2: Documented intentional input drop on encode failure in
SimBridge — re-queuing would retry bad data and server tick has
already advanced.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server expects a MessagePack array of PlayerInput objects in one framed
message per tick, not individual inputs per frame. Added
Protocol.encode_player_inputs() for batch encoding. Changed SimBridge to
buffer raw input dicts and batch-encode in _process(). Also fixed server
port default (9876) and positional arg format to match server CLI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical fixes from Hoshe review:
1. Race condition: defer TCP connect to _process() with retry logic
(MAX_CONNECT_RETRIES=20, 100ms interval) so server has time to bind.
2. Buffer corruption: disconnect on oversized message instead of
clearing valid buffered data (_corrupt flag, fail-safe).
3. Silent input drop: send_input() returns Error so callers can detect
encode/validation failures.
Warnings addressed:
- ServerProcess validates server_path exists before spawning
- SIGKILL and health check TODOs documented for future work
- Diagonal keybindings documented as intentional deferral
- send_message uses single put_data() call (no partial write risk)
- Static frame helpers documented as D-030 Layer 2 test-only
New tests (36 total, up from 33):
- Partial read scenario (chunked TCP delivery)
- Multi-message sequential decode (exercises buffer corruption fix)
- send_input error return on invalid action
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wire SimBridge to use LocalBridge for TCP transport in non-test mode:
_process() polls for incoming snapshots and flushes outbound inputs.
Add 4 diagonal movement variants (NE, SE, SW, NW) to InputMapper and
wire mapping, ordered clockwise. Register diagonal input actions in
project.godot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- decode_snapshot() reports dropped entities via push_error and returns
decode_errors count so callers can detect partial data (D-010
information boundary compliance)
- receive_bytes() warns when overwriting unconsumed snapshot, documents
latest-wins semantics
- Rename misleading test to test_encode_produces_nonempty_bytes
- Fix tick rate comment: 10 ticks/game-minute per D-031
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hoshe critical fixes:
- _action_enum_to_wire uses InputMapper.Action constants instead of
fragile integer literals; OPEN_MENU explicitly handled as client-only
- Remove int() coercion on tick/entity_id — use direct assignment since
GDScript int is signed 64-bit (safe for realistic tick values)
- Check encode result before buffering in send_input() — reject empty
bytes instead of corrupting the outbound stream
- Test snapshot now uses Protocol format {tick, entities} instead of
legacy schema; GameState updated to derive player position from
entity data; main.gd and world_renderer.gd updated accordingly
Hoshe warnings:
- 5 negative tests added (truncated bytes, wrong type, missing fields,
empty bytes, encode validation) — 20/20 tests pass
- receive_bytes signal is emitted at consume time in poll_snapshot by
design (documented in code)
Tyre suggestions:
- Remove duplicated root-level fixtures — single source of truth in
client/tests/fixtures/msgpack/
- gen_fixtures.rs writes directly to client/ directory
- Add `make fixtures` target for regeneration
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SimBridge now encodes player inputs via Protocol.encode_player_input()
and exposes receive_bytes()/drain_outbound() for the transport layer.
Test mode still works unchanged. Transport (ticket #79) will call
these methods to complete the IPC pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix input_mapper double-check bug (redundant InputEventKey + pressed filter)
- Add bounds checking for position arrays in entity_renderer and game_state
- Make test snapshot deterministic (incrementing counter, not wall clock)
- Fix tween overlap in monologue_display (cancel active tween before new one)
- Extract TILE_SIZE constant from magic number 32
- Add 5 D-030 Layer 1 fixture tests for snapshot parsing (7/7 total passing)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>