Skip to content

Standalone save & persistence improvements#874

Open
MhaWay wants to merge 14 commits intorwmt:devfrom
MhaWay:pr/standalone-save-trigger-foundation
Open

Standalone save & persistence improvements#874
MhaWay wants to merge 14 commits intorwmt:devfrom
MhaWay:pr/standalone-save-trigger-foundation

Conversation

@MhaWay
Copy link
Copy Markdown

@MhaWay MhaWay commented Apr 11, 2026

Summary

Consolidated PR containing all standalone save improvements (previously split across #874-#877):

  • Save trigger foundation: standalone protocol and session identification, typed autosave and join-point reason plumbing
  • Snapshot persistence: File.Replace atomic writes, safe struct defaults, single-snapshot autosave
  • Async reconnect fixes: reset server instance in TearDown, int.TryParse in SeedFromSaveZip
  • Prepublish maintenance: correct misleading log text in designator patches
  • Save path unification: unified standalone save path, Days autosave support

Changes

  1. Split standalone save trigger foundation
  2. Unify standalone save path and add Days autosave support
  3. Add standalone snapshot persistence
  4. Add safe defaults to snapshot state structs
  5. Use File.Replace in SaveGameToFile_Overwrite and eliminate double snapshot
  6. Enforce standalone async time and control fixes
  7. Reset MultiplayerServer.instance in TearDown
  8. Use int.TryParse in SeedFromSaveZip
  9. Clean up standalone prepublish maintenance
  10. Correct misleading blocked log text in designator patches

Validation

  • dotnet build Source/Multiplayer.sln -c Release
  • dotnet test Source/Tests/Tests.csproj -c Release --no-build

Note

PRs #875, #876, #877 have been closed and consolidated here as requested.

Copilot AI review requested due to automatic review settings April 11, 2026 15:40
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR lays the groundwork for a “standalone save trigger” flow by extending the handshake to identify standalone servers and by plumbing a typed autosave/join-point request reason from client to server, so standalone clients can request join point creation for save- and travel-related events.

Changes:

  • Extend protocol handshake (Server_ProtocolOk) to include whether the server is standalone; persist this on the client session.
  • Introduce ClientAutosavingPacket + JoinPointRequestReason and route autosave/save/world-travel triggers through it to server-side join point creation.
  • Adjust standalone-specific join/join-point behavior and add a synthetic autosave timer for standalone connections.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
Source/Common/Networking/Packet/ProtocolPacket.cs Adds isStandaloneServer field to protocol OK packet for client session identification.
Source/Common/Networking/Packet/AutosavingPacket.cs Introduces typed autosaving packet with a reason enum.
Source/Common/JoinPointRequestReason.cs Adds enum describing why a join point is requested (save, world travel, etc.).
Source/Common/Networking/State/ServerPlayingState.cs Updates autosaving handler to typed packet + reason; loosens world upload policy for standalone.
Source/Common/Networking/State/ServerJoiningState.cs Adjusts join-point creation policy for standalone joins; sends standalone flag during handshake.
Source/Common/PlayerManager.cs Treats first non-arbiter joiner as “primary” faction on standalone servers.
Source/Client/Networking/State/ClientJoiningState.cs Stores isStandaloneServer from protocol OK on the client session.
Source/Client/Session/MultiplayerSession.cs Adds session flag + helper property for “connected to standalone server”.
Source/Client/Session/Autosaving.cs Sends typed autosaving packet after save; changes save routine to return bool and tweaks file replacement logic.
Source/Client/Windows/SaveGameWindow.cs Sends autosaving packet on manual save; special-cases standalone to trigger server-side save flow.
Source/Client/Patches/VTRSyncPatch.cs On standalone, triggers a join point when leaving a map (world travel).
Source/Client/ConstantTicker.cs Adds standalone-only synthetic autosave timer.
Source/Client/AsyncTime/AsyncWorldTimeComp.cs Adjusts join-point creation upload behavior during CreateJoinPoint.
Source/Tests/PacketTest.cs Updates packet roundtrip expectations for the extended protocol OK packet.
Source/Tests/packet-serializations/ServerProtocolOkPacket.verified.txt Updates verified serialization output for the extended protocol OK packet.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 274 to 280
private static void CreateJoinPointAndSendIfHost()
{
Multiplayer.session.dataSnapshot = SaveLoad.CreateGameDataSnapshot(SaveLoad.SaveAndReload(), Multiplayer.GameComp.multifaction);

if (!TickPatch.Simulating && !Multiplayer.IsReplay &&
(Multiplayer.LocalServer != null || Multiplayer.arbiterInstance))
if (!TickPatch.Simulating && !Multiplayer.IsReplay)
SaveLoad.SendGameData(Multiplayer.session.dataSnapshot, true);
}
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

CreateJoinPointAndSendIfHost now uploads world data from every non-simulating, non-replay client. On non-standalone servers the server will still reject Client_WorldDataUpload from non-host/non-arbiter, but the upload cost (fragmenting + network/bandwidth + compression work) is still paid by all clients, which can be very expensive for large saves. Please gate SendGameData so only the designated uploader sends (e.g., arbiter/local host, plus standalone clients), or introduce a server-directed "uploader" selection instead of broadcasting uploads from everyone.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Related to my comment #874 (comment)

Comment thread Source/Client/Session/Autosaving.cs Outdated
{
Log.Error($"Exception saving multiplayer game as {fileNameNoExtension}: {e}");
Messages.Message("MpGameSaveFailed".Translate(), MessageTypeDefOf.SilentInput, false);
return false;
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

SaveGameToFile_Overwrite now returns bool and swallows exceptions internally. Callers that relied on exceptions to detect failure (e.g., code wrapping this call in try/catch) will no longer observe failures unless they check the return value, and may continue follow-up steps assuming a save exists. Consider either re-throwing (and letting callers handle) or ensure all call sites are updated to handle the false return explicitly.

Suggested change
return false;
throw;

Copilot uses AI. Check for mistakes.
Comment thread Source/Common/Networking/Packet/ProtocolPacket.cs
if (!Autosaving.SaveGameToFile_Overwrite(curText, currentReplay))
return;

Multiplayer.Client.Send(new ClientAutosavingPacket(JoinPointRequestReason.Save));
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

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

ClientAutosavingPacket is sent after saving even when currentReplay is true. In replay sessions the connection ignores non-command packets, so this send is a no-op and adds confusing coupling between replay saving and network join-point logic. Consider skipping the send when currentReplay is true (or when Multiplayer.Client is null) to keep replay saves purely local.

Suggested change
Multiplayer.Client.Send(new ClientAutosavingPacket(JoinPointRequestReason.Save));
if (!currentReplay && Multiplayer.Client != null)
Multiplayer.Client.Send(new ClientAutosavingPacket(JoinPointRequestReason.Save));

Copilot uses AI. Check for mistakes.
@notfood notfood added enhancement New feature or request. 1.6 Fixes or bugs relating to 1.6 (Not Odyssey). standalone server Fix or bugs relating to the standalone server. labels Apr 11, 2026
@notfood notfood moved this to In review in 1.6 and Odyssey Apr 11, 2026

if (!TickPatch.Simulating && !Multiplayer.IsReplay &&
(Multiplayer.LocalServer != null || Multiplayer.arbiterInstance))
if (!TickPatch.Simulating && !Multiplayer.IsReplay)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of having every player send the game data back to the server, could you have the server send info which player is expected to send the game data back? WorldData.cs Server.commands.Send(CommandType.CreateJoinPoint, ScheduledCommand.NoFaction, ScheduledCommand.Global, Array.Empty<byte>()); - the Array.Empty could be changed to include playerId or perhaps a value of true could be sent to the desired player and false to the rest?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

What do you think about this approach: when a player wants to save, they request a joinpoint from the server, and the server asks a specific player to create it? I'm still leaning towards the idea that the player who triggered the save should be the one doing it for the maps they have loaded — so the server would still delegate it back to the requester. But if there are two players, maybe this server-mediated step would better coordinate who should do it?

Honestly, I'm not sure it adds much in practice — it feels like an extra round-trip for the same result. But I'd like your thoughts on it.

The philosophy I'm following is "each player uploads their own maps." The goal is to avoid a bottleneck where, say, 10 players need to save sequentially through a single designated uploader. On a standalone server there's no natural "authoritative" client like the host in a hosted game, so having each player responsible for their own maps seemed like the most resilient approach.

Currently, the IssuedBySelf gate (which was missing from PR #876+ due to a rebase oversight — now restored) ensures that when a CreateJoinPoint command is broadcast, only the client who originally requested the save actually executes CreateJoinPointAndSendIfHost. The other clients receive the command but skip execution. So the Array.Empty<byte>() payload doesn't need to carry a playerId — the client-side gate already handles it by checking currentExecutingCmdIssuedBySelf.

That said, if you'd prefer making this explicit via the command payload (e.g. embedding the requester's playerId so the server decides who saves), I'm open to it. It would make the intent clearer on the wire, even if the behavior is the same.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm still leaning towards the idea that the player who triggered the save should be the one doing it for the maps they have loaded

I'm fine with that, and that's not my issue with this change. The issue is that right now everyone saves every map and everyone sends the world data back to the server.

The philosophy I'm following is "each player uploads their own maps." The goal is to avoid a bottleneck where, say, 10 players need to save sequentially through a single designated uploader. On a standalone server there's no natural "authoritative" client like the host in a hosted game, so having each player responsible for their own maps seemed like the most resilient approach.

That probably is a better approach. Not sure how much of a bottleneck it actually would be. Also, right now there isn't such a functionality to have the player only save their own map. If it exists in another PR of yours, please include that info in this PR's description so I'm aware of the overarching goal and have more context to review on

Currently, the IssuedBySelf gate (which was missing from PR #876 due to a rebase oversight — now restored) ensures that when a CreateJoinPoint command is broadcast, only the client who originally requested the save actually executes CreateJoinPointAndSendIfHost. The other clients receive the command but skip execution. So the Array.Empty() payload doesn't need to carry a playerId — the client-side gate already handles it by checking currentExecutingCmdIssuedBySelf.

I only reviewed this PR and didn't look at the others. If that PR fixes my concern, please move the relevant part to this PR. Also, IssuedBySelf will only work if you send the CreateJoinPoint command with a player provided. Right now, StartJoinPointCreation never sends an associated player

That said, if you'd prefer making this explicit via the command payload (e.g. embedding the requester's playerId so the server decides who saves), I'm open to it. It would make the intent clearer on the wire, even if the behavior is the same.

As long as the IssuedBySelf code you are referencing keeps working in non-standalone mode, I'm fine with just doing that. It's probably even better than my suggestion, so I think I'd even prefer your approach

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

As soon as i get home ill send you more context, i was working on it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This PR does not implement map streaming itself, but it keeps the standalone join-point flow compatible with the upcoming streaming architecture. In hosted MP, join-point creation is host/arbiter-owned; in standalone, snapshot creation/upload must already happen client-side, and future streaming work extends that further by allowing the upload work to be split across assigned clients. Some of the standalone branching here is meant to preserve that separation cleanly without changing hosted behavior.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Also i used ur idea about make the server handle who is the one who save for the streaming map one

Comment thread Source/Client/Session/Autosaving.cs Outdated
Comment on lines +56 to +59
if (dst.Exists)
dst.Delete();

tmp.MoveTo(dst.FullName);
Copy link
Copy Markdown
Member

@mibac138 mibac138 Apr 11, 2026

Choose a reason for hiding this comment

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

I'm not particularly against this change, but I'm curious why is this better? Edit: it probably isn't #874 (comment)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Had errors on overwrite tmp, this fixed

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Moved to replace

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you might've missed something in the commit/push? It looks like it's still using move for me

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

you should see it now

}

// On standalone, trigger a join point when leaving a map
if (Multiplayer.session?.ConnectedToStandaloneServer == true)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't it going to be pretty annoying (for the players) to save the game every time anyone changes the map? As far as I remember, the game pauses for everyone during join point creation

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Tecnically just for the one saving and for the maps it is on. The idea is to let players save their maps, but im still trying to test

Comment thread Source/Client/ConstantTicker.cs Outdated

private static void TickAutosave()
{
// Only standalone connections use the synthetic autosave timer.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand this comment. autosaveCounter is also used by other code in this method?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You're right, the "synthetic" wording was confusing. I've rewritten the comment to:

// When connected to a remote standalone server, the client drives
// the autosave timer using the interval received at connection time
// (from the server's TOML settings via ServerProtocolOkPacket).

The point is: when the client is connected to a remote standalone server (Multiplayer.LocalServer == null), it can't read server.settings directly. So the autosave interval and unit are now sent in the ServerProtocolOkPacket at connection time — the client stores them in session.autosaveInterval / session.autosaveUnit and drives its own timer from those values.

The standalone path now also supports both Minutes and Days (in-game) units, just like the host-local path.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That makes sense that the client doesn't have access to server settings, but why are you sending the autosave settings to the client then? I think it'd be better to have the server run the timer and send a CreateJoinPoint command to the client

Also, I'm not sure what do you mean by "supports both Minute and Days"? This code explicitly only supports minutes (which I'm fine with, I don't think it's critical to support autosaving by Days in the standalone server)

if (session.autosaveUnit != AutosaveUnit.Minutes || session.autosaveInterval <= 0) return;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Umh no, i was using the autosave timing setting from the toml, isn't it using the game days?

Comment thread Source/Client/ConstantTicker.cs Outdated
Comment on lines +169 to +175
var forceJoinPoint = packet.reason == JoinPointRequestReason.Save;

// On standalone, any playing client can trigger a join point (always, regardless of settings)
// On hosted, only the host can trigger and only if the Autosave flag is set
if (Server.IsStandaloneServer ||
(Player.IsHost && Server.settings.autoJoinPoint.HasFlag(AutoJoinPointFlags.Autosave)))
Server.worldData.TryStartJoinPointCreation(forceJoinPoint);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unless I'm missing something, this change along with the changes to CreateJoinPointAndSendIfHost and HandleWorldDataUpload, mean that anyone can request a join point to be created, which makes everyone on the server upload a joinpoint to the server, and then the server just accepts the world data from every player. I don't think this is a good workflow. I haven't really thought deeply about this, but I think I'd prefer the server to choose one player to send the game data, or in some other way determine a single player to save.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I actually found a todo somewhere about the implementation to load map joinpoints independently and thought it might lighten the loading.. so the idea is that Each player can load their own active maps

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The intent here was not to have every client produce and upload a joinpoint. The idea is that only the player who explicitly triggers a save, or another deliberate action tied to their current context, should send the joinpoint for that map.

The main goal is to avoid unnecessary interruptions or loading work for players who are active on separate maps. I tried to keep this as continuity-friendly as possible: if the server arbitrarily designates a client to provide the joinpoint, that creates awkward edge cases where the chosen player may be in the middle of leaving, changing state, or otherwise not be the best source for that save. Tying joinpoint creation to an explicit player action keeps the flow more predictable and reduces the impact on everyone else.

Comment thread Source/Client/Windows/SaveGameWindow.cs Outdated
@MhaWay MhaWay force-pushed the pr/standalone-save-trigger-foundation branch from 94ad756 to dc76dbc Compare April 12, 2026 09:16
@MhaWay MhaWay force-pushed the pr/standalone-save-trigger-foundation branch from dc76dbc to 67f32f9 Compare April 12, 2026 10:12
@MhaWay MhaWay changed the title Split standalone save trigger foundation Standalone save & persistence improvements Apr 12, 2026
MhaWay added 3 commits April 12, 2026 21:06
…ignment

- CreateJoinPointAndSendIfHost: restore LocalServer/arbiter guard for hosted
  mode so only host/arbiter uploads world data (was accidentally ungated)
- Standalone path kept separate with ConnectedToStandaloneServer gate
- Remove redundant ofPlayer assignment in ChangeRealPlayerFaction
  (FactionContext.Set already does the same thing on the next line)
…oin point

- Add no-op HandleKeepAlive to TestJoiningState and TestLoadingKeepAliveState
  to prevent crash when server sends KeepAlive during async handshake
- Disable autoJoinPoint in test server settings since the test server has
  no game simulation to process CreateJoinPoint commands
@MhaWay MhaWay requested a review from mibac138 April 14, 2026 19:42
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes in this file look unrelated to the rest of the PR. Unless I'm wrong, please extract these changes to another PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed. This ended up being unrelated cleanup rather than part of the standalone persistence change. I removed it locally from this PR scope.


// Not in a landing session, use vanilla logic for player control
__result = Current.Game.PlayerHasControl;
__result = true;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The changes in this file look unrelated to the rest of the PR. Unless I'm wrong, please extract these changes to another PR.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed. This was not essential to the standalone save/persistence work. I dropped it locally from this PR as part of the scope cleanup.

Comment on lines +179 to +180
{
Log.Warning(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This shouldn't ever happen, did you observe this behavior? Is this related to the rest of this PR? Unless it is, please move this change to another PR

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You're right for this PR. I did not have a convincing standalone-persistence-specific justification for carrying this here, so I removed it locally from the PR scope.

I may keep a variant of this behavior only in the later streaming branch, where reconnect/stale-command timing is part of the actual problem being worked on, but it does not belong in this foundation PR.

public override void Handle(IChatSource source, string[] args)
{
if (!Server.worldData.TryStartJoinPointCreation(true))
if (!Server.worldData.TryStartJoinPointCreation(true, sourcePlayer: source as ServerPlayer))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Commands can not only be run by the players, but also by the server (by writing the command name in the console). Doesn't this change break the command when running from the server?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Uh i didnt know that, ill test it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch. In standalone this was indeed wrong for the server-console path.

The intent of passing sourcePlayer here was to preserve the issuing player when the command comes from a real player, because that later becomes the origin for CreateJoinPoint. But when the command is issued from the server console, source as ServerPlayer is null, and in standalone that can leave us without any client actually executing the join point creation.

I fixed that locally in WorldData.TryStartJoinPointCreation: if standalone starts a join point without a source player, it now falls back to a real playing player (prefer host, otherwise the first playing player), and aborts with a log if there is none. So the player-issued case keeps the original attribution, while the console-issued case still works correctly.

Server.worldData.TryStartJoinPointCreation();
var forceJoinPoint = packet.reason == JoinPointRequestReason.Save;

ServerLog.Detail($"Received Client_Autosaving from {Player.Username}, standalone={Server.IsStandaloneServer}, isHost={Player.IsHost}, reason={packet.reason}, force={forceJoinPoint}");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reformat - too long line. Also applies to some of the other logging calls in this file, please fix them too.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed. I reformatted the long logging lines in this file locally while cleaning up the standalone snapshot handling changes, so the next push will include that.

public bool pauseOnDesync = true;
public TimeControl timeControl;

public void EnforceStandaloneRequirements(bool isStandaloneServer)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't isStandaloneServer always going to be true? The parameter seems redundant in a method called Enforce*Standalone*Requirements

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, agreed. That parameter ended up redundant in this PR shape. I removed it locally and switched the call sites to a parameterless EnforceStandaloneRequirements().

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I didn't review this file yet, but as a first thought, why does this use a custom save format?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

The main reason is that in standalone the dedicated server needs a durable server-owned representation of the accepted game state, and that state is naturally split into world data, session data, command state, and per-map data.

A single monolithic save/replay archive works poorly for that use case because most updates are partial: after an autosave or a standalone snapshot upload, we often only need to replace one artifact, not rebuild the whole thing. Keeping those pieces separate lets the server update them incrementally and atomically (File.Replace/move per artifact), which is much safer for crash recovery than rewriting one large bundle every time.

So this is not meant as a new interchange format. It is the server's internal durable persistence layout for standalone. That also happens to be the shape that later map-streaming work can build on, but this PR is still only the standalone persistence/upload groundwork, not the streaming implementation itself.

Comment thread Source/Common/WorldData.cs Outdated
return true;
}

private static byte[] ComputeHash(params byte[][] payloads)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: Effectively the same code is used in SaveLoad. Maybe extract this to some utility method and use both here and there?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, I agree with that direction. WorldData and SaveLoad should ideally share one helper for the standalone world/session hash computation so the upload side and the validation side cannot drift.

I kept this PR focused and answered the same point in the SaveLoad thread, but if I touch this area again I would do it as that shared-helper extraction rather than as two separate local cleanups.

{
public StandaloneWorldSnapshotState() { }
public int tick;
public int leaseVersion;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the leaseVersion?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It was leftover scaffolding from the later streaming/job-based variant, where a versioned lease made sense while assignments were still evolving.

In this PR it does not carry its weight, because the standalone-only path does not actually need that extra discriminator. I removed it locally from the snapshot packets and the corresponding server-side state so the standalone groundwork here stays minimal.

Co-authored-by: Michael <5672750+mibac138@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.6 Fixes or bugs relating to 1.6 (Not Odyssey). enhancement New feature or request. standalone server Fix or bugs relating to the standalone server.

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

4 participants