StationAutoSave¶
How the autosave timer works, what gates a fire, and why a fresh -new dedicated-server world logs Save Failed: Folder name is empty. every interval instead of autosaving.
Timer architecture: wall-clock, not game time¶
StationAutoSave (static class, 0.2.6403.27689 client decompile line 267571) schedules with a System.Timers.Timer, so the countdown runs on WALL-CLOCK time. Time.timeScale = 0 (a paused world, or a tool freezing the sun) does not slow or stop the schedule; pause is only consulted at fire time (next section).
public static class StationAutoSave
{
private static readonly System.Timers.Timer _autoSaveTimer = new System.Timers.Timer();
private static async void TimerElapsed(object sender, ElapsedEventArgs e)
{
await UniTask.SwitchToMainThread();
AutoSaveNow();
}
Timer.Elapsed fires on a ThreadPool thread; the handler marshals to the main thread through the UniTask player loop, which runs on a headless server even while the game tick is parked (see SimulationTickDriverHooks.md).
ResetAutoSave() (line 267592) stops the timer and, when Settings.CurrentData.AutoSave && !GameManager.IsTutorial && !GameManager.IsNewTutorial, restarts it with Interval = Settings.CurrentData.SaveInterval * 1000. In batch mode it prints the Auto save stopped / Auto save started (<interval>) console lines. Every call restarts the countdown from zero; it is called from GameManager.StartGame, World.NewAsync, and the load paths, so back-to-back stopped/started pairs at world start are normal.
Gates at fire time¶
private static void AutoSaveNow()
{
if (!GameManager.IsTutorial && !GameManager.IsNewTutorial && !WorldManager.IsGamePaused && GameManager.GameState == GameState.Running && !Assets.Scripts.Networking.NetworkManager.IsClient)
{
AutoSaveTask().Forget();
}
}
A PAUSED world silently skips the autosave (no log line at all): the timer keeps running, the fire is discarded. Diagnostic consequence: on a headless server, ANY autosave-attempt line at the interval mark (success or failure) is positive evidence the world was unpaused and Running at that moment.
AutoSaveTask awaits SaveHelper.AutoSave(XmlSaveLoad.Instance.CurrentStationName, ...) and prints the SaveResult message on failure via ConsoleWindow.PrintError.
Fresh -new worlds cannot autosave: empty station name¶
On a dedicated server started with -new <Map>, XmlSaveLoad.Instance.CurrentStationName is empty until a first named save assigns one. The autosave then fails name validation before any save work starts, and the server logs, every SaveInterval seconds:
(verbatim from a 2026-07-02 dedicated-server run at 0.2.6403.27689; world created 15:03:24, SaveInterval 300, failure logged exactly at +300 s). The failure happens before SaveHelper.PrepareToSave, so PauseGameTick is never called and the simulation is not disturbed: an InspectorPlus PauseGameTick tracer recorded zero calls across the interval.
Consequences:
- A
-newworld produces NO real autosaves until it gets a name. A consolesave "<name>"assigns one, but delivering it by writing to the server process's stdin is a no-op (observed at 0.2.6228.27061 and re-confirmed at 0.2.6403.27689:savequeued via the launcher control file produced no save folder and no log response; the cause was found at 0.2.6428.27798 on 2026-08-15 and is not what the wording here implied until then). The server's console reads keystrokes from the Win32 console input buffer viaConsole.ReadKey(), never a stdin stream, and with-logFileon the launch line the console object is not constructed at all: DedicatedServerSettings, "The console channel". The samesave "<name>"DOES work when delivered in-process, through a BepInEx plugin callingConsoleWindow.Submit, or from a connected client viaserverrun. Starting with-load <SaveName>instead gives the world a name from the start and autosaves work normally, and remains the simplest route. - When a test plan uses "an AutoSave line" as the world-is-ticking readiness marker (
TestRig/RESEARCH.md, "Dedicated-server internals worth knowing"), on a-newworld watch for theSave Failed: Folder name is empty.line instead; it is emitted at the same cadence and implies the same unpaused-and-running state.
Verification history¶
- 2026-08-15: the cause behind this page's "stdin save no-op" cross-check was determined at 0.2.6428.27798 by a fresh validator under
Research/WORKFLOW.mdRule 3. The OBSERVATION recorded here at 0.2.6228.27061 and 0.2.6403.27689 is upheld exactly: asavequeued through the launcher's control file (which relays into the server's redirected stdin) produces no save folder and no log line. The IMPLIED cause was wrong. The server's console does exist and does feedCommandLine.Process, but it reads keystrokes from the Win32 console input buffer viaConsole.ReadKey()rather than any stdin stream, and it is not constructed at all when-logFileis on the launch line, which the rig always passes. Result: the "Fresh -new worlds cannot autosave" consequence bullet is reworded to separate the observation from the mechanism, to point at the resolved section, and to record that the samesave "<name>"DOES assign a station name when delivered in-process or viaserverrun. Full evidence and the paired live arms: DedicatedServerSettings, "The console channel", 2026-08-15 entry. No timer or save-gate claim on this page was re-read, so no section stamp moved. - 2026-08-13: the two TestRig launchers were replaced by one,
TestRig/testrig.ps1, with positional verbs and-Target, and the rig's per-half documents were consolidated intoTestRig/CLAUDE.md,TestRig/MANUAL.mdandTestRig/RESEARCH.md. Pointers and command spellings on this page follow. No game-internals claim changed and none was re-verified, so no section stamp moved. - 2026-07-02: page created during the headless-tick investigation. Timer/gate code quoted verbatim from the 0.2.6403.27689 client decompile (lines 267571-267633); the empty-folder failure and its exact +interval timing observed live on the dedicated server the same day. The "stdin save no-op" cross-check is the launcher
-Savecommand timing out with noSavedline and no folder appearing underdata/saves/.
Open questions¶
- Which code path assigns
CurrentStationNameon the dedicated server besides-load(for example, whether the first client-driven manual save or an adminserverrun savesets it). Not needed for current test flows;-loadcovers them.