assets/avatars/catalog.json referenced 14 GLB files that were never in the
repo, so the avatar system could not load a single avatar. And the loader's
assembly path had never run against a real skeleton -- it called
Skeleton3D.has_bone() (no such method in Godot 4) and hand-copied bones in a
way that dropped the skin binding.
- scripts/tools/gen_placeholder_avatars.gd: generates the pack headlessly via
GLTFDocument -- one rigged/skinned base body (the full 24-bone canonical
rig from a T-pose) + 13 primitive attachments. Crude on purpose; real art
swaps in as a pure content change (catalog paths unchanged).
- assets/avatars/*.glb: the generated pack (checked in).
- avatar_loader.gd: rewrote _parse_glb_as_avatar to lift the skin-bound
Skeleton3D that generate_scene() already produces, instead of rebuilding it.
Attachments now mount via BoneAttachment3D to the catalog's mount_bone and
are actually added to the avatar tree (they were parsed then dropped).
Fixed the has_bone() crash, the null ARRAY_BONES cast in _retarget_skin,
and the Array[String] type mismatch on the accessory list.
- smoke_test_avatar_system.gd: replaced the "asserts it fails gracefully
because the files don't exist" section with a real assembly check --
Skeleton3D present, full 24-bone rig, canonical bones resolve by name,
visible body mesh, equipped items mounted to bones. 14/14 from a cold cache.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
Root cause: a stale .godot/global_script_class_cache.cfg was force-committed
to the repo (last regenerated before the avatar system merged), and Godot
does not rebuild the global class registry on a plain headless scene run --
only an editor scan / `--import` does. So every cross-file `class_name`
reference in the avatar module failed to resolve from a fresh clone, and the
AvatarSystem autoload failed to instantiate entirely.
- Untrack .godot/ and sandbox_probe/.godot/ (already in .gitignore; were
force-added during old WIP checkpoints)
- Reference sibling avatar scripts via preload() constants instead of their
global class_name, so the module parses independent of scan order. Keeps
the class_name declarations for editor/tooling use.
- avatar_loadout.gd's static from_json() factory self-references via a
preload const too (a script can't resolve its own class_name mid-compile
on a cold cache).
- smoke_test_avatar_system.gd: same preload treatment so it runs from a bare
`--script` invocation.
Verified from a wiped .godot/: main_menu.tscn and game.tscn boot with zero
script errors; smoke_test_avatar_system 10/10, smoke_test_player 15/15,
smoke_test_main_menu_ui 13/13, smoke_test_test_level PASS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
- avatar_slot_registry.gd: _slot_data/_slot_definitions made static (every
caller uses AvatarSlotRegistry.method() on the class, never an instance,
so the old instance _init() populating them was never invoked); moved
registry build to static func _static_init()
- avatar_catalog.gd: compute_loadout_hash() return type uint32 -> int
(GDScript has no fixed-width uint type)
- avatar_loader.gd: GLTFDocument.parse() (doesn't exist) -> the real
append_from_buffer()+generate_scene() API; Error.parse_error() (doesn't
exist) -> error_string(); Mesh.ARRAY_JOINTS (doesn't exist) ->
Mesh.ARRAY_BONES; removed a dead 'mesh is MultiMesh' check that can
never be true (MeshInstance3D.mesh is always Mesh, never MultiMesh);
fixed _merge_node_into_avatar to recurse into a Skeleton3D's children
(skinned meshes are commonly children of the skeleton in a GLB export,
the old code returned before ever reaching them)
- avatar_customization_ui.gd: added the missing _on_slot_button_pressed
handler (referenced by a button connect, never defined -- just calls
the existing _select_slot())
Verified: godot --headless --import + --quit is fully clean, and the
avatar_customization.tscn scene now instantiates successfully.
Remaining, scoped separately: avatar_visualizer.gd calls a
AvatarLoader.load_slot() static async API and SlotItem/SlotResult types
that don't exist -- AvatarLoader's real API (matching the project's own
avatar.md spec: one canonical skeleton, attachments retargeted onto it)
is instance-based load_base_body()/load_attachment(). AvatarVisualizer
needs rewriting to use that real API; it's the one out of step with spec,
not AvatarLoader.
- 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