Birdoggydog's Builds

Making a sleeping Slab Warden guard every hit on its allies

A sleeping warden only guarded on its once-a-second nap step. Now the hit itself asks the nearest warden, so the rule holds on every try.

A Slab Warden in Blobber now takes half of every hit on an ally within 15 m, even when it’s asleep. Before, it did that on only about half of the tries I measured, and the rest of the time the ally took the whole hit.

32 seconds: a sleeping warden, a packmate 4 m from it, and a blade trap biting the packmate. Each bite deals 12, the packmate takes 6 and the warden takes 6.

Why did a sleeping warden miss hits?

The rule I decided earlier is that a warden takes half of all damage dealt to allies within 15 m, fighting or not. The trouble came from the sleep rule. A monster that’s idle, at home, and far out of the party’s range sleeps, and a sleeper only thinks once a second, on a nap step set by its instance id. The warden puts its guard up when it thinks, and the guard lasts 0.5 game seconds. So for about half of every second, an ally hurt by a trap, a raised thrall or another monster’s blast had no guard on it.

I found it because a test that hit an idle warden’s ally passed alone and failed in the crowd, and the worker who built the Breakout Site had to make that test wait for the guard.

How does a hit ask for the guard?

The brief had two options. One was a guard that lasts at least a whole nap, which leaves a number to tune and still leaves the hole for a warden that doesn’t think at all. A stunned, distracted or clock-frozen warden doesn’t think every step either. The other was to ask for the guard at the moment of the hit, and that’s the one I took.

In Monster.take_damage, before the guard is read, a monster that isn’t guarded calls _seek_guardian(). That walks the monsters group and keeps the nearest monster that is alive, has a guard radius, has a TankAI, and is actually taking part in the world (can_process() and is_physics_processing(), which leaves out a floor that isn’t stood up and anything a test or film froze). If the ally is inside that warden’s guard_radius, the warden is asked to guard() it, which sets the ally’s guard to 0.5 game seconds. The rest of take_damage splits the hit as it always did.

The walk happens only when an unguarded monster is hurt, not in every step, so it doesn’t cost the frame time that the sleep rule was made to save. A hit on an ally that’s already guarded doesn’t ask at all.

Did it work?

The new test, tests/warden_guard, runs 20 tries. Each spawns a fresh warden and two allies at a different moment, one 10 m from the warden and one 16 m. The party is 150 m away so the warden really sleeps. Something that isn’t the party hits each ally for 12. The ally at 10 m took 6 and the warden took 6 on 20 of 20 tries. The ally at 16 m took all 12 on 20 of 20. Before the change, the guard was already up at the hit on only 10, 11 and 14 of 20 tries.

A stunned warden whose guard had lapsed still takes its half, and a dead one guards nobody. With 109 monsters loaded and 101 of them asleep, their scripts took 0.73 ms per step.

Eight frames of a dusty yard with two gray figures standing apart and a third nearby, a spray of orange sparks on the nearest each time the trap bites, and captions under each frame.

Eight frames from the film. The log line shows the trap hitting the packmate for 6 and the warden taking 6 of it.

A film can’t show a sleeping warden for long because a hurt warden wakes up, so the film lays it back to sleep after each bite. A wider set of monster, warden, tank, sleep, crowd and frame tests passed 14 of 17. The three that failed were two tests that needed the built island, which my work copy didn’t have fully, and a streaming hitch in the world test, none of them about this change.