Skip to content

Latest commit

 

History

History
367 lines (308 loc) · 21.4 KB

File metadata and controls

367 lines (308 loc) · 21.4 KB

Friction Log: Cyberpunk HUD Build

Date: 2026-04-15 Exercise: Build a full cyberpunk HUD + pause menu in test_project/ using only MCP tools. Purpose: Stress-test UI, theme, animation, input, autoload, and signal tools under real composition pressure.

Summary

Built a cyberpunk-styled HUD with health/shield bars, ability cooldowns, ammo counter, log feed, and a full pause menu with settings panel. All authored via MCP tools -- zero hand-edited .tscn/.tres/project.godot.

Approximate MCP calls by family:

  • node_*: ~15 (create, reparent, set_property, delete)
  • ui_build_layout: 6 calls (TopLeft, TopRight, BottomLeft, BottomRight, LogFeed, PauseOverlay)
  • theme_*: 12 calls (1 create + 7 styleboxes + 4 colors + 2 font sizes + 2 constants + 1 apply)
  • animation_*: 12 calls (1 player_create + 4 create_simple + 2 create + 3 add_property_track + 1 add_method_track + 1 set_autoplay)
  • input_map_*: 9 calls (3 add_action + 6 bind_event)
  • script_*: 4 calls (2 create + 2 attach)
  • autoload_*: 1 call (add)
  • signal_*: 2 calls (both failed -- deliberate poke)
  • scene_*: ~8 calls (create, open, save, get_hierarchy)
  • batch_execute: 1 call (7 node_create ops)
  • editor_*: ~6 calls (state, screenshot, logs)

Where composers held up: ui_build_layout handled all static layout including 13-node deeply nested PauseOverlay in one atomic call. animation_create_simple handled multi-target tweens (same node different properties) and multi-tween clips cleanly. theme_* covered all standard slots.

Where I fell back to low-level: animation_add_property_track for multi-keyframe shake animation (3+ keyframes per track). node_set_property for all theme_override_* properties. animation_add_method_track for method-call tracks.


Friction Entries

1. theme_create fails if parent directory doesn't exist

  • Tool: theme_create
  • What I tried: theme_create res://themes/cyberpunk.tres when themes/ dir didn't exist
  • What happened: Error 19 (ERR_FILE_NOT_FOUND or ERR_FILE_CANT_WRITE)
  • What I wanted: Auto-create parent directories
  • Workaround: Call filesystem_write_text res://themes/.gitkeep first to create the directory
  • Proposed tool change: theme_create (and other file-creating tools) should DirAccess.make_dir_recursive_p() before saving

2. Can't rename scene root via node_rename

  • Tool: node_rename
  • What I tried: Rename /cyberpunk_hud (scene root) to HUD
  • What happened: INVALID_PARAMS: Cannot rename the scene root
  • What I wanted: Scene root renamed to HUD
  • Workaround: Worked with the auto-generated name cyberpunk_hud
  • Proposed tool change: Allow renaming the scene root -- Godot's editor UI allows this

3. No scene instancing tool

  • Tool: (missing)
  • What I tried: Instance cyberpunk_hud.tscn into main.tscn
  • What happened: node_create only creates by type, not by scene reference. No scene_instance or similar tool exists.
  • What I wanted: node_create with a scene_path parameter, or a dedicated scene_instance tool
  • Workaround: Created a runtime loader script (hud_loader.gd) that preloads and instantiates the scene
  • Proposed tool change: Add a scene_instance tool or extend node_create with an optional scene_path parameter

4. editor_screenshot game view fails

  • Tool: editor_screenshot
  • What I tried: editor_screenshot source="game" while the project was running
  • What happened: Error: 'source' (KeyError-style)
  • What I wanted: Screenshot of the running game view
  • Workaround: No workaround -- could only capture the editor viewport, which doesn't show the CanvasLayer HUD
  • Proposed tool change: Fix the source="game" path in the screenshot handler

5. theme_set_stylebox_flat only supports uniform borders

  • Tool: theme_set_stylebox_flat
  • What I tried: Wanted thick bottom border + thin top border for a neon underline effect
  • What happened: Only border_width (uniform all sides) is available
  • What I wanted: border_width_top, border_width_bottom, border_width_left, border_width_right
  • Workaround: Used uniform border width
  • Proposed tool change: Add per-side border width parameters (StyleBoxFlat supports this natively via border_width_top/bottom/left/right)

6. theme_apply rejects CanvasLayer

  • Tool: theme_apply
  • What I tried: Apply theme to the scene root (CanvasLayer)
  • What happened: INVALID_PARAMS: Node /cyberpunk_hud is not a Control or Window (got CanvasLayer)
  • What I wanted: Theme applied to cascade to all children
  • Workaround: Created a ThemeRoot (Control, full_rect) under the CanvasLayer, reparented all regions under it, applied theme there
  • Proposed tool change: Could auto-detect this pattern and suggest the workaround, or ui_build_layout could accept a theme parameter that auto-wraps

7. ui_build_layout can't set theme_override_* properties

  • Tool: ui_build_layout
  • What I tried: "theme_override_colors/font_color": "#00eaff" in properties dict
  • What happened: INVALID_PARAMS: Property 'theme_override_colors/font_color' not found on PanelContainer
  • What I wanted: Theme overrides settable in the layout spec
  • Workaround: Build layout first, then call node_set_property for each override
  • Proposed tool change: Support theme_override_* property paths in the layout builder's property coercion

8. signal_connect can't target autoload singletons (deliberately tested)

  • Tool: signal_connect
  • What I tried: Both /root/GameState and GameState as source path
  • What happened: INVALID_PARAMS: Source node not found for both forms
  • What I wanted: Wire signals from an autoload to a scene node
  • Workaround: Connected signals in GDScript code (_ready() function)
  • Proposed tool change: signal_connect could resolve autoload paths by checking the ProjectSettings autoload registry, not just the edited scene tree

9. No animation_delete tool

  • Tool: (missing)
  • What I tried: Recreate damage_shake animation after node paths changed
  • What happened: INVALID_PARAMS: Animation 'damage_shake' already exists. Delete it first or choose a different name.
  • What I wanted: Delete or overwrite an existing animation clip
  • Workaround: Created new clips with incremented names (dmg_shake2, dmg_shake3) -- left stale clips in the library
  • Proposed tool change: Add animation_delete tool. Also consider an overwrite parameter on animation_create

10. scene_open reports play mode after stop

  • Tool: scene_open
  • What I tried: Open a scene immediately after project_stop returned success
  • What happened: EDITOR_NOT_READY: Editor is in play mode — stop the game first even though editor_state showed is_playing: false
  • What I wanted: Scene opens after stop completes
  • Workaround: Wait 3-5 seconds and retry
  • Proposed tool change: Readiness gating should wait for the play-state transition to fully settle, or scene_open should internally retry

11. No per-side border/corner/margin in theme_set_stylebox_flat

  • Tool: theme_set_stylebox_flat
  • What I tried: Needed asymmetric content margins (more padding top for header areas)
  • What happened: Only content_margin (uniform) available, same for corner_radius and border_width
  • What I wanted: content_margin_top, content_margin_bottom, corner_radius_top_left, etc.
  • Workaround: Used uniform values
  • Proposed tool change: Expose per-side parameters -- StyleBoxFlat supports all of these natively

12. Stale animation tracks after layout restructuring

  • Tool: animation_*
  • What I tried: Restructured node tree (moved HealthBar deeper), existing animation tracks still pointed at old paths
  • What happened: Animations silently target non-existent paths at runtime (no error, just no effect)
  • What I wanted: Either a way to update track paths, or a validation tool that flags broken track references
  • Workaround: Recreated animations with new names (see #9) pointing at new paths
  • Proposed tool change: Add animation_update_track_path or a animation_validate tool that checks track paths resolve

Deliberate Poke Checklist

# Poke Result
1 ui_build_layout can't express theme_override_* Confirmed -- falls back to node_set_property
2 StyleBoxFlat uniform-side limitation Confirmed -- no per-side border width
3 Tween coercion on self_modulate, custom_minimum_size Works -- hex strings for Color, dict for Vector2 both coerce correctly
4 animation_create_simple duplicate-target Partial -- same node + different property works fine; only same node + same property is rejected (better than expected)
5 Animating stylebox property inside theme resource Not tested -- used self_modulate on ProgressBar as planned workaround
6 signal_connect target form for autoloads Both forms fail -- neither /root/GameState nor bare GameState resolves
7 input_map_bind_event modifiers + multiple bindings Works -- Shift+E and plain E coexist on ability_2, input_map_list shows both correctly
8 animation_create_simple with many concurrent tweens Works -- tested 2 tweens on same node (different properties) in one call
9 Atomic undo across batch_execute Works -- 7-node batch created atomically with undoable: true
10 Scene instancing gap Confirmed -- no MCP tool for instancing a scene into another scene
11 Theme apply before vs after children Moot -- theme applied to empty container first, children added after; cascade worked once children existed

Tests That Should Exist But Don't

These gaps were surfaced by the exercise:

  1. Multi-track composer test: animation_create_simple with 2+ tweens targeting the same node but different properties
  2. Theme cascade + override interplay: Apply theme to parent, then set theme_override_* on a child, verify both apply
  3. signal_connect to autoload: Test and document the error clearly (currently generic "not found")
  4. Realistic batch_execute workflow: batch that creates nodes + sets properties + applies anchor presets as sub-commands
  5. theme_create missing directory: Test error path and verify error message is actionable
  6. editor_screenshot source="game": Integration test for game-view capture
  7. Deep ui_build_layout undo: 10+ node deep tree, verify single Ctrl+Z undoes the whole tree
  8. Scene instancing workflow: Document the gap and test the runtime-loader workaround pattern

Files Created

File Method Description
cyberpunk_hud.tscn scene_create + MCP tools HUD scene with all regions
themes/cyberpunk.tres theme_create + theme_set_* Full cyberpunk theme
autoload/game_state.gd script_create GameState singleton
cyberpunk_hud.gd script_create HUD controller script
hud_loader.gd script_create Runtime scene instancing workaround
project.godot autoload_add + input_map_* Modified: autoload + 3 input actions

2026-04-17 v2 polish pass

Confirmed shipped since the original exercise: animation_delete, per-side StyleBoxFlat params (border/corners/margins/shadow dicts), editor_screenshot source="game", overwrite on animation_create*. New friction during the polish:

13. First-time plugin install requires manual Project Settings → Plugins tick

  • Tool: N/A (setup)
  • What I tried: Auto-enable godot_ai on a fresh project so MCP tools work end-to-end without the user touching the UI.
  • What happened: Without the plugin enabled there's no MCP session, so we can't call EditorInterface.set_plugin_enabled. Writing [editor_plugins] enabled=PackedStringArray("res://addons/godot_ai/plugin.cfg") into project.godot before first launch gets stripped because Godot hasn't indexed the addon yet on first boot.
  • Workaround: Launch Godot once (indexes addons), then add the entry and relaunch. Or have the user tick the checkbox once.
  • Proposed tool change: Ship a script/install-plugin <project-path> that symlinks + does the two-phase boot, or document the sequence.

14. Floating children inside a Container get force-laid-out

  • Tool: node_create, ui_set_anchor_preset
  • What I tried: Corner-tick ColorRects pinned to HealthGroup's four corners to frame the panel.
  • What happened: Direct children of PanelContainer are stretched to fill its rect. ui_set_anchor_preset top_left has no visible effect inside a Container.
  • Workaround: Skipped corner ticks. The v3 pass fixed it by reparenting the overlay to the grandparent (a MarginContainer whose children aren't re-anchored once they have a custom layout, effectively still forcing a single-rect fill, but sitting on the outer edge).
  • Proposed tool change: ui_set_anchor_preset could detect "parent is a Container" and return a helpful error pointing to the workaround, or a ui_float_within(panel_path, corner) helper that wraps + places correctly.

15. animation_preset_pulse is scale-only, not a generic pulse

  • Tool: animation_preset_pulse
  • What I tried: Drive a blinking cursor via modulate-alpha ping-pong (property=modulate:a, from=0.2, to=1.0).
  • What happened: The preset hard-codes scale as the animated property; property isn't a param. Only the from_scale / to_scale naming hints at that.
  • Workaround: animation_create_simple with a modulate tween and loop_mode=pingpong.
  • Proposed tool change: Either rename to animation_preset_scale_pulse, or generalise with property/from/to params.

16. AnimationPlayer is single-channel — autoplay + idle loop conflict

  • Tool: animation_set_autoplay
  • What I tried: Autoplay hud_fade_in AND start the cursor_blink idle loop together on scene load.
  • What happened: Autoplay takes one clip name, AnimationPlayer plays one clip at a time; the blink couldn't start until the fade finished.
  • Workaround: ap.queue("cursor_blink") in _ready() after the fade play call. Queue mechanism runs cursor_blink once the intro ends.
  • Proposed tool change: Document the queue pattern in animation_set_autoplay's docstring, or add a animation_queue tool that sets up the queue without a script edit.

17. MCP session drops (code 1001) mid-batch on long runs

  • Tool: N/A (HTTP transport)
  • What I tried: Long sequence of node_create + node_set_property calls (Phase C ability polish, ~40+ calls).
  • What happened: Mid-stream Session not found / No active Godot session. Plugin log showed MCP | disconnected (code 1001) followed by reconnect attempts. After reconnect the session was no longer "active" and needed session_activate again.
  • Workaround: Pin every call with an explicit session_id once the drop happened, and re-activate on each reconnect.
  • Proposed tool change: Either preserve active-session identity across reconnects, or surface a clearer "session disconnected" error.

2026-04-19 v3 recipe-based pass

Plugin v1.1.0 shipped control_draw_recipe (the tool proposed in the v2.5 planning prompt) and the _mcp_game_helper autoload. Using them to rebuild v2's decorations and add radar/gauge/crosshair/waveform/scanlines surfaced:

18. node_reparent rejects moving a node to its ancestor

  • Tool: node_reparent
  • What I tried: node_reparent /cyberpunk_hud/ThemeRoot/TopLeft/HealthGroup/Brackets with new_parent=/cyberpunk_hud/ThemeRoot/TopLeft (moving Brackets up one level to its grandparent).
  • What happened: INVALID_PARAMS: Cannot reparent a node to itself or its descendant. The destination is an ancestor, not a descendant — the guard is rejecting the wrong case.
  • Workaround: node_delete + node_create with attach + property set (loses undo atomicity, doubles the call count).
  • Proposed tool change: Fix the ancestor/descendant check. A node's grandparent is a legal reparent target (you can do this in the Godot editor via drag-and-drop).

19. editor_screenshot source="game" times out after editor_reload_plugin

  • Tool: editor_screenshot
  • What I tried: After pulling a newer plugin and calling editor_reload_plugin (which rotated the session ID), immediately take a game-view screenshot while the previously-running game was still alive.
  • What happened: 15s timeout on the IPC. The running game process had the old plugin's _mcp_game_helper bound, so the debugger-channel relay the new plugin uses couldn't reach it.
  • Workaround: project_stop + project_run to respawn the game under the new plugin, then screenshot works.
  • Proposed tool change: editor_reload_plugin could stop any running game automatically (or at least return a warning flagging the stale autoload).

20. animation_create_simple rejects duplicate targets even for sequential keyframes

  • Tool: animation_create_simple
  • What I tried: Two tweens on the same self_modulate property — first a fast fade to red (0→0.05s), then a slow fade back to white (0.05→0.3s).
  • What happened: INVALID_PARAMS: Duplicate tween target '...:self_modulate' — merge keyframes into a single track via animation_add_property_track instead of two separate tweens.
  • Workaround: Did exactly that — animation_create + one animation_add_property_track with 3 keyframes. The error message is accurate and actionable, so really this is a UX suggestion.
  • Proposed tool change: animation_create_simple could auto-merge when two tweens on the same target have non-overlapping time ranges and the second's from matches the first's to (the common "flash then settle" case). Would save the caller from rewriting.

2026-04-19 v3.2 angular-frame pass

Built the v3.2 "cyberpunk angular HUD" look on top of v3 — polyline frames with diagonal corner cuts, chevron header bands, ruler tick marks, inner double-stroke, corner flags, plus an orbiting data-packet dot and scanline drift. Two net-new scripts (angular_frame.gd, orbit_dot.gd), two patched scripts (scanlines.gd, cyberpunk_hud.gd), and three new scene nodes on hud_v3.tscn.

21. Broken plugin symlink gave phantom "plugin_version: 0.0.1" session

  • Tool: session lifecycle
  • What I tried: Activate the cyberpunk-hud-demo session and take a game-view screenshot. The session listed plugin_version: 0.0.1 (wrong — current is 1.1.0).
  • What happened: Screenshots silently timed out. addons/godot_ai was a symlink to /Users/davidsarno/Documents/godot-ai/plugin/addons/godot_ai but that path had been emptied; the real plugin now lives under /Users/davidsarno/godot-ai/plugin/addons/godot_ai. Godot booted an in-memory copy of a stale plugin snapshot, so the session connected but the game_helper relay couldn't service screenshot requests.
  • Workaround: Fix the symlink (rm + re-ln to the real path), call editor_reload_plugin, re-session_activate under the new rotated id, then project_stop + project_run. Screenshots then worked.
  • Proposed tool change: session_list could surface broken/empty addon paths as a warning alongside plugin_version; or the plugin could report its resolved source path so stale in-memory sessions are obvious.

22. Game screenshot timeout error lists two likely causes but not the

plugin-path one

  • Tool: editor_screenshot source="game"
  • What I tried: Screenshot the running game right after project_run.
  • What happened: Game screenshot timed out. ... check Project Settings → Autoload. ... for headless ... use source='viewport' instead. In fact the _mcp_game_helper autoload WAS registered (the game log showed registered mcp capture (debugger active=true, logger=true)), but the addon path was stale (see #21).
  • Workaround: Cross-check session_list.plugin_version against the known-good plugin version, and re-symlink the addon if they don't match.
  • Proposed tool change: Extend the error body to include a third hypothesis: "plugin source may be stale — check session_list plugin_version and the resolved addon path".

23. PanelContainer content_margin forces child Controls to draw from an

inset origin when emulating outer-edge frames

  • Tool: control_draw_recipe-style draw scripts
  • What I tried: Draw a polyline outline that traces the outer visible edge of a PanelContainer via a Control child.
  • What happened: PanelContainer force-fits its children to the inner content rect (outer - content_margin * 2), so the child's (0,0) is offset from the panel's true outer corner. Naively drawing from (0,0) to size gave an outline that was inset by the content margin.
  • Workaround: In _ready(), read parent.get_theme_stylebox("panel") and cache get_margin(SIDE_*), then draw outline from (-ml, -mt) to (size.x + mr, size.y + mb). Zero the parent's border_width/corner_radius /shadow via a per-instance stylebox override so the bg fill stays but the polyline "owns" the frame.
  • Proposed tool change: A panel_frame_recipe helper (or an opt-in draw_outer_rect flag on control_draw_recipe) that auto-accounts for the parent PanelContainer's content margins would remove the margin math boilerplate. Everyone building HUD frames does this.