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>
export_presets.cfg had binary_format/embed_pck=false on both presets,
so every export produced two files (agp-game.exe + agp-game.pck) that
have to stay together and correctly named or Godot refuses to start:
"Couldn't load project data ... Is the .pck missing?" -- hit for real
running a downloaded PC1 playtest exe today (only the .exe got copied
over, not the .pck).
Verified both platforms now produce a single self-contained binary:
Windows PE32+ (104,580,904 bytes, no separate .pck) and Linux ELF64
(71,140,152 bytes, no separate .pck).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the manual multi-step shell commands (docker compose, alembic,
uvicorn with env vars, manual teardown) with the one-click dashboard
flow at /checkpoint/PC1 in ~/agp (commit 261c45f): Start Backend / Stop
& Clean Up, and Build / Download a pre-configured Windows .exe. No
terminal commands needed on either machine.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Architect independently reclaimed this task after my fix landed and
rewrote the same doc with more precise per-file test descriptions and a
more specific gap list -- genuinely better in places (caught real detail
I'd summarized loosely) but its own investigation also introduced one
factual error (claimed 18 tests in test_auth_age_gate.py; verified via
pytest --collect-only, it's 17) and dropped two things worth keeping:
the exact commit hash for reproducibility, and the Client export section
(it never looked at the export step).
Merged: kept the architect's richer per-file coverage descriptions and
gap analysis, restored the verified counts/commit hash/export section/
--quit gotcha from my original.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Validation results from the 2026-08-20 checkpoint run (fresh clone,
commit 927f9bd: migrations, 68/68 pytest, all 4 Godot smoke tests, both
platform exports) plus manual playtest instructions for backend on
cloudbison / client on pinkbison. First of docs/checkpoints/PC<N>.md,
one per Package Checkpoint going forward.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Was written 2026-08-19 and badly stale by the time the full PC1
checkpoint validation ran 2026-08-20: said 38 backend tests (actually
68), said zero integration/endpoint tests exist (4 real integration
test files landed since), and said nothing had merged for Godot smoke
tests (4 real ones did).
Verified for real, fresh clone of main, commit 927f9bd: migrations +
68/68 pytest + all 4 Godot smoke tests + both platform exports (Linux
ELF64 + Windows PE32+, confirmed valid via file) all clean.
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.
- auth.py: _create_access_token had no jti claim, so two refreshes
within the same wall-clock second produced byte-identical access
tokens (same failure mode already fixed for refresh tokens earlier)
- test_refresh_endpoint.py: usernames (jwttoken, refreshuser, etc.)
collided with test_auth_endpoints.py's own test usernames once run
in the same pytest session against a shared DB with no per-test
isolation -- exactly how run_checkpoint_tests.py invokes pytest.
Namespaced this file's usernames with a refep_ prefix.
Full backend suite (63 tests) passes clean end-to-end against a real
Postgres with real migrations applied.
- 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)
Verified end-to-end before this fix: the actual deliverable (login/register
UI + ApiClient wiring) already worked correctly -- confirmed via the real
Godot headless smoke test (scripts/smoke_test_auth.gd), 6/6 passing against
a live backend. This commit only fixes the bonus backend integration test
file the task also added, which had 4 separate real bugs:
1. Imported override_get_db/SessionLocal from app.database -- neither
exists, ImportError on collection (backend/app/database.py only ever
exposed get_db + the async engine/session factory).
2. Base.metadata.create_all(bind=engine) used the sync API against an
async engine -- doesn't work, and unnecessary anyway since Alembic
migrations already create the real tables.
3. Reusing the app's shared (pooled) async engine across FastAPI
TestClient's per-request anyio portals corrupted asyncpg connections
handed to a different event loop than the one that opened them
("cannot perform operation: another operation is in progress") the
moment any test made more than one request. Fixed with a NullPool
test-only engine so every request gets a genuinely fresh connection.
4. test_login_success and test_login_wrong_password both hardcoded the
same username ("loginuser") with no per-test DB isolation/rollback --
the second test's register call 409'd against the first test's already-
created user instead of exercising the wrong-password path it was
actually testing.
Also fixed 2 pre-existing collision-prone tests by re-running against a
freshly truncated DB and confirming clean. Full suite: 49/49 passing
(38 pre-existing + 11 new).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
passlib 1.7.4 (last release, unmaintained since ~2020) detects its bcrypt
backend via bcrypt.__about__.__version__, which bcrypt>=4.1 removed. Every
hash_password/verify_password call was raising 'password cannot be longer
than 72 bytes' -- a misleading downstream symptom of the real
AttributeError: module 'bcrypt' has no attribute '__about__'.
Without this pin, pip install -r requirements.txt silently grabs whatever
the latest bcrypt is and breaks register/login outright -- confirmed this
broke 3 separate pieces of work this session (manual register curl tests,
and now a real Godot smoke test) purely from environment drift, not from
any actual code bug. Verified the fix works from a genuinely clean venv +
pip install, not just patching the already-installed shared venv.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>