e59cf158e5
- 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
81 lines
2.4 KiB
GDScript
81 lines
2.4 KiB
GDScript
extends SceneTree
|
|
## Smoke test: a primed avatar loadout ends up rendered on the in-world player.
|
|
##
|
|
## Primes AvatarSystem directly (no backend needed -- main_menu.gd is what
|
|
## fetches the loadout over HTTP in the real flow) then loads scenes/game.tscn
|
|
## and checks the Player swapped its placeholder capsule for the assembled
|
|
## avatar.
|
|
##
|
|
## Run: godot --headless --path . --script res://scripts/smoke_test_avatar_in_world.gd
|
|
|
|
var passed := 0
|
|
var failed := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
_run()
|
|
|
|
|
|
func _run() -> void:
|
|
await process_frame
|
|
|
|
var avatars := root.get_node_or_null("AvatarSystem")
|
|
if not _check("AvatarSystem autoload exists", avatars != null):
|
|
return _finish()
|
|
|
|
_check("catalog loads", await avatars.ensure_catalog())
|
|
|
|
var bases: Array = avatars.get_assets_for_slot("base_body")
|
|
if not _check("catalog has a base body", bases.size() > 0):
|
|
return _finish()
|
|
|
|
var ok: bool = avatars.set_loadout_from_dict({
|
|
"base_body": bases[0],
|
|
"slots": {"hair": "hair_ponytail_01", "chest": "chest_bandit_01"},
|
|
})
|
|
_check("loadout primes from a backend-shaped dict", ok and avatars.has_loadout())
|
|
|
|
var err := change_scene_to_file("res://scenes/game.tscn")
|
|
_check("game.tscn loads", err == OK)
|
|
# Give _ready() (scene swap + avatar assembly) a few frames to settle.
|
|
for i in 6:
|
|
await process_frame
|
|
|
|
var game := current_scene
|
|
var player: Node = game.get_node_or_null("Player") if game else null
|
|
if not _check("player exists in the world", player != null):
|
|
return _finish()
|
|
|
|
var avatar := player.get_node_or_null("Avatar")
|
|
_check("player rendered the assembled avatar (not the capsule)", avatar != null)
|
|
|
|
var capsule = player.get_node_or_null("CapsuleMesh")
|
|
_check("placeholder capsule mesh is hidden", capsule != null and not capsule.visible)
|
|
|
|
if avatar:
|
|
var skel := avatar.get_node_or_null("Skeleton3D") as Skeleton3D
|
|
_check("avatar carries the canonical rig", skel != null and skel.get_bone_count() == 24)
|
|
var mounts := 0
|
|
if skel:
|
|
for c in skel.get_children():
|
|
if c is BoneAttachment3D:
|
|
mounts += 1
|
|
_check("equipped hair + chest are bone-mounted", mounts >= 2)
|
|
|
|
_finish()
|
|
|
|
|
|
func _check(description: String, condition: bool) -> bool:
|
|
if condition:
|
|
print(" PASS: ", description)
|
|
passed += 1
|
|
else:
|
|
printerr(" FAIL: ", description)
|
|
failed += 1
|
|
return condition
|
|
|
|
|
|
func _finish() -> void:
|
|
print("\n=== SMOKE RESULT: %d/%d passed ===" % [passed, passed + failed])
|
|
quit(0 if failed == 0 else 1)
|