feat(ui): add in-game debug console with tilde toggle and command dispatch (#581)
- debug_console.gd: new ModalLayer Control — tilde key toggles bottom-40% panel, command history (up/down), SimBridge dispatch for all DebugCommandKind variants: ticks, contaminate, tp, activate, triangle, npc, triangles, pop, status, help - debug_console.tscn: minimal scene node; UI built programmatically in _ready() - input_mapper.gd: DEBUG_COMMAND action added to Action enum - sim_bridge.gd: DEBUG_COMMAND → "DebugCommand" wire mapping - protocol.gd: v18 debug_response decode (command, text, success fields) - game_state.gd: debug_response field + apply_snapshot one-shot handling - main.gd: @onready ref, router registration, _consume_debug_response(), settings signal - settings_dialog.gd: debug_console_toggled signal + CheckButton toggle row (+36px height), reads initial state from user://settings.cfg; CheckButton state loaded from PREFS_PATH - main.tscn: DebugConsole node on ModalLayer, load_steps 27→28 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -81,6 +81,11 @@ var rng_seed: Variant = null
|
||||
# One-shot: consumed by main.gd after display, then set back to null.
|
||||
var save_result: Variant = null
|
||||
|
||||
# v18 fields (#580): debug console response from server.
|
||||
# {command: String, text: String, success: bool} or null.
|
||||
# One-shot: consumed by main.gd and forwarded to DebugConsole, then set to null.
|
||||
var debug_response: Variant = null
|
||||
|
||||
# #257: Pending load path — set by main menu "Load Game" selection.
|
||||
# main.gd sends LOAD_GAME on startup if non-empty, then clears this field.
|
||||
# Format: user://saves/<game-id>/<filename>.sav or "" if no pending load.
|
||||
@@ -311,6 +316,12 @@ func apply_snapshot(snapshot: Dictionary) -> void:
|
||||
else:
|
||||
save_result = null
|
||||
|
||||
# v18: debug_response (#580) — debug console command result.
|
||||
if snapshot.has("debug_response") and snapshot.debug_response is Dictionary:
|
||||
debug_response = snapshot.debug_response
|
||||
else:
|
||||
debug_response = null
|
||||
|
||||
# v14: player_knowledge (#264, D-041) — partial KG dump for journal panel.
|
||||
# Only update when field is present (null means no change, server sends when KG changes).
|
||||
if snapshot.has("player_knowledge") and snapshot.player_knowledge is Dictionary:
|
||||
|
||||
@@ -24,6 +24,7 @@ enum Action {
|
||||
TELEPORT_HUB, # #501: Home key — Gauntlet dev teleport (not production fast-travel)
|
||||
SAVE_GAME, # #554: F5 quicksave — sends SaveGame to server with save path
|
||||
LOAD_GAME, # #554: F6 quickload — sends LoadGame to server with save path
|
||||
DEBUG_COMMAND, # #581: debug console command dispatch — sends DebugCommandKind to server
|
||||
}
|
||||
|
||||
var input_queue: Array[Dictionary] = []
|
||||
|
||||
@@ -416,6 +416,8 @@ static func action_enum_to_wire(action: int) -> String:
|
||||
return "SaveGame" # #554: F5 quicksave (D-085)
|
||||
InputMapper.Action.LOAD_GAME:
|
||||
return "LoadGame" # #554: F6 quickload (D-085)
|
||||
InputMapper.Action.DEBUG_COMMAND:
|
||||
return "DebugCommand" # #581: debug console command dispatch
|
||||
_:
|
||||
push_warning("SimBridge: unknown action enum %s" % action)
|
||||
return ""
|
||||
|
||||
@@ -22,6 +22,7 @@ extends Node2D
|
||||
@onready var bug_report_dialog = $ModalLayer/BugReportDialog # #495: F12 WRONG button
|
||||
@onready var settings_dialog = $ModalLayer/SettingsDialog # #528: audio settings (ESC/OPEN_MENU)
|
||||
@onready var loading_screen = $ModalLayer/LoadingScreen # #257: blocking overlay during load
|
||||
@onready var debug_console = $ModalLayer/DebugConsole # #581: tilde debug console
|
||||
|
||||
var _last_dialogue_npc_id: int = -1 # D-064: NPC entity_id for WalkAway input
|
||||
var _last_dialogue_npc_name: String = "" # #535: NPC name for dialogue_response attribution
|
||||
@@ -122,6 +123,11 @@ func _ready() -> void:
|
||||
_router.register("conversation_ended", _consume_conversation_ended)
|
||||
_router.register("dialogue_response", _consume_dialogue_response)
|
||||
_router.register("save_result", _consume_save_result)
|
||||
_router.register("debug_response", _consume_debug_response)
|
||||
|
||||
# #581: Wire settings_dialog debug console toggle → debug_console.set_enabled
|
||||
if settings_dialog and debug_console:
|
||||
settings_dialog.debug_console_toggled.connect(debug_console.set_enabled)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
@@ -382,6 +388,8 @@ func _consume_dialogue_response() -> void:
|
||||
if GameState.dialogue_response == null or not dialogue_box:
|
||||
return
|
||||
var dr: Dictionary = GameState.dialogue_response
|
||||
# v0.1: falls back to _last_dialogue_npc_id if wire omits speaker_entity_id.
|
||||
# Edge case: fast re-engagement with a different NPC could misattribute — low probability.
|
||||
var speaker_entity_id: int = dr.get("speaker_entity_id", _last_dialogue_npc_id)
|
||||
var speaker_color_index: int = dr.get("speaker_color_index", -1)
|
||||
var speaker_name: String = dr.get("speaker_name", _last_dialogue_npc_name)
|
||||
@@ -414,6 +422,14 @@ func _consume_save_result() -> void:
|
||||
monologue_display.show_notification(msg)
|
||||
|
||||
|
||||
# #581: Forward debug_response from server to the debug console.
|
||||
func _consume_debug_response() -> void:
|
||||
if GameState.debug_response == null or not debug_console:
|
||||
return
|
||||
debug_console.append_response(GameState.debug_response)
|
||||
GameState.debug_response = null
|
||||
|
||||
|
||||
# D-061: Handle dialogue option selection → send to server
|
||||
func _on_dialogue_option_selected(response_id: String, text: String) -> void:
|
||||
SimBridge.send_input({
|
||||
|
||||
@@ -244,6 +244,17 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
"error": raw_save.get("error"),
|
||||
}
|
||||
|
||||
# v18: debug_response (#580) — debug console command result.
|
||||
# {command: String, text: String, success: bool}
|
||||
var debug_response: Variant = null
|
||||
var raw_debug: Variant = raw.get("debug_response")
|
||||
if raw_debug is Dictionary:
|
||||
debug_response = {
|
||||
"command": str(raw_debug.get("command", "")),
|
||||
"text": str(raw_debug.get("text", "")),
|
||||
"success": bool(raw_debug.get("success", false)),
|
||||
}
|
||||
|
||||
# TODO(server): Send stationary_ticks in ObserverSnapshot (D-071, D-020).
|
||||
# Server already tracks this in ListeningFocus component (server/src/simulation/listening.rs).
|
||||
# When server populates this field, client-side accumulation fallback in game_state.gd
|
||||
@@ -320,6 +331,7 @@ static func decode_snapshot(bytes: PackedByteArray) -> Variant:
|
||||
"examine_result": examine_result,
|
||||
"player_knowledge": player_knowledge,
|
||||
"save_result": save_result,
|
||||
"debug_response": debug_response,
|
||||
"stationary_ticks": stationary_ticks,
|
||||
"zone_id": zone_id,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user