10 KiB
name, description
| name | description |
|---|---|
| th1-network-sync | TH1 project-specific network synchronization guide for Unity C# multiplayer, Steam P2P, GameNetSender/GameNetReceiver, SteamLobbyManager, SimpleP2P, lobby MapConfig/MemberCiv player slot/team/AI/ready-state sync before game start, MapData/NetData recovery and diagnostics, action sync, host start/resume, ForceUpdate, heartbeat, ordered delivery, large message chunking, send-failure handling, and lobby UI rollback. Use whenever Codex works on TH1 networking, multiplayer rooms, member civ/ready/team/AI slot config, multiplayer saves, deterministic sync, P2P queueing, MapData broadcasts or MapDataDebug diff tools, ActionConfirm/ActionExecute, reconnect, Timer-driven network callbacks, or any bug that may affect multiplayer reliability or ordering. |
TH1 Network Sync
Core Rule
Treat multiplayer changes as state-machine changes, not only message sends. Before editing, trace the full path:
GameNetSender -> SteamLobbyManager -> SimpleP2P -> GameNetReceiver -> Main/ActionLogic/MapData.
Do not let one peer advance local game state unless the matching network send/receive contract succeeded.
First Files To Read
Unity/Assets/Scripts/TH1_Logic/Steam/SimpleP2P.csUnity/Assets/Scripts/TH1_Logic/Steam/SteamLobbyManager.csUnity/Assets/Scripts/TH1_Logic/Steam/GameNetSender.csUnity/Assets/Scripts/TH1_Logic/Steam/GameNetReceiver.csUnity/Assets/Scripts/TH1_Data/NetData.csUnity/Assets/Scripts/TH1_Data/MapData.csUnity/Assets/Scripts/TH1_Logic/Core/Main.csUnity/Assets/Scripts/TH1_Logic/Action/ActionLogic.csUnity/Assets/Scripts/TH1_Instance/Timer.csUnity/Assets/Scripts/TH1_Logic/Editor/NetworkStressEditorWindow.cswhen changing or interpreting network stress tests or theTools/Steam MapData一致性诊断editor window
For the current network contract summary, read references/network-contract.md.
For the one-click Steam P2P stress tool, report fields, and current healthy baseline, read references/network-stress.md.
Workflow
-
Classify the message.
- Critical:
GameStart,ForceUpdate,ActionConfirm,ActionExecute, fullMapData, reconnect/restore messages. - Health/status: heartbeat, map confirm, lobby state/config, member ready state, chat.
- Critical messages must return
boolor otherwise expose failure to the caller before local state advances.
- Critical:
-
Preserve ordering.
- Keep game messages on
SimpleP2Pper-peer outgoing queues. - Do not bypass ordered envelopes for normal game messages.
- Do not send direct raw Steam messages for gameplay sync unless you also preserve FIFO and failure semantics.
- Keep game messages on
-
Preserve all-or-nothing broadcast for critical messages.
- Broadcast through
SteamLobbyManager.BroadcastMessage. - Preflight all lobby targets before enqueueing any target.
- If any target cannot queue, return failure and avoid local progression.
- Never accept "some peers got the critical message" as a valid state.
- Broadcast through
-
Gate local state on send success.
- Host
GameStartmust succeed beforeSaveMapData,RefreshTurn, room UI close, or final success return. - Owner
ActionExecutebroadcast must succeed before owner local execute. - Client
ActionConfirmmust succeed before client local execute;TurnEndmay send and then stop local execution. - Heartbeat send timestamps should only update after the send was accepted.
- Host
-
Validate
MapDatabefore sending or applying.- Reject null maps,
DeserializedMissingCriticalData, wrongNetMode, and missing core maps. - Call
Net.RefreshPlayerNet(mapData)and respect itsboolresult. - Ensure every current lobby member maps to a valid
PlayerId. - Do not repair missing critical data silently during deserialization.
- Reject null maps,
-
Keep lobby
MapConfighost-authoritative.- Pre-game room settings,
MemberCiv, player slot/team/AI flags, and ready state live inMain.Instance.MapConfig. MapConfig.MultiCivsis now the full player-slot list and should be sized toPlayerCount; do not treat it as only current lobby members.- Each
MemberCivslot usesIndexas the stable player position.MemberId != 0means a real member is bound,MemberId == 0 && IsAImeans an AI slot,MemberId == 0 && !IsAI && IsHostControlledmeans a host/local-controlled real player slot, andTeamId == 0means no team. - Host-controlled slots are real players, not AI and not lobby members. Keep
IsReady=false, do not add them toNet.Players, skip them when assigning/moving real lobby members, and allow them inAreAllLobbyMembersReady(). Net.RefreshPlayerNet(mapData)maps only current lobbyMemberIdvalues toPlayerId; host-controlled slots must not require a Steam member mapping or duplicate the host's mapping.- Local control should go through
MapData.CanLocalControlPlayer(...): in singleplayer all real local slots are controllable; in multiplayer the member's mapped slot is controllable, and only the lobby owner may also controlIsHostControlledslots. When switching turn identity for UI/input, restore the default local member slot on non-local turns. - Call
MapConfig.EnsurePlayerSlots(NetMode.Multi)before reading or mutating lobby slots, especially after changingPlayerCountor receiving host config. - Clients may optimistically update their own
MemberCivonly afterChangeCivsend succeeds, then still accept hostUpdateLobbyDataas authority. - Clients entering a room should request host lobby data until current lobby members match
MapConfig.MultiCivs. - Host must refresh lobby members before sending lobby config; new guests default not ready, and the owner is always ready.
- Changing civ, team, AI slot ownership, slot assignment, or host room settings should clear guest ready state.
- UI room-row reconciliation must count host-controlled slots as occupied seats, not open seats, and must not auto-convert them between open and AI.
- Host start/resume must require
AreAllLobbyMembersReady(). Net.RefreshPlayerNet(mapData)maps real lobbyMemberIdvalues to the slot-createdPlayerId; AI and host-controlled slots must not require a lobby member mapping.TeamIddrives in-game teammate diplomacy, so host and clients must agree on the full slot list beforeGameStart.
- Pre-game room settings,
-
Roll back failed start/resume.
- Snapshot
MapData,InputLogic,MapInteractionLogic, andMapGeneratorLogicbefore host start/resume mutation. - On failure or exception, restore the snapshot, dispose/reinitialize render state as appropriate, cancel pending start timers, and keep lobby UI open.
- UI code must only invoke room-close/start callbacks after the start method returns
true.
- Snapshot
-
Keep receiver failure atomic.
- Deserialize and dispatch inside try/catch.
- If incoming
GameStartorForceUpdatevalidation fails, do not hide room UI and do not leave the game inForceUpdating. - Restore previous game state if
NetResumeMatchfails.
-
Preserve the stress-test path.
NetworkStressMessageis a diagnostics message and must not mutate gameplay state.GameNetReceivershould ignoreP2PMsgType.NetworkStress; the editor tool listens throughSimpleP2P.OnMessageReceivedEvent.- Stress probes must still use
Lobby.BroadcastMessage/SendMessageToPeerso they cover the real ordered queue and large-message chunking path. - Keep per-run
RunIdisolation so stale packets or ACKs from a previous test cannot pollute a new report. - The host should export after collecting client reports or after the configured report wait timeout; clients should retry report sends briefly after test completion.
-
Use
MapDataDebugMessagefor pre-reconnect divergence captures.
P2PMsgType.MapDataDebugcarries currentMapDataonly for diagnostics: member-to-host or host-broadcast.- The receiver compares the incoming map against local
Main.MapDataand logs[MapDataDebug]; it must not callNetResumeMatch, changeGameState, requestForceUpdate, or mutate gameplay state. - The editor entry is
Tools/Steam MapData一致性诊断; use it when normalForceUpdatediff logs are too late because the host has already advanced. - Keep the send path on
GameNetSender/ lobby queues so large-map diagnostics still cover ordered P2P chunking.
Checks Before Finishing
Run:
dotnet build Unity/Assembly-CSharp.csproj --no-restore
If editor tooling changed, also run:
dotnet build Unity/Assembly-CSharp-Editor.csproj --no-restore
For network-heavy changes, inspect these risks explicitly:
- Could a critical broadcast partially enqueue?
- Could a caller ignore a failed send and still mutate game state?
- Could a client treat optimistic
MemberCivor ready state as authoritative before hostUpdateLobbyData? - Could the host start/resume while any guest is not ready or current lobby members do not match
MapConfig.MultiCivs? - Could an empty non-AI slot (
MemberId == 0 && !IsAI && !IsHostControlled) start the game accidentally? - Could a host-controlled slot be counted as an open UI seat, converted to AI/open by reconciliation, or inserted into
Net.Players? - Could code reintroduce the old assumption that
MultiCivs.Count == current lobby member count? - Could
MemberCiv.Index,PlayerId, orTeamIddiverge between host and clients beforeGameStart? - Could
MapDatadeserialize with missing core fields and still be used? - Could
MapDataDebugMessageaccidentally trigger reconnect, ForceUpdate, or any map mutation? - Could a timer callback fire after the target UI/object state is gone?
- Could a retry loop or ordered gap wait forever?
- If stress tooling or queue throughput changed, does the one-click two-machine report meet the baseline in
references/network-stress.md?
What Not To Do
- Do not add a new direct send path around
SimpleP2Pqueues for gameplay sync. - Do not silently swallow
SendMessageToPeerorBroadcastMessagefailure. - Do not call
GameNetReceiverfrom a partial large-message chunk. - Do not close the multiplayer room UI before host start returns success.
- Do not add separate pre-game ready/config state outside
MapConfigunless you also define host-authoritative reconciliation. - Do not shrink
MapConfig.MultiCivsto only real lobby members; it represents every player slot in the match. - Do not derive player position from lobby member order after slots exist; use
MemberCiv.Index. - Do not start the host game from client-local optimistic ready state.
- Do not call
GC.Collect()in match entry paths as a networking fix. - Do not use
MapDataDebugMessageas a repair/sync mechanism; it is logging-only diagnostics.