← back to handoffs

The Ostranauts fire system — what burns, why, and how to make it stop

A modder-facing walkthrough of every JSON record and code path that makes fires happen on derelicts, stations, and your own ship.

What this page is. A reference for modders editing JSON who want to know which records control fire behaviour — what spawns fire, what carries it forward, what stops it, and what damage a fire does each tick. Every claim ends with either a data/…json path or a decomp/Assembly-CSharp/…cs:line citation so you can audit it on your own install.

The decomp/Assembly-CSharp/ snapshot this page cites is an ILSpy/dnSpy decompile of the Assembly-CSharp.dll from the build pinned in this repo (0.15.0.x). Field-name, condition-trigger and JSON-shape claims move slowly between builds and are durable. Specific constants (MAX_FIRES = 170, DURATION = 5.0, the 0.005 welding-spark coefficient) could change patch-to-patch — re-check against your local DLL if a number drives a decision you can't easily reverse.

The one-paragraph summary

A fire in Ostranauts is a synthetic SysFire CondOwner that the engine spawns onto an existing object (its host) when an ignition event fires. Each tick, it consumes O2 in the room, raises temperature, can damage nearby crew, applies StatDamage to its host, and rolls to spread onto an adjacent solid object. The roll uses the room's O2 partial pressure, total pressure, and temperature, multiplied by the target's flammability tier (IsFireproof blocks; IsFlammable 6×; IsBurnable 2.5×; nothing 0.25×). Ignition events come from four code paths: ship-vs-ship combat hits on damaged interior items, damaged powered devices/conduits emitting sparks, crew welding sparks, and explosions. The only permanent stoppers are vacuum, lack of O2, fire extinguisher chem, or someone stamping on it — fires do not have a passive decay timer; what looks like "going out on its own" is the spread chance hitting zero in a depressurised or O2-starved room.

The data view: what serialises into your save

Look at any ship file in <save>.zip → ships/<regID>.json. The top-level dict has these fire-relevant fields:

aFires verified
List of CondOwner IDs that were burning when the save was written. Empty [] or null on most files. On load, Ship.cs:1104-1106 walks this list and re-queues each ID via CrewSim.vfxFire.AddFireCO(strCOID), which puts them into the global "waiting to spawn a fire VFX" pool.
DMGStatus verified
Integer enum: 0=New, 1=Used, 2=Damaged, 3=Derelict (Ship.cs:9743-9753). Drives what happens on first load (Ship.cs:974-997): New ships skip damage; Used applies DamageAllCOs(0.33f); Damaged and Derelict run BreakIn(), which heavily damages systems and can leave them flagged sparkable.
bPrefill verified
True until first board. Once a player visits with LoadState ≥ Loaded.Edit, PreFillRooms() + the DMGStatus-dependent damage pass runs, then this clears. In the saves under test-data/save/whole-folders/, 127 of 141 ships are still pending — derelicts only get rolled out properly when the player boards them.
fBreakInMultiplier verified
Float in [0, 1]. Controls how aggressive BreakIn() is. Scaled down by 1 / (1 + IsDueBonusDerelict) if the player has the bonus-derelict flag (Ship.cs:2261-2268). Higher = more wrecking, more sparkable items, more potential for fire after first board.
aCOs & aItems verified
The CondOwners that live on this ship. After PreFill, fixtures with IsFlammable, IsBurnable, IsFireproof, IsPowerConduit, IsPowered, etc. are spread through these arrays. None of them are intrinsically "on fire" — the burning state is the aFires list, not a per-CO flag.

And on rooms (CondOwners with IsRoom=1.0x1) in aCOs, three conditions feed the spread roll:

StatGasPpO2 verified
O2 partial pressure in the room. 0 in vacuum, ~20.7 in a healthy Earth-mix.
StatGasPressure verified
Total pressure. 0 in vacuum, ~101.3 standard.
StatGasTemp verified
Gas temperature in Kelvin. 293 (≈20 °C) is the reference; fires push this up.

What an ignition event actually does

An "ignition" is always a call to VFXFire.AddFireCO(strCOID). That puts the target CO's ID into a waiting pool. Each frame, VFXFire.Update() pulls from the pool and calls AddFireAt(coParent, fire), which:

  1. Refuses if the host has IsHuman, IsCarried, or objCOParent != null — humans don't host fires; carried/contained items don't either (VFXFire.cs:232-236).
  2. Refuses if the host's ship has IsTutorialDerelictHidden (VFXFire.cs:237-241).
  3. Spawns a fresh SysFire CondOwner (via the SysFire record in data/condowners/condowners.json) parented to the host's tile, sets fEpochExpire = StarSystem.fEpoch + 5.0 seconds, and starts the VFX/audio (VFXFire.cs:242-258).
  4. Caps at MAX_FIRES = 170 simultaneous active fires across the whole sim (VFXFire.cs:485). Past that, new ignitions queue but don't render until existing ones expire.
  5. If the player owns the ship the fire is on, queues a Firefight Task that crew will auto-pick up (VFXFire.cs:266-282).
The SysFire CondOwner record — data/condowners/condowners.json:34667
{
  "strName" : "SysFire",
  "strNameFriendly" : "Fire",
  "strItemDef" : "SysFire",
  "strType"    : "Item",
  "aInteractions" : [
    "ACTFireExtinguish",
    "ACTFireStamp"
  ],
  "aStartingConds" : [
    "IsFire=1.0x1",
    "IsNonHighlightable=1.0x1",
    "IsHiddenInv=1.0x1",
    "IsReadyHeat=1.0x1",
    "StatHeatArea=1.0x2.0",
    "StatHeatVol=1.0x0.5",
    "StatSolidTemp=1.0x1073.0"
  ],
  "mapGUIPropMaps"  : [ "Panel A", "HeaterFire" ],
  "aUpdateCommands" : [
    "GasRespire2,Fire,null",
    "Heater,TIsAirtightNoHuman"
  ]
}
Two update commands wire the fire up to the gas + heat sims. GasRespire2,Fire attaches a GasPump running the Fire respire profile (consume O2, produce CO/CO2/H2SO4/NH3/Smoke). Heater,TIsAirtightNoHuman attaches the Stefan–Boltzmann Heater radiating from 1073 K (≈800 °C, StatSolidTemp) with area 2 and effective volume 0.5.
The Fire respire profile — data/gasrespires/gasrespires.json:360-413
{
  "strName": "Fire",
  "strPtA": "GasInput",   "strPtB": "GasOutput",
  "strCTA": "TIsAirtightNoHuman",  "strCTB": "TIsAirtightNoHuman",
  "fVol": 0.1,  "fSignalCheckRate": 3.0,
  "aGases" : [
    { "strName": "FireCO2Data",    "strGasIn": "O2", "strGasOut": "CO2",   "fConvRate": 0.8,    ... },
    { "strName": "FireCOData",     "strGasIn": "O2", "strGasOut": "CO",    "fConvRate": 0.12,   ... },
    { "strName": "FireH2SO4Data",  "strGasIn": "O2", "strGasOut": "H2SO4", "fConvRate": 0.001,  ... },
    { "strName": "FireNH3Data",    "strGasIn": "O2", "strGasOut": "NH3",   "fConvRate": 0.00125, ... },
    { "strName": "FireSmokeData",  "strGasIn": "O2", "strGasOut": "Smoke", "fConvRate": 0.15,   ... }
  ]
}
O2 is consumed and converted into a cocktail of CO, CO2, H2SO4, NH3, and Smoke. All five are toxic to humans (see CONDRespireHumanCO / HumanCO2 / HumanH2SO4 / HumanNH3 / HumanSmoke in data/loot/loot.json) — even if the fire doesn't reach your crew physically, the smoke and CO can kill them.

The four ignition paths

Every AddFireCO call in the codebase belongs to one of these. If you mod data, only the second and third are easy to tune via JSON; the first and fourth are coefficient-driven from data but the trigger lives in code.

1. Ship-vs-ship combat hits — fFireChanceCoeff per attack mode

DamageSystem.ApplyDamageToCell at decomp/Assembly-CSharp/Ostranauts/Ships/DamageSystem.cs:405-427 rolls a fire chance every time a missile, mass-driver round, or PD round damages an interior CondOwner that's not crew-shaped:

double num3 = 0.25;
if (dataCOWrapper.CO.HasCond("IsFireproof"))      num3 = 0.0;
else if (dataCOWrapper.CO.HasCond("IsFlammable")) num3 = 0.9;
else if (dataCOWrapper.CO.HasCond("IsBurnable"))  num3 = 0.5;
num3 *= (double)jam.fFireChanceCoeff;
if (MathUtils.Rand(0.0, 1.0, ...) <= num3)
    CrewSim.vfxFire.AddFireCO(dataCOWrapper.CO.strID);

The jam here is a JsonShipAttack (the ship-weapon record, not the per-CO attack mode). All seven ship-attack records in data/attackmodes/shipAttacks/shipAttacks.json have fFireChanceCoeff set explicitly:

AttackfFireChanceCoeff
MassDriverAttack0.10
MissileAttack01 / 02 / 030.25
RailgunAttack0.25
PointDefenseImpact / ScuttleImpact0.10

So a missile hit on a flammable interior item rolls 0.9 × 0.25 = 22.5 % per damaged tile. On a burnable item, 12.5 %. On unclassified solid scenery, 6.25 %. On a fireproof item, never. This is the biggest source of fires you'll see in mid-combat.

Modder lever — Option F-1. To make combat less fire-heavy without disabling it, halve every fFireChanceCoeff in data/attackmodes/shipAttacks/shipAttacks.json. To kill ship-combat fires entirely, set them all to 0.0. The damage stays; only the ignition stops.

2. Sparking off damaged powered devices and conduits

Two distinct sparking systems exist. They run on different conditions and have different triggers, but they share the same end point: VFXSparks.AddSparkAt(pos)VFXFire.Spread(pos, ship, 0.5f) (VFXSparks.cs:82).

2a. Continuous background sparking — Ship.Sparks()

Ship.cs:2056-2101. Called every frame from the ship update loop, but only the actively-walked ship sparks. Five gates and a single random draw decide whether any spark fires this frame; then one candidate is picked and tested, and at most one spark is emitted per call.

Gates before the random draw

The per-frame chance

float num = 0.02f;                       // 2% base
num += num * CrewSim.TimeElapsedScaled(); // ×(1 + sim-speed factor)
num *= mapICOs.Count / 1200f;             // ship-size scaling
if (Rand(0,1) > num) return;

The shuffled candidate list

If the per-frame draw passes, Sparks() looks at a per-ship cached list aSparkables. If empty, BuildSparkables() rebuilds it: enumerate all COs currently passing TIsSparkable, then insert each into a new list at a random index (effectively a Fisher–Yates-ish shuffle). Each Sparks() call pops the front of the shuffled list until it drains, at which point the next call rebuilds it from fresh ship state. Removed/repaired items drop out automatically.

The per-candidate check (the while loop)

while (aSparkables.Count > 0)
{
    CondOwner co = aSparkables[0]; aSparkables.RemoveAt(0);
    if (co != null && ctSparkable.Triggered(co, null, true))
    {
        Tile t = GetTileAtWorldCoords1(co.tf.position.x, co.tf.position.y, …);
        if (t == null || t.aConnectedPowerCOs.Count == 0) break;   // (1)

        double dmg = 1.0 - co.GetDamageState();
        if (dmg >= 0.5)                                            // (2)
        {
            vfxSparks.AddSparkAt(co.tf.position);                  // (3)
            double self = Min(0.01, StatDamageMax / 800);          // (4)
            co.AddCondAmount("StatDamage", self, 0, 0);
            return;
        }
        break;
    }
}
  1. Power-network gate. The candidate's tile must be wired into a power network — aConnectedPowerCOs.Count > 0 on the tile. Cut power to a section and nothing in it sparks, even if half its conduits are damaged. This is the cleanest in-game counter-play short of disabling the flicker setting: pull the breaker, walk the section, fix what's broken, then power it back up.
  2. ≥50 % damage gate. GetDamageState() returns 1.0 (intact) → 0.0 (broken), so 1.0 - state ≥ 0.5 means the item is at least 50 % of the way to the broken threshold. An item at 20 % damage will pass TIsSparkable (which only checks StatDamage > 0) and end up in the candidate list, but won't actually spark — it's queued waiting for damage to accumulate.
  3. One spark per call, max. The function returns after a successful spark. So even on a megastation where 50 items qualify, you get one spark per frame the per-frame draw passes, not fifty. The shuffle randomises which qualifying item that single spark lands on.
  4. Self-damage death spiral. Each spark adds min(0.01, StatDamageMax / 800) to the item's own StatDamage. For an item with StatDamageMax = 10, that's 10/800 = 0.0125 → clamped to 0.01 absolute. A damaged conduit that keeps sparking is accumulating its own damage at roughly 1 % of max per spark. Eventually it crosses the IsDamaged threshold and falls out of TIsSparkableConduits (the trigger aForbids IsDamaged) — at which point it stops sparking and is now fully broken. Sparks are a self-terminating death spiral for the item emitting them.

The break-on-fail quirk

If the chosen candidate passes the Triggered() re-test but fails either the power-tile check or the damage-state check, the function breaks out of the while loop instead of trying the next candidate. In a mixed powered/unpowered ship, the shuffle means a few "wasted" frames hitting unpowered candidates between actual sparks. Probably accidental from a code-review perspective but it does throttle the rate further.

Putting it together — back-of-envelope

A ~1000-installed-CO derelict just boarded, no sim compression, flicker setting at default:

Each spark calls VFXFire.Spread(pos, ship, 0.5f). In a hot pressurised room with a flammable adjacent target, one spark has ~0.21 × 0.5 × 6.0 ≈ 0.63 chance of starting a fire. About one fire per ~3 seconds of walkaround on a damaged-but-pressurised rust bucket if the room conditions are right — which matches the lived experience of derelicts seeming to burst into flame the moment you open the door.

Modder lever — Option F-1b. If a player reports "I fireproofed everything and fires still start," the most likely culprit isn't your IsFireproof coverage — it's the spread roll picking some other nearby flammable cushion or wall fitting that you didn't tag. The spark itself is fire-tier-agnostic; the tier check happens in VFXFire.Spread on the target, not the sparkable source. Audit room contents, not just the sparkable.

TIsSparkable and its sub-triggers — data/condtrigs/condtrigs.json:2368-2370
{ "strName" : "TIsSparkable",          "fChance" : 0.2, "aReqs" : [ ], "aTriggers" : [ "TIsSparkableConduits", "TIsSparkableDevices" ], "bAND" : false },
{ "strName" : "TIsSparkableConduits",  "fChance" : 1.0, "aReqs" : [ "IsPowerConduit", "IsInstalled", "StatDamage" ], "aForbids" : [ "IsDamaged" ], "bAND" : true },
{ "strName" : "TIsSparkableDevices",   "fChance" : 1.0, "aReqs" : [ "IsPowered",      "IsInstalled", "StatDamage" ], "aForbids" : [ "IsDamaged" ], "bAND" : true },
StatDamage as an aReqs entry means "value > 0", not "is the broken flag." So this fires on items that have some damage but aren't fully broken yet. The outer TIsSparkable.fChance = 0.2 gates the per-frame draw.

2b. Per-impact sparking — DamageSystem

Same ApplyDamageToCell block as path 1, lines 423-426. Every ship-attack hit that damages a sparkable interior CO triggers a spark in addition to the fire roll. The condition trigger here is TIsSparkableDmgSys (no StatDamage requirement and no IsDamaged forbid — every hit on a powered/conduit item sparks).

TIsSparkableDmgSysdata/condtrigs/condtrigs.json:2371-2373
{ "strName" : "TIsSparkableDmgSys",         "fChance" : 1.0, "aReqs" : [ "IsInstalled" ], "aTriggers" : [ "TIsSparkableDmgSysConduits", "TIsSparkableDmgSysDevices" ], "bAND" : false },
{ "strName" : "TIsSparkableDmgSysConduits", "fChance" : 1.0, "aReqs" : [ "IsPowerConduit" ], "bAND" : true },
{ "strName" : "TIsSparkableDmgSysDevices",  "fChance" : 1.0, "aReqs" : [ "IsPowered" ],      "bAND" : true },

2c. BeatManager "part failure" tension event — BeatManager.PartFailure

BeatManager.cs:737-779. When the narrative tension manager rolls a tension_part_failure event, it picks the most-damaged sparkable on the player's current ship (per TIsPartFailRandom: IsInstalled + StatDamage, forbids IsCarried and vessel-tanks at data/condtrigs/condtrigs.json:1830), sets its StatDamage straight to StatDamageMax (fully breaks it), then rolls TIsSparkable on it and emits a spark if it passes.

3. Crew welding sparks

Crew.cs:780-794. Every crew member has a Sparks boolean property; setting it true activates the welding-VFX and — if the crew member doesn't have IsMaintenanceTechNPC — calls CrewSim.vfxFire.Spread(this.tf.position, CrewSim.shipCurrentLoaded, 0.005f). So every welding tick from your crew has a 0.5 % chance modifier on the spread roll. Maintenance-tech NPCs (e.g. station-employed welders) are exempt; their welding never starts fires.

4. Explosions

Explosion.cs:178-194. Same flammability tier coefficients as path 1 (0.9 / 0.5 / 0.25), but without the fFireChanceCoeff multiplier — so an explosion hitting a flammable item lights it with 90 % probability per damaged CO. Explosion COs come from reactor blow-ups, mining charges, and ammo cooking off.

Fire spread — the room-chance formula

This is the single most important formula in the system. Both spark-to-fire conversion (the 0.5× path) and fire-to-fire spread (the 1.0× path, called from VFXFireInstance.ProcessFireEffects) go through VFXFire.Spread() at VFXFire.cs:314-388, which calls VFXFire.GetRoomChance(coRoom):

// VFXFire.cs:472-482
public static double GetRoomChance(CondOwner coRoom)
{
    double O2  = coRoom.GetCondAmount("StatGasPpO2");
    double P   = coRoom.GetCondAmount("StatGasPressure");
    double T   = coRoom.GetCondAmount("StatGasTemp");
    return O2 / 20.0 * O2 / P * T / 293.0;
}

Numerically that's (PpO2² · Temp) / (20 · P · 293). The reference point — Earth-mix room (PpO2 ≈ 20.7, P ≈ 101.3, T = 293) — gives about 20.7² / (20 × 101.3) × 1.0 ≈ 0.21, i.e. ~21 % per draw on the base case. From there:

Spread() then picks a random adjacent solid CO and multiplies the room chance by:

Target's flammabilityMultiplier
IsFireproof× 0.00
IsFlammable× 6.00
IsBurnable× 2.50
(none of the above)× 0.25

Then a single uniform Rand(0, 1) draw decides. So a hot Earth-mix room with a flammable target sees 0.21 × 6.0 ≈ 1.0 — practically certain on the first spread tick of a single fire.

VFXFireInstance.ProcessFireEffects at VFXFireInstance.cs:88-144 additionally multiplies the host's room chance by 5.0 for the per-fire "bContinue" roll — every fire essentially tries to spread itself with an amplified base chance. If that roll passes, it then calls Spread() which itself rolls a fresh per-target chance. The doubled gate is why fires propagate fast in good conditions.

What a single fire does each tick

The active fire's fEpochExpire is 5 simulated seconds from spawn. When it expires (VFXFire.UpdateCleanupFireProcessFireEffects at VFXFire.cs:42-48 and VFXFireInstance.cs:88-144), three things happen in order:

  1. Spread roll against the host's adjacent room (chance = GetRoomChance(adjRoom) × 5.0). If it passes, bContinue = true, Spread() picks an adjacent target and rolls again with the per-target tier. If bContinue still true at the end, the fire re-hosts onto itself for another 5-second cycle. If false, the fire is reaped.
  2. Crew damage. Every IsHuman or IsRobot within 1.5 tiles of the fire that isn't IsFireproof takes a Wound.Damage call using AModeDefaultFire (fDmgBlunt=0, fDmgCut=3, fDmgEnv=0, fPenetration=0 per data/attackmodes/coAttacks/attackmodes.json:93-103). If a player-crew member is hit, the BeatManager tension timer resets — a heads-up that fire damage is now part of the narrative arc.
  3. Host damage. StatDamage += Random.Range(1, 3) × damageModifier, where the modifier is 1.0 if host is IsFlammable, 0.5 if IsBurnable, else 0.25 (VFXFireInstance.cs:147-166). A typical flammable cushion takes 1–3 damage per 5 s; a steel wall takes 0.25–0.75.

The fire also marks the underlying tile with IsTileBurning (VFXFireInstance.cs:185) — a short-duration condition (fDuration: 0.0014 ≈ 2 minutes ingame, bResetTimer: true, fClampMax: 1.0 per data/conditions/conditions.json:1221) that other systems can detect.

The doom loops

Loop A — sparks ↔ damage ↔ fire ↔ damage. A damaged power conduit sparks (path 2a, 0.2 base chance per draw, throttled by ship-size factor). The spark calls Spread() at 0.5×. If it lands in a hot pressurised room next to a flammable target, that's 0.21 × 0.5 × 6.0 ≈ 0.63 per attempt — a coin-flip-and-then-some. A fire spawns. The fire damages adjacent items (path 5 below); some of those are also IsPowerConduit or IsPowered; once they hit ≥50 % damage they become sparkable too. Sparkings increase. More fires. Loop closes.

This is the primary "spread my fire by breaking adjacent electrics" pattern the user asked about.

Loop B — fire heating its own room. The fire's Heater radiates with StatSolidTemp = 1073 K via the Stefan–Boltzmann update (Heater.cs:148-202). The room's StatGasTemp rises. GetRoomChance ∝ T, so the spread chance goes up linearly with room temperature. The new fires also radiate. The temperature climbs faster. Until O2 runs out — at which point the loop self-arrests.

Loop C — auto-firefighting deferred-task pileup. When a fire spawns on a player-owned ship, VFXFire.cs:266-282 queues a Firefight task into workManager. AI crew that satisfy the AIFirefight pledge (data/pledges/pledges.json:78-89) pick it up. If they're IsCraven, IsAgliophobic, or have DcSecurity05 (Terrified) and lack IsBrave / IsMasochist / IsLeader traits, TIsFireFlee (data/condtrigs/condtrigs.json:1219-1222) gates the action toward "Flee" instead — they actively run away from the fire they were ordered to fight. While the player's crew runs in circles, the fire spreads. Not strictly a doom-loop, more a doom-stall.

What's data vs. what's code

Tunable from JSON (data)

KnobWhereEffect
Per-attack fire chancedata/attackmodes/shipAttacks/shipAttacks.jsonfFireChanceCoeffShip-weapon ignition probability (path 1).
Sparkable item setdata/condtrigs/condtrigs.jsonTIsSparkable* / TIsSparkableDmgSys*Which item conditions count as sparkable. Edit the aReqs / aForbids.
Spreadable target setdata/condtrigs/condtrigs.jsonTIsFireSpreadableWhat kinds of CO the spread loop is willing to consider as targets (currently IsFlammable | IsBurnable | IsSolid | IsRoom, forbids IsFireproof).
Flammability tier per itemdata/condowners/condowners*.jsonaStartingCondsAdd or remove IsFlammable=1.0x1 / IsBurnable=1.0x1 / IsFireproof=1.0x1 on items to shift them between tiers.
Fire's heat outputSysFire.aStartingConds in condowners.jsonStatSolidTemp, StatHeatArea, StatHeatVolDrops or raises how fast a fire heats its room.
Fire's gas outputdata/gasrespires/gasrespires.json → the Fire profile → aGases[].fConvRateHow much O2 burns per tick and what proportion comes out as CO/CO2/H2SO4/NH3/Smoke.
Extinguisher chargeItmToolFireEx01 in condowners.jsonStatFireChem=1.0x11.0Number of sprays per extinguisher (11 by default).
Extinguish-per-spraydata/loot/loot.jsonCONDACTFireExtinguishAllowThem (IsExtinguished=0.66x1.0) and CONDACTFireStampAllowThem (0.33x1.0)Sprays per fire (2 sprays / 3 stamps to clear at default values).
AI flee gatingdata/condtrigs/condtrigs.jsonTIsFireFlee*Which traits + mood thresholds make crew run from fires instead of fighting them.
Firefight pledge / prioritydata/pledges/pledges.jsonAIFirefight.nPriorityWhere firefighting sits in the AI's task-pile priority (currently 9, just under combat-join at 10).

Hard-coded in C# (code)

Constant / behaviourLocationValue
Max simultaneous firesVFXFire.cs:485170
Fire tick durationVFXFire.cs:4915.0 s
Fire VFX fade timeVFXFire.cs:4882.0 s
GetRoomChance formulaVFXFire.cs:472-482O2² · T / (20 · P · 293)
Flammability tier multipliers (spread)VFXFire.cs:367-3820 / 6.0 / 2.5 / 0.25
Flammability tier multipliers (combat ignition)DamageSystem.cs:405-4170 / 0.9 / 0.5 / 0.25
Flammability tier multipliers (explosion ignition)Explosion.cs:178-1900 / 0.9 / 0.5 / 0.25
Fire-per-fire spread amplificationVFXFireInstance.cs:104-107× 5.0
Spark-to-fire spread chance modifierVFXSparks.cs:82× 0.5
Welding-spark spread chance modifierCrew.cs:790× 0.005
Spark self-damage per sparkShip.cs:2094min(0.01, StatDamageMax / 800)
Background sparking enable thresholdShip.cs:2090only when item ≥50 % broken
Crew damage attack modeVFXFireInstance.cs:49-128AModeDefaultFire (3 cut, range 1.5)
Maintenance-tech welding-spark exemptionCrew.cs:787-791any CO with IsMaintenanceTechNPC
Fire host blacklistVFXFire.cs:232-241IsHuman, IsCarried, contained items, tutorial-derelict-hidden ship

Counter-play and prevention

In-game, by the player

Modded, in JSON

What the test saves look like

Both saves under test-data/save/whole-folders/ have zero active fires. Verified by parsing every ships/<reg>.json in the zips:

That's expected: the player is docked at K-Leg in peacetime. The rust-buckets you'd find fires on come from one of:

  1. a derelict that's already been boarded once (so bPrefill == false) and is now suffering background Ship.Sparks() on its damaged conduits — but our test save's "Used" / "Derelict" ships are nearly all still bPrefill == true, so they haven't run their first-load damage yet;
  2. a ship that took ship-vs-ship combat hits recently and rolled a missile/PD fire (path 1);
  3. a ship where a tension event triggered PartFailure while it was the player's active ship.

The 127 bPrefill == true derelicts in this save are still "rolled up" — their aCOs arrays are small because PreFillRooms hasn't run. As soon as the player boards one of these, Ship.cs:983-989 runs BreakIn(), which heavily damages installed systems. After that, every visit puts these ships in scope for both path 2a and path 1 fires.

Confidence — what was verified vs. inferred

ClaimStatus
Fire is a SysFire CondOwner with GasRespire2,Fire and Heater,TIsAirtightNoHuman update commandsverified at data/condowners/condowners.json:34667-34695
GetRoomChance formula = O2² · T / (20 · P · 293)verified at VFXFire.cs:472-482
Spread tier coefficients (Fireproof 0, Flammable 6.0, Burnable 2.5, none 0.25)verified at VFXFire.cs:367-382
Combat-ignition tier coefficients (Fireproof 0, Flammable 0.9, Burnable 0.5, none 0.25)verified at DamageSystem.cs:405-417 and Explosion.cs:178-190
fFireChanceCoeff is per-ship-attack, not per-CO-attackverified at JsonShipAttack.cs:54
BreakIn() attack mode AModeDerelictBreakIn doesn't carry a fire-chance coefficientverifiedAModeDerelictBreakIn is a JsonAttackMode consumed by DamageRay at DamageSystem.cs:637-756, which has no fire-roll path; fFireChanceCoeff only exists on JsonShipAttack
Sparks call Spread() at 0.5×verified at VFXSparks.cs:82
Welding sparks call Spread() at 0.005×verified at Crew.cs:790
IsMaintenanceTechNPC exempts welding sparks from fire spreadverified at Crew.cs:787-791
TIsSparkable requires damaged but not IsDamagedverified at data/condtrigs/condtrigs.json:2369-2370
User-setting nFlickerAmount < 0 disables background sparking entirelyverified at Ship.cs:2062-2065
Flicker dropdown has three labelled options — "Off" (-1) / "Soft" (1) / "Full" (2, default), so "Off" is reachable from the UI without Custom Parametersverified at GUIOptions.cs:71-85 (dictionary literal) + JsonUserSettings.cs:157 (default)
Flicker dropdown lives on the General tab, not Videoverified in-game against the live build pinned in this repo (0.15.0.x). The Video tab carries Resolution / Window Style / FPS / Ambient Occlusion / Parallax / LoS / Screen Shake / Turbo only; the Flicker Amount dropdown is one panel over on General.
Background sparks emit one per call max, scaled by mapICOs.Count / 1200verified at Ship.cs:2066-2101
Each background spark self-damages its source by min(0.01, StatDamageMax / 800), eventually pushing the item to IsDamaged and removing it from TIsSparkableConduitsverified at Ship.cs:2092-2095 + data/condtrigs/condtrigs.json:2369
TIsSparkableDmgSys requires only IsInstalled + power, no damage requirementverified at data/condtrigs/condtrigs.json:2371-2373
Fire's host damage uses 1–3 × tier modifier per 5 s tickverified at VFXFireInstance.cs:141-166
Fire's crew damage uses AModeDefaultFire (3 cut, range 1.5)verified at VFXFireInstance.cs:119-128 and data/attackmodes/coAttacks/attackmodes.json:93-103
Fire respire emits CO/CO2/H2SO4/NH3/Smoke from O2verified at data/gasrespires/gasrespires.json:360-413
MAX_FIRES = 170 hard capverified at VFXFire.cs:485
Fire tick is 5 s with 2 s fadeverified at VFXFire.cs:488-491
Fires won't spawn on IsHuman / IsCarried / contained items / IsTutorialDerelictHidden shipsverified at VFXFire.cs:232-241
Extinguisher gives IsExtinguished=0.66x1.0 per spray; stamp gives 0.33x1.0verified at data/loot/loot.json:3706-3717
Vacuum / depressurised rooms are excluded from spread candidates via DcGasPressure01verified at VFXFire.cs:339-358
Heater's heat transfer is Stefan–Boltzmann with StatSolidTemp = 1073 K on SysFireverified at Heater.cs:148-202 and data/condowners/condowners.json:34685
Test saves contain zero active fires (aFires empty across 141 ships)verified by parsing every ship file in cares catherine ruiz_1778427283.zip
"Fires consume room O2 until the spread chance drops to zero, then go out on their own" is the only self-arrest mechanic for firesinferred — derived from the lack of a passive decay timer in SysFire's starting conds and the structure of ProcessFireEffects. Not directly traced in code as the "no fuel = fire dies" path; consider it the practical result of the spread-chance formula returning 0 once O2 bottoms out.
tension_part_failure event chance lives in BeatManager's dictEventChances and is editable from datainferredBeatManager.cs:741 reads it from dictEventChances but the seed file path wasn't located in this repo's data/ tree under that name. Search your install for tension_part_failure to find the actual file before editing.
Halvorson extinguisher's IsFireproof=1.0x1 means it survives being dropped in a burning roominferred — the cond is present at condowners.json:28670 and VFXFire.Spread hard-zeros the chance for IsFireproof targets, so the extinguisher should never light. Untested live.

Quick-reference card

Find active fires in a save: walk ships/<reg>.json → aFires[] for non-empty arrays. Each entry is a CondOwner.strID string; cross-reference against the same ship's aCOs[] to identify the host.

Audit a ship's fire risk: count CO's with IsFlammable / IsBurnable / IsFireproof in aStartingConds. Count IsPowerConduit+IsInstalled+StatDamage>0 entries for sparkability surface area. Check rooms' StatGasPpO2 / StatGasPressure / StatGasTemp against the O2² · T / (20 · P · 293) formula.

Stop a fire spreading in your save edit: set the room's StatGasPressure to 0 in aConds — the spread loop drops the room from candidates immediately. Or add "IsExtinguished=1.0x1" to the burning SysFire CO's aConds; on load it'll fade on the next tick (VFXFire.cs:121-124).

The four code paths that ignite fires: ship-weapon hits (DamageSystem.ApplyDamageToCell, gated by fFireChanceCoeff); damaged powered items / conduits (Ship.Sparks + VFXSparks.AddSparkAt + VFXFire.Spread); crew welding (Crew.Sparks setter at 0.005× rate); explosions (Explosion.cs).