From f6ef39cb1698b4fce0e233418cfe6b05f1793494 Mon Sep 17 00:00:00 2001
From: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
Date: Thu, 5 Nov 2026 09:04:30 -0800
Subject: [PATCH] ai/default: Look the bodyguard up again after dai_unit_attack() acts

dai_unit_attack() read the attacker's bodyguard before the action and
dereferenced the pointer afterwards. However, the action could have
removed the bodyguard, if civil war wiped a unit standing beside the
attacker. 

Keep the bodyguard's id and look it up again after the action, the way
the attacker already is.

Signed-off-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me>
---
 ai/default/daitools.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/ai/default/daitools.c b/ai/default/daitools.c
index c8d95daf8a..70d7df7da5 100644
--- a/ai/default/daitools.c
+++ b/ai/default/daitools.c
@@ -820,6 +820,7 @@ bool dai_unit_attack(struct ai_type *ait, struct unit *punit,
   struct unit *ptrans;
   struct unit *bodyguard = aiguard_guard_of(ait, punit);
   int sanity = punit->id;
+  int guard_id = (bodyguard != nullptr) ? bodyguard->id : 0;
   bool alive;
   struct city *tcity;
   const struct civ_map *nmap = &(wld.map);
@@ -1075,6 +1076,11 @@ bool dai_unit_attack(struct ai_type *ait, struct unit *punit,
                    0, "", ACTION_UNIT_MOVE3);
   }
   alive = (game_unit_by_number(sanity) != nullptr);
+  /* The action can remove the bodyguard too: capturing a capital starts
+   * a civil war, and the stack resolution against the new player wipes
+   * units standing beside the attacker. Look the guard up again rather
+   * than trusting the pointer read before the action. */
+  bodyguard = (guard_id > 0) ? game_unit_by_number(guard_id) : nullptr;
 
   if (alive && same_pos(ptile, unit_tile(punit))
       && bodyguard != nullptr
-- 
2.55.0

