Skip to content

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:

15:08:24: Save Failed: Folder name is empty.

(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 -new world produces NO real autosaves until it gets a name. A console save "<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: save queued 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 via Console.ReadKey(), never a stdin stream, and with -logFile on the launch line the console object is not constructed at all: DedicatedServerSettings, "The console channel". The same save "<name>" DOES work when delivered in-process, through a BepInEx plugin calling ConsoleWindow.Submit, or from a connected client via serverrun. 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 -new world watch for the Save 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.md Rule 3. The OBSERVATION recorded here at 0.2.6228.27061 and 0.2.6403.27689 is upheld exactly: a save queued 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 feed CommandLine.Process, but it reads keystrokes from the Win32 console input buffer via Console.ReadKey() rather than any stdin stream, and it is not constructed at all when -logFile is 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 same save "<name>" DOES assign a station name when delivered in-process or via serverrun. 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 into TestRig/CLAUDE.md, TestRig/MANUAL.md and TestRig/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 -Save command timing out with no Saved line and no folder appearing under data/saves/.

Open questions

  • Which code path assigns CurrentStationName on the dedicated server besides -load (for example, whether the first client-driven manual save or an admin serverrun save sets it). Not needed for current test flows; -load covers them.