Commit Graph

6 Commits

Author SHA1 Message Date
algodoogle 61d92052ac Rebuild multiplayer on the stock spawner and synchronizer
Adding an object to the game no longer requires any networking code. The
MultiplayerSpawner runs in its default mode — no spawn_function, no payload
dictionary — and NetReplication builds each object's SceneReplicationConfig
from a convention, so scenes carry no hand-authored replication at all.

Every replicated node gets two generated synchronizers: NetSync for script
state, always server-owned, and NetXform for position, handed to whoever is
holding the object. That split is what makes grab prediction work — a
synchronizer never applies inbound state on the peer that owns it, so a
player's own hand drives an object with no round trip while is_dirty and
friends keep flowing one way from the server.

Interaction is now two RPCs for the whole game (NetGrab), and client gating is
one rule applied to every object (NetWorld). Deleted: net_pickable.gd, the
replicated net_held_by field and its held-state juggling, the
grant/reject/force-release negotiation, the static-item despawn RPC, and the
per-scene replication configs. Authority is the single source of truth for who
simulates an object.

Two things the convention had to learn, both found by the test suite:

  * Addon scripts are excluded. godot-xr-tools' snap zones and pickables expose
    a public `enabled`, which is exactly the flag each peer must set for itself
    — so replicating it meant the server sent `enabled = true` back over every
    client's gate, and stations went on grabbing objects out of the local
    player's hands.
  * Arrays of nodes are excluded. Array[Node3D] and Array[FoodItem] would
    otherwise try to serialise live node references.

table.gd's replicated state loses its underscore prefix, which now marks a
variable as private and unreplicated; two in-place array mutations there were
skipping their setters, and the progress bar could divide by zero on a client.

Suite: 146/146 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 22:04:22 +01:00
algodoogle 96ea2dd1ea mp fix 2026-07-28 19:41:31 +01:00
algodoogle 23ec41c1d6 Fix five multiplayer sync bugs, add automated two-instance test
Adds test/multiPlayerTest.tscn plus a driver that runs the kitchen flow
across two game instances: grab/drop, dirt station, sink washing, hob
cooking, counter combining and plating. Runs headless (run_mp_test.ps1),
in two visible windows (run_mp_test_windowed.ps1), or by hand with
keyboard controls (play_mp_test.ps1). 108 checks, exits non-zero on
failure.

After every step both peers snapshot every item's position and rendered
state and the server diffs them. Targeted assertions only look at the
thing a step touched, which misses desyncs elsewhere - that audit is
what caught the last bug below.

Bugs found and fixed:

- net_pickable: apply_held_state() only wrote `enabled` in its
  non-authority branch, so once a client grabbed an item every other
  peer set enabled=false and regaining authority never restored it. The
  server could then never pick that item up again, and a station would
  "snap" it (emitting has_picked_up, so a plate still got marked dirty)
  while pick_up() bailed out on the disabled item - leaving the zone
  holding an item with no grab driver.

- network_manager: station gating only happened in the spawn path, so
  stations baked into a scene file kept running their snap zones on
  clients and grabbed items straight out of the local hand. Added
  gate_existing_stations().

- network_manager: despawn_item() only freed the server's copy. Items
  baked into a scene aren't tracked by the MultiplayerSpawner, so
  consuming one left a ghost on every client, which then blocked the
  station it sat in and got grabbed instead of its replacement.

- network_manager: the snap-into-station decision read the server's own
  copy of the item position, but the reliable release RPC routinely
  overtakes the synchronizer's unordered position updates - so it acted
  on a stale position and teleported items back into the station they
  had just been carried away from. The releasing peer now sends its
  final transform and the server adopts it first.

- container: contained_ids.append()/erase() mutate the array in place,
  which never fires the setter that rebuilds the plate's visuals. The
  peer that put food on a plate was the only peer that never redrew it;
  remote peers looked right because the synchronizer assigns there.

Also null-guards XRServer.get_tracker() in the vendored xr-tools hand
grab point, which threw on every successful grab without an XR runtime,
and adds multiplayer_world.populate_from_layout so debug scenes can bake
their own content instead of spawning the whole kitchen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-26 02:00:50 +01:00
algodoogle 4ca0a05d1b Fix world population race and add multiplayer diagnostics
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>
2026-07-25 19:42:49 +01:00
algodoogle 596d777fae asd 2026-07-25 19:26:43 +01:00
algodoogle f7024db98d added multi 2026-07-25 18:20:12 +01:00