using BardFrontline.Core; var suite = new Suite(); var engine = new Rotation(); Actor Self(uint hp = 60000, params uint[] states) => new(1, "Self", hp, 60000, 0, false, true, false, 0, [.. states]); Actor Enemy(ulong id = 10, uint hp = 30000, float distance = 20, params uint[] states) => new(id, "Enemy", hp, 60000, distance, true, true, false, 0, [.. states]); CombatSnapshot Snap(Actor? player = null, params Actor[] enemies) => new(player ?? Self(), 10000, 10000, true, false, 10, false, enemies, [], 0); ActionDecision? Decide(CombatSnapshot s, Fake f, Settings? c = null, bool emergency = false) => engine.Decide(s, c ?? new(), f, emergency); void IsSkill(ActionDecision? d, Skill skill) => Suite.Equal(skill, d?.Skill); void None(ActionDecision? d) => Suite.True(d == null, $"Expected no action, got {d}"); suite.Run("Default configuration and invalid persisted values", () => { var c = new Settings(); Suite.True(c.AutoAttack && !c.IgnoreEnemyGuard && !c.HelpParty); Suite.True(Actions.All.All(c.Enabled)); c.GuardHpPercent = float.NaN; c.ElixirPercent = float.PositiveInfinity; c.RecuperateMissingHp = -10; c.TargetOrder = (TargetOrder)99; c.Normalize(); Suite.Equal(15f, c.GuardHpPercent); Suite.Equal(1, c.RecuperateMissingHp); Suite.Equal(TargetOrder.LowestHp, c.TargetOrder); }); suite.Run("Missing March prioritizes Apex over charged Harmonic", () => IsSkill(Decide(Snap(null, Enemy()), new(Skill.ApexArrow, Skill.HarmonicArrow, Skill.PowerfulShot)), Skill.ApexArrow)); suite.Run("March present prioritizes Harmonic", () => IsSkill(Decide(Snap(Self(60000, Statuses.March), Enemy()), new(Skill.ApexArrow, Skill.HarmonicArrow)), Skill.HarmonicArrow)); suite.Run("Harmonic is usable at each charge count, zero falls through", () => { foreach (var count in new[] { 1, 2, 3, 4 }) { var fake = new Fake(Skill.HarmonicArrow, Skill.PowerfulShot); fake.Charges[Skill.HarmonicArrow] = count; IsSkill(Decide(Snap(null, Enemy()), fake), Skill.HarmonicArrow); } var empty = new Fake(Skill.HarmonicArrow, Skill.PowerfulShot); empty.Charges[Skill.HarmonicArrow] = 0; IsSkill(Decide(Snap(null, Enemy()), empty), Skill.PowerfulShot); }); suite.Run("Repertoire uses Pitch and prevents wasteful generators", () => { IsSkill(Decide(Snap(Self(60000, Statuses.Repertoire), Enemy()), new(Skill.PitchPerfect, Skill.PowerfulShot)), Skill.PitchPerfect); None(Decide(Snap(Self(60000, Statuses.Repertoire), Enemy()), new(Skill.RepellingShot, Skill.SilentNocturne, Skill.PowerfulShot))); }); suite.Run("No Repertoire does not attempt Pitch", () => IsSkill(Decide(Snap(null, Enemy()), new(Skill.PitchPerfect, Skill.PowerfulShot)), Skill.PowerfulShot)); suite.Run("Blast ready consumes Blast and blocks Apex", () => IsSkill(Decide(Snap(Self(60000, Statuses.BlastReady), Enemy()), new(Skill.ApexArrow, Skill.BlastArrow)), Skill.BlastArrow)); suite.Run("No Blast ready falls through to Apex", () => IsSkill(Decide(Snap(Self(60000, Statuses.March), Enemy()), new(Skill.BlastArrow, Skill.ApexArrow)), Skill.ApexArrow)); suite.Run("Offensive abilities preserve RSR order", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(distance: 8)); IsSkill(Decide(s, new(Skill.RepellingShot, Skill.SilentNocturne, Skill.EagleEyeShot, Skill.EncoreOfLight)), Skill.RepellingShot); IsSkill(Decide(s, new(Skill.SilentNocturne, Skill.EagleEyeShot, Skill.EncoreOfLight)), Skill.SilentNocturne); IsSkill(Decide(s, new(Skill.EagleEyeShot, Skill.EncoreOfLight)), Skill.EagleEyeShot); IsSkill(Decide(s with { Player = Self(60000, Statuses.EncoreReady) }, new(Skill.EncoreOfLight)), Skill.EncoreOfLight); }); suite.Run("Repelling out of range falls through to Nocturne", () => IsSkill(Decide(Snap(null, Enemy(distance: 18)), new(Skill.RepellingShot, Skill.SilentNocturne)), Skill.SilentNocturne)); suite.Run("Ready weapons prevent offensive ability starvation of GCD", () => IsSkill(Decide(Snap(null, Enemy(distance: 8)), new(Skill.PowerfulShot, Skill.RepellingShot)), Skill.PowerfulShot)); suite.Run("Unavailable high priority skills fall through", () => IsSkill(Decide(Snap(null, Enemy()), new(Skill.PowerfulShot)), Skill.PowerfulShot)); suite.Run("Disabled offensive skills are never selected", () => { var c = new Settings(); c.DisabledSkills.Add(Skill.HarmonicArrow); IsSkill(Decide(Snap(null, Enemy()), new(Skill.HarmonicArrow, Skill.PowerfulShot), c), Skill.PowerfulShot); }); suite.Run("Limit Break requires full gauge, combat and target", () => { var f = new Fake(Skill.FinalFantasia); var s = Snap(null, Enemy()) with { LimitFull = true }; IsSkill(Decide(s, f), Skill.FinalFantasia); None(Decide(s with { LimitFull = false }, f)); None(Decide(s with { InCombat = false }, f)); None(Decide(s with { Enemies = [] }, f)); None(Decide(s with { Player = Self(60000, Statuses.Fantasia) }, f)); None(Decide(s with { Player = Self(60000, Statuses.EncoreReady) }, f)); }); suite.Run("Encore requires its status and uses hostile target", () => { var f = new Fake(Skill.EncoreOfLight); None(Decide(Snap(null, Enemy()), f)); var d = Decide(Snap(Self(60000, Statuses.EncoreReady), Enemy(33)), f); IsSkill(d, Skill.EncoreOfLight); Suite.Equal(33ul, d!.TargetId); }); suite.Run("Only equipped role actions are attempted", () => { var f = new Fake(Skill.Bravery, Skill.Dervish, Skill.EagleEyeShot); None(Decide(Snap(null, Enemy()), f)); var bravery = Decide(Snap(Self(60000, Statuses.RoleBravery), Enemy()), f); IsSkill(bravery, Skill.Bravery); Suite.Equal(1ul, bravery!.TargetId); IsSkill(Decide(Snap(Self(60000, Statuses.RoleDervish), Enemy()), f), Skill.Dervish); IsSkill(Decide(Snap(Self(60000, Statuses.RoleEagle), Enemy()), f), Skill.EagleEyeShot); None(Decide(Snap(Self(60000, Statuses.RoleBravery, Statuses.Bravery), Enemy()), f)); None(Decide(Snap(Self(60000, Statuses.RoleDervish, Statuses.Dervish), Enemy()), f)); }); suite.Run("Own Guard stops all actions including healing and purify", () => None(Decide(Snap(Self(1000, Statuses.Guard, 1343), Enemy()), new(Actions.All)))); suite.Run("Pending Guard stops actions before buff arrives", () => None(Decide(Snap(Self(1000, 1343), Enemy()) with { GuardPending = true }, new(Actions.All)))); suite.Run("Self cleanse priority is Paean then Purify then Guard", () => { var s = Snap(Self(1000, 1343), Enemy()); IsSkill(Decide(s, new(Skill.WardensPaean, Skill.Purify, Skill.Guard, Skill.Recuperate)), Skill.WardensPaean); IsSkill(Decide(s, new(Skill.Purify, Skill.Guard, Skill.Recuperate)), Skill.Purify); IsSkill(Decide(s, new(Skill.Guard, Skill.Recuperate)), Skill.Guard); }); suite.Run("All six cleanse statuses work, disabled Purify trigger is respected", () => { foreach (var id in Statuses.Controls) { var s = Snap(Self(60000, id)); IsSkill(Decide(s, new(Skill.Purify)), Skill.Purify); var c = new Settings(); c.DisabledPurifyStatuses.Add(id); None(Decide(s, new(Skill.Purify), c)); IsSkill(Decide(s, new(Skill.WardensPaean), c), Skill.WardensPaean); } }); suite.Run("Paean acknowledgement delay does not immediately spend Purify", () => None(Decide(Snap(Self(60000, 1343)) with { CleansePending = true }, new(Skill.Purify)))); suite.Run("Successful cleanse stops retries after debuff disappears", () => None(Decide(Snap(), new(Skill.WardensPaean, Skill.Purify)))); suite.Run("Guard threshold and fallback when unavailable", () => { IsSkill(Decide(Snap(Self(9000)), new(Skill.Guard, Skill.Recuperate)), Skill.Guard); IsSkill(Decide(Snap(Self(9001)), new(Skill.Guard, Skill.Recuperate)), Skill.Recuperate); IsSkill(Decide(Snap(Self(1000)), new(Skill.Recuperate)), Skill.Recuperate); None(Decide(Snap(Self(1000)) with { InCombat = false }, new(Skill.Guard))); }); suite.Run("Recuperate works without an enemy, respects deficit and available MP", () => { IsSkill(Decide(Snap(Self(45000)), new(Skill.Recuperate)), Skill.Recuperate); None(Decide(Snap(Self(45001)), new(Skill.Recuperate))); var f = new Fake(Skill.Recuperate) { Mp = 1999 }; None(Decide(Snap(Self(30000)) with { Mp = 1999 }, f)); }); suite.Run("Five second revive protection includes boundary", () => { var s = Snap(Self(1000)) with { AliveSeconds = 4.999 }; None(Decide(s, new(Skill.Guard, Skill.Recuperate, Skill.Elixir))); IsSkill(Decide(s with { AliveSeconds = 5 }, new(Skill.Guard)), Skill.Guard); IsSkill(Decide(s with { Player = Self(1000, 1343) }, new(Skill.Purify)), Skill.Purify); }); suite.Run("Party Paean is opt-in and lowest HP ratio first", () => { var ally = Enemy(20, 10000, 10, 1343) with { Enemy = false }; var other = Enemy(21, 20000, 5, 1345) with { Enemy = false }; var s = Snap() with { Party = [other, ally] }; None(Decide(s, new(Skill.WardensPaean))); var d = Decide(s, new(Skill.WardensPaean), new() { HelpParty = true }); Suite.Equal(20ul, d!.TargetId); IsSkill(Decide(s with { Player = Self(10000) }, new(Skill.WardensPaean, Skill.Recuperate), new() { HelpParty = true }), Skill.Recuperate); }); suite.Run("Elixir stationary resource thresholds and no enemy requirement", () => { var f = new Fake(Skill.Elixir); IsSkill(Decide(Snap(Self(10000)), f), Skill.Elixir); IsSkill(Decide(Snap() with { Mp = 1000 }, f), Skill.Elixir); None(Decide(Snap(), f)); None(Decide(Snap(Self(10000)) with { Moving = true }, f)); }); suite.Run("Guard, invincible, untargetable and occluded enemies remain elixir threats", () => { foreach (var enemy in new[] { Enemy(distance: 30), Enemy(distance: 30, states: [Statuses.Guard]), Enemy(distance: 30, states: [1302]), Enemy(distance: 30) with { Targetable = false } }) None(Decide(Snap(Self(10000), enemy), new(Skill.Elixir) { Occluded = [10] })); IsSkill(Decide(Snap(Self(10000), Enemy(distance: 31)), new(Skill.Elixir)), Skill.Elixir); IsSkill(Decide(Snap(Self(10000), Enemy() with { Dead = true }), new(Skill.Elixir)), Skill.Elixir); }); suite.Run("Sprint requires movement, no attack target, and no existing buff", () => { var s = Snap() with { Moving = true }; IsSkill(Decide(s, new(Skill.Sprint)), Skill.Sprint); None(Decide(s with { Moving = false }, new(Skill.Sprint))); None(Decide(s with { Enemies = [Enemy()] }, new(Skill.Sprint))); None(Decide(s with { Player = Self(60000, Statuses.Sprint) }, new(Skill.Sprint))); }); suite.Run("Automatic attack off preserves common survival skills", () => { var c = new Settings { AutoAttack = false }; None(Decide(Snap(null, Enemy()), new(Skill.PowerfulShot), c)); IsSkill(Decide(Snap(Self(30000), Enemy()), new(Skill.PowerfulShot, Skill.Recuperate), c), Skill.Recuperate); }); suite.Run("Emergency-only cast probe never suggests offense or routine elixir", () => { None(Decide(Snap(null, Enemy()), new(Actions.All), emergency: true)); None(Decide(Snap() with { Mp = 0 }, new(Skill.Elixir), emergency: true)); Suite.True(Decide(Snap(Self(30000)), new(Skill.Recuperate), emergency: true)!.Emergency); }); suite.Run("Lowest HP across both hostile groups, not HP ratio or shield", () => { var a = Enemy(11, 10000) with { MaxHp = 20000 }; var b = Enemy(12, 20000) with { MaxHp = 100000 }; var s = Snap(null, b, a); Suite.Equal(11ul, Decide(s, new(Skill.PowerfulShot))!.TargetId); Suite.Equal(12ul, Decide(s, new(Skill.PowerfulShot), new() { TargetOrder = TargetOrder.LowestHpRatio })!.TargetId); }); suite.Run("HP ties use distance then current target then stable ID", () => { var s = Snap(null, Enemy(12, distance: 10), Enemy(11, distance: 10), Enemy(10, distance: 15)); Suite.Equal(11ul, Decide(s, new(Skill.PowerfulShot))!.TargetId); Suite.Equal(12ul, Decide(s with { CurrentTarget = 12 }, new(Skill.PowerfulShot))!.TargetId); }); suite.Run("Nearest and mark ordering are independently selectable", () => { var s = Snap(null, Enemy(11, 100, 20), Enemy(12, 50000, 10) with { AttackMarker = 2 }, Enemy(13, 60000, 15) with { AttackMarker = 1 }); Suite.Equal(12ul, Decide(s, new(Skill.PowerfulShot), new() { TargetOrder = TargetOrder.Nearest })!.TargetId); Suite.Equal(13ul, Decide(s, new(Skill.PowerfulShot), new() { TargetOrder = TargetOrder.AttackMarker })!.TargetId); }); suite.Run("Invalid candidates do not block valid next candidate", () => { var good = Enemy(99, 40000); var s = Snap(null, Enemy(10, 1) with { Dead = true }, Enemy(11, 2) with { Targetable = false }, Enemy(12, 3) with { Enemy = false }, Enemy(13, 4, 80), Enemy(14, 5), good); Suite.Equal(99ul, Decide(s, new(Skill.PowerfulShot) { Occluded = [14] })!.TargetId); }); suite.Run("Guard off excludes normal attacks, on allows them", () => { var s = Snap(null, Enemy(states: [Statuses.Guard])); None(Decide(s, new(Skill.PowerfulShot))); IsSkill(Decide(s, new(Skill.PowerfulShot), new() { IgnoreEnemyGuard = true }), Skill.PowerfulShot); }); suite.Run("Eagle Eye bypasses Guard but not invincibility", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(states: [Statuses.Guard])); IsSkill(Decide(s, new(Skill.EagleEyeShot)), Skill.EagleEyeShot); None(Decide(s with { Enemies = [Enemy(states: [1302, Statuses.Guard])] }, new(Skill.EagleEyeShot), new() { IgnoreEnemyGuard = true })); }); suite.Run("All independent skill switches prevent corresponding request", () => { var c = new Settings { DisabledSkills = Actions.All.ToHashSet(), HelpParty = true }; None(Decide(Snap(Self(1000, 1343, Statuses.RoleEagle, Statuses.EncoreReady), Enemy()), new(Actions.All), c)); }); suite.Run("Dead player cannot receive any decision", () => None(Decide(Snap(Self(0), Enemy()), new(Actions.All)))); suite.Run("Gate waits for new sequence acknowledgement, not stale ack", () => { var g = new RequestGate(); g.Submitted(new(Skill.PowerfulShot, 10, "test"), 0, 10, 11); Suite.True(g.Update(.5, 11, 10, false, false)); Suite.True(!g.Update(.6, 11, 11, false, false)); }); suite.Run("Gate handles delayed sequence increment", () => { var g = new RequestGate(); g.Submitted(new(Skill.PowerfulShot, 10, "test"), 0, 10, 10); Suite.True(g.Update(.5, 10, 10, false, false)); Suite.True(!g.Update(.6, 11, 11, false, false)); }); suite.Run("Gate sequence rollover", () => { var g = new RequestGate(); g.Submitted(new(Skill.PowerfulShot, 10, "test"), 0, 65535, 0); Suite.True(g.Update(.2, 0, 65535, false, false)); Suite.True(!g.Update(.3, 0, 0, false, false)); }); suite.Run("Guard and cleanse guard against delayed status replication", () => { foreach (var skill in new[] { Skill.Guard, Skill.Purify, Skill.WardensPaean }) { var g = new RequestGate(); g.Submitted(new(skill, 1, "test"), 0, 1, 2); Suite.True(g.Update(.1, 2, 2, false, false)); Suite.True(!g.Update(.5, 2, 2, false, false)); } }); suite.Run("Actual Guard or changed debuff confirms pending status action", () => { var g = new RequestGate(); g.Submitted(new(Skill.Guard, 1, "test"), 0, 1, 2); Suite.True(!g.Update(.1, 2, 1, true, false)); g.Submitted(new(Skill.WardensPaean, 1, "test"), 0, 1, 2); Suite.True(!g.Update(.1, 2, 1, false, true)); }); suite.Run("Timeout causes bounded per-skill per-target backoff", () => { var g = new RequestGate(); g.Submitted(new(Skill.PowerfulShot, 10, "test"), 0, 1, 2); Suite.True(!g.Update(2, 2, 1, false, false)); Suite.True(g.IsBlocked(Skill.PowerfulShot, 10, 2.1)); Suite.True(!g.IsBlocked(Skill.PowerfulShot, 11, 2.1)); Suite.True(!g.IsBlocked(Skill.HarmonicArrow, 10, 2.1)); Suite.True(!g.IsBlocked(Skill.PowerfulShot, 10, 2.5)); }); suite.Run("Reset removes pending actions and stale backoff", () => { var g = new RequestGate(); var d = new ActionDecision(Skill.Guard, 1, "test"); g.Fail(d, 1, "failure"); g.Submitted(d, 1, 1, 2); g.Reset(); Suite.True(g.Pending == null && !g.IsBlocked(Skill.Guard, 1, 1.1)); Suite.True(!g.Update(1.1, 2, 1, false, false)); }); suite.Run("Finisher Eagle preempts normal GCD, buffs and LB", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(hp: 9000)) with { LimitFull = true }; var d = Decide(s, new(Skill.EagleEyeShot, Skill.ApexArrow, Skill.FinalFantasia)); IsSkill(d, Skill.EagleEyeShot); Suite.True(d!.Reason.Contains("收割")); }); suite.Run("Finisher changes target to lowest effective HP in Eagle range", () => { var shielded = Enemy(10, 1000, 10) with { ShieldHp = 20000 }; var distant = Enemy(11, 8000, 38, Statuses.Guard); var near = Enemy(12, 9000, 10); var s = Snap(Self(60000, Statuses.RoleEagle), shielded, near, distant) with { CurrentTarget = 10 }; Suite.Equal(11ul, Decide(s, new(Skill.EagleEyeShot, Skill.PowerfulShot))!.TargetId); }); suite.Run("Finisher eligibility includes shield, exact threshold, and invulnerability", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(hp: 8000) with { ShieldHp = 2000 }); var c = new Settings { EagleStrategy = EagleStrategy.FinisherOnly }; IsSkill(Decide(s, new(Skill.EagleEyeShot), c), Skill.EagleEyeShot); None(Decide(s with { Enemies = [Enemy(hp: 8000) with { ShieldHp = 2001 }] }, new(Skill.EagleEyeShot), c)); None(Decide(s with { Enemies = [Enemy(hp: 8000) with { Invulnerable = true }] }, new(Skill.EagleEyeShot), c)); }); suite.Run("First strategy uses Eagle normally without finisher; reserve strategy holds it", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy()); IsSkill(Decide(s, new(Skill.EagleEyeShot)), Skill.EagleEyeShot); None(Decide(s, new(Skill.EagleEyeShot), new() { EagleStrategy = EagleStrategy.FinisherOnly })); IsSkill(Decide(s, new(Skill.EagleEyeShot, Skill.PowerfulShot)), Skill.PowerfulShot); }); suite.Run("Survival and shared native availability still take precedence over finishing", () => { var s = Snap(Self(1000, Statuses.RoleEagle, 1343), Enemy(hp: 1000)); IsSkill(Decide(s, new(Skill.EagleEyeShot, Skill.Purify)), Skill.Purify); IsSkill(Decide(s, new(Skill.EagleEyeShot, Skill.Guard)), Skill.Guard); IsSkill(Decide(s, new(Skill.EagleEyeShot, Skill.Recuperate)), Skill.Recuperate); IsSkill(Decide(s with { Player = Self(60000, Statuses.RoleEagle) }, new(Skill.PowerfulShot)), Skill.PowerfulShot); None(Decide(s, new(Skill.EagleEyeShot), emergency: true)); }); suite.Run("Finisher setting is adjustable and does not change ordinary HP sorting", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(10, 11000), Enemy(11, 12000)); var c = new Settings { EagleFinisherHp = 11000, EagleStrategy = EagleStrategy.FinisherOnly }; IsSkill(Decide(s, new(Skill.EagleEyeShot, Skill.PowerfulShot), c), Skill.EagleEyeShot); var shielded = Enemy(10, 5000) with { ShieldHp = 50000 }; Suite.Equal(10ul, Decide(s with { Enemies = [shielded, Enemy(11, 10000)] }, new(Skill.PowerfulShot))!.TargetId); }); suite.Run("Eagle range counts as an attack target even while Eagle is on cooldown", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(distance: 38)) with { Moving = true }; None(Decide(s, new(Skill.Sprint))); IsSkill(Decide(s with { Player = Self() }, new(Skill.Sprint)), Skill.Sprint); }); suite.Run("Enabled only in a started Frontline match as living unmounted Bard", () => { var active = new RuntimeConditions(true, true, false, BattlefieldMode.Frontline, true, false, true, true, false, false, true); Suite.True(ExecutionPolicy.PauseReason(active) == null); RuntimeConditions[] inactive = [active with { Enabled = false }, active with { LoggedIn = false }, active with { ChangingArea = true }, active with { Mode = BattlefieldMode.None }, active with { DutyStarted = false }, active with { DutyCompleted = true }, active with { Bard = false }, active with { Alive = false }, active with { Mounted = true }, active with { Occupied = true }, active with { CoreReady = false }]; foreach (var s in inactive) Suite.True(ExecutionPolicy.PauseReason(s) != null); }); suite.Run("Dispatcher never overrides a queued action, animation lock, active cast, pending request or Guard", () => { Suite.True(ExecutionPolicy.CanDispatch(false, 0, false, false, false)); Suite.True(!ExecutionPolicy.CanDispatch(true, 0, false, false, false)); Suite.True(!ExecutionPolicy.CanDispatch(false, .01f, false, false, false)); Suite.True(!ExecutionPolicy.CanDispatch(false, 0, true, false, false)); Suite.True(!ExecutionPolicy.CanDispatch(false, 0, false, true, false)); Suite.True(!ExecutionPolicy.CanDispatch(false, 0, false, false, true)); }); suite.Run("Weaving reserves shared GCD boundary without blocking off-GCD windows", () => { Suite.True(ExecutionPolicy.CanWeave(0)); Suite.True(ExecutionPolicy.CanWeave(.65f)); Suite.True(ExecutionPolicy.CanWeave(2.4f)); Suite.True(!ExecutionPolicy.CanWeave(.64f)); }); suite.Run("Only matching action, target and native sequence establishes cast ownership", () => { Suite.True(ExecutionPolicy.OwnsCast(29391, 10, 5, 29391, 10, 5)); Suite.True(!ExecutionPolicy.OwnsCast(29391, 10, 5, 29391, 10, 6)); Suite.True(!ExecutionPolicy.OwnsCast(29391, 10, 5, 29391, 11, 5)); Suite.True(!ExecutionPolicy.OwnsCast(29391, 10, 5, 29055, 10, 5)); }); suite.Run("Enemy Guard cancellation follows the same ignore switch as target selection", () => { Suite.True(ExecutionPolicy.ShouldCancelCast(Skill.PowerfulShot, true, false, true, false, null, true, false)); Suite.True(!ExecutionPolicy.ShouldCancelCast(Skill.PowerfulShot, true, false, true, true, null, true, false)); Suite.True(ExecutionPolicy.ShouldCancelCast(Skill.PowerfulShot, true, true, false, true, null, true, false)); }); suite.Run("Manual casts are untouched; plugin offensive casts yield to survival", () => { var heal = new ActionDecision(Skill.Recuperate, 1, "heal", true); Suite.True(!ExecutionPolicy.ShouldCancelCast(Skill.PowerfulShot, false, true, true, false, heal, true, true)); Suite.True(ExecutionPolicy.ShouldCancelCast(Skill.PowerfulShot, true, false, false, false, heal, true, false)); }); suite.Run("Elixir completes safely when a routine heal comes off cooldown", () => { var heal = new ActionDecision(Skill.Recuperate, 1, "heal", true); Suite.True(!ExecutionPolicy.ShouldCancelCast(Skill.Elixir, true, false, false, false, heal, false, false)); Suite.True(ExecutionPolicy.ShouldCancelCast(Skill.Elixir, true, false, false, false, heal, true, false)); var purify = new ActionDecision(Skill.Purify, 1, "cleanse", true); Suite.True(ExecutionPolicy.ShouldCancelCast(Skill.Elixir, true, false, false, false, purify, false, true)); }); suite.Run("Long elixir cast is not classified as request timeout", () => { var g = new RequestGate(); g.Submitted(new(Skill.Elixir, 1, "drink"), 0, 1, 2); Suite.True(g.Update(3, 2, 1, false, false, ownedCastInProgress: true)); Suite.True(!g.Update(4.5, 2, 2, false, false)); Suite.True(!g.IsBlocked(Skill.Elixir, 1, 4.5)); }); suite.Run("Guard is never classified as generic invulnerability", () => { Suite.True(!Statuses.IsInvulnerability(Statuses.Guard, 15024)); Suite.True(Statuses.IsInvulnerability(1302, 0)); Suite.True(Statuses.IsInvulnerability(99999, 15024)); Suite.True(!Statuses.IsInvulnerability(Statuses.March, 0)); }); suite.Run("Target planning updates during acknowledgement wait without replacing accepted target", () => { var gate = new RequestGate(); gate.Submitted(new(Skill.PowerfulShot, 10, "already sent"), 0, 1, 2); var s = Snap(null, Enemy(10, 40000), Enemy(11, 5000)) with { CurrentTarget = 10 }; var f = new Fake(Skill.PowerfulShot); Suite.True(gate.Update(.05, 2, 1, false, false)); Suite.Equal(11ul, TargetSelector.PlanTarget(s, new(), f, f)!.Id); Suite.Equal(10ul, gate.Pending!.TargetId); Suite.True(!ExecutionPolicy.CanDispatch(false, 0, false, gate.Pending != null, false)); }); suite.Run("Target planning follows current HP changes without waiting for target death", () => { var f = new Fake(Skill.PowerfulShot); var s = Snap(null, Enemy(10, 5000), Enemy(11, 10000)); Suite.Equal(10ul, TargetSelector.PlanTarget(s, new(), f, f)!.Id); s = s with { Enemies = [Enemy(10, 20000), Enemy(11, 10000)], CurrentTarget = 10 }; Suite.Equal(11ul, TargetSelector.PlanTarget(s, new(), f, f)!.Id); }); suite.Run("Guard-only target remains available to Eagle outside normal job range", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(22, 30000, 38, Statuses.Guard)); var f = new Fake(Skill.EagleEyeShot); Suite.Equal(22ul, TargetSelector.PlanTarget(s, new(), f, f)!.Id); IsSkill(Decide(s, f), Skill.EagleEyeShot); }); suite.Run("Guard finisher planning preempts unguarded ordinary target", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(10, 7000) with { ShieldHp = 10000 }, Enemy(11, 8000, 38, Statuses.Guard)); var f = new Fake(Skill.EagleEyeShot, Skill.PowerfulShot); Suite.Equal(11ul, TargetSelector.PlanTarget(s with { CurrentTarget = 10 }, new(), f, f)!.Id); }); suite.Run("Eagle on cooldown does not lure ordinary attacks onto Guard", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(10, 5000, 20, Statuses.Guard), Enemy(11, 40000)); var f = new Fake(Skill.PowerfulShot); Suite.Equal(11ul, TargetSelector.PlanTarget(s, new(), f, f)!.Id); Suite.True(TargetSelector.PlanTarget(s with { Enemies = [s.Enemies[0]] }, new(), f, f) == null); }); suite.Run("Target planning respects own Guard, auto-attack switch and manual Eagle disable", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Enemy(states: [Statuses.Guard])); var f = new Fake(Skill.EagleEyeShot); Suite.True(TargetSelector.PlanTarget(s, new() { AutoAttack = false }, f, f) == null); Suite.True(TargetSelector.PlanTarget(s with { GuardPending = true }, new(), f, f) == null); Suite.True(TargetSelector.PlanTarget(s with { Player = Self(60000, Statuses.Guard, Statuses.RoleEagle) }, new(), f, f) == null); Suite.True(TargetSelector.PlanTarget(s, new() { DisabledSkills = [Skill.EagleEyeShot] }, f, f) == null); }); Actor Ice(ulong id = 50, uint hp = 1000) => Enemy(id, hp, 10) with { IsObjective = true, Name = "Ice", MaxHp = 1000000 }; Actor TimedSelf(params (uint Status, float Seconds)[] procs) => Self(60000, procs.Select(p => p.Status).ToArray()) with { StatusRemaining = procs.ToDictionary(p => p.Status, p => p.Seconds) }; suite.Run("Objectives enabled by default and ordinary rotation attacks ice", () => { Suite.True(new Settings().AttackObjectives); var d = Decide(Snap(null, Ice()), new(Skill.PowerfulShot)); IsSkill(d, Skill.PowerfulShot); Suite.Equal(50ul, d!.TargetId); IsSkill(Decide(Snap(Self(60000, Statuses.BlastReady), Ice()), new(Skill.BlastArrow)), Skill.BlastArrow); }); suite.Run("Players outrank lower HP or marked ice under every sorting mode", () => { foreach (var order in Enum.GetValues()) { var s = Snap(null, Ice() with { AttackMarker = 1 }, Enemy(10, 50000, 20)); Suite.Equal(10ul, Decide(s, new(Skill.PowerfulShot), new() { TargetOrder = order })!.TargetId); var f = new Fake(Skill.PowerfulShot); Suite.Equal(10ul, TargetSelector.PlanTarget(s, new() { TargetOrder = order }, f, f)!.Id); } }); suite.Run("Global player priority prevents a higher priority ice skill preempting a player skill", () => { var f = new Fake(Skill.ApexArrow, Skill.SilentNocturne) { Blocked = [(Skill.ApexArrow, 10)] }; var d = Decide(Snap(null, Ice(), Enemy()), f); IsSkill(d, Skill.SilentNocturne); Suite.Equal(10ul, d!.TargetId); }); suite.Run("Invalid players permit objective fallback", () => { foreach (var player in new[] { Enemy() with { Dead = true }, Enemy() with { Targetable = false }, Enemy(distance: 80), Enemy(states: [Statuses.Guard]), Enemy() with { Invulnerable = true }, Enemy() }) { var f = new Fake(Skill.PowerfulShot) { Occluded = player.Status.Count == 0 && player.Alive && player.Targetable && !player.Invulnerable && player.Distance == 20 ? [10] : [] }; Suite.Equal(50ul, Decide(Snap(null, player, Ice()), f)!.TargetId); } }); suite.Run("Eagle player priority follows readiness and equipped role", () => { var s = Snap(Self(60000, Statuses.RoleEagle), Ice(), Enemy(10, 30000, 38, Statuses.Guard)); var d = Decide(s, new(Skill.EagleEyeShot, Skill.PowerfulShot)); IsSkill(d, Skill.EagleEyeShot); Suite.Equal(10ul, d!.TargetId); Suite.Equal(50ul, Decide(s, new(Skill.PowerfulShot))!.TargetId); Suite.Equal(50ul, Decide(s with { Player = Self() }, new(Skill.EagleEyeShot, Skill.PowerfulShot))!.TargetId); Suite.Equal(50ul, Decide(s, new(Skill.EagleEyeShot, Skill.PowerfulShot), new() { EagleStrategy = EagleStrategy.FinisherOnly })!.TargetId); }); suite.Run("Eagle never spends on objectives even in finisher range", () => { foreach (var strategy in Enum.GetValues()) { var s = Snap(Self(60000, Statuses.RoleEagle), Ice()); var f = new Fake(Skill.EagleEyeShot); var c = new Settings { EagleStrategy = strategy, DisabledSkills = Actions.All.Where(a => a != Skill.EagleEyeShot).ToHashSet() }; None(Decide(s, f, c)); Suite.True(TargetSelector.EagleFinisher(s, c, f) == null); Suite.True(TargetSelector.PlanTarget(s, c, f, f) == null); } }); suite.Run("Objective switch excludes ice from offense and display planning", () => { var s = Snap(null, Ice()); var c = new Settings { AttackObjectives = false }; var f = new Fake(Skill.PowerfulShot); None(Decide(s, f, c)); Suite.True(TargetSelector.PlanTarget(s, c, f, f) == null); Suite.Equal(10ul, Decide(s with { Enemies = [Ice(), Enemy()] }, f, c)!.TargetId); IsSkill(Decide(s with { Player = Self(30000) }, new(Skill.Recuperate), c), Skill.Recuperate); }); suite.Run("Dead inaccessible or immune objectives are skipped", () => { foreach (var ice in new[] { Ice() with { Dead = true }, Ice() with { Targetable = false }, Ice() with { Invulnerable = true }, Ice() with { Hp = 0 }, Ice() with { Distance = 80 } }) None(Decide(Snap(null, ice), new(Skill.PowerfulShot))); None(Decide(Snap(null, Ice()), new(Skill.PowerfulShot) { Occluded = [50] })); }); suite.Run("Ice does not prevent safe elixir but protected players still do", () => { var s = Snap(Self(10000), Ice()); IsSkill(Decide(s, new(Skill.Elixir, Skill.PowerfulShot)), Skill.Elixir); Suite.True(!TargetSelector.HasNearbyThreat(s)); Suite.True(TargetSelector.HasNearbyThreat(s with { Enemies = [Ice(), Enemy(states: [Statuses.Guard])] })); None(Decide(s with { Enemies = [Ice(), Enemy(states: [Statuses.Guard])] }, new(Skill.Elixir))); }); suite.Run("Objective target planning returns to players as they enter range", () => { var f = new Fake(Skill.PowerfulShot); var s = Snap(null, Ice(), Enemy(distance: 80)); Suite.Equal(50ul, TargetSelector.PlanTarget(s, new(), f, f)!.Id); Suite.Equal(10ul, TargetSelector.PlanTarget(s with { Enemies = [Ice(), Enemy()], CurrentTarget = 50 }, new(), f, f)!.Id); }); suite.Run("Display follows objective fallback when all player attacks are unavailable", () => { var f = new Fake(Skill.PowerfulShot) { Blocked = [(Skill.PowerfulShot, 10)] }; var s = Snap(null, Ice(), Enemy()); Suite.Equal(50ul, Decide(s, f)!.TargetId); Suite.Equal(50ul, TargetSelector.PlanTarget(s, new(), f, f)!.Id); }); suite.Run("Expiring Pitch Blast and Encore preempt ordinary damage and LB", () => { foreach (var (skill, status) in new[] { (Skill.PitchPerfect, Statuses.Repertoire), (Skill.BlastArrow, Statuses.BlastReady), (Skill.EncoreOfLight, Statuses.EncoreReady) }) { var s = Snap(TimedSelf((status, 3)), Enemy()) with { LimitFull = true }; IsSkill(Decide(s, new(skill, Skill.HarmonicArrow, Skill.FinalFantasia)), skill); } }); suite.Run("Nonexpiring or unknown proc duration preserves normal RSR ordering", () => { foreach (var seconds in new[] { 3.01f, 10f, 0f, -1f, float.NaN }) IsSkill(Decide(Snap(TimedSelf((Statuses.Repertoire, seconds)), Enemy()), new(Skill.PitchPerfect, Skill.HarmonicArrow)), Skill.HarmonicArrow); IsSkill(Decide(Snap(Self(60000, Statuses.Repertoire), Enemy()), new(Skill.PitchPerfect, Skill.HarmonicArrow)), Skill.HarmonicArrow); }); suite.Run("Earliest expiration wins and unusable expiring skills fall through", () => { var s = Snap(TimedSelf((Statuses.Repertoire, 2), (Statuses.BlastReady, 1), (Statuses.EncoreReady, .5f)), Enemy()); IsSkill(Decide(s, new(Skill.PitchPerfect, Skill.BlastArrow, Skill.EncoreOfLight)), Skill.EncoreOfLight); IsSkill(Decide(s, new(Skill.PitchPerfect, Skill.BlastArrow)), Skill.BlastArrow); IsSkill(Decide(s, new(Skill.HarmonicArrow, Skill.PitchPerfect), new() { DisabledSkills = [Skill.PitchPerfect] }), Skill.HarmonicArrow); }); suite.Run("Survival and player Eagle finishers outrank expiration rescue", () => { var s = Snap(TimedSelf((Statuses.RoleEagle, 100), (Statuses.Repertoire, 1)), Enemy(hp: 1000)); IsSkill(Decide(s, new(Skill.PitchPerfect, Skill.EagleEyeShot)), Skill.EagleEyeShot); IsSkill(Decide(s with { Player = s.Player with { Hp = 30000 } }, new(Skill.PitchPerfect, Skill.Recuperate)), Skill.Recuperate); None(Decide(s, new(Skill.PitchPerfect), new() { AutoAttack = false })); }); suite.Run("Expiration rescue also damages objectives without defeating player priority", () => { var s = Snap(TimedSelf((Statuses.BlastReady, 1)), Ice()); Suite.Equal(50ul, Decide(s, new(Skill.BlastArrow))!.TargetId); Suite.Equal(10ul, Decide(s with { Enemies = [Ice(), Enemy()] }, new(Skill.BlastArrow))!.TargetId); }); suite.Run("Shared recast identification includes additional groups", () => { Suite.True(ExecutionPolicy.SharesGcd(57, -1, 57)); Suite.True(ExecutionPolicy.SharesGcd(10, 57, 57)); Suite.True(!ExecutionPolicy.SharesGcd(10, -1, 57)); Suite.True(!ExecutionPolicy.SharesGcd(-1, -1, -1)); }); suite.Run("Inactive or completed recasts cannot create a phantom weave lock", () => { Suite.Equal(0f, ExecutionPolicy.RecastRemaining(false, 2.4f, 2)); Suite.True(ExecutionPolicy.CanWeave(ExecutionPolicy.RecastRemaining(false, 2.4f, 2))); Suite.True(!ExecutionPolicy.CanWeave(ExecutionPolicy.RecastRemaining(true, 2.4f, 2))); Suite.Equal(0f, ExecutionPolicy.RecastRemaining(true, 2.4f, 3)); }); suite.Run("Encore fires before ready filler and proc generators without waiting for expiry", () => { var s = Snap(TimedSelf((Statuses.EncoreReady, 29)), Enemy(distance: 8)); var d = Decide(s, new(Skill.PowerfulShot, Skill.ApexArrow, Skill.HarmonicArrow, Skill.RepellingShot, Skill.SilentNocturne, Skill.EncoreOfLight)); IsSkill(d, Skill.EncoreOfLight); Suite.True(d!.Reason.Contains("极限技后续")); }); suite.Run("Encore cannot be starved by short weave windows under stacked haste", () => { foreach (var remaining in new[] { .01f, .3f, .64f }) { var f = new Fake(Skill.PowerfulShot, Skill.EncoreOfLight) { SharedGcdRemaining = remaining }; IsSkill(Decide(Snap(TimedSelf((Statuses.EncoreReady, 25)), Enemy()), f), Skill.EncoreOfLight); Suite.True(!ExecutionPolicy.CanWeave(remaining, Skill.SilentNocturne)); } }); suite.Run("Encore still yields to survival and Eagle finishers but outranks ordinary Eagle", () => { var s = Snap(Self(60000, Statuses.EncoreReady, Statuses.RoleEagle), Enemy()); IsSkill(Decide(s, new(Skill.EncoreOfLight, Skill.EagleEyeShot)), Skill.EncoreOfLight); IsSkill(Decide(s with { Enemies = [Enemy(hp: 9000)] }, new(Skill.EncoreOfLight, Skill.EagleEyeShot)), Skill.EagleEyeShot); IsSkill(Decide(s with { Player = s.Player with { Hp = 30000 } }, new(Skill.EncoreOfLight, Skill.Recuperate)), Skill.Recuperate); }); suite.Run("Encore cannot bypass its status toggle target or native availability", () => { var s = Snap(Self(60000, Statuses.EncoreReady), Enemy()); IsSkill(Decide(s with { Player = Self() }, new(Skill.EncoreOfLight, Skill.PowerfulShot)), Skill.PowerfulShot); IsSkill(Decide(s, new(Skill.EncoreOfLight, Skill.PowerfulShot), new() { DisabledSkills = [Skill.EncoreOfLight] }), Skill.PowerfulShot); IsSkill(Decide(s, new(Skill.PowerfulShot)), Skill.PowerfulShot); None(Decide(s with { Enemies = [Enemy(states: [Statuses.Guard])] }, new(Skill.EncoreOfLight))); None(Decide(s with { Enemies = [Enemy(distance: 30)] }, new(Skill.EncoreOfLight))); None(Decide(s, new(Skill.EncoreOfLight) { Occluded = [10] })); None(Decide(s with { GuardPending = true }, new(Skill.EncoreOfLight))); }); suite.Run("Encore uses players before ice and does not divert available player attacks", () => { var s = Snap(Self(60000, Statuses.EncoreReady), Ice(), Enemy()); Suite.Equal(10ul, Decide(s, new(Skill.EncoreOfLight))!.TargetId); var d = Decide(s, new(Skill.EncoreOfLight, Skill.PowerfulShot) { Blocked = [(Skill.EncoreOfLight, 10)] }); IsSkill(d, Skill.PowerfulShot); Suite.Equal(10ul, d!.TargetId); Suite.Equal(50ul, Decide(s with { Enemies = [Ice()] }, new(Skill.EncoreOfLight))!.TargetId); }); suite.Run("Encore remains usable at zero player MP and requires the transformed LB ID", () => { var s = Snap(Self(60000, Statuses.EncoreReady), Enemy()) with { Mp = 0 }; IsSkill(Decide(s, new(Skill.EncoreOfLight) { Mp = 0 }), Skill.EncoreOfLight); None(Decide(s, new(Skill.EncoreOfLight) { AdjustedIds = { [Skill.EncoreOfLight] = (uint)Skill.FinalFantasia } })); }); suite.Run("All known Harmonic variants survive native ID validation and reach rotation", () => { foreach (var id in new uint[] { 41464, 41465, 41466, 41964 }) { var f = new Fake(Skill.HarmonicArrow, Skill.PowerfulShot) { AdjustedIds = { [Skill.HarmonicArrow] = id } }; IsSkill(Decide(Snap(null, Enemy()), f), Skill.HarmonicArrow); f.Charges[Skill.HarmonicArrow] = 0; IsSkill(Decide(Snap(null, Enemy()), f), Skill.PowerfulShot); } }); suite.Run("Harmonic variant acceptance does not permit unknown or unrelated transformations", () => { foreach (var id in new uint[] { 0, 41965, (uint)Skill.EncoreOfLight }) IsSkill(Decide(Snap(null, Enemy()), new(Skill.HarmonicArrow, Skill.PowerfulShot) { AdjustedIds = { [Skill.HarmonicArrow] = id } }), Skill.PowerfulShot); Suite.True(!Actions.Matches(Skill.PowerfulShot, (uint)Skill.PitchPerfect)); Suite.True(!Actions.Matches(Skill.ApexArrow, (uint)Skill.BlastArrow)); Suite.True(!Actions.Matches(Skill.EncoreOfLight, 41964)); }); suite.Run("Battlefield mode identification supports Frontline and Rival Wings only", () => { Suite.Equal(BattlefieldMode.Frontline, Battlefield.Identify(18, true)); Suite.Equal(BattlefieldMode.RivalWings, Battlefield.Identify(39, true)); foreach (var use in new uint[] { 0, 3, 12, 28, 37, 99 }) Suite.Equal(BattlefieldMode.None, Battlefield.Identify(use, true)); Suite.Equal(BattlefieldMode.None, Battlefield.Identify(39, false)); Suite.Equal(BattlefieldMode.None, Battlefield.Identify(18, false)); }); suite.Run("Rival Wings runs on foot and retains all ordinary pause gates", () => { var active = new RuntimeConditions(true, true, false, BattlefieldMode.RivalWings, true, false, true, true, false, false, true); Suite.True(ExecutionPolicy.PauseReason(active) == null); foreach (var stopped in new[] { active with { Enabled = false }, active with { LoggedIn = false }, active with { ChangingArea = true }, active with { DutyStarted = false }, active with { DutyCompleted = true }, active with { Bard = false }, active with { Alive = false }, active with { Mounted = true }, active with { Occupied = true }, active with { CoreReady = false }, active with { Piloting = true } }) Suite.True(ExecutionPolicy.PauseReason(stopped) != null); Suite.True(ExecutionPolicy.PauseReason(active with { Piloting = true })!.Contains("机甲")); }); suite.Run("Mech detection covers condition status and content armor with mode isolation", () => { Suite.True(Battlefield.IsPiloting(BattlefieldMode.RivalWings, true, false, 0)); Suite.True(Battlefield.IsPiloting(BattlefieldMode.RivalWings, false, true, 0)); Suite.True(Battlefield.IsPiloting(BattlefieldMode.RivalWings, false, false, 1)); Suite.True(!Battlefield.IsPiloting(BattlefieldMode.RivalWings, false, false, 0)); Suite.True(!Battlefield.IsPiloting(BattlefieldMode.Frontline, true, true, 1)); Suite.True(!Battlefield.IsPiloting(BattlefieldMode.None, true, true, 1)); }); suite.Run("Battle NPC candidates exclude pets companions owned summons and noncombat kinds", () => { foreach (byte kind in new byte[] { 0, 1, 5 }) { Suite.True(Battlefield.IsBattleNpcCandidate(kind, 0)); Suite.True(Battlefield.IsBattleNpcCandidate(kind, 0xE0000000)); Suite.True(!Battlefield.IsBattleNpcCandidate(kind, 12345)); } foreach (byte kind in new byte[] { 2, 3, 4, 6, 7, 9 }) Suite.True(!Battlefield.IsBattleNpcCandidate(kind, 0)); }); suite.Run("Rival Wings hostile NPCs block elixir even behind walls or while untargetable", () => { foreach (var npc in new[] { Ice() with { ThreateningObjective = true }, Ice() with { ThreateningObjective = true, Targetable = false }, Ice() with { ThreateningObjective = true, Invulnerable = true } }) { var s = Snap(Self(10000), npc); Suite.True(TargetSelector.HasNearbyThreat(s)); None(Decide(s, new(Skill.Elixir) { Occluded = [npc.Id] }, new() { AttackObjectives = false })); } }); suite.Run("Distant dead and friendly NPCs do not block elixir and ice behavior is unchanged", () => { foreach (var npc in new[] { Ice(), Ice() with { ThreateningObjective = true, Distance = 31 }, Ice() with { ThreateningObjective = true, Dead = true }, Ice() with { ThreateningObjective = true, Enemy = false } }) IsSkill(Decide(Snap(Self(10000), npc), new(Skill.Elixir)), Skill.Elixir); }); suite.Run("Players outrank enemy mech and NPC targets while Eagle avoids mechs", () => { var mech = Enemy(50, 1000, 10, Statuses.Warmachina) with { IsObjective = true, ThreateningObjective = true }; var s = Snap(Self(60000, Statuses.RoleEagle), mech, Enemy(10, 50000)); Suite.Equal(10ul, Decide(s, new(Skill.PowerfulShot, Skill.EagleEyeShot))!.TargetId); var onlyMech = s with { Enemies = [mech] }; IsSkill(Decide(onlyMech, new(Skill.PowerfulShot, Skill.EagleEyeShot)), Skill.PowerfulShot); None(Decide(onlyMech, new(Skill.EagleEyeShot))); None(Decide(onlyMech, new(Skill.PowerfulShot), new() { AttackObjectives = false })); }); suite.Run("Unattackable structures and friendly battle NPCs cannot receive attacks", () => { foreach (var npc in new[] { Ice() with { Enemy = false }, Ice() with { Invulnerable = true }, Ice() with { Targetable = false } }) None(Decide(Snap(null, npc with { ThreateningObjective = true }), new(Skill.PowerfulShot, Skill.HarmonicArrow))); }); suite.Run("Rival Wings NPC threats let emergency healing interrupt unsafe elixir", () => { var heal = new ActionDecision(Skill.Recuperate, 1, "heal", true); var s = Snap(Self(10000), Ice() with { ThreateningObjective = true }); Suite.True(ExecutionPolicy.ShouldCancelCast(Skill.Elixir, true, false, false, false, heal, TargetSelector.HasNearbyThreat(s), false)); Suite.True(!ExecutionPolicy.ShouldCancelCast(Skill.Elixir, true, false, false, false, heal, TargetSelector.HasNearbyThreat(s with { Enemies = [Ice()] }), false)); }); OptimizationTests.Run(suite); PressureTests.Run(suite); suite.Finish(); sealed class Fake(params Skill[] ready) : IActionAvailability, ITargetProbe { public Dictionary Charges { get; } = []; public Dictionary AdjustedIds { get; } = []; public float? SharedGcdRemaining { get; init; } public HashSet Occluded { get; init; } = []; public HashSet<(Skill, ulong)> Blocked { get; init; } = []; public int Mp { get; init; } = 10000; public bool CanTarget(Skill skill, Actor a) => a.Alive && a.Targetable && !Occluded.Contains(a.Id) && (!Actions.IsHostile(skill) || a.Distance <= (skill == Skill.RepellingShot ? 10 : skill == Skill.SilentNocturne ? 20 : skill == Skill.EagleEyeShot ? 40 : 25)); public bool CanUse(Skill skill, Actor a) => ready.Contains(skill) && !Blocked.Contains((skill, a.Id)) && CanTarget(skill, a) && Actions.Matches(skill, AdjustedIds.GetValueOrDefault(skill, (uint)skill)) && (SharedGcdRemaining is not { } remain || skill is not (Skill.EncoreOfLight or Skill.SilentNocturne or Skill.RepellingShot) || ExecutionPolicy.CanWeave(remain, skill)) && (!Charges.TryGetValue(skill, out var count) || count > 0) && (skill is not (Skill.Recuperate or Skill.Purify) || Mp >= 2000); } sealed class Suite { private int passed, failed; public void Run(string name, Action test) { try { test(); passed++; Console.WriteLine($"PASS {name}"); } catch (Exception e) { failed++; Console.WriteLine($"FAIL {name}: {e.Message}"); } } public static void True(bool b, string message = "Assertion failed") { if (!b) throw new Exception(message); } public static void Equal(T expected, T actual) { if (!EqualityComparer.Default.Equals(expected, actual)) throw new Exception($"Expected {expected}, got {actual}"); } public void Finish() { Console.WriteLine($"{passed} passed, {failed} failed"); Environment.ExitCode = failed == 0 ? 0 : 1; } }