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
[]ornullon most files. On load,Ship.cs:1104-1106walks this list and re-queues each ID viaCrewSim.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 appliesDamageAllCOs(0.33f); Damaged and Derelict runBreakIn(), 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 undertest-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 theaFireslist, 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:
- Refuses if the host has
IsHuman,IsCarried, orobjCOParent != null— humans don't host fires; carried/contained items don't either (VFXFire.cs:232-236). - Refuses if the host's ship has
IsTutorialDerelictHidden(VFXFire.cs:237-241). - Spawns a fresh
SysFireCondOwner (via theSysFirerecord indata/condowners/condowners.json) parented to the host's tile, setsfEpochExpire = StarSystem.fEpoch + 5.0seconds, and starts the VFX/audio (VFXFire.cs:242-258). - Caps at
MAX_FIRES = 170simultaneous active fires across the whole sim (VFXFire.cs:485). Past that, new ignitions queue but don't render until existing ones expire. - If the player owns the ship the fire is on, queues a
FirefightTask 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"
]
}
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, ... }
]
}
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:
| Attack | fFireChanceCoeff |
|---|---|
MassDriverAttack | 0.10 |
MissileAttack01 / 02 / 03 | 0.25 |
RailgunAttack | 0.25 |
PointDefenseImpact / ScuttleImpact | 0.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
- Ship must be fully loaded —
nLoadState ≥ Ship.Loaded.Edit. Sparks don't render in shallow-load (e.g. an unboarded ship in the system view). - Sim not paused —
CrewSim.Pausedshort-circuits. - User has flicker enabled —
DataHandler.GetUserSettings().nFlickerAmount < 0short-circuits. This is a player-facing graphics setting exposed as a three-option dropdown (GUIOptions.cs:71-85): "Off" = -1, "Soft" = 1, "Full" = 2 (the default). Picking Off in the dropdown disables background sparking entirely, killing the spark-feeds-fire arm of Loop A. No Custom Parameters editing required.
Where to find it: the dropdown is part of the main options panel, not the Video sub-tab. If a player tells you they don't see it under Video, point them at the General tab. The screenshot a player will recognise has Resolution / Window Style / FPS Limit / Ambient Occlusion on the Video page; Flicker Amount lives elsewhere in the same dialog.
Side-effects: the same setting also kills electrical flicker on thePowered(Powered.cs:107-112) andVisibility(Visibility.cs:71-84) systems — i.e. lights stop flickering visually too. Most players who care about fire risk will accept that trade.
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;
- Base 2%/frame at no sim compression on a "reference" 1200-installed-CO ship.
- Time-scaled. Compressing sim speed (e.g. ×8) roughly doubles the multiplier — fast-forwarding does increase fire risk.
- Ship-size scaled. A 300-CO tug gets 0.5 %/frame; a 6000-CO megastation gets 10 %/frame. Big rust buckets spark much more often per real-time second than small ones.
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;
}
}
- Power-network gate. The candidate's tile must be wired into a power network —
aConnectedPowerCOs.Count > 0on 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. - ≥50 % damage gate.
GetDamageState()returns 1.0 (intact) → 0.0 (broken), so1.0 - state ≥ 0.5means the item is at least 50 % of the way to the broken threshold. An item at 20 % damage will passTIsSparkable(which only checksStatDamage > 0) and end up in the candidate list, but won't actually spark — it's queued waiting for damage to accumulate. - 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. - Self-damage death spiral. Each spark adds
min(0.01, StatDamageMax / 800)to the item's ownStatDamage. For an item withStatDamageMax = 10, that's10/800 = 0.0125→ clamped to0.01absolute. A damaged conduit that keeps sparking is accumulating its own damage at roughly 1 % of max per spark. Eventually it crosses theIsDamagedthreshold and falls out ofTIsSparkableConduits(the triggeraForbidsIsDamaged) — 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:
- Per-frame gate:
0.02 × ~1 × 1000/1200 ≈ 0.017→ ~1.7 % per frame. - At 60 FPS, ~1 Sparks() attempt every second.
- Of those, maybe half pass the power-tile + ≥50 %-damage filter (depends on what got broken in
BreakIn()). - So roughly one actual spark every ~2 seconds on this ship while you're walking around.
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).
TIsSparkableDmgSys — data/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:
- Vacuum or near-vacuum (P ≈ 0): the formula divides by zero, but rooms with
DcGasPressure01(the low-pressure discomfort threshold) are filtered out beforeGetRoomChanceis even called (VFXFire.cs:339-358), so the divide-by-zero is never reached in practice — and the candidate is rejected. - O2-starved but pressurised (e.g. pure CO2 at 101 kPa): O2 → 0 makes the chance 0.
- Hot, dense, oxygenated: chance grows as O2² × T. Worst case for the player.
Spread() then picks a random adjacent solid CO and multiplies the room chance by:
| Target's flammability | Multiplier |
|---|---|
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.Update → CleanupFire → ProcessFireEffects at VFXFire.cs:42-48 and VFXFireInstance.cs:88-144), three things happen in order:
- 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. IfbContinuestill true at the end, the fire re-hosts onto itself for another 5-second cycle. If false, the fire is reaped. - Crew damage. Every
IsHumanorIsRobotwithin 1.5 tiles of the fire that isn'tIsFireprooftakes aWound.Damagecall usingAModeDefaultFire(fDmgBlunt=0, fDmgCut=3, fDmgEnv=0, fPenetration=0perdata/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. - Host damage.
StatDamage += Random.Range(1, 3) × damageModifier, where the modifier is1.0if host isIsFlammable,0.5ifIsBurnable, else0.25(VFXFireInstance.cs:147-166). A typical flammable cushion takes1–3damage per 5 s; a steel wall takes0.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)
| Knob | Where | Effect |
|---|---|---|
| Per-attack fire chance | data/attackmodes/shipAttacks/shipAttacks.json → fFireChanceCoeff | Ship-weapon ignition probability (path 1). |
| Sparkable item set | data/condtrigs/condtrigs.json → TIsSparkable* / TIsSparkableDmgSys* | Which item conditions count as sparkable. Edit the aReqs / aForbids. |
| Spreadable target set | data/condtrigs/condtrigs.json → TIsFireSpreadable | What kinds of CO the spread loop is willing to consider as targets (currently IsFlammable | IsBurnable | IsSolid | IsRoom, forbids IsFireproof). |
| Flammability tier per item | data/condowners/condowners*.json → aStartingConds | Add or remove IsFlammable=1.0x1 / IsBurnable=1.0x1 / IsFireproof=1.0x1 on items to shift them between tiers. |
| Fire's heat output | SysFire.aStartingConds in condowners.json → StatSolidTemp, StatHeatArea, StatHeatVol | Drops or raises how fast a fire heats its room. |
| Fire's gas output | data/gasrespires/gasrespires.json → the Fire profile → aGases[].fConvRate | How much O2 burns per tick and what proportion comes out as CO/CO2/H2SO4/NH3/Smoke. |
| Extinguisher charge | ItmToolFireEx01 in condowners.json → StatFireChem=1.0x11.0 | Number of sprays per extinguisher (11 by default). |
| Extinguish-per-spray | data/loot/loot.json → CONDACTFireExtinguishAllowThem (IsExtinguished=0.66x1.0) and CONDACTFireStampAllowThem (0.33x1.0) | Sprays per fire (2 sprays / 3 stamps to clear at default values). |
| AI flee gating | data/condtrigs/condtrigs.json → TIsFireFlee* | Which traits + mood thresholds make crew run from fires instead of fighting them. |
| Firefight pledge / priority | data/pledges/pledges.json → AIFirefight.nPriority | Where firefighting sits in the AI's task-pile priority (currently 9, just under combat-join at 10). |
Hard-coded in C# (code)
| Constant / behaviour | Location | Value |
|---|---|---|
| Max simultaneous fires | VFXFire.cs:485 | 170 |
| Fire tick duration | VFXFire.cs:491 | 5.0 s |
| Fire VFX fade time | VFXFire.cs:488 | 2.0 s |
GetRoomChance formula | VFXFire.cs:472-482 | O2² · T / (20 · P · 293) |
| Flammability tier multipliers (spread) | VFXFire.cs:367-382 | 0 / 6.0 / 2.5 / 0.25 |
| Flammability tier multipliers (combat ignition) | DamageSystem.cs:405-417 | 0 / 0.9 / 0.5 / 0.25 |
| Flammability tier multipliers (explosion ignition) | Explosion.cs:178-190 | 0 / 0.9 / 0.5 / 0.25 |
| Fire-per-fire spread amplification | VFXFireInstance.cs:104-107 | × 5.0 |
| Spark-to-fire spread chance modifier | VFXSparks.cs:82 | × 0.5 |
| Welding-spark spread chance modifier | Crew.cs:790 | × 0.005 |
| Spark self-damage per spark | Ship.cs:2094 | min(0.01, StatDamageMax / 800) |
| Background sparking enable threshold | Ship.cs:2090 | only when item ≥50 % broken |
| Crew damage attack mode | VFXFireInstance.cs:49-128 | AModeDefaultFire (3 cut, range 1.5) |
| Maintenance-tech welding-spark exemption | Crew.cs:787-791 | any CO with IsMaintenanceTechNPC |
| Fire host blacklist | VFXFire.cs:232-241 | IsHuman, IsCarried, contained items, tutorial-derelict-hidden ship |
Counter-play and prevention
In-game, by the player
- Fire extinguisher (Halvorson
ItmToolFireEx01). 11 charges ofStatFireChem, expends 1 per spray, each spray adds0.66to the fire'sIsExtinguishedcondition. Two sprays clears one fire.ACTFireExtinguishAllowindata/interactions/interactions.json:1183appliesCONDACTFireExtinguishAllowThemfromdata/loot/loot.json:3706-3712. The extinguisher itself isIsFireproof, so it never ignites. - Stamp it out (no tool).
ACTFireStampatdata/interactions/interactions.json:1263-1291. Three stamps per fire (IsExtinguished=0.33x1.0perCONDACTFireStampAllowThem). Slower and exposes the crew toAModeDefaultFirewounds. - Vent the room. Cut a wall or open a door to vacuum. Pressure → 0 → room is rejected from the spread candidate list (
VFXFire.cs:339-358filters rooms withDcGasPressure01). Fires already burning won't take new spread targets and won't damage adjacent items in that room. They still consume the residual O2 and self-extinguish. - Cut power. No tile-connected power = no
Ship.Sparks()atShip.cs:2085(aConnectedPowerCOs.Count == 0short-circuits). Doesn't stop fires already burning, but kills the spark-feeds-fire arm of Loop A. - Set Flicker Amount to "Off" in the options panel. The dropdown is on the General tab of the options dialog (not Video), and its three labelled values map to integers: Off = -1, Soft = 1, Full = 2 (default). Choosing Off sends
nFlickerAmount = -1, which makesShip.cs:2062-2065short-circuit all background sparking. Doesn't affect ship-combat ignition (path 1), welding (path 3), or explosions (path 4), but completely removes the continuous spark-on-damaged-conduits arm of Loop A. Side-effect: lights also stop flickering visually (Powered.cs:107-112andVisibility.cs:71-84branch on the same flag). Useful when running a long-haul rust bucket where you can't keep the whole ship powered-down. - Don't fight fires with cravens. See Loop C — crew with
IsCraven,IsAgliophobic, or panicking underDcSecurity05will refuse-then-flee. Bring theIsBrave/IsMasochist/IsLeadercrew member to the front line, or assign a maintenance-tech NPC if you have one — they auto-extinguish via theAIMaintenanceTechWorkFireExtinguishpledge.
Modded, in JSON
- Option F-2 — make derelicts safer to break in. Lower
fBreakInMultiplierat spawn time for the templates you care about, or grant the playerIsDueBonusDerelictthrough a plot edit (Ship.cs:2261-2268divides the multiplier by1 + IsDueBonusDerelicton first board). - Option F-3 — make your favourite items fireproof. Add
IsFireproof=1.0x1toaStartingCondson the condowner. The flammability check is strict tiering —IsFireproofshort-circuits at the top in bothVFXFire.SpreadandDamageSystem.ApplyDamageToCell. Useful for protecting reactor cores, fuel-cell drawers, oxygen tanks, etc. - Option F-4 — gentler fire-tier coefficients. The 6× / 2.5× spread multipliers live in C# and can't be edited from JSON. But you can demote items from
IsFlammabletoIsBurnable(or to neither) in their condowner record — same effective result for that item. - Option F-5 — re-key the extinguisher. Bump
StatFireChem=1.0x11to1.0x30for a longer-lasting extinguisher, or raiseIsExtinguished=0.66x1.0inCONDACTFireExtinguishAllowThemto1.0x1.0so each spray kills a fire outright. - Option F-6 — disable BeatManager part-failures. Set
tension_part_failurechance to0in the BeatManager's event-chance config (lives indata/beat/or equivalent — search fordictEventChancesseed file in your install). Kills path 2c without affecting paths 2a/2b.
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:
cares catherine ruiz_1778427283.zip— 141 ships, 0 withaFiresnon-empty, 0 instances of"SysFire"in any aCOs, 0 instances ofIsTileBurning.cares catherine ruiz_1778371145.zip— same shape (sampled).
That's expected: the player is docked at K-Leg in peacetime. The rust-buckets you'd find fires on come from one of:
- a derelict that's already been boarded once (so
bPrefill == false) and is now suffering backgroundShip.Sparks()on its damaged conduits — but our test save's "Used" / "Derelict" ships are nearly all stillbPrefill == true, so they haven't run their first-load damage yet; - a ship that took ship-vs-ship combat hits recently and rolled a missile/PD fire (path 1);
- a ship where a tension event triggered
PartFailurewhile 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
| Claim | Status |
|---|---|
Fire is a SysFire CondOwner with GasRespire2,Fire and Heater,TIsAirtightNoHuman update commands | verified 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-attack | verified at JsonShipAttack.cs:54 |
BreakIn() attack mode AModeDerelictBreakIn doesn't carry a fire-chance coefficient | verified — AModeDerelictBreakIn 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 spread | verified at Crew.cs:787-791 |
TIsSparkable requires damaged but not IsDamaged | verified at data/condtrigs/condtrigs.json:2369-2370 |
User-setting nFlickerAmount < 0 disables background sparking entirely | verified 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 Parameters | verified at GUIOptions.cs:71-85 (dictionary literal) + JsonUserSettings.cs:157 (default) |
| Flicker dropdown lives on the General tab, not Video | verified 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 / 1200 | verified 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 TIsSparkableConduits | verified at Ship.cs:2092-2095 + data/condtrigs/condtrigs.json:2369 |
TIsSparkableDmgSys requires only IsInstalled + power, no damage requirement | verified at data/condtrigs/condtrigs.json:2371-2373 |
Fire's host damage uses 1–3 × tier modifier per 5 s tick | verified 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 O2 | verified at data/gasrespires/gasrespires.json:360-413 |
MAX_FIRES = 170 hard cap | verified at VFXFire.cs:485 |
| Fire tick is 5 s with 2 s fade | verified at VFXFire.cs:488-491 |
Fires won't spawn on IsHuman / IsCarried / contained items / IsTutorialDerelictHidden ships | verified at VFXFire.cs:232-241 |
Extinguisher gives IsExtinguished=0.66x1.0 per spray; stamp gives 0.33x1.0 | verified at data/loot/loot.json:3706-3717 |
Vacuum / depressurised rooms are excluded from spread candidates via DcGasPressure01 | verified at VFXFire.cs:339-358 |
Heater's heat transfer is Stefan–Boltzmann with StatSolidTemp = 1073 K on SysFire | verified 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 fires | inferred — 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 data | inferred — BeatManager.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 room | inferred — 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).