All players spawned at the exact same PlayerSpawn point and were on the
same collision layer, so two overlapping CharacterBody3Ds triggered a
physics depenetration shove that could launch one through the 1-unit-thick
floor.
- server.gd: golden-angle spawn spread -- each join lands ~1m+ from the
others, growing outward, never exactly repeating.
- server_player.tscn + player.tscn: players on collision layer 2, mask 1
(world only) -- players no longer collide with each other at all
(fine for a social game; also removes the shove).
- player_spawner.gd + player.gd: hard safety net -- if y < -10 (any
tunnel-through), snap back to the spawn instead of falling forever.
Verified: 3 server players spawn 1.15m+ apart, none fall, all rest on the
floor. 2p multiplayer + network_client smokes green.
Client-side -- rebuild the exe.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
- 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
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
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