The per-peer spread offset used the raw ENet peer id directly
((peer_id - 1) * 1.5), assuming ids are small sequential numbers. Real
peer ids are large effectively-random 32-bit values, so a joining
client's XR rig was being shifted by hundreds of millions of units off
the origin — the world had actually replicated correctly, the player
was just teleported far away from all of it, with float precision bad
enough at that range to jitter the view and destabilize physics.
Bound the offset to a small deterministic slot instead; the host still
keeps its original baked spot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The world was populated before NetworkManager.world_ready() actually
called host()/join(), so owns_world() read true for every peer
(including a joining client) and each one built its own local,
unreplicated copy instead of the client receiving the server's spawn
through the MultiplayerSpawner. Population now happens after the
session is actually established.
Also adds net-log coverage for spawn/despawn, station gating, item
authority handoff, station snapping, avatar spawn/despawn, and scene
transitions, so multiplayer behavior is visible in
%TEMP%\vryhungry_net_<pid>.log instead of failing silently.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>