Avatar: rebuild the placeholder base body as a Roblox-classic (R6) figure --
six chunky solid box parts (head, torso, two arms, two legs) skinned one
part per bone onto the same 24-bone canonical rig, arms hanging at the
sides instead of a T-pose, plus a painted-on smile face. Classic
yellow/blue/green "noob" palette, flat single-colour surfaces (the old
capsule-segment chain read as a banded scarecrow). Attachments resized to
match the new proportions; head/hair/glove primitives are now boxes too
(dropped ~130KB spheres to ~3KB each).
Build stamp: it never rendered because `set_anchors_and_offsets_preset()`
plus `.position` on a zero-size Label collapses the rect to negative width.
Switched to a bottom-anchored full-width strip with explicit offsets and
text alignment, brighter colour + black outline. Shows on the main menu
(bottom-right) and in-world HUD (bottom-left) as `build PC3 <commit>`.
Verified: avatar smoke 14/14 from source and against the exported pack;
game.tscn + main_menu.tscn boot headless with no script errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
config/version didn't survive export cleanly. Instead checkpoint_ops writes
assets/build_info.json at build time (a plain JSON file, packed by the same
include_filter as the avatar catalog) and the menu + in-world HUD read it
with FileAccess. Shows 'build PC3 <commit>' so playtest reports say exactly
which build is running.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015jtxAiu5dAW4WxCNdGkrkJ
- Clients now hard-snap to the server's spawn position on the first
snapshot. player.gd starts everyone on the fixed PlayerSpawn marker but
the server spreads spawns out -- so one client saw players ~1m apart
(correct) while the other saw them stacked (its local player still on the
marker, never corrected because the ~1m drift was under the old 1.5m
threshold). Threshold also tightened to 0.6m.
- game.gd._exit_tree() closes the ENet connection cleanly when you leave a
world, so the server fires peer_disconnected and removes your player.
Before, leaving just changed scene and the server never knew -- everyone
else kept seeing a frozen "ghost".
- server.gd: once-a-second sweep of players whose peer is no longer
connected, as a backstop for a missed disconnect.
- Build stamp: checkpoint_ops bakes "<PC> <commit>" into
application/config/version at export; the menu and in-world HUD show
"build PC3 <hash>" so playtest reports aren't ambiguous about which
build is running.
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
- 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