Nothing under ui/ or vfx/ was marked @tool, so every hand-drawn control
rendered as an empty rect in the editor. BuildMenu.tscn positions its
buttons with hand-tuned pixel offsets (offset_left = -204) around a glyph
identified only as `icon = 8` - laying out invisible boxes by number.
@tool on the three scene-placed components that are safe to run in the
editor, covering 23 of the 34 placements: IconButton (20 scenes),
TouchJoystick (2), NeonPanel (1).
Two of them mutate persisted properties on _ready, which under @tool
would write computed values back into the .tscn every time the scene was
opened, so both are guarded with Engine.is_editor_hint():
NeonPanel._fit_to_screen() rewrites all four offsets
TouchJoystick._relayout() overwrites max_radius, which is @export
IconButton's _gui_input is guarded too, so clicking a button on the
editor canvas does not emit `pressed`.
The autoload-heavy drawers (ResourceBar, PriceTag, QuestPanel,
ResourceTransferList, SkillTreeView, FormationEditor - 6 to 16
GameState./Quests. references each) are left alone; they need their call
sites guarded first.
Verified: 172/172 headless, and a --headless --editor pass loads the
project with no script errors. That pass only proves the scripts parse
under @tool - it never opens a scene, so it does not exercise the
in-editor _draw.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019AJMdB7iuHuLFZM1aBu3GD
The card is authored at a fixed 704x1200 around a portrait phone. On a
window wider than it is tall the stretched canvas is about 1080 units high,
so the card sat at y=-60 and hung over both edges - and the confirm and
cancel buttons are anchored to its own bottom edge, so they went off screen
with it. The popup was not merely ugly on a desktop, it was a dead end:
nothing on it could be pressed.
NeonPanel now fits itself to the viewport and re-fits when the window
changes. It only ever shrinks; growing the card would stretch proportions
that are tuned rather than fluid. The fitting adjusts offsets around a
fixed centre, so it applies only to a card pinned to a point - a panel
stretched between different anchors already follows its parent and is left
alone.
Verified across portrait, 16:9, ultrawide and small-laptop shapes, on the
card and on both buttons.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Everything lived in one flat directory, so a diff gave no clue what area it
touched and finding the file you wanted meant knowing its exact name. Twelve
folders now, grouped by what a thing IS rather than what type it is - a
vessel's definition, its component scenes and its upgrade resources sit
together, because that is the set you read when you change one of them.
buildings core controllers enemies logistics planets
resources shaders surface ui vessels vfx world
332 files moved and every res:// reference rewritten - 306 files touched,
including the autoload and main-scene paths in project.godot. Nothing renamed:
this is purely where files sit, so the diff is a pile of pure moves and no
behaviour is buried in it. ui_theme.tres, icon.svg and web_shell.html stay at
the root, where Godot and the export preset expect them.
Verified three ways, because a suite alone would not have caught all of it:
the full 117 pass, the web export builds clean, and the game renders.
The export is why that matters. The FIRST export after the move failed with
"Attempt to open script 'res://GameState.gd'" - the old path - while
project.godot on disk plainly said res://core/GameState.gd. That is this
project's documented Android FUSE mtime trap: Godot served a cached copy of a
file whose content had changed. A `touch` fixed it. Nothing in the test suite
would have found that, because the suite runs from a copy on a native
filesystem - the one place the bug cannot happen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>