Overview
Match flow exercised
Screenshots














Report
Features exercised
1. Lobby create + join — PASS
- Host (
net) opened the Async Lobby, set name/race/map, clicked Create Game. Server created anAsync-mode game and listed it. - Second player (
guest) saw the open game in a separate tab, set name/race, clicked Join. Players column updated tonet, gueston both clients. - Screenshots:
02-lobby-create.png,03-lobby-join.png.
2. Loadout + economy (two players) — PASS
- Both started with 4837 credits. Host bought Shotgun (80) + Cluster Grenade (110) + Ninja Rope (50) = 4597cr left. Guest bought Sniper (120) + Banana Bomb (130) + Teleport (100) = 4487cr left.
- Roster panel synced across both clients. Host-only Begin Match button (async requires explicit start). Clicking it moved both players to the game page.
- Screenshots:
04-loadout-host.png,05-loadout-guest.png.
3. Turn recording + batch submission — PASS (caveat: issue #1)
- Active player's client shows
🎮 RECORDING, accumulates oneBatchTickper animation frame (~60 Hz). A/D move, ↑↓ aim, Enter fire, Space submit. - Submit Turn (N ticks) sends a
TurnActionBatchover SignalR. 6 turns accepted:
| # | Player | Unit | Weapon | Ticks | Accepted |
|---|---|---|---|---|---|
| 1 | net | 1/3 | Rocket | 3350 | ✓ |
| 2 | net | 2/3 | Shotgun | 4463 | ✓ |
| 3 | net | 3/3 | Cluster Grenade | 5929 | ✓ |
| 4 | guest | 1/3 | Sniper | 484 | ✓ |
| 5 | guest | 2/3 | Banana Bomb | 2763 | ✓ |
| 6 | guest | 3/3 | Rocket | 153 | ✓ |
- After each submit, the server simulated the batch deterministically and broadcast a
TurnReplayedevent; the client played back replay frames (⏳ Replay playing…) then armed the next unit.
4. Turn rotation across players — PASS
- Turns rotate one unit at a time: net u1 → net u2 → net u3 → guest u1 → guest u2 → guest u3 → net u1 (round 2).
- Inactive client shows
⏸️ Waiting for your turn…with a disabled submit button. - After guest's unit 3, the turn correctly cycled back to
net(unit 1/3) — full round-trip confirmed. - Screenshots:
09-turn-passed-to-guest.png,10-guest-turn.png.
5. Admin undo / commit moderation — PASS
- The
netplayer (admin) sees an extra toolbar with Undo Turn / Commit Turn. Non-admin (guest) does not. - Undo: server logged
AdminUndo: success=True; last committed turn rolled back. - Commit: submitted a turn, clicked Commit → server logged
AdminCommit: admin=net; turn finalized, next unit armed, replay played. - Screenshots:
11-admin-toolbar.png,12-admin-undo.png,13-replay-playing.png,14-game-state.png.
6. Per-player state isolation — PASS
- Each client saw its own funds (net 4597cr, guest 4487cr) and its own loadout in the action bar.
- HUD showed the correct active player name and unit index on both clients.
Known issues found
1. Large turn batches exceed the 2 MiB SignalR limit NOTE / robustness FIXED
Three submissions were rejected because the serialized TurnActionBatch exceeded SignalR's MaximumReceiveMessageSize (2 MiB, set in Program.cs):
| Attempt | Ticks | Result |
|---|---|---|
| 1st | 26176 | Connection closed — message size exceeded |
| 2nd | 21403 | Connection closed — message size exceeded |
| 3rd | 8998 | Connection closed — message size exceeded |
Successful batches ranged 153–5929 ticks. Recording accumulates one tick per animation frame (~60 Hz), so a turn held open ~2.5 min (idle aiming / slow interaction) produces ~9000 ticks and trips the limit.
Fix: Added MaxRecordedTicks = 5000 cap in MultiplayerGame.razor.cs. The client auto-submits when the cap is reached (~83s at 60 FPS), keeping the batch under ~1.5 MiB. Regression test in TurnRecordingCapTests.
2. Loadout roster funds display lag MINOR FIXED
The shared "Players" roster panel sometimes showed a player's pre-purchase funds for a beat after buying. Cosmetic sync-timing issue, not a data bug.
Fix: BuyItemAsync, SellItemAsync, and ReorderLoadoutAsync in Loadout.razor.cs now call StateHasChanged() immediately after the hub call completes.
3. Client recording state not cleared after admin undo MINOR FIXED
After Undo Turn, the server rolled back the turn but the client kept showing 🎮 RECORDING with a stale tick count.
Fix: Added AdminActionDto to GameDtos.cs, wired an OnAdminAction event in GameHubConnection.cs, and registered a handler in MultiplayerGame.razor.cs that clears _turnTicks and re-arms recording on undo. Regression test in TurnRecordingCapTests.
Conclusion
The async PvP loop works end-to-end: lobby → loadout → turn recording → batch submission → deterministic replay → turn rotation across both players → admin undo/commit. All three issues found in this run have been fixed: the turn-batch size cap prevents SignalR disconnects, admin undo now clears client recording state, and loadout purchases update the display immediately.