Reimplement multiplayer command ownership and document network flow - #220
Reimplement multiplayer command ownership and document network flow#220Krarilotus wants to merge 4 commits into
Conversation
|
Added native system-message evidence to the linked network-flow note in 8a9c79d. The player-removal branch calls the identity translator at Crusader 0x490755, then immediately calls removePlayerFromLobby at 0x49075B without reloading ECX: another concrete caller for the receiver-preservation requirement in this implementation. The note also distinguishes host timing/hash resets from timed-command dispatch and records the system type constants against the SDK header. This update changes documentation only; the C++ implementation and its previously reported 6,156 differential comparisons are unchanged. The larger receive routine is analyzed, not claimed newly reimplemented. |
TheRedDaemon
left a comment
There was a problem hiding this comment.
The wiki entry is very technical, but since it relates to the very complex multiplayer behaviour, I think it is ok.
One day this could link to the object definitions to make it more understandable, but currently one might just end up creating old references should something still move.
Also, should you not do it, you really need to read everything yourself. You can always split notes for your modules out, but the assessments regarding them are not interesting here in this wiki.
It also helps on another level, if it starts to annoy you while reading and you think it is too much, then it might actually be 🙂 .
| actual dispatch order; transport arrival order is insufficient. | ||
| 6. `processWaitingCommands` (`0x4892F0`) translates each sender again, sets the | ||
| execution action/parameter state, calls the handler and marks the entry | ||
| processed. The translator is `0x47EAF0`, reimplemented by this contribution. |
There was a problem hiding this comment.
The translator is0x47EAF0, reimplemented by this contribution.
That needs to be removed. The wiki has nothing to do with the function you currently implement.
| receive routine does not reconstruct a higher epoch. Long-duration recording | ||
| and replay must preserve that distinction rather than assuming identical wire | ||
| and internal formats. This observation does not establish a reproduced wrap bug. |
There was a problem hiding this comment.
Long-duration recording and replay must preserve that distinction
Did something UCP module related slip through here?
| calls the identity translator at `0x490755`. The next call is | ||
| `removePlayerFromLobby` at `0x49075B`, without reloading ECX. This is another | ||
| concrete native caller requiring this reimplementation to retain ECX. |
There was a problem hiding this comment.
The ECX part is interesting for the reimplementation, but not for the wiki.
| Polling roster and synchronization fields at simulation boundaries cannot | ||
| establish that no system event occurred between them. Recorder diagnostics can | ||
| observe the type-switch entry before mutation, but replaying those events still | ||
| requires their semantics and timing. DirectPlay system structures can contain | ||
| process pointers; copying their bytes is not a portable replay format. |
| ## Why one save and a timed-command log are insufficient | ||
|
|
||
| The original save section table does not cover active mode or the full network | ||
| handle array. It does cover a saved mode copy, AI-slot values and local player | ||
| slot. The single-player load-dialog path (`0x4950B0`) clears network handles and | ||
| recreates the local entry. Multiplayer playback therefore needs explicit roster | ||
| restoration and command ownership, not merely a changed mode flag or slot zero. | ||
|
|
||
| `queueSynchronizedAutosaveProtocol` (`0x48C660`) uses host wall-clock elapsed time, | ||
| then queues a save command carrying the simulation tick and a unit checksum. | ||
| `checkGameSync` (`0x48CB00`) compares peer evidence and starts the resync state | ||
| machine. `recomputeHashesAndSendResync` (`0x48CC90`) hashes selected game arrays; | ||
| `sendPendingResyncCommandsInBudget` (`0x48E680`) sends mismatching sections in | ||
| bounded batches, retaining its category/item position between calls. These | ||
| include units, buildings, player data and tile-map regions. Resync replaces | ||
| simulation data; treating every immediate command as presentation-only is wrong. | ||
|
|
||
| A useful recorder must account separately for timed command execution, immediate | ||
| state-changing commands, roster/host transitions and resync transfers. Reliable | ||
| transport delivery alone cannot restore missing replay state or make | ||
| frame/audio-driven RNG calls deterministic. An extension's ordinary save | ||
| integration also does not demonstrate integration with native resync transfers. |
There was a problem hiding this comment.
This whole part:
There is some general information about the technical side in here, but most is about replay.
| ## Validation scope | ||
|
|
||
| The accompanying player-identity checker compiles and executes the actual C++ | ||
| function against the originals in both variants. It covers last-match semantics, | ||
| sentinels, missing handles, relocated receivers and the retained ECX value. | ||
| Other routines in this note were inspected in the named Ghidra project and | ||
| original assembly; they are not newly reimplemented or live-validated here. | ||
| See [reproduction instructions](../../tools/reimplementation-tests/PLAYER-IDENTITY.md). |
There was a problem hiding this comment.
That is likely a leftover?
TL;DR: Reimplements how the game identifies the player behind a multiplayer command. The accompanying article explains how command delivery and synchronization fit together.
GameSynchronyState::translateMultiplayerIDsIntoPlayerIDsreturns the local slot in either single-player mode. In other modes it checks slots 1–8, retaining the last match and returning zero if none matches. It does not change game state.The implementation now follows TheRedDaemon's review: two separate early returns and a simple loop. A fresh MSVC2005 SP1
/O2DLL build and linked reccmp comparison report 100% at Crusader 0x0047EAF0. The status entry is updated; the optional emulation scripts have been removed from the PR now that the function matches. No live DLL deployment was performed.The wiki describes the surrounding timed-command, immediate-command and synchronization responsibilities. Those larger routines are documented analysis, not additional reimplementations in this PR.