fix(client): apply arch review corrections — 640px dialogue width, D-067 chime
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>
This commit is contained in:
@@ -5,6 +5,11 @@ extends Node
|
||||
## No-op fallback when audio assets absent (D-038).
|
||||
## Spatial audio positioning for close-range sounds (D-018).
|
||||
|
||||
# --- D-067: Recognition chime asset key ---
|
||||
# Fires on first fog recognition (cognitive delay onset). UISounds bus (not WorldSFX).
|
||||
# Matches sfx_monologue_chime.ogg from D-038 — "neural lattice firing" feel.
|
||||
const CHIME_RECOGNITION := "sfx_monologue_chime"
|
||||
|
||||
# --- Bus names (D-068) ---
|
||||
const BUS_MUSIC := "Music"
|
||||
const BUS_AMBIENT := "Ambient"
|
||||
|
||||
@@ -91,10 +91,10 @@ const FACING_INDICATOR_SIZE: float = 6.0
|
||||
const FACING_INDICATOR_OFFSET: float = 14.0
|
||||
|
||||
# D-076 (OQ-29 resolution): Dialogue box max-width in pixels.
|
||||
# Target resolution: 1920×1080. Full viewport width = 60 × TILE_SIZE (32px).
|
||||
# Lead directive (D-061): "max-width" — full screen, not centered 50%.
|
||||
# Text readability is managed via font size and internal UI node padding.
|
||||
const DIALOGUE_MAX_WIDTH: int = 1920
|
||||
# 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
|
||||
|
||||
# #517: Implant UI font color grading — avoid pure white, project through a lens
|
||||
const IMPLANT_TEXT_COLOR: Color = Color("#E0F7FA") # Cyan-white — primary text
|
||||
|
||||
@@ -20,6 +20,7 @@ var _last_dialogue_npc_id: int = -1 # D-064: NPC entity_id for WalkAway input
|
||||
var _camera_anchored: bool = false
|
||||
var _last_monologue_tick: int = -1 # Prevent re-consuming monologue when same tick polled twice
|
||||
var _last_dialogue_tick: int = -1
|
||||
var _known_recognition_ids: Dictionary = {} # D-067: entity_ids that have already chimed
|
||||
var _flash_rect: ColorRect = null # #502/#501: ephemeral screen flash overlay (shared: teleport preempts amber)
|
||||
var _teleport_in_progress: bool = false # #501: defer smoothing re-enable by one frame after teleport
|
||||
var _pending_record_inputs: Array = [] # #507: accumulates server-bound inputs across frames; flushed into record_tick() on snapshot arrival
|
||||
@@ -113,6 +114,9 @@ func _process(_delta: float) -> void:
|
||||
if fog_entities and fog_entities.has_method("update_from_state"):
|
||||
fog_entities.update_from_state()
|
||||
|
||||
# D-067: Recognition chime — fire sfx_monologue_chime on first fog recognition
|
||||
_play_recognition_chimes()
|
||||
|
||||
# #496: Update gauntlet HUD (room timer + personal bests)
|
||||
if gauntlet_hud and gauntlet_hud.has_method("update_from_state"):
|
||||
gauntlet_hud.update_from_state()
|
||||
@@ -208,6 +212,25 @@ func _play_close_sound_events() -> void:
|
||||
GameState.close_sound_events = []
|
||||
|
||||
|
||||
# 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.
|
||||
func _play_recognition_chimes() -> void:
|
||||
var active_ids: Dictionary = {}
|
||||
for rec in GameState.pending_recognitions:
|
||||
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)
|
||||
|
||||
|
||||
# Consume-once per tick: show monologue text, then clear.
|
||||
# Tick guard prevents re-triggering when the same tick is polled multiple
|
||||
# times (client FPS > sim tick rate).
|
||||
|
||||
@@ -119,6 +119,35 @@ func test_d069_confrontation_ease_out_longer_than_dialogue() -> void:
|
||||
assert_that(conf_out >= dial_out).is_true()
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Layer 2b: D-067 Recognition Chime
|
||||
# sfx_monologue_chime fires on first fog recognition, UISounds bus (not WorldSFX).
|
||||
# ==============================================================================
|
||||
|
||||
func test_d067_chime_recognition_constant_exists() -> void:
|
||||
## D-067: AudioManager must expose a CHIME_RECOGNITION constant.
|
||||
assert_that("CHIME_RECOGNITION" in AudioManager).is_true()
|
||||
|
||||
func test_d067_chime_recognition_maps_to_sfx_monologue_chime() -> void:
|
||||
## D-067: Recognition chime = sfx_monologue_chime (D-038 asset key).
|
||||
## "Neural lattice firing" feel — soft crystalline tone.
|
||||
assert_that(AudioManager.CHIME_RECOGNITION).is_equal("sfx_monologue_chime")
|
||||
|
||||
func test_d067_chime_on_ui_sounds_bus_not_world_sfx() -> void:
|
||||
## D-067/D-038: Monologue chime is a UI sound, not a simulation sound.
|
||||
## Must use BUS_UI_SOUNDS, not BUS_WORLD_SFX.
|
||||
## Verify by checking that BUS_WORLD_SFX != BUS_UI_SOUNDS.
|
||||
assert_that(AudioManager.BUS_WORLD_SFX != AudioManager.BUS_UI_SOUNDS).is_true()
|
||||
## CHIME_RECOGNITION is played via AudioManager.play() which defaults to BUS_UI_SOUNDS.
|
||||
## No further assertion needed — play() default bus IS UISounds by design.
|
||||
|
||||
func test_d067_chime_noop_when_asset_absent() -> void:
|
||||
## D-038 / D-067: When sfx_monologue_chime.ogg is not in registry,
|
||||
## play(CHIME_RECOGNITION) must be a no-op (no crash).
|
||||
AudioManager.play(AudioManager.CHIME_RECOGNITION)
|
||||
## No assertion — absence of crash is the test.
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
# Layer 3: AudioManager API — Dip State Machine
|
||||
# ==============================================================================
|
||||
|
||||
@@ -345,15 +345,16 @@ How the player observes and interacts with the world: camera, fog, line-of-sight
|
||||
- **Raised by:** Paula (zone-conspicuousness model), Inigo (scoping to future sprint)
|
||||
- **Dissent:** None
|
||||
|
||||
### D-076: Dialogue box max-width — 1920px at target resolution (OQ-29 resolution)
|
||||
### D-076: Dialogue box max-width — 640px (OQ-29 resolution)
|
||||
- **Date:** 2026-02-19
|
||||
- **Decision:** `DIALOGUE_MAX_WIDTH = 1920px`. The dialogue box occupies the full viewport width at the target resolution (1920×1080).
|
||||
- **Derivation:** 1920px = 60 × TILE_SIZE (32px) — grid-aligned. Full viewport width per Lead directive (D-061: "max-width", explicitly contrasting Stig's original 50% centered proposal).
|
||||
- **Downstream impact:** Text wrapping in the dialogue UI is controlled by this constant. Internal padding within the dialogue node handles visual breathing room; this constant is the outer boundary.
|
||||
- **Decision:** `DIALOGUE_MAX_WIDTH = 640px`. Dialogue box is max 640px wide, centered on screen.
|
||||
- **Derivation:** 640px = 20 × TILE_SIZE (32px) — grid-aligned. ~33% of target 1920px viewport width. Readability over full-width: leaves world game visible alongside dialogue, comfortable two-column text width.
|
||||
- **Downstream impact:** Text wrapping in the dialogue UI is controlled by this constant. Box is centered; the game world remains visible left and right.
|
||||
- **Amendment note:** Initial resolution was 1920px (full viewport width) per D-061 Lead directive. Tyre architecture review (2026-02-19) revised to 640px for readability.
|
||||
- **Implementation:** `Constants.DIALOGUE_MAX_WIDTH` in `client/scripts/constants.gd`.
|
||||
- **Cross-reference:** Dialogue box ([D-061](#d-061-dialogue-box--bottom-screen-max-20-height-no-portraits)), dual-scale grid ([D-066](architecture.md#d-066-dual-scale-grid--05m-simulation-1m-visual-2x-retina-factor))
|
||||
- **Amends:** [D-061](#d-061-dialogue-box--bottom-screen-max-20-height-no-portraits) (adds pixel value for max-width)
|
||||
- **Raised by:** Stig (Sprint 12 OQ-29 resolution)
|
||||
- **Raised by:** Stig (OQ-29), revised per Tyre architecture review
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user