fix(ai): 应用 Task 1+2 代码审查意见
ResourceFull 未挂 BossResource 时改为显式抛——原先静默返回 false, 是 CLAUDE.md 第 6 条禁止的下游兜底,且与紧邻的 Boss facet 行为不一致。 FakeBossControl.DistanceToAnchor 不再忽略 index。测试改用 TearDown 清理 (本项目关闭了 Domain/Scene Reload,失败断言会把物体永久留在打开的场景里)。 BeginPhaseTransition 去掉死的默认参数;接口补充重入契约说明。 顺带去重:FakeAiContext 实现 IEnemyActor,删除两个测试内的本地 Ctx 副本 ——一个接口成员改动要同步三份是复发性成本。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,26 +7,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
{
|
||||
public class PerceptionSkeletonTests
|
||||
{
|
||||
sealed class Ctx : IAiContext, IEnemyActor
|
||||
{
|
||||
public FakeSensor S = new FakeSensor();
|
||||
public FakeLocomotion L = new FakeLocomotion();
|
||||
public FakeCombat C = new FakeCombat();
|
||||
public FakeVitals V = new FakeVitals();
|
||||
public Blackboard BB = new Blackboard();
|
||||
public FakeBossControl B = new FakeBossControl();
|
||||
public bool HasAlert = true;
|
||||
|
||||
public ISensor Sensor => S;
|
||||
public IEnemyLocomotion Locomotion => L;
|
||||
public ICombatant Combat => C;
|
||||
public IActorVitals Vitals => V;
|
||||
public Blackboard Blackboard => BB;
|
||||
public IBossControl Boss => B;
|
||||
public bool HasAlertState => HasAlert;
|
||||
}
|
||||
|
||||
static AiRuntime Build(Ctx ctx, IUnawareModule unaware = null, IEngagementModule engagement = null)
|
||||
static AiRuntime Build(FakeAiContext ctx, IUnawareModule unaware = null, IEngagementModule engagement = null)
|
||||
{
|
||||
var b = new BrainBuilder();
|
||||
PerceptionSkeleton.Add(b,
|
||||
@@ -40,7 +21,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Unaware_ToEngagement_WhenInChaseZone()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx);
|
||||
ctx.S.Chase = true;
|
||||
rt.Tick(0.1f);
|
||||
@@ -50,7 +31,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Unaware_ToAlert_WhenInVision_AndHasAlert()
|
||||
{
|
||||
var ctx = new Ctx { HasAlert = true };
|
||||
var ctx = new FakeAiContext { HasAlert = true };
|
||||
var rt = Build(ctx);
|
||||
ctx.S.Vision = true;
|
||||
rt.Tick(0.1f);
|
||||
@@ -60,7 +41,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Unaware_StaysUnaware_WhenInVision_ButNoAlertState()
|
||||
{
|
||||
var ctx = new Ctx { HasAlert = false };
|
||||
var ctx = new FakeAiContext { HasAlert = false };
|
||||
var rt = Build(ctx);
|
||||
ctx.S.Vision = true;
|
||||
rt.Tick(0.1f);
|
||||
@@ -70,7 +51,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void ChaseZone_BeatsAlert_WhenBothActive()
|
||||
{
|
||||
var ctx = new Ctx { HasAlert = true };
|
||||
var ctx = new FakeAiContext { HasAlert = true };
|
||||
var rt = Build(ctx);
|
||||
ctx.S.Chase = true; ctx.S.Vision = true;
|
||||
rt.Tick(0.1f);
|
||||
@@ -81,7 +62,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void UpgradeEdge_BeatsUnawareInternalDwellEdge()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx, new AlternatingIdlePatrol(idleDwell: 1f, patrolDwell: 1f));
|
||||
Assert.AreEqual(AlternatingIdlePatrol.Idle, rt.CurrentStateName);
|
||||
ctx.S.Chase = true;
|
||||
@@ -94,7 +75,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Alert_ToEngagement_WhenEnteringChaseZone()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx);
|
||||
ctx.S.Vision = true; rt.Tick(0.1f);
|
||||
Assert.AreEqual(PerceptionSkeleton.Alert, rt.CurrentStateName);
|
||||
@@ -105,7 +86,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Alert_ToRest_WhenLeavingVision()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx);
|
||||
ctx.S.Vision = true; rt.Tick(0.1f);
|
||||
ctx.S.Vision = false; rt.Tick(0.1f);
|
||||
@@ -115,7 +96,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Alert_FacesLastKnown()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
ctx.S.Last = new UnityEngine.Vector2(7f, 2f);
|
||||
var rt = Build(ctx);
|
||||
ctx.S.Vision = true; rt.Tick(0.1f);
|
||||
@@ -127,7 +108,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void AfterEngagement_GoesToRest_NeverBackToAlert()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx);
|
||||
ctx.S.Chase = true; rt.Tick(0.1f);
|
||||
Assert.AreEqual(RushEngagement.Rush, rt.CurrentStateName);
|
||||
@@ -140,7 +121,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Died_TransitionsToDeath_FromAnyState()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx);
|
||||
ctx.S.Chase = true; rt.Tick(0.1f);
|
||||
Assert.AreEqual(RushEngagement.Rush, rt.CurrentStateName);
|
||||
@@ -154,7 +135,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Died_WorksEvenWhenNotControllable()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx);
|
||||
ctx.V.Controllable = false; // 已是 Dead 物理态
|
||||
rt.Send(AiSignal.Died);
|
||||
@@ -165,7 +146,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Death_IsTerminal_NoConditionEdgesEvaluatedAfterwards()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx);
|
||||
rt.Send(AiSignal.Died);
|
||||
rt.Tick(0.1f);
|
||||
@@ -224,7 +205,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
[Test]
|
||||
public void Skeleton_WithApproachAttack_EscalatesAndDisengages()
|
||||
{
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx, new SinglePost(LocomotionMode.Patrol), new ApproachAttackEngagement());
|
||||
Assert.AreEqual(SinglePost.Post, rt.CurrentStateName);
|
||||
|
||||
@@ -246,7 +227,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
public void Skeleton_WithDisguiseThenPatrol_DisengagesToPatrol_NotDisguise()
|
||||
{
|
||||
// 唯一 Entry != Rest 的未发现层:出生伪装静止,脱战只回巡逻,伪装态从此不可达。
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var rt = Build(ctx, new DisguiseThenPatrol());
|
||||
Assert.AreEqual(DisguiseThenPatrol.Disguise, rt.CurrentStateName);
|
||||
|
||||
@@ -267,7 +248,7 @@ namespace BaseGames.Tests.EditMode.AI
|
||||
public void EntryCanBeOverridden_ForEnemiesWithPrefixStates()
|
||||
{
|
||||
// 骨架注释承诺的出路:掉落链 / 出场链这类前置态,调 Add 之后再覆盖 Entry。
|
||||
var ctx = new Ctx();
|
||||
var ctx = new FakeAiContext();
|
||||
var b = new BrainBuilder();
|
||||
PerceptionSkeleton.Add(b, new SinglePost(LocomotionMode.Patrol),
|
||||
new RushEngagement("rush", RushExit.OnLostTarget));
|
||||
|
||||
Reference in New Issue
Block a user