diff --git a/Assets/_Game/Scripts/Enemies/AIBrain.meta b/Assets/_Game/Scripts/Enemies/AIBrain.meta
new file mode 100644
index 00000000..e8d13898
--- /dev/null
+++ b/Assets/_Game/Scripts/Enemies/AIBrain.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4dbaeb7890d1b7140b255b738fdc3ce0
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs
new file mode 100644
index 00000000..d6020fa6
--- /dev/null
+++ b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs
@@ -0,0 +1,105 @@
+using UnityEngine;
+using BaseGames.AI;
+
+namespace BaseGames.Enemies
+{
+ ///
+ /// 把 EnemyBase 的子系统包装成 BrainGraph 的能力接口(每敌人一实例)。
+ /// 状态逻辑只依赖这些接口,不直接引用具体子系统。
+ ///
+ public sealed class EnemyBrainContext : IAiContext, ISensor, IMover, ICombatant, IActorVitals
+ {
+ readonly EnemyBase _enemy;
+ readonly Blackboard _blackboard = new Blackboard();
+ Vector2 _lastKnown;
+ float _lostTimer;
+
+ public EnemyBrainContext(EnemyBase enemy) { _enemy = enemy; }
+
+ // ---- IAiContext ----
+ public ISensor Sensor => this;
+ public IMover Mover => this;
+ public ICombatant Combat => this;
+ public IActorVitals Vitals => this;
+ public Blackboard Blackboard => _blackboard;
+
+ /// 每帧由 EnemyAiBrain 在 Tick 之前调用,维护最后已知位置与丢失计时。
+ public void Refresh(float dt)
+ {
+ if (_enemy.IsPlayerVisible() && _enemy.PlayerTransform != null)
+ {
+ _lastKnown = _enemy.PlayerTransform.position;
+ _lostTimer = 0f;
+ }
+ else
+ {
+ _lostTimer += dt;
+ }
+ }
+
+ /// 对象池复用时清空临时态。
+ public void ResetScratch()
+ {
+ _blackboard.Clear();
+ _lastKnown = _enemy.transform.position;
+ _lostTimer = 0f;
+ }
+
+ // ---- ISensor ----
+ public bool SeesPlayer() => _enemy.IsPlayerVisible();
+ public bool InRange(float range) => _enemy.IsPlayerInRange(range);
+ public bool LostFor(float seconds) => !_enemy.IsPlayerVisible() && _lostTimer >= seconds;
+ public Vector2 LastKnown => _lastKnown;
+
+ // ---- IMover ----
+ public void MoveTo(Vector2 target) => _enemy.MoveTo(target);
+ public void FacePlayer() => _enemy.FacePlayer();
+ public void Stop() => _enemy.StopMovement();
+ public void WalkRandom()
+ {
+ var nav = _enemy.Nav;
+ if (nav != null && !nav.IsMoving) nav.WalkToRandom();
+ }
+ public void LookAround() => _enemy.BeginLookAround();
+
+ // ---- ICombatant ----
+ public bool UseAbility(string abilityId)
+ {
+ var a = _enemy.Abilities?.Get(abilityId);
+ return a != null && a.Execute();
+ }
+ public bool IsAbilityRunning(string abilityId = null)
+ {
+ var reg = _enemy.Abilities;
+ if (reg == null) return false;
+ if (abilityId != null)
+ {
+ var a = reg.Get(abilityId);
+ return a != null && a.IsRunning;
+ }
+ var all = reg.All;
+ for (int i = 0; i < all.Count; i++)
+ if (all[i].IsRunning) return true;
+ return false;
+ }
+ public bool NoAbilityRunning => !IsAbilityRunning();
+ public bool CanUseAbility(string abilityId)
+ {
+ var a = _enemy.Abilities?.Get(abilityId);
+ return a != null && a.CanUse;
+ }
+
+ // ---- IActorVitals ----
+ public bool IsAlive => _enemy.IsAlive;
+ public bool IsControllable => _enemy.CurrentState == EnemyStateType.Controlled;
+ public float HpPercent
+ {
+ get
+ {
+ var stats = _enemy.Stats;
+ return stats != null && stats.MaxHP > 0 ? (float)stats.CurrentHP / stats.MaxHP : 0f;
+ }
+ }
+ public bool HpBelow(float ratio) => HpPercent < ratio;
+ }
+}
diff --git a/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs.meta b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs.meta
new file mode 100644
index 00000000..9934fd4b
--- /dev/null
+++ b/Assets/_Game/Scripts/Enemies/AIBrain/EnemyBrainContext.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9459e1a1d93189547ab3354d181bf4ff
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/_Game/Scripts/Enemies/BaseGames.Enemies.asmdef b/Assets/_Game/Scripts/Enemies/BaseGames.Enemies.asmdef
index 38eff23e..a6bc0a21 100644
--- a/Assets/_Game/Scripts/Enemies/BaseGames.Enemies.asmdef
+++ b/Assets/_Game/Scripts/Enemies/BaseGames.Enemies.asmdef
@@ -3,6 +3,7 @@
"rootNamespace": "BaseGames.Enemies",
"references": [
"BaseGames.Core",
+ "BaseGames.AI",
"BaseGames.Core.Events",
"BaseGames.Combat",
"BaseGames.Feedback",