23ec41c1d6
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>
62 lines
2.5 KiB
PowerShell
62 lines
2.5 KiB
PowerShell
# Opens two windows on the multiplayer test scene in MANUAL mode, so you can
|
|
# drive the plate / dirt-station flow yourself and watch both peers.
|
|
#
|
|
# powershell -File test\play_mp_test.ps1
|
|
#
|
|
# No scripted sequence runs. Click a window to focus it, then:
|
|
#
|
|
# 1 grab the plate 2 drop it
|
|
# 3 carry it to the dirt zone 4 drop it at the dirt zone
|
|
# 5 check it snapped + went dirty
|
|
# 6 run the whole automatic sequence (server window only)
|
|
# 0 dump the current state of everything
|
|
# C toggle between the fixed debug camera and the XR rig camera
|
|
#
|
|
# Keys act on whichever window has focus, so you can grab on the CLIENT and
|
|
# watch the SERVER window follow. Each window opens on a fixed camera looking
|
|
# at the test area, overlays its own step log, and writes it to
|
|
# logs\mptest_server.log / logs\mptest_client.log.
|
|
#
|
|
# The typical repro: on the CLIENT press 1, 2 (grab and drop), then on the
|
|
# SERVER press 1, 2. Then on the CLIENT press 1, 3, 4 and press 5 on both.
|
|
|
|
param(
|
|
[string]$Godot = "C:\Users\Aqua Aurora\Desktop\Godot_v4.7-stable_win64.exe",
|
|
# Pass -Solo to open a single window with no networking, to compare the
|
|
# same steps against single-player behaviour.
|
|
[switch]$Solo
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$proj = Split-Path -Parent $PSScriptRoot
|
|
$scene = "res://test/multiPlayerTest.tscn"
|
|
|
|
. (Join-Path $PSScriptRoot "mp_window_layout.ps1")
|
|
|
|
function Start-Instance($extraArgs) {
|
|
$a = "--xr-mode off --resolution 900x600 --path `"$proj`" $scene"
|
|
if ($extraArgs) { $a += " -- $($extraArgs -join ' ')" }
|
|
return Start-Process -FilePath $Godot -ArgumentList $a -PassThru
|
|
}
|
|
|
|
if ($Solo) {
|
|
Write-Host "Opening a single offline window (no networking)."
|
|
$null = Move-GameWindow (Start-Instance $null) 300 100
|
|
return
|
|
}
|
|
|
|
Write-Host "Opening SERVER window (left)..."
|
|
$w = Move-GameWindow (Start-Instance @("--server")) 20 60
|
|
Start-Sleep -Seconds 5
|
|
Write-Host "Opening CLIENT window (right)..."
|
|
$null = Move-GameWindow (Start-Instance @("--join", "127.0.0.1")) (20 + $w + 12) 60
|
|
|
|
Write-Host ""
|
|
Write-Host "Both windows are up. Click one to focus it, then press:"
|
|
Write-Host " 1 grab 2 drop 3 carry-to-dirt 4 drop-at-dirt 5 verify 0 dump"
|
|
Write-Host " 6 runs the whole automatic sequence (server window only) C toggles the camera"
|
|
Write-Host ""
|
|
Write-Host "Step logs: $(Join-Path $proj 'logs\mptest_server.log')"
|
|
Write-Host " $(Join-Path $proj 'logs\mptest_client.log')"
|
|
Write-Host "Close the windows when you're done (Esc quits)."
|