From 5675c3fd0bc2654893397baf5d0861fbb8a8f37a Mon Sep 17 00:00:00 2001 From: Matt Wise Date: Thu, 7 May 2026 18:39:14 -0600 Subject: [PATCH] Drop pull-aware fatigue when puller has died mid-tick MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a puller dies of TTL on the same tick a pull resolves, the move's body-weight fatigue could be stranded on the pulled creep instead of dying with the puller. Reproduction (screeps-ok catalog MOVE-PULL-012): a 2-MOVE puller pulls a 2-WORK harvester; on the puller's last tick, the puller is iterated first, `movement.execute` then the lifetime check calls `_die` and `delete roomObjects[puller._id]`. The pulled creep's later `movement.execute` enters `_add-fatigue` and the chain walk while(!!object._pulled && !!roomObjects[object._pulled]) { object = roomObjects[object._pulled]; } silently terminates because the puller is gone, then applies the move's fatigue to the pulled creep itself. With no MOVE parts to clear it the harvester is stuck forever (fatigue=4). Vanilla happens to produce the intended outcome when the pulled creep is iterated first, so the visible behavior was order-dependent. Distinguish "no `_pulled`" (genuine chain head) from "`_pulled` but target missing" (dead puller). In the dead-link case return without landing fatigue anywhere — the move's pull-aware fatigue is buried with the puller it should have routed to. The "rest first" branch is preserved verbatim, so the negative-dFatigue / own-MOVE-clear path is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/processor/intents/creeps/_add-fatigue.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/processor/intents/creeps/_add-fatigue.js b/src/processor/intents/creeps/_add-fatigue.js index 0a7959ed..23f47296 100644 --- a/src/processor/intents/creeps/_add-fatigue.js +++ b/src/processor/intents/creeps/_add-fatigue.js @@ -21,7 +21,11 @@ module.exports = function(object, dFatigue, {roomObjects, bulk}) { } } - while(!!object._pulled && !!roomObjects[object._pulled]) { + while(!!object._pulled) { + if(!roomObjects[object._pulled]) { + // Puller died this tick; pull-aware fatigue has no live destination, drop it. + return; + } object = roomObjects[object._pulled]; }