Files
MaidEngine/docs/superpowers/specs/2026-04-05-f1-debug-menu-design.md
2026-04-05 20:53:04 -04:00

5.8 KiB

F1 Debug Menu Design

Overview

An F1-toggled debug menu overlay that works regardless of the current scene. Introduces a new root scene (game.tscn) that hosts both the debug menu and the active game scene as children. The game pauses underneath while the debug menu is open.

Scene Tree Structure

Game (Node, process_mode = PROCESS_MODE_ALWAYS)
├── DebugMenu (CanvasLayer, layer 100)
│   ├── Panel (PanelContainer)
│   │   ├── SceneList (VBoxContainer of Buttons)
│   │   └── CommandInput (LineEdit)
│   └── ResultLabel (Label, brief flash for command output)
└── ActiveSceneContainer (Node)
    └── <currently loaded scene>
  • Game is the new main scene, replacing strategy_phase.tscn as the entry point in project.godot.
  • DebugMenu uses a high CanvasLayer (layer 100) so it always renders above the active scene.
  • ActiveSceneContainer is a plain Node whose child gets swapped when changing scenes.
  • Game has process_mode = PROCESS_MODE_ALWAYS so it can receive F1 input even while the tree is paused.

Responsibilities

Game (game.gd)

Thin shell only:

  • F1 input toggles DebugMenu visibility.
  • When debug menu opens: sets ActiveSceneContainer.process_mode = PROCESS_MODE_DISABLED.
  • When debug menu closes: sets ActiveSceneContainer.process_mode = PROCESS_MODE_INHERIT.
  • Loads the default scene (strategy_phase) into ActiveSceneContainer at startup.
  • Passes a reference to ActiveSceneContainer to DebugMenu at _ready().

DebugMenu (debug_menu.gd)

Owns all debug functionality:

  • Scene registry: hardcoded array of scene entries.
  • Scene swapping: frees old child of ActiveSceneContainer, instantiates new scene, applies setup if present.
  • Command console: parses input, dispatches to registered commands or falls back to expression eval.
  • Command registration: holds an explicit Array[ConsoleCommand] populated at _ready().

Scene Swapping

Registry Format

A hardcoded array in DebugMenu:

var scene_registry: Array = [
    { "name": "Strategy Phase", "path": "res://scenes/strategy_phase.tscn" },
    { "name": "Strategy Phase (Test Data)", "path": "res://scenes/strategy_phase.tscn", "setup": "test_data" },
    { "name": "Main Menu", "path": "res://scenes/main_menu.tscn" },
    { "name": "Visual Novel", "path": "res://scenes/vn_scene.tscn" },
    { "name": "Dialogue", "path": "res://scenes/dialogue_scene.tscn" },
]

Swap Flow

  1. Free the current child of ActiveSceneContainer.
  2. Instantiate the new scene from path and add as child.
  3. If the entry has a "setup" key, call a setup method on DebugMenu that applies that configuration to the new scene instance.
  4. Close the debug menu and signal Game to unpause.

Setup Hooks

Setup functions live in DebugMenu, dispatched by matching the "setup" string key. This keeps test configurations self-contained within the debug system.

Command Console

Input

A LineEdit at the bottom of the debug panel. On Enter:

  1. Split input text — first token is the command name, rest are arguments.
  2. Check against registered commands by matching get_command_name().
  3. If found, call run() with args and context.
  4. If no match, fall back to Godot's Expression class (evaluated with the active scene as base instance).
  5. Show the result (or error) in ResultLabel, then hide it after 2 seconds using a Timer or create_tween().

Command Pattern

Base class:

class_name ConsoleCommand

func get_command_name() -> String:
    return ""

func get_help_text() -> String:
    return ""

func run(args: Array, context: Dictionary) -> String:
    return ""

Each command extends ConsoleCommand and overrides these three methods. The context dictionary provides access to the active scene container, debug menu reference, and other shared state without coupling commands to specific nodes.

Commands are explicitly registered in an Array[ConsoleCommand] in DebugMenu._ready().

Starter Commands

  • help — lists all registered commands with their help text.
  • swap <name> — scene swap by name (matches against registry entry names).
  • list_scenes — prints available scene registry entries.

Expression Eval Fallback

Uses Godot's Expression class. The expression executes with the current active scene as the base instance, allowing direct access to nodes and properties in the loaded scene. Errors are displayed in ResultLabel.

File Organization

nodes/
├── game.gd                          # Thin shell
├── debug_menu.gd                    # Scene registry, command console, swap logic

resources/resource_definitions/
├── console_command.gd               # Base class

resources/console_commands/
├── swap_command.gd
├── help_command.gd
├── list_scenes_command.gd

scenes/
├── game.tscn                        # New root scene (new main_scene in project.godot)

prefabs/
├── debug_menu.tscn                  # CanvasLayer with panel, buttons, input, result label

Existing scenes are unchanged — they are loaded as children of ActiveSceneContainer instead of being the root.

Key Decisions

  • No autoload: The debug menu is a UI node, not a headless service. The XXXServer autoload pattern is reserved for game services like a future SceneManagerServer.
  • Explicit command registration: Commands are listed in an array rather than auto-discovered from a folder. Simpler and more readable for a small number of commands. Can be swapped to auto-discovery later if needed.
  • Single-line output: ResultLabel shows one result at a time with a brief display. No scrollable history for now.
  • Hardcoded scene list: Entries can carry setup data/context, which auto-discovery from .tscn files wouldn't support.