[2.1.17] Crash when a segmented unit's body is one node (SegmentedUnitAI::update)
Posted: Wed Sep 09, 2026 5:26 pm
AI Disclosure
I encountered this problem while developing a mod, and asked Claude Opus 5 to nail it down to a minimal reproduction. Most of the composition here is from the AI, but I have reviewed and understand and endorse all of it.
The bug
LuaSegmentedUnit.set_body_nodes accepts a list of one position without complaint, and the unit afterwards reports a one node body. The next time that unit scans for something to attack, the engine asks the body for its head position, gets an empty optional back, and the game aborts with std::bad_optional_access.
Because the crash happens on the unit's next scan rather than on the call itself, it lands somewhere between the call and ticks_per_scan ticks later, which makes it look intermittent and unrelated to the mod code that caused it. A body of two nodes is fine; only one is a problem.
What I did
Created a small-demolisher with LuaSurface.create_segmented_unit, then called set_body_nodes on it with a single position taken from its own body. Nothing else: no player interaction, no other mods, and the game is left completely alone after the call.
What happened
The game crashed 70 ticks later, at map tick 130.
What I expected
Either a one node body that the engine can go on updating, or set_body_nodes rejecting the list and raising a Lua error, the way other runtime API calls do when given something they cannot use. A Lua error would be much easier to track down than a crash in a later tick.
Version and build
Version: 2.1.17 (build 87315, linux64, steam), running headless with --benchmark. Base, elevated-rails, quality, and space-age, plus the attached repro mod, which has no data stage and 20 lines of control.lua.
Stack trace
Reproduction
This control.lua is also attached:
Enable it on a fresh freeplay map and wait a couple of seconds. Replacing the last line's single node list with two nodes makes the crash go away, and the unit then runs indefinitely.
Where it came from
I hit this while developing a mod that animates a segmented unit sinking into the ground by taking body nodes off it a few at a time. The last frame of that animation is a single node, which gave no indication that it was the problem: the crash arrived a second later, from inside the AI update, with nothing pointing back at the set_body_nodes call.
I encountered this problem while developing a mod, and asked Claude Opus 5 to nail it down to a minimal reproduction. Most of the composition here is from the AI, but I have reviewed and understand and endorse all of it.
The bug
LuaSegmentedUnit.set_body_nodes accepts a list of one position without complaint, and the unit afterwards reports a one node body. The next time that unit scans for something to attack, the engine asks the body for its head position, gets an empty optional back, and the game aborts with std::bad_optional_access.
Because the crash happens on the unit's next scan rather than on the call itself, it lands somewhere between the call and ticks_per_scan ticks later, which makes it look intermittent and unrelated to the mod code that caused it. A body of two nodes is fine; only one is a problem.
What I did
Created a small-demolisher with LuaSurface.create_segmented_unit, then called set_body_nodes on it with a single position taken from its own body. Nothing else: no player interaction, no other mods, and the game is left completely alone after the call.
What happened
The game crashed 70 ticks later, at map tick 130.
What I expected
Either a one node body that the engine can go on updating, or set_body_nodes rejecting the list and raising a Lua error, the way other runtime API calls do when given something they cannot use. A Lua error would be much easier to track down than a crash in a later tick.
Version and build
Version: 2.1.17 (build 87315, linux64, steam), running headless with --benchmark. Base, elevated-rails, quality, and space-age, plus the attached repro mod, which has no data stage and 20 lines of control.lua.
Stack trace
Code: Select all
/include/c++/16.1.0/optional (126): __throw_bad_optional_access
src/Entity/Segments/SegmentedUnitController.cpp (506): getHeadPosition
src/Entity/Segments/SegmentedUnitAI.cpp (437): scanForNearbyEnemyToAttack
src/Entity/Segments/SegmentedUnitAI.cpp (393): maybeScanForDisturbanceOrAggravator
src/Entity/Segments/SegmentedUnitAI.cpp (193): computeNextState
src/Entity/Segments/SegmentedUnitAI.cpp (136): operator()<SegmentedUnitAI::Patrolling>
src/Entity/Segments/SegmentedUnitAI.cpp (135): update
src/Entity/Segments/SegmentedUnitController.cpp (143): update
src/Surface/Surface.cpp (2500): update
src/Map/Map.cpp (1338): updateEntities
This control.lua is also attached:
Code: Select all
script.on_nth_tick(60, function()
local surface = game.surfaces.nauvis
if not storage.unit then
storage.unit = surface.create_segmented_unit({
name = "small-demolisher",
force = "enemy",
position = { 0, 0 },
})
return
end
if storage.cut then return end
local unit = storage.unit
unit.set_body_nodes({ unit.get_body_nodes()[1] })
storage.cut = true
end)
Where it came from
I hit this while developing a mod that animates a segmented unit sinking into the ground by taking body nodes off it a few at a time. The last frame of that animation is a single node, which gave no indication that it was the problem: the crash arrived a second later, from inside the AI update, with nothing pointing back at the set_body_nodes call.