Commit Graph

40 Commits

Author SHA1 Message Date
daddy 80f24bfd01 Avatars actually show; camera stops hugging the player
checkpoint / checkpoint (push) Successful in 1m40s
- SpringArm3D was colliding with the player's own capsule (same layer) ->
  camera yanked up against the head. add_excluded_object(self) in _ready +
  gentler default pitch, shorter arm, lower pivot.
- AvatarSystem.build_current_avatar() is now self-sufficient: loads the
  catalog itself and falls back to a bare default loadout if login didn't
  prime one. So the in-world player always shows an avatar, never the
  capsule, as long as the assets are packed (they are now).
- New AvatarSystem.assemble_avatar(): builds a standalone avatar with a
  throwaway loader, touching no shared state -- so it's safe to call once
  for the local player AND once per remote peer. (load_avatar()'s shared
  loader reset()s and freed the previous avatar on every call.)
- game.gd: remote players render as the assembled avatar too (shared default
  look, tinted per-peer, name tag) instead of a plain capsule.

Client-side -- rebuild the exe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-05 23:52:10 +00:00
daddy 54bc763dff Fix backwards controls + the exe's bare-capsule avatar
checkpoint / checkpoint (push) Successful in 1m41s
Two real bugs from the last change, both only visible in a real run:

1. Controls reversed: the third-person SpringArm3D had a 180° Y rotation
   that put the camera IN FRONT of the player looking back -- a mirror, so
   W read as backward and mouse pitch as inverted. SpringArm3D extends its
   child along +Z, not -Z; with no rotation the camera sits behind the
   player (+Z world) and looks -Z straight at them. Verified the vectors
   headless: camera behind, W decreases Z, mouse-up looks up.

2. Bare capsule instead of an avatar in the .exe: avatar_catalog.gd reads
   assets/avatars/catalog.json with FileAccess.open(), but the export used
   `all_resources` + empty include_filter, which does NOT pack plain .json
   files. ensure_catalog() failed -> no loadout -> build_current_avatar()
   returned null -> capsule stayed visible. From source it worked because
   res:// is the real filesystem. Fixed: include_filter="*.json" in both
   export presets (verified catalog.json + the .glb pack are now in the
   exported .pck).

- scripts/verify_export_contents.gd + a checkpoint-runner step: export a
  pack and assert the non-resource runtime files are inside it. This is
  the guard for the "works from source, broken in the .exe" class.

Client-side -- rebuild the exe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-05 23:30:00 +00:00
daddy 87f78b0226 Third-person camera + fix the avatar customize preview
checkpoint / checkpoint (push) Successful in 1m34s
You couldn't see your own character because the camera was first-person
(inside the avatar's head), and the customize screen's 3D preview rendered
nothing.

- player.tscn: camera now hangs off a SpringArm3D on a CameraPivot, 4 m
  behind + above the player, pulled in on collision. player.gd: yaw still
  turns the body, pitch orbits the arm (starts tilted down to frame the
  avatar).
- avatar_customization_ui.gd: the preview SubViewport had own_world_3d=false,
  so it shared the Control-only main world -- no scenario, nothing rendered.
  Set own_world_3d=true, add a WorldEnvironment with ambient fill + a
  key/fill light pair, make the camera current, tweak framing.
- smoke_test_player: assert the third-person SpringArm rig (18/18).
- smoke_test_enter_world: wait longer for the scene change -- a real
  POST /join spawns a dedicated server, which is slow (9/9).

Client-side change -- rebuild the exe to get it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-05 23:13:07 +00:00
daddy 7ccdd3063a Fix: players fall through the floor in multiplayer
checkpoint / checkpoint (push) Failing after 1m34s
scenes/server_player.tscn was a bare CharacterBody3D with NO CollisionShape3D,
so on the server move_and_slide() never hit the ground and is_on_floor() was
always false -> the server player fell forever, and the client (which snaps to
the server's authoritative position past a drift threshold) fell with it.

Give the server player the same CapsuleShape3D (r=0.35, h=1.6) as the client.
The 2-player test now also asserts the server keeps the player on the ground
(on_floor / min_y), which is how this slipped through -- the tests only
checked "position changes" and "sees the other player".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-05 22:58:55 +00:00
daddy 7bd8124199 PC3 Phase 3.2: real age verification (date of birth, not a checkbox)
POST /api/auth/register now requires a real date_of_birth (ISO YYYY-MM-DD).
The server computes age (auth.calculate_age) and rejects under-18s with 403;
a future/nonsensical date is 400. The DOB is stored and surfaced only to
that user (/api/auth/me).

Still self-attested (a user can enter a false DOB) -- but a concrete,
stored, legally-meaningful data point and the seam for a future ID-check
pass. Encryption-at-rest for the column and the Tier-2 hook are not done;
the limitation is stated plainly in docs/architecture/content_moderation.md 5.2.

- auth.py: RegisterRequest.date_of_birth (date); MIN_AGE=18; register()
  computes + enforces; UserResponse.date_of_birth typed `date | None`
  (the old `str | None` would have 500'd /me once a DOB was actually stored).
- api_client.gd / main_menu.gd: the Over18 checkbox is now a
  "Date of birth (YYYY-MM-DD)" field with a client-side sanity check.
- tests: test_auth_age_gate.py rewritten around the endpoint + age math
  (adult/minor/exactly-18/future-dob/stored); ~25 over_18 references across
  6 backend test files + 6 game smoke tests updated. Backend 74 passed;
  game smokes green (auth 6/6, menu_full_flow 12/12, e2e 4/4, etc.);
  fresh-clone boot PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-05 22:18:38 +00:00
daddy dba865ae4b PC3 Phase 2.3: networked play path in the game
- Session autoload: carries {world_id, host, port} from the world browser
  to the game scene.
- world_list.gd "Enter World": POST /api/worlds/{id}/join -> Session.begin
  -> load game.tscn. On 503 (backend has no orchestration) falls back to
  local single-player; other errors surface on the card.
- api_client.gd: join_world().
- game.gd (new, on game.tscn root): local single-player by default; when
  Session.active, connects a NetworkClient to the world's dedicated server
  and runs networked -- forwards the local Player's input each physics
  frame, snaps to the server position past a generous drift threshold
  (the Player keeps predicting locally with real collision), renders other
  players from snapshots (labelled capsules, lerped -- avatar appearance
  sync is a follow-up, the server doesn't replicate loadouts yet), and
  returns to the world list on disconnect.
- player.gd: exposes net_input (the input frame it applied) and
  apply_server_correction().
- network_client.gd: send_input() + get_own_server_state() for callers that
  do their own collision-aware prediction.
- scripts/mp_game_flow_client.gd + run_game_multiplayer_test.sh: two real
  clients each register -> join via the API -> game.tscn -> networked;
  A sees B and moves. PASS.

Verified end to end against an orchestration-enabled backend. Non-networked
smokes green (enter_world 9/9, world_list 5/5, player 15/15,
avatar_in_world 10/10, main_menu_full_flow 12/12).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-05 22:07:06 +00:00
daddy e59cf158e5 PC3 Phase 1.4-1.6: avatars wired into the game end to end
- api_client.gd: get_avatar_loadout() / save_avatar_loadout()
- main_menu.gd: on successful auth (fresh or session-restore), load the
  catalog + fetch the saved loadout into AvatarSystem so the in-world
  player renders as the player's avatar. Best-effort: a slow/failed prime
  never gates getting in (UI goes authenticated first, prime runs after).
  New "Customize Avatar" button (enabled only when signed in).
- avatar_system.gd: local-player convenience API -- ensure_catalog(),
  set_loadout_from_dict() (from the backend's GET shape), has_loadout(),
  build_current_avatar().
- player.gd: swaps the placeholder capsule for the assembled avatar
  (feet-aligned to the capsule bottom) when a loadout is primed; silent
  fallback to the capsule otherwise. Camera stays first-person for now
  (third-person is a noted follow-up).
- avatar_visualizer.gd: rewrote it -- the old version referenced
  AvatarLoader.SlotResult / load_slot() which never existed, so it didn't
  compile and took avatar_customization_ui.gd down with it. Now a thin
  wrapper over AvatarLoader for the preview.
- avatar_customization_ui.gd: seeds from the live loadout, saves through to
  AvatarSystem + PUT /api/avatar/loadout (local file kept as fallback),
  fixed a Camera3D.look_at() before-in-tree crash.
- scenes/avatar_customization.tscn rebuilt (old one used the Godot-3
  `ViewportContainer` node type) + avatar_customization_screen.gd wrapper
  that returns to the menu on close.
- smoke_test_avatar_in_world.gd: primed loadout -> game.tscn -> player
  shows the assembled avatar, capsule hidden, rig + bone mounts present.

Verified: cold-cache smokes green (player 15/15, avatar_system 14/14,
avatar_in_world 10/10, main_menu_ui 13/13); against a live backend
main_menu_full_flow 12/12, main_menu_back 20/20, world_list 4/4,
enter_world 9/9, e2e 4/4; plus a manual register->default-loadout->
customize->save->re-fetch-persisted round trip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
2026-09-05 21:50:41 +00:00
coder-bot 59ca5a4c6e Merge main into agent/896fbb97de1d
# Conflicts:
#	.godot/editor/filesystem_cache10
#	.godot/editor/project_metadata.cfg
#	.godot/global_script_class_cache.cfg
#	.godot/uid_cache.bin
#	project.godot
#	scenes/main.tscn
#	scenes/main_menu.tscn
#	scripts/main_menu.gd
#	scripts/main_menu.gd.uid
2026-08-21 01:26:13 +00:00
claude-checkpoint-tooling 7be50e3313 Fix real scene collision + a real regression from removing the Server autoload
Two genuine bugs, found by reproducing rather than trusting the branch's
own state:

1. scenes/player.tscn collided with PC2's client-side player scene at the
   exact same path (server-authoritative PlayerNode vs. the local
   single-player character) -- whichever merged last would silently
   overwrite the other. The branch's own last commit had literally
   committed unresolved <<<<<<< conflict markers into the file instead of
   resolving them. Renamed the server's version to
   scenes/server_player.tscn, restored scenes/player.tscn to match main's
   real client scene exactly, repointed scripts/server.gd's preload.

2. scenes/server.tscn's root Node had no script attached at all -- it only
   ever worked before because project.godot had `Server` registered as a
   project-wide autoload (the exact wholesale-overwrite this task was
   already asked to fix). Once that autoload was correctly removed,
   nothing attached server.gd to the scene tree anymore and the dedicated
   server silently did nothing. Attached scripts/server.gd directly to
   scenes/server.tscn's root node instead.

Verified: godot --headless --import/--quit both clean, full 10-suite
client smoke test all passing, and the dedicated server actually listens
on UDP port 7777 when launched explicitly (`godot --path . scenes/server.tscn`)
-- confirmed with `ss -ulnp`, not just trusting the startup print (first
checked with `ss -tlnp`, which only shows TCP and falsely suggested it
wasn't listening at all; ENet is UDP).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6rUEynmv2QmweocAQYARN
2026-08-21 00:59:24 +00:00
architect-bot 736ebce0d0 WIP checkpoint: Address review feedback: Implement Godot dedicated server scene + player spawning 2026-08-21 00:44:47 +00:00
coder-bot 8be962248d Merge main into agent/9bfb90314053
# Conflicts:
#	scenes/worlds/test_level.tscn
2026-08-21 00:39:28 +00:00
claude-checkpoint-tooling 7790670559 WIP: add Camera3D, fix jump/forward-direction bugs, add mouse capture, build game.tscn composition 2026-08-21 00:39:13 +00:00
architect-bot 4dda636ed0 WIP checkpoint: Address review feedback: Implement Godot dedicated server scene + player spawning 2026-08-21 00:01:15 +00:00
coder-bot 945c498a31 WIP checkpoint: PC2: Basic visible player character representation 2026-08-21 00:01:08 +00:00
architect-bot 4276a89a6a WIP checkpoint: Address review feedback: Implement Godot dedicated server scene + player spawning 2026-08-20 23:31:27 +00:00
coder-bot fdc43bd5f1 WIP checkpoint: PC2: Build a basic playable test level scene 2026-08-20 23:01:55 +00:00
architect-bot 406ff3457e WIP checkpoint: PC2: Basic visible player character representation 2026-08-20 22:50:48 +00:00
architect-bot f70b5b2ee5 WIP checkpoint: PC2: Basic visible player character representation 2026-08-20 22:28:06 +00:00
architect-bot 2fa7b1f475 WIP checkpoint: PC2: Basic visible player character representation 2026-08-20 22:26:08 +00:00
coder-bot 7f753cb1b8 WIP checkpoint: PC2: Build a basic playable test level scene 2026-08-20 22:17:48 +00:00
coder-bot 88197ffe8f WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-20 21:28:00 +00:00
claude-checkpoint-tooling 0ecc16d963 Registration: replace date-of-birth field with a simple 18+ confirmation
The register form collected and validated an actual date of birth just to
throw it away (backend never stored it beyond a nullable column). Per the
roadmap note: don't collect/store DOB right now, just get a client-side
"I am 18 or older" confirmation. RegisterRequest.date_of_birth (str) is now
over_18 (bool); the endpoint rejects over_18=false with 400 same as before.
calculate_age() is kept (unused by /register now) since real DOB collection
is coming back in a future pass and deleting it broke test_auth_age_gate.py's
import for no benefit.

Frontend: DOBEdit LineEdit replaced with an Over18Check CheckBox; api_client.gd's
register() now takes a bool instead of a date string. All Godot callers
(smoke_test_auth/e2e/world_list/main_menu_ui/main_menu_full_flow) and backend
test files updated to match. Full suite verified against a live backend:
61/61 pytest, 6/6 Godot smoke tests (33 individual assertions), all passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6rUEynmv2QmweocAQYARN
2026-08-20 16:25:27 +00:00
coder-bot af50819a16 WIP checkpoint: Main menu: fix Register-mode UI trapping the user (remove the confusing mode toggle) 2026-08-20 14:01:20 +00:00
claude-checkpoint-tooling 8fc9d378e4 Fix: world cards render at ~1px wide once real data exists
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>
2026-08-20 13:35:46 +00:00
claude-checkpoint-tooling 3679e4dbc4 Fix: Login/Register unreachable from the main menu -- no button opened it
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>
2026-08-20 13:13:38 +00:00
coder-bot b3227f5b78 Wire browse-worlds screen to GET /api/worlds
- 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)
2026-08-19 21:57:39 +00:00
coder-bot 03da22aa9e WIP checkpoint: Implementation: Browse worlds screen wired to GET /api/worlds 2026-08-19 21:20:52 +00:00
coder-bot 62e15d0c87 WIP checkpoint: Implementation: Browse worlds screen wired to GET /api/worlds 2026-08-19 21:20:52 +00:00
reviewer-bot dbdc01f8d6 Merge remote-tracking branch 'origin/main' into merge-fix
# Conflicts:
#	.gitignore
#	project.godot
2026-08-18 14:40:41 +00:00
coder-bot 803405aba4 WIP checkpoint: Implement avatar loader and customization UI in Godot 2026-08-18 14:03:07 +00:00
coder-bot 61ee81a974 WIP checkpoint: fix script-attach typo, missing class_name, var name mismatch
- scenes/avatar_customization.tscn: script_extense -> script (typo meant
  the customization UI script was never actually attached to the scene)
- scripts/avatar/avatar_loadout.gd: add missing class_name AvatarLoadout
  (avatar_system.gd references the type everywhere; only this file among
  the avatar scripts lacked its class_name)
- scripts/main_menu.gd: _ready() referenced _customize_button/_settings_button/
  _quit_button but the @onready vars are named without the leading
  underscore
2026-08-18 13:44:41 +00:00
coder-bot 689c51e179 WIP checkpoint: Implement avatar loader and customization UI in Godot 2026-08-18 00:47:59 +00:00
coder-bot 97949d6b44 WIP checkpoint: Implement chunk streaming system in GDScript 2026-08-18 00:08:24 +00:00
coder-bot 5090341591 Bring in Godot project + address review feedback (this branch had none of it)
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).
2026-08-17 21:24:43 +00:00
architect-bot 37a311b0c5 WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-17 18:15:38 +00:00
agent-bot 11e812cb6b WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-17 18:05:34 +00:00
agent-bot 5097cdd9f4 WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-17 18:05:34 +00:00
agent-bot 2224c34aa8 WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-17 18:05:34 +00:00
agent-bot 2c5f6fcb04 WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-17 18:05:34 +00:00
agent-bot c5d2eb6bd2 WIP checkpoint: Implement Godot dedicated server scene + player spawning 2026-08-17 18:05:34 +00:00