Skip to content

Reimplement multiplayer command ownership and document network flow - #220

Open
Krarilotus wants to merge 4 commits into
sourcehold:mainfrom
Krarilotus:reimplement/network-player-identity
Open

Reimplement multiplayer command ownership and document network flow#220
Krarilotus wants to merge 4 commits into
sourcehold:mainfrom
Krarilotus:reimplement/network-player-identity

Conversation

@Krarilotus

@Krarilotus Krarilotus commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

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::translateMultiplayerIDsIntoPlayerIDs returns 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 /O2 DLL 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.

@Krarilotus

Copy link
Copy Markdown
Contributor Author

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 TheRedDaemon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The translator is0x47EAF0, reimplemented by this contribution.
That needs to be removed. The wiki has nothing to do with the function you currently implement.

Comment on lines +37 to +39
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long-duration recording and replay must preserve that distinction

Did something UCP module related slip through here?

Comment on lines +49 to +51
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ECX part is interesting for the reimplementation, but not for the wiki.

Comment on lines +62 to +66
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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replay talk.

Comment on lines +68 to +89
## 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole part:
There is some general information about the technical side in here, but most is about replay.

Comment on lines +91 to +98
## 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).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is likely a leftover?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants