AtmosphereSaveData (saved atmospheres)¶
The record a save uses for one atmosphere's gas mix. The <Atmospheres> list in world.xml holds one <AtmosphereSaveData> for every saved world atmosphere (one grid cell), Thing atmosphere and network atmosphere (a pipe network's or a landing pad network's). On load, <NetworkReferenceId>, then <ThingReferenceId>, then <Position> decides which one a record restores, so only the network records are keyed by <NetworkReferenceId>. Every gas species is an individual double XML element, and <Chlorine> holds the Pollutant gas. Mods editing atmospheres offline can zero out unwanted species; relative to a dominant gas, small changes have negligible thermal impact because energy scales with total moles.
Gas species list¶
Each pipe network's gas mix is an <AtmosphereSaveData> in world.xml keyed by <NetworkReferenceId>. All gas species are individual double XML elements (Oxygen, Nitrogen, CarbonDioxide, Volatiles, Chlorine, Water, PollutedWater, NitrousOxide, LiquidNitrogen, LiquidOxygen, LiquidVolatiles, Steam, LiquidCarbonDioxide, LiquidPollutant, LiquidNitrousOxide, Hydrogen, LiquidHydrogen, Hydrazine, LiquidHydrazine, LiquidAlcohol, Helium, LiquidSodiumChloride, Silanol, LiquidSilanol, HydrochloricAcid, LiquidHydrochloricAcid, Ozone, LiquidOzone). Set unwanted species to 0. Energy scales with total moles; small changes relative to dominant gas have negligible thermal impact.
The 28 names and their order are unchanged at 0.2.6428.27798, where each is a public double field of Assets.Scripts.Atmospherics.AtmosphereSaveData (442139-442221). A save written by 0.2.6228.27061 carries the same 28 elements in the same order, with values written to up to 17 significant digits (for example <Nitrogen>63.491536002691731</Nitrogen>), more than a float carries. Which record belongs to what, and how a world or Thing record is matched on load, is under "Which atmospheres the list holds" below.
The whole record¶
Around the gases, the record carries the atmosphere's identity and geometry. In document order: <ReferenceId> (the base class ReferencableSaveData declares [XmlElement] public long ReferenceId;, 329531-329537), <Position> (Vector3), the 28 gas elements, <Energy> and <Volume> (double), <Direction> (Vector3), <NetworkReferenceId> and <ThingReferenceId> (long), <CleanBurnRate> (float), <MothershipReferenceId> (long). The 0.2.6228.27061 save writes all 37 elements in exactly this order in every record. The constructor that takes an Atmosphere fills the non-gas fields like this (442252-442253, 442282-442288):
Position = atmosphere.WorldPosition;
Direction = atmosphere.Direction;
// ...
Volume = atmosphere.Volume.ToDouble();
Energy = atmosphere.GasMixture.TotalEnergy.ToDouble();
ReferenceId = atmosphere.ReferenceId;
NetworkReferenceId = atmosphere.AtmosphericsNetwork?.ReferenceId ?? 0;
ThingReferenceId = (atmosphere.Thing ? atmosphere.Thing.ReferenceId : 0);
MothershipReferenceId = 0L;
CleanBurnRate = atmosphere.CleanBurnRate;
The class also has a get-only TotalQuantity (442244), so it is never written to the save. It sums 25 of the 28 species, leaving out LiquidCarbonDioxide, LiquidPollutant and LiquidNitrousOxide, and nothing in Assembly-CSharp reads it:
public double TotalQuantity => Oxygen + Nitrogen + CarbonDioxide + Methane + Chlorine + Water + PollutedWater + NitrousOxide + LiquidNitrogen + LiquidOxygen + LiquidMethane + Steam + Hydrogen + LiquidHydrogen + Hydrazine + LiquidHydrazine + LiquidAlcohol + Helium + LiquidSodiumChloride + Silanol + LiquidSilanol + HydrochloricAcid + LiquidHydrochloricAcid + Ozone + LiquidOzone;
Energy and temperature on load¶
<Energy> is the mixture's total energy, GasMixture.TotalEnergy (442283). Atmosphere.Load builds every species with MoleEnergy.Zero and then sets the total (443703-443736). The TotalEnergy setter shares the value across the species in proportion to each one's heat capacity (448433-448469, for example Oxygen.Energy = new MoleEnergy(value.ToDouble() * (Oxygen.HeatCapacity / heatCapacity).ToDouble());), so the loaded mix has one temperature. GasMixture.Temperature computes it as IdealGas.Temperature(TotalEnergy, HeatCapacity) (448480-448494), which is energy divided by heat capacity (451517-451524, 455206-455209). Removing a species while keeping <Energy> therefore raises the temperature by the ratio of the old heat capacity to the new one, which is the arithmetic behind the note above.
A record whose <Energy> is 0 or less but which holds any moles is loaded at 20 degrees C instead. Chemistry.Temperature.TwentyDegrees is new TemperatureKelvin(293.15) (447310), and MoleEnergy(HeatCapacity, TemperatureKelvin) is their product (453686-453689). The end of Load (443731-443736):
GasMixture.TotalEnergy = new MoleEnergy(saveData.Energy);
CleanBurnRate = saveData.CleanBurnRate;
if (saveData.Energy <= 0.0 && GasMixture.GetTotalMolesGassesAndLiquids > MoleQuantity.Zero)
{
GasMixture.TotalEnergy = new MoleEnergy(GasMixture.HeatCapacity, Chemistry.Temperature.TwentyDegrees);
}
Which gas each element carries¶
Three element names are not the C# name of the gas they hold. The field called Chlorine carries the Pollutant gas (Chemistry.GasType.Pollutant), and Methane's two fields are written as <Volatiles> and <LiquidVolatiles>. So a world.xml reader or a save editor looking for pollutant in an <AtmosphereSaveData> reads <Chlorine>: the record has no <Pollutant> element, while the liquid form keeps its own name, <LiquidPollutant>.
The two renames are not alike. Volatiles is the XML name the game gives Methane everywhere: Chemistry.GasType declares [XmlEnum("Volatiles")] Methane = 8u and [XmlEnum("LiquidVolatiles")] LiquidMethane = 0x200u (447347-447348, 447359-447360). Chlorine belongs to this record alone. Chemistry.GasType has no Chlorine member and names the gas [XmlEnum("Pollutant")] Pollutant = 0x10u (447349-447350), and no other XML-serialised type in Assembly-CSharp has a member named Chlorine. The only other field of that name is a float in NetworkMessages.GasMixtureMessage (285663-285723), a binary network message written with WriteSingle, whose name appears elsewhere in Assembly-CSharp only in MessageFactory's two tables (IndexToMessageType, 196573, and MessageTypeToIndex, where it is index 129, 196726).
Other gas records keep the gas's own name:
- The planetary atmosphere in the same world.xml writes it as
<Pollutant>.PlanetaryAtmosphereSaveData(221447-221459) holds fourGlobalGasMixSaveDatarecords (<GlobalGasMix>,<LiquidClouds>,<IceClouds>,<IceCaps>), each declaring[XmlElement("Pollutant")] public DoubleReference Pollutant;(210747-210748), saved fromglobalGasMix.Get(Chemistry.GasType.Pollutant)(210835) and loaded back intoPollutant(210979).DoubleReferenceholds its number in[XmlAttribute] public double Value;(54862-54865), so a save shows<Pollutant Value="0" />. The same record also writes<Volatiles>and<LiquidVolatiles>for Methane (210744-210745, 210765-210766). MoleMixture(454197), the gas mix aReciperequires (Recipe.RequiredMix, 146484), declares[XmlElement("Volatiles")] public double Methane;(454205-454206) but keepspublic double Pollutant;under its own name (454208).
The fields (442148-442152, 442169-442170, 442178-442179):
[XmlElement("Volatiles")]
public double Methane;
[XmlElement]
public double Chlorine;
// ...
[XmlElement("LiquidVolatiles")]
public double LiquidMethane;
// ...
[XmlElement]
public double LiquidPollutant;
Saving, in the constructor that takes an Atmosphere (442250, 442257-442258):
Methane = atmosphere.GasMixture.Methane.Quantity.ToDouble();
Chlorine = atmosphere.GasMixture.Pollutant.Quantity.ToDouble();
Loading, in Atmosphere.Load(AtmosphereSaveData saveData) (443695, 443706-443707):
GasMixture.Methane = new Mole(Chemistry.GasType.Methane, new MoleQuantity(saveData.Methane), MoleEnergy.Zero);
GasMixture.Pollutant = new Mole(Chemistry.GasType.Pollutant, new MoleQuantity(saveData.Chlorine), MoleEnergy.Zero);
LiquidMethane and LiquidPollutant round-trip the same way under their own fields (saved 442264 and 442267, loaded 443713 and 443716). The dedicated server build carries the same class and the same Load, textually identical (Assembly-CSharp.DedicatedServer.decompiled.cs 428501-428658 and 430063-430106), so a save written by a dedicated server uses the same names.
In the 0.2.6228.27061 Lunar save described below, 108 of the 1188 records carry a nonzero <Chlorine>: 104 world records (the largest 0.0327 mol), 3 pipe network records (the largest 837.69 mol) and 1 Thing record (355.04 mol). No record carries a nonzero <LiquidPollutant>, and no record has a <Pollutant> element.
Which atmospheres the list holds¶
The <Atmospheres> list is XmlSaveLoad.WorldData.Atmospheres (273416-273417). XmlSaveLoad.GetWorldData (273945) fills it by running AtmosphericsManager.AllAtmospheres.ForEach(AtmosphereSaveAction) (273986) over every registered atmosphere (the pool is described on Atmosphere), and the action keeps three kinds (273609-273622):
private static readonly Action<Atmosphere> AtmosphereSaveAction = delegate(Atmosphere atmosphere)
{
if (atmosphere != null && !atmosphere.BeingDestroyed && atmosphere.WillSave)
{
if (atmosphere.Mode == AtmosphereHelper.AtmosphereMode.Network && atmosphere.IsAwaitingEvent)
{
throw new System.Exception(atmosphere.DisplayName + " was modified during the save process. Save aborted due to possible data corruption");
}
if (atmosphere.IsValidWorld() || atmosphere.IsValidThing() || (atmosphere.AtmosphericsNetwork != null && atmosphere.AtmosphericsNetwork.IsNetworkValid()))
{
_temporaryWorldData.Atmospheres.Add(new AtmosphereSaveData(atmosphere));
}
}
};
- A world atmosphere, the gas of one grid cell.
IsValidWorld()requiresMode == Worldand thatAtmosphericsController.SampleGlobalAtmosphereat its cell returns this very atmosphere (443447-443454;SampleGlobalAtmosphereisGetAtmosphereLocal(grid) ?? PlanetaryAtmosphereSimulation.ReadOnlyGlobal(grid), 201957-201964). - A Thing's internal atmosphere.
IsValidThing()requiresMode == Thingand aThingwhoseReferenceId > 0(443456-443463). - A network's atmosphere: any
AtmosphericsNetworkwhoseIsNetworkValid()is true.AtmosphericsNetwork : StructureNetwork(192394) reachesReferencableNetwork<INetworkedStructure>(194235), whoseIsNetworkValid()isMembers.Count > 0(193326-193334), and neither subclass overrides it. The two subclasses arePipeNetwork(277800) andLandingPadNetwork(192994). EveryAtmosphericsNetworkconstructed outside loading on the simulating side creates its atmosphere in the constructor (_atmosphere = new Atmosphere(this, 0L);, 192568-192575), so a landing pad network's gas is saved here too, keyed by<NetworkReferenceId>exactly like a pipe network's.
WillSave is a public field that starts true (442364), and nothing in Assembly-CSharp sets it on an atmosphere. A network atmosphere still awaiting an event when the save runs aborts the whole save with the exception above.
The constructor copies both ids straight from the atmosphere (442285-442286), so a world atmosphere, which has no network and no Thing, is saved with <NetworkReferenceId>0</NetworkReferenceId> and <ThingReferenceId>0</ThingReferenceId>.
XmlSaveLoad.LoadWorld (274151) first recreates every network listed in <PipeNetworks>, <LandingPadNetworks> and the other network id lists (LoadInNetworks, called at 274246, body 274452-274479), then reads the atmosphere records in this order (274328-274368):
for (int k = 0; k < worldData.Atmospheres.Count; k++)
{
AtmosphereSaveData atmosphereSaveData = worldData.Atmospheres[k];
Atmosphere atmosphere = null;
if (atmosphereSaveData.Volume <= 0.0)
{
continue;
}
if (atmosphereSaveData.NetworkReferenceId > 0)
{
long networkReferenceId = atmosphereSaveData.NetworkReferenceId;
AtmosphericsNetwork atmosphericsNetwork = Referencable.Find<AtmosphericsNetwork>(networkReferenceId) ?? new PipeNetwork(networkReferenceId);
if (atmosphericsNetwork != null)
{
atmosphere = new Atmosphere(atmosphericsNetwork, atmosphereSaveData.ReferenceId);
atmosphericsNetwork.AssignAtmosphere(atmosphere);
atmosphere.Mode = AtmosphereHelper.AtmosphereMode.Network;
atmosphere.AtmosphericsNetwork = atmosphericsNetwork;
}
}
else if (atmosphereSaveData.ThingReferenceId > 0)
{
Thing thing = Referencable.Find<Thing>(atmosphereSaveData.ThingReferenceId);
if (!thing)
{
continue;
}
thing.AssignInternalAtmosphereOnLoad(new VolumeLitres(atmosphereSaveData.Volume), atmosphereSaveData.ReferenceId);
atmosphere = thing.InternalAtmosphere;
if (atmosphere == null)
{
UnityEngine.Debug.LogWarning("Unable to load atmosphere for " + thing.DisplayName, thing);
continue;
}
}
else
{
atmosphere = GridController.World.AtmosphericsController.CloneGlobalAtmosphere(new WorldGrid(atmosphereSaveData.Position), atmosphereSaveData.ReferenceId);
}
atmosphere?.Load(atmosphereSaveData);
}
For a save editor this means:
- A record with
<Volume>0 or less is dropped before its ids are read. - A nonzero
<NetworkReferenceId>wins over<ThingReferenceId>. A network id that matches no loaded network creates a newPipeNetworkwith that id. - A
<ThingReferenceId>that matches no Thing drops the record. - A record with both ids at 0 restores the world atmosphere of the grid cell containing
<Position>, so for world records<Position>is the key.
The same record type is also embedded in two Things' own save data, under the field's name rather than as <AtmosphereSaveData>: HeatExchangerSaveData carries atmos2 and atmos3 (386306-386313, written by HeatExchangerDeprecated.InitialiseSaveData, 386449-386456), and StirlingEngineSaveData carries HotInputAtmosphere, HotSideAtmosphere and ColdSideAtmosphere (430346-430356, written by StirlingEngine.InitialiseSaveData, 431037-431045). Their pollutant is in <Chlorine> too.
A sample: a Lunar save written by 0.2.6228.27061, at day 283 (committed as Mods/SprayPaintPlus/playtests/fixtures/SprayPaintPlusWorld.save). Its list holds 1188 records, the count world_meta.xml gives as <NumberOfAtmospheres>: 1061 world records with both ids at 0, 80 network records whose ids are all in <PipeNetworks> (its <LandingPadNetworks /> is empty), and 47 Thing records. No record has both ids set, and none has <Volume> 0 or less.
Verification history¶
- 2026-04-20: page created from the Research migration. Source: F0238 (Pipe network AtmosphereSaveData gas species list).
- 2026-09-27: conflict on "numeric type of the gas elements". Previous claim: every gas species is an individual float XML element (summary and "Gas species list"). New finding: every gas field of
AtmosphereSaveDatais a C#double(442139-442221 in the 0.2.6428.27798 decompile). Fresh validator verdict: B is correct, quoting[XmlElement] public double Oxygen;(442139-442140); it also found a 0.2.6428.27798 save writing gas values to 17 significant digits. Result: "float" became "double" in the summary and in "Gas species list"; the 28 names and their order were re-read and are unchanged. - 2026-09-27: conflict on "which atmospheres the list holds and how records are keyed". Previous claim: per-pipe-network gas mix, each entry keyed by
<NetworkReferenceId>(summary, and the title "AtmosphereSaveData (pipe networks)"). New finding: the list holds world, Thing and pipe network atmospheres, and only pipe network records are keyed by<NetworkReferenceId>. Fresh validator verdict: neither; the list holds world atmospheres, Thing internal atmospheres and the atmosphere of anyAtmosphericsNetworkwith members, which is pipe networks and landing pad networks alike, and on load a record withVolume <= 0is skipped, thenNetworkReferenceId > 0restores a network's atmosphere (creating aPipeNetworkwhen no network has that id), thenThingReferenceId > 0restores that Thing's internal atmosphere (dropped when the Thing is missing), and otherwise the world atmosphere atnew WorldGrid(Position); quotingXmlSaveLoad.LoadWorld274332-274366. Result: title changed to "AtmosphereSaveData (saved atmospheres)", summary rewritten, "Which atmospheres the list holds" added with the verdict. The statement that each pipe network's gas mix is a record keyed by<NetworkReferenceId>stays: the validator confirmed it. - 2026-09-27: additive, read in the 0.2.6428.27798 decompile and a 0.2.6228.27061 Lunar save. Added "The whole record", "Energy and temperature on load" and "Which gas each element carries":
<Chlorine>holdsGasMixture.Pollutantin both the save constructor andAtmosphere.Load,<Volatiles>and<LiquidVolatiles>hold Methane and LiquidMethane, the planetary atmosphere andMoleMixturekeep the namePollutant, and the dedicated server build is identical. A drafted claim that every atmosphere in that save holds 0 under<Chlorine>was not carried over: 108 of its 1188 records hold a nonzero value. "Gas species list" re-read and restamped. Frontmatter sources now name the decompile lines, the dedicated server decompile and the save; the migration source is marked as removed from the tree.
Open questions¶
None at creation.