The actual fix (main_menu.gd's _ready() checking ApiClient.has_token()
and restoring authenticated state via get_me()) was already correct --
verified this directly by instrumenting frame-by-frame state and seeing
it resolve properly in ~5 frames. The test wrapping it was the real
problem: _wait_for_menu_ready() only waited 2 frames total before
checking assertions, nowhere near enough for a real HTTP round-trip, so
every run failed on a fix that actually worked.
Root fix: added a real auth_state_ready signal to main_menu.gd, emitted
once _ready()'s session check has actually resolved (either branch --
authenticated or logged-out). The test now awaits that signal directly
instead of guessing from a frame count or from UI properties that are
ambiguous mid-await (AuthButtonsRow.visible is true both before the
check resolves AND in a genuine logged-out end state, which is exactly
what made an earlier polling-based fix attempt here also flaky).
Also fixed a second real bug in the test itself: unique_id used
Time.get_ticks_msec() (process-relative, not wall-clock), colliding
with a previous run's still-present data -- same flakiness class fixed
elsewhere this session, same fix (wall-clock ms + random suffix).
Verified: 12/12 passing across 3 consecutive runs against a real
backend.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reported directly by the user after seeding a test world through the
browse-worlds screen: "screen is blank now". Two compounding layout
bugs, both real:
1. world_list.tscn's root VBoxContainer had no anchors at all, only
size_flags -- but its parent (the scene's root node) is a plain
Control, not a Container, so size_flags on a Control's direct child
are meaningless there. Without anchors it never actually filled the
screen, so it (and everything under it) collapsed toward zero size.
2. size_flags_horizontal/vertical used the raw integer 4 everywhere
expand-fill was clearly intended (ScrollContainer, its WorldList
child, HeaderHBox, TitleLabel) -- but 4 is Control.SIZE_SHRINK_CENTER
in Godot 4, not SIZE_EXPAND_FILL (which is 3). ScrollContainer with
SHRINK_CENTER takes only its own minimum size instead of expanding,
cascading a ~1px width down through every card and every label.
Confirmed the actual enum values by asking Godot directly rather
than assuming.
Both are exactly the kind of bug a headless smoke test checking only
"did cards get added to the tree" can't catch -- child_count was
correctly non-zero, nothing crashed, the content was just rendered
invisibly thin. Verified the fix is real by deliberately reintroducing
bug #2 and confirming the test now correctly fails (card width 25px,
below the 200px sanity threshold) before restoring it.
Also replaced the card background: it was a ColorRect manually moved to
child index 0 of a VBoxContainer, which doesn't work as an overlay --
a VBoxContainer lays out ALL its children in sequence, so the "moved to
front" ColorRect rendered as an empty dark bar ABOVE the text, not
behind it. Now a proper PanelContainer with a StyleBoxFlat background,
the idiomatic way to get a colored panel behind auto-laid-out content
in Godot.
smoke_test_world_list.gd strengthened to actually catch this class of
bug going forward: seeds a real world via POST /api/worlds/ first (the
checkpoint runner's fresh DB meant the populated-card path never ran in
CI at all before this, only empty-list did), and asserts each card's
real computed width, not just that cards exist.
Full checkpoint suite re-verified clean on this branch: migrations,
68/68 pytest, all 6 Godot smoke tests, both platform exports.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reported directly by the user playtesting the exported exe: Play was
permanently disabled and there was no way to log in or register. Root
cause: main_menu.tscn wired Play and Quit, but nothing was ever
connected to _on_login_button_pressed()/_on_register_button_pressed() --
the AuthPanel, its form, and all its backend-calling logic were fully
correct, just completely unreachable. The script's own top comment even
said "(the existing Play/Quit buttons)" as if Play/Quit somehow doubled
as login triggers, which they never did -- a sign nobody ever actually
clicked through the running scene, only checked things at the API level.
Fix: added a real AuthButtonsRow (LoginButton, RegisterButton) wired to
the existing handlers, which now hide themselves once auth succeeds.
Also fixed grab_focus() targeting the AuthPanel (which can't accept
focus, just a background warning) to focus the username field instead.
Verified via two new smoke tests (smoke_test_main_menu_ui.gd,
smoke_test_main_menu_full_flow.gd) using a *real signal emission* on
the buttons, not a direct method call -- that distinction is exactly
what would have caught the original bug. Full click-through confirmed
against a real backend: open register form, fill it, submit, Play
actually becomes enabled, welcome message shows, auth buttons hide.
Also documents a real headless-testing gotcha found while verifying
this: a synthetic Button.pressed.emit() on an await-using handler
doesn't reliably get driven forward in --script/SceneTree mode the way
real GUI input dispatch does -- confirmed by comparing direct-call vs
emit() behavior side by side. Not a bug in the actual click-driven flow,
a test-harness quirk to route around (call the handler directly,
awaited, instead of emitting the signal).
Full checkpoint suite re-verified clean on this branch: migrations,
68/68 pytest, all 6 Godot smoke tests (including the 2 new ones, picked
up automatically by run_checkpoint_tests.py's glob).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Tester had already written a complete, correct test (4/4 steps pass
against a real backend) but stalled investigating DB/migration setup
after its own verification run silently exited with no result -- the
task spec's suggested invocation included --quit, which kills a --script
SceneTree test before its awaited HTTP calls finish (same Godot
--script-mode gotcha documented elsewhere this session).
Renamed smoke_test.gd -> smoke_test_e2e.gd: the task spec named it
without the checkpoint runner's expected smoke_test_*.gd suffix, so
scripts/run_checkpoint_tests.py's glob would have silently never picked
it up. Also fixed the --quit flag in the file's own usage comments.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resolves conflict in main_menu.gd's play button: kept this branch's real
world_list.tscn transition over main's stale TODO placeholder (main's
version predates this fix; nothing else has touched the play button
since). Brings in PR #37's /auth/refresh endpoint and its tests cleanly,
no other conflicts.
- main_menu.gd: Play button now navigates to world_list.tscn
- world_list.gd: fix invalid theme_override_constants property-path syntax
(use add_theme_constant_override), fix @onready node paths missing
/HeaderHBox/, cast Variant dict values to String to avoid type-inference
errors when cards actually render
- world_list.tscn: wire up Back/Refresh button signal connections (scene
had zero [connection] blocks, buttons were dead)
- backend: fix real bug where World.visibility enum sent member names
("PUBLIC") to Postgres instead of lowercase values, breaking both GET
and POST /api/worlds/ for any real query
- backend: fix WorldResponse.created_at typed as str instead of datetime,
causing a second 500 on GET once the enum bug was fixed
- add smoke_test_world_list.gd: headless scene-level smoke test that
registers a user, instantiates the real scene, and confirms world cards
render from live backend data (not just an API-level check)
Adds export_presets.cfg (Linux/X11 + Windows Desktop, x86_64) -- didn't
exist at all before this, which is why the task got stuck: you can't run
godot --export-release without presets already defined, and Godot only
auto-generates this file interactively through the editor. Hand-wrote a
minimal valid one and verified both platforms actually export and produce
real binaries (not just that the command exits 0):
- export/linux/agp-game.x86_64 -- confirmed real ELF 64-bit executable via `file`
- export/windows/agp-game.exe -- 104MB PE binary
scripts/build.sh wraps both exports, documents the exact Godot version +
export template requirement, and was run end-to-end (not just written) to
confirm it works as a real CI entrypoint would invoke it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs/testing/test_inventory.md: honest, current list of what automated
coverage actually exists (38 backend pytest tests across 2 files, 0 Godot
tests merged yet) and what doesn't (no integration/endpoint tests at all,
no coverage yet for the in-review RS256/moderation work) -- verified
against a genuinely fresh checkout + fresh Postgres, not just read off the
file tree.
scripts/run_checkpoint_tests.py: runs the whole inventory in one shot --
throwaway Postgres, migrations, pytest, then starts the backend server and
runs every scripts/smoke_test_*.gd against it headlessly. Verified working
both with and without a smoke test present (temporarily tested against
PC1's not-yet-merged scripts/api_client.gd + smoke_test_api.gd, removed
before this commit -- that PR carries its own files).
docs/testing/README.md: what running this at a Package Checkpoint review
does and doesn't prove, and how to grow the inventory as real coverage
gets added.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Two Godot --script-mode-only quirks, neither a bug in ApiClient itself
(verified working against a real backend, health_check returns 200):
1. Bare autoload identifiers don't resolve at compile time in --script/
SceneTree runs (the node exists at runtime, root.has_node() is true,
but referencing it as a plain global name fails to compile) -- use
get_node() instead.
2. A freshly add_child()'d HTTPRequest isn't reliably "inside tree" yet
at _initialize() time, since --script mode hasn't processed a frame
yet -- one process_frame await fixes it.
Verified: full smoke test now passes end-to-end against a live backend
(docker compose postgres + alembic migrations + uvicorn), confirming
ApiClient.health_check() genuinely round-trips.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This branch never actually had the Godot scaffold — a workspace-reuse bug
(see agp source repo) let leftover backend/**/__pycache__/*.pyc files from
an unrelated task get swept into this branch's only prior commit, which
was checkpointed under this task's title despite containing zero Godot
files. Removing that garbage and bringing over the real project from
agent/997c429bc6b4 (the original PR's branch) with the review feedback
addressed:
- Play button no longer references the nonexistent scenes/game.tscn.
- Wired PlayButton/QuitButton [connection] entries in main_menu.tscn.
- Removed the invalid uid="uid://main_menu".
- Added .gitignore for .godot/.
- Removed the non-standard [export]/[export_options] sections from
project.godot (Godot 4 uses export_presets.cfg, not this).