FireFront 0.19: A Quarter-Million Distance Checks, and Two Bug Reports That Were One Bug
By RavenIron Games
FireFront turns Valheim’s fire into a moving front: structures and trees burn down for real, fire crawls across open ground, wind stretches it downwind, and the whole spreading blaze remembers who lit it.
The 0.19 series was almost entirely about the two things that decide whether a fire simulation is playable — what it costs, and whether it looks like what it is doing. Both were fixed by measurement, and one of them by a measurement that corrected our own earlier advice.
1. Spread Was Testing Every Burnable Against Every Fire
A tester’s log finally showed the shape of the problem. Hosting on 0.19.3 with a maxed fire, they had 2176 spread candidates against 50 burning objects and 46 ground cells — and SpreadPass compared every candidate to every burner, on each 0.75-second cycle.
That is roughly a quarter of a million distance checks a cycle, each one carrying a type dispatch and a ZDO lookup. Their measured frametime spikes were 101.9–146.3ms, which is exactly where that arithmetic lands.
The fix rests on an observation about the data rather than the code: spread candidates are static. Trees and walls do not move. So they are bucketed into a 16m spatial grid whenever the candidate list is rebuilt, and a burner only examines the cells its own reach actually touches.
Cost now follows the size of the fire instead of how much wood is lying around the map. Two smaller wins came with it — the cheap distance check runs before the type dispatch and ZDO lookup rather than after, and bucket lists are pooled so re-bucketing doesn’t allocate. Behaviour is unchanged: the same candidates ignite, they are simply found without walking the whole world.
One correction worth recording. Our earlier advice had been that affected testers were on an old pre-0.18.6 build. That was wrong. This tester was on 0.19.3 with every prior performance fix already in place; this loop was the part none of them had touched. A confident diagnosis survived several releases because it sounded right, and only a log killed it.
2. What The Ceiling Actually Is
Once cost tracked fire size, it became fair to ask how much devastation actually fits. WatchTheWorldBurn is the answer — one switch that makes fire contagious instantly, removes firebreaks and water, ignores rain, lets burned ground relight, and pushes every cap to its ceiling.
Relayed to a dedicated server mid-burn, every flag flipped in the next heartbeat:
before: burning 22/150, ground 39/150, fires 3, maturity 25%, radius 8m, interval 0.75s,
firebreaks True, waterblocks True, exhaustion True, leash True, ramp enabled True
after: burning 1316/2600, ground 6500/6500, fires 13, maturity 0%, radius 15m, interval 0.25s,
firebreaks False, waterblocks False, exhaustion False, leash False, ramp enabled False
Ground pinned at its ceiling, so it was cap-limited rather than out of fuel. Server load: one core saturated — 10.2 CPU-seconds per 10 seconds of wall clock, single-threaded — and still keeping cadence. That is the practical ceiling, and it is the honest answer to “how much can it take.”
For scale: at 1316 burners, the pre-0.19.8 code would have been doing roughly 2.6 million distance checks per cycle, four times a second. That configuration only exists because of the grid.
Two restraints are deliberate. Your visual caps are left exactly as you set them, because GroundVfxMaxConcurrent and GroundDamageMaxConcurrent are what actually cost frames — someone who wants the world to burn still decides how much of it their machine renders. And if LowSpecPreset is also on, low spec wins: a machine that cannot cope is a harder constraint than a preference for spectacle, and getting that precedence backwards ends in somebody’s game freezing.
3. Two Reports, One Bug
Then two complaints arrived from play: the fire went out, and trees aren’t falling.
The second one was checkable immediately, and the answer was no — the trees were falling. The log showed burn-downs, a growing regrowth queue, and zero kill failures. The simulation was never affected at all.
Both reports were the same defect, and it was cosmetic. At a 60-second burn duration, an over-aggressive smouldering downgrade meant a fire spent its last 33 seconds looking extinguished while still being contagious and still burning anything standing in it. A player watching a fire that looks dead does not conclude “the visuals are wrong”; they conclude the fire stopped, and that the trees inside it must have stopped too.
What changed:
- The light is shrunk, not destroyed. Deleting it took the glow with it. Range is the dominant cost of a realtime light — it decides how many objects the light must touch — so halving range and intensity keeps most of the saving while the fire visibly still has heat in it.
- Flames drop to 35%, not 12%, and stay a warm ember orange instead of a near-black red. 12% was invisible.
- Intermittent flare-ups. This is the part the first attempt missed, and it’s the whole insight: a steady weak trickle reads as dying, while irregular bursts read as still burning, just not raging — and they cost nothing between bursts.
- Smoke barely reduced, since smoke is the signature of smouldering.
The first version failed because it treated smouldering as less fire. What was actually wanted was intermittent fire, which is a different thing, and the part that makes it read as alive rather than finished.
4. The Default That Never Arrived
A postscript that will save someone an afternoon.
0.19.13 moved the smoulder threshold’s default from 0.45 to 0.65. For anyone already running FireFront, it did nothing — because BepInEx persists config values to disk, so the 0.45 that an earlier build had already written silently overrode the new default. A changed default only ever reaches an install that has never run an older version.
The lesson generalises past this mod: if you ship a retuned default, say so explicitly and tell people to set it, because most of your users will never see it. And since the threshold is an aesthetic value that needs iterating by eye, 0.19.14 made it live-settable — fireset smoulderafter <0.05-1> — rather than something that requires a config edit and a server restart, which is the worst possible loop for anything you tune by looking at it.
Full details, console reference and compatibility notes on the FireFront project page.