From fc184baf4a10347988af92914e7489fc4558bc7b Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Tue, 31 Mar 2026 21:51:42 -0700 Subject: [PATCH 01/12] [Communication] InterceptorBase.cs now supports mailbox. --- .../Scripts/Interceptors/InterceptorBase.cs | 74 ++++++++++++++++--- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/Assets/Scripts/Interceptors/InterceptorBase.cs b/Assets/Scripts/Interceptors/InterceptorBase.cs index 0609caeed..2868bd1f3 100644 --- a/Assets/Scripts/Interceptors/InterceptorBase.cs +++ b/Assets/Scripts/Interceptors/InterceptorBase.cs @@ -5,6 +5,13 @@ // Base implementation of an interceptor. public abstract class InterceptorBase : AgentBase, IInterceptor { + + // Sets Mailbox NodeType to "Interceptor" + public override CommsNode NodeType => CommsNode.Interceptor; + + //CommsParent establishes Parent – Child relationship. Provides quick parent lookup. + public IAgent CommsParent { get; set; } + public event InterceptorEventHandler OnHit; public event InterceptorEventHandler OnMiss; public event InterceptorEventHandler OnDestroyed; @@ -101,17 +108,20 @@ public bool EvaluateReassignedTarget(IHierarchical target) { } public void AssignSubInterceptor(IInterceptor subInterceptor) { - if (subInterceptor.CapacityRemaining <= 0) { - return; - } + if (subInterceptor == null || subInterceptor.Capacity <= 0) { return; } // Find a new target for the sub-interceptor within the parent interceptor's assigned targets. IHierarchical target = HierarchicalAgent.FindNewTarget(subInterceptor.HierarchicalAgent, subInterceptor.CapacityRemaining); + if (target != null) { + SendAssignTargetToSub(subInterceptor, target); + } else { + SendAssignRequestToParent(subInterceptor); + } // Evaluate the new target and decide whether to continue searching for other targets. if (!subInterceptor.EvaluateReassignedTarget(target)) { // Propagate the sub-interceptor target assignment to the parent interceptor above. - OnAssignSubInterceptor?.Invoke(subInterceptor); + SendReassignRequestToParent(target); } } @@ -123,7 +133,7 @@ public void ReassignTarget(IHierarchical target) { // another sub-interceptor(s) to pursue the target(s). // 3. Propagate the target re-assignment to the parent interceptor above. if (CapacityPlannedRemaining <= 0) { - OnReassignTarget?.Invoke(target); + SendReassignRequestToParent(target); return; } @@ -155,7 +165,7 @@ protected override void FixedUpdate() { List escapingTargets = targetHierarchicals.Where(EscapeDetector.IsEscaping).ToList(); foreach (var target in escapingTargets) { - OnReassignTarget?.Invoke(target); + SendReassignRequestToParent(target); } if (escapingTargets.Count == targetHierarchicals.Count) { RequestReassignment(this); @@ -279,7 +289,7 @@ private void RegisterMiss(IInterceptor interceptor) { RequestTargetReassignment(interceptor); // Request a new target from the parent interceptor. - OnAssignSubInterceptor?.Invoke(interceptor); + SendAssignRequestToParent(interceptor);; } private void RegisterDestroyed(IInterceptor interceptor) { @@ -305,7 +315,7 @@ private void RequestTargetReassignment(IInterceptor interceptor) { private void RequestReassignment(IInterceptor interceptor) { if (interceptor.IsReassignable) { // Request a new target from the parent interceptor. - OnAssignSubInterceptor?.Invoke(interceptor); + SendAssignRequestToParent(interceptor);; } } @@ -335,7 +345,7 @@ private IEnumerator UnassignedTargetsManager(float period) { filteredTargets.OrderBy(target => Vector3.Distance(Position, target.Position)); var excessTargets = orderedTargets.Skip(CapacityPlannedRemaining); foreach (var target in excessTargets) { - OnReassignTarget?.Invoke(target); + SendReassignRequestToParent(target); } unassignedTargets = orderedTargets.Take(CapacityPlannedRemaining); } else { @@ -362,4 +372,50 @@ private IEnumerator UnassignedTargetsManager(float period) { newSubHierarchical.RecursiveCluster(maxClusterSize: CapacityPerSubInterceptor); } } + + // AssignSubInterceptorRequest to parent. + private void SendAssignRequestToParent(IInterceptor subInterceptor) { + IAgent parent = CommsParent; + if (parent == null || subInterceptor == null) { return; } + SendMessage(new AssignSubInterceptorRequestMessage(this, parent, subInterceptor)); + } + + // ReassignTargetRequest to parent. + private void SendReassignRequestToParent(IHierarchical target) { + IAgent parent = CommsParent; + if (parent == null || target == null) { return; } + SendMessage(new ReassignTargetRequestMessage(this, parent, target)); + } + + // SendAssignTarget to child. + private void SendAssignTargetToSub(IInterceptor subInterceptor, IHierarchical target) { + if (subInterceptor == null || target == null) { return; } + SendMessage(new AssignTargetMessage(this, subInterceptor, target)); + } + + // Send into Mailbox. + private void SendMessage(Message message) { + if (message == null) { return; } + Mailbox mailbox = Mailbox.GetOrCreateInstance(); + if (mailbox == null) { return; } + mailbox.Send(message); + } + + // Execution happens here after recieving and reading message. PayloadData is read and handled. + protected override void OnMessage(Message message) { + if (message == null) { + return; + } + switch (message) { + case AssignSubInterceptorRequestMessage assignRequest: + AssignSubInterceptor(assignRequest.PayloadData.SubInterceptor); + break; + case ReassignTargetRequestMessage reassignRequest: + ReassignTarget(reassignRequest.PayloadData.Target); + break; + case AssignTargetMessage assignTarget: + EvaluateReassignedTarget(assignTarget.PayloadData.Target); + break; + } + } } From b03a77e4a39e8482deaa29a194b0d3ab7b51c67a Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Tue, 31 Mar 2026 22:24:55 -0700 Subject: [PATCH 02/12] [Communication] Fixed bugs: EvaluateReassignedTarget not conisdered when false --- Assets/Scripts/Interceptors/InterceptorBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Scripts/Interceptors/InterceptorBase.cs b/Assets/Scripts/Interceptors/InterceptorBase.cs index 2868bd1f3..d66722008 100644 --- a/Assets/Scripts/Interceptors/InterceptorBase.cs +++ b/Assets/Scripts/Interceptors/InterceptorBase.cs @@ -415,6 +415,10 @@ protected override void OnMessage(Message message) { break; case AssignTargetMessage assignTarget: EvaluateReassignedTarget(assignTarget.PayloadData.Target); + // EvaluateReassignedTarget. If it is false then bounce back: send a RequestReassignment upwards. + if (!EvaluateReassignedTarget(assignTarget.PayloadData.Target)) { + RequestReassignment(this); + } break; } } From 7a5faf749e40a5137de789311dd3a97b7b85af46 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Tue, 31 Mar 2026 22:35:06 -0700 Subject: [PATCH 03/12] [Communication] Fixed bugs: subinterceptors not reassigning --- Assets/Scripts/Interceptors/InterceptorBase.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Assets/Scripts/Interceptors/InterceptorBase.cs b/Assets/Scripts/Interceptors/InterceptorBase.cs index d66722008..51a9e0fad 100644 --- a/Assets/Scripts/Interceptors/InterceptorBase.cs +++ b/Assets/Scripts/Interceptors/InterceptorBase.cs @@ -118,11 +118,6 @@ public void AssignSubInterceptor(IInterceptor subInterceptor) { } else { SendAssignRequestToParent(subInterceptor); } - // Evaluate the new target and decide whether to continue searching for other targets. - if (!subInterceptor.EvaluateReassignedTarget(target)) { - // Propagate the sub-interceptor target assignment to the parent interceptor above. - SendReassignRequestToParent(target); - } } public void ReassignTarget(IHierarchical target) { From 4a7f0a344d86a9539e2bc4864b43c09b53e35c72 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Tue, 28 Apr 2026 15:42:16 -0700 Subject: [PATCH 04/12] [Communication] Fix CodeRabbit Issues InterceptorBase --- .../Scripts/Interceptors/InterceptorBase.cs | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/Assets/Scripts/Interceptors/InterceptorBase.cs b/Assets/Scripts/Interceptors/InterceptorBase.cs index 51a9e0fad..ada70e661 100644 --- a/Assets/Scripts/Interceptors/InterceptorBase.cs +++ b/Assets/Scripts/Interceptors/InterceptorBase.cs @@ -5,11 +5,10 @@ // Base implementation of an interceptor. public abstract class InterceptorBase : AgentBase, IInterceptor { - // Sets Mailbox NodeType to "Interceptor" public override CommsNode NodeType => CommsNode.Interceptor; - //CommsParent establishes Parent – Child relationship. Provides quick parent lookup. + // CommsParent establishes Parent – Child relationship. Provides quick parent lookup. public IAgent CommsParent { get; set; } public event InterceptorEventHandler OnHit; @@ -108,7 +107,9 @@ public bool EvaluateReassignedTarget(IHierarchical target) { } public void AssignSubInterceptor(IInterceptor subInterceptor) { - if (subInterceptor == null || subInterceptor.Capacity <= 0) { return; } + if (subInterceptor == null || subInterceptor.Capacity <= 0) { + return; + } // Find a new target for the sub-interceptor within the parent interceptor's assigned targets. IHierarchical target = HierarchicalAgent.FindNewTarget(subInterceptor.HierarchicalAgent, @@ -284,7 +285,7 @@ private void RegisterMiss(IInterceptor interceptor) { RequestTargetReassignment(interceptor); // Request a new target from the parent interceptor. - SendAssignRequestToParent(interceptor);; + SendAssignRequestToParent(interceptor); } private void RegisterDestroyed(IInterceptor interceptor) { @@ -310,7 +311,7 @@ private void RequestTargetReassignment(IInterceptor interceptor) { private void RequestReassignment(IInterceptor interceptor) { if (interceptor.IsReassignable) { // Request a new target from the parent interceptor. - SendAssignRequestToParent(interceptor);; + SendAssignRequestToParent(interceptor); } } @@ -371,32 +372,42 @@ private IEnumerator UnassignedTargetsManager(float period) { // AssignSubInterceptorRequest to parent. private void SendAssignRequestToParent(IInterceptor subInterceptor) { IAgent parent = CommsParent; - if (parent == null || subInterceptor == null) { return; } + if (parent == null || subInterceptor == null) { + return; + } SendMessage(new AssignSubInterceptorRequestMessage(this, parent, subInterceptor)); } // ReassignTargetRequest to parent. private void SendReassignRequestToParent(IHierarchical target) { IAgent parent = CommsParent; - if (parent == null || target == null) { return; } + if (parent == null || target == null) { + return; + } SendMessage(new ReassignTargetRequestMessage(this, parent, target)); } // SendAssignTarget to child. private void SendAssignTargetToSub(IInterceptor subInterceptor, IHierarchical target) { - if (subInterceptor == null || target == null) { return; } + if (subInterceptor == null || target == null) { + return; + } SendMessage(new AssignTargetMessage(this, subInterceptor, target)); } // Send into Mailbox. private void SendMessage(Message message) { - if (message == null) { return; } + if (message == null) { + return; + } Mailbox mailbox = Mailbox.GetOrCreateInstance(); - if (mailbox == null) { return; } + if (mailbox == null) { + return; + } mailbox.Send(message); } - // Execution happens here after recieving and reading message. PayloadData is read and handled. + // Execution happens here after receiving and reading message. PayloadData is read and handled. protected override void OnMessage(Message message) { if (message == null) { return; @@ -409,12 +420,13 @@ protected override void OnMessage(Message message) { ReassignTarget(reassignRequest.PayloadData.Target); break; case AssignTargetMessage assignTarget: - EvaluateReassignedTarget(assignTarget.PayloadData.Target); - // EvaluateReassignedTarget. If it is false then bounce back: send a RequestReassignment upwards. - if (!EvaluateReassignedTarget(assignTarget.PayloadData.Target)) { + bool accepted = EvaluateReassignedTarget(assignTarget.PayloadData.Target); + // EvaluateReassignedTarget. If it is false then bounce back: send a RequestReassignment + // upwards. + if (!accepted) { RequestReassignment(this); } break; } - } + } } From 1758c383234e84eb45a43a3eafeace09c6d80f54 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Tue, 28 Apr 2026 16:08:46 -0700 Subject: [PATCH 05/12] [Communication] Kept CommsParent Logic --- .../Scripts/Interceptors/InterceptorBase.cs | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Assets/Scripts/Interceptors/InterceptorBase.cs b/Assets/Scripts/Interceptors/InterceptorBase.cs index ada70e661..d777055a5 100644 --- a/Assets/Scripts/Interceptors/InterceptorBase.cs +++ b/Assets/Scripts/Interceptors/InterceptorBase.cs @@ -5,10 +5,8 @@ // Base implementation of an interceptor. public abstract class InterceptorBase : AgentBase, IInterceptor { - // Sets Mailbox NodeType to "Interceptor" - public override CommsNode NodeType => CommsNode.Interceptor; - - // CommsParent establishes Parent – Child relationship. Provides quick parent lookup. + // Optional mailbox receiver used to route requests to the logical parent interceptor or IADS + // proxy. public IAgent CommsParent { get; set; } public event InterceptorEventHandler OnHit; @@ -302,7 +300,7 @@ private void RequestTargetReassignment(IInterceptor interceptor) { List targetHierarchicals = target.LeafHierarchicals(activeOnly: true, withTargetOnly: false); foreach (var targetHierarchical in targetHierarchicals) { - OnReassignTarget?.Invoke(targetHierarchical); + SendReassignRequestToParent(targetHierarchical); } RequestReassignment(interceptor); @@ -372,19 +370,27 @@ private IEnumerator UnassignedTargetsManager(float period) { // AssignSubInterceptorRequest to parent. private void SendAssignRequestToParent(IInterceptor subInterceptor) { IAgent parent = CommsParent; - if (parent == null || subInterceptor == null) { + if (subInterceptor == null) { + return; + } + if (parent == null) { + OnAssignSubInterceptor?.Invoke(subInterceptor); return; } - SendMessage(new AssignSubInterceptorRequestMessage(this, parent, subInterceptor)); + SendMailboxMessage(new AssignSubInterceptorRequestMessage(this, parent, subInterceptor)); } // ReassignTargetRequest to parent. private void SendReassignRequestToParent(IHierarchical target) { IAgent parent = CommsParent; - if (parent == null || target == null) { + if (target == null) { + return; + } + if (parent == null) { + OnReassignTarget?.Invoke(target); return; } - SendMessage(new ReassignTargetRequestMessage(this, parent, target)); + SendMailboxMessage(new ReassignTargetRequestMessage(this, parent, target)); } // SendAssignTarget to child. @@ -392,11 +398,11 @@ private void SendAssignTargetToSub(IInterceptor subInterceptor, IHierarchical ta if (subInterceptor == null || target == null) { return; } - SendMessage(new AssignTargetMessage(this, subInterceptor, target)); + SendMailboxMessage(new AssignTargetMessage(this, subInterceptor, target)); } // Send into Mailbox. - private void SendMessage(Message message) { + private void SendMailboxMessage(Message message) { if (message == null) { return; } @@ -420,11 +426,9 @@ protected override void OnMessage(Message message) { ReassignTarget(reassignRequest.PayloadData.Target); break; case AssignTargetMessage assignTarget: - bool accepted = EvaluateReassignedTarget(assignTarget.PayloadData.Target); - // EvaluateReassignedTarget. If it is false then bounce back: send a RequestReassignment - // upwards. - if (!accepted) { - RequestReassignment(this); + IHierarchical assignedTarget = assignTarget.PayloadData.Target; + if (assignedTarget != null) { + HierarchicalAgent.Target = assignedTarget; } break; } From aebd411920adbb03bf3a5aa7aa7fec55fb222544 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Tue, 28 Apr 2026 16:09:27 -0700 Subject: [PATCH 06/12] [Communication] When release subinterceptor keep track of commsparent --- Assets/Scripts/Interceptors/CarrierBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Assets/Scripts/Interceptors/CarrierBase.cs b/Assets/Scripts/Interceptors/CarrierBase.cs index f35363519..f8a2c2f3c 100644 --- a/Assets/Scripts/Interceptors/CarrierBase.cs +++ b/Assets/Scripts/Interceptors/CarrierBase.cs @@ -48,6 +48,9 @@ private IEnumerator ReleaseManager(float period) { if (agent is IInterceptor subInterceptor) { subInterceptor.OnAssignSubInterceptor += AssignSubInterceptor; subInterceptor.OnReassignTarget += ReassignTarget; + if (subInterceptor is InterceptorBase interceptorBase) { + interceptorBase.CommsParent = this; + } if (subInterceptor.Movement is MissileMovement movement) { movement.FlightPhase = Simulation.FlightPhase.Boost; } From 78863f141ba75debfbc7ed292a40b08c815ae788 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Tue, 28 Apr 2026 16:11:26 -0700 Subject: [PATCH 07/12] [Communication] InterceptorBase passed all InterceptorBaseMailboxTests --- .../EditMode/InterceptorBaseMailboxTests.cs | 294 ++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs diff --git a/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs new file mode 100644 index 000000000..b55066b3e --- /dev/null +++ b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs @@ -0,0 +1,294 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.Serialization; +using UnityEngine; + +public class InterceptorBaseMailboxTests : TestBase { + private Mailbox _mailbox; + private SimManager _simManager; + private TestInterceptor _interceptor; + + [SetUp] + public void SetUp() { + SetMailboxInstance(null); + SetSimManagerInstance(null); + + _simManager = CreateSimManagerStub(); + SetPrivateField(_simManager, "_dummyAgents", new List()); + SetSimManagerInstance(_simManager); + SetElapsedTime(0f); + + _mailbox = new GameObject("Mailbox").AddComponent(); + + _interceptor = new GameObject("Interceptor").AddComponent(); + _interceptor.gameObject.AddComponent(); + _interceptor.HierarchicalAgent = new HierarchicalAgent(_interceptor); + _interceptor.InvokeAwakeForTest(); + _interceptor.StaticConfig = CreateStaticConfig(Configs.AgentType.CarrierInterceptor); + } + + [TearDown] + public void TearDown() { + if (_interceptor?.TargetModel != null) { + _interceptor.DestroyTargetModel(); + } + if (_interceptor != null) { + Object.DestroyImmediate(_interceptor.gameObject); + } + if (_mailbox != null) { + Object.DestroyImmediate(_mailbox.gameObject); + } + SetMailboxInstance(null); + SetSimManagerInstance(null); + } + + // Verifies that a mailbox-delivered AssignTarget message updates the interceptor target. + [Test] + public void MailboxDelivery_AssignTargetMessage_AssignsHierarchicalTarget() { + var target = new FixedHierarchical(position: new Vector3(10f, 0f, 0f)); + var message = + new AssignTargetMessage(new StubAgent(Configs.AgentType.Vessel), _interceptor, target); + + _mailbox.Configure(null); + _mailbox.Send(message); + + InvokePrivateMethod(_mailbox, "Update"); + + Assert.AreSame(target, _interceptor.HierarchicalAgent.Target); + } + + // Verifies that a mailbox-delivered reassignment request is queued for later clustering. + [Test] + public void MailboxDelivery_ReassignTargetRequest_QueuesUnassignedTarget() { + SetPrivateField(_interceptor, "_capacityPerSubInterceptor", 1); + SetPrivateField(_interceptor, "_numSubInterceptorsPlannedRemaining", 1); + + var target = new FixedHierarchical(position: new Vector3(5f, 0f, 0f)); + var message = new ReassignTargetRequestMessage(new StubAgent(Configs.AgentType.Vessel), + _interceptor, target); + + _mailbox.Configure(null); + _mailbox.Send(message); + + InvokePrivateMethod(_mailbox, "Update"); + + var queuedTargets = GetPrivateField>(_interceptor, "_unassignedTargets"); + Assert.True(queuedTargets.Contains(target)); + } + + // Verifies that a sub-interceptor assignment request is sent through the mailbox to the parent + // receiver when no target is currently available. + [Test] + public void AssignSubInterceptor_WithoutTarget_SendsAssignRequestToParentMailboxReceiver() { + var parent = new StubAgent(Configs.AgentType.Vessel); + _interceptor.CommsParent = parent; + + var subInterceptor = new StubInterceptor(Configs.AgentType.MissileInterceptor, capacity: 1); + subInterceptor.HierarchicalAgent = new HierarchicalAgent(subInterceptor); + + AssignSubInterceptorRequestMessage deliveredMessage = null; + _mailbox.OnMessageDelivered += (_, message) => { + if (message is AssignSubInterceptorRequestMessage assignRequest && + ReferenceEquals(assignRequest.Receiver, parent)) { + deliveredMessage = assignRequest; + } + }; + + _mailbox.Configure(null); + _interceptor.AssignSubInterceptor(subInterceptor); + + InvokePrivateMethod(_mailbox, "Update"); + + Assert.NotNull(deliveredMessage); + Assert.AreSame(_interceptor, deliveredMessage.Sender); + Assert.AreSame(parent, deliveredMessage.Receiver); + Assert.AreSame(subInterceptor, deliveredMessage.PayloadData.SubInterceptor); + } + + // Verifies that reassignment propagation uses the mailbox when the interceptor has no remaining + // planned capacity. + [Test] + public void ReassignTarget_WithoutPlannedCapacity_SendsRequestToParentMailboxReceiver() { + SetPrivateField(_interceptor, "_capacityPerSubInterceptor", 1); + SetPrivateField(_interceptor, "_numSubInterceptorsPlannedRemaining", 0); + _interceptor.CommsParent = new StubAgent(Configs.AgentType.ShoreBattery); + + var target = new FixedHierarchical(position: new Vector3(3f, 0f, 0f)); + ReassignTargetRequestMessage deliveredMessage = null; + _mailbox.OnMessageDelivered += (_, message) => { + if (message is ReassignTargetRequestMessage reassignRequest && + ReferenceEquals(reassignRequest.Receiver, _interceptor.CommsParent)) { + deliveredMessage = reassignRequest; + } + }; + + _mailbox.Configure(null); + _interceptor.ReassignTarget(target); + + InvokePrivateMethod(_mailbox, "Update"); + + Assert.NotNull(deliveredMessage); + Assert.AreSame(_interceptor, deliveredMessage.Sender); + Assert.AreSame(_interceptor.CommsParent, deliveredMessage.Receiver); + Assert.AreSame(target, deliveredMessage.PayloadData.Target); + } + + private static SimManager CreateSimManagerStub() { + return (SimManager)FormatterServices.GetUninitializedObject(typeof(SimManager)); + } + + private static Configs.StaticConfig CreateStaticConfig(Configs.AgentType agentType) { + return new Configs.StaticConfig { + AgentType = agentType, + BodyConfig = new Configs.BodyConfig { Mass = 1f, CrossSectionalArea = 1f }, + LiftDragConfig = new Configs.LiftDragConfig { DragCoefficient = 1f, LiftDragRatio = 1f }, + }; + } + + private static void SetMailboxInstance(Mailbox mailbox) { + FieldInfo instanceField = typeof(Mailbox).GetField( + "k__BackingField", BindingFlags.NonPublic | BindingFlags.Static); + instanceField.SetValue(null, mailbox); + } + + private static void SetSimManagerInstance(SimManager simManager) { + FieldInfo instanceField = + typeof(SimManager) + .GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Static); + instanceField.SetValue(null, simManager); + } + + private void SetElapsedTime(float elapsedTime) { + FieldInfo elapsedTimeField = typeof(SimManager) + .GetField("k__BackingField", + BindingFlags.NonPublic | BindingFlags.Instance); + elapsedTimeField.SetValue(_simManager, elapsedTime); + } + + private sealed class TestInterceptor : InterceptorBase, IAgent { + public void InvokeAwakeForTest() { + base.Awake(); + } + + void IAgent.CreateTargetModel(IHierarchical target) {} + + void IAgent.DestroyTargetModel() {} + + void IAgent.UpdateTargetModel() {} + } + + private sealed class StubInterceptor : IInterceptor { + private readonly int _capacity; + + public event InterceptorEventHandler OnHit; + public event InterceptorEventHandler OnMiss; + public event InterceptorEventHandler OnDestroyed; + public event InterceptorEventHandler OnAssignSubInterceptor; + public event TargetReassignEventHandler OnReassignTarget; + public event AgentTerminatedEventHandler OnTerminated; + + public HierarchicalAgent HierarchicalAgent { get; set; } + public Configs.StaticConfig StaticConfig { get; set; } + public Configs.AgentConfig AgentConfig { get; set; } + public IMovement Movement { get; set; } + public IController Controller { get; set; } + public ISensor Sensor { get; set; } + public IAgent TargetModel { get; set; } + public Vector3 Position { get; set; } + public Vector3 Velocity { get; set; } = Vector3.forward; + public float Speed => Velocity.magnitude; + public Vector3 Acceleration { get; set; } + public Vector3 AccelerationInput { get; set; } + public bool IsPursuer => true; + public float ElapsedTime => 0f; + public bool IsTerminated { get; private set; } + public GameObject gameObject => null; + public Transform Transform => null; + public Vector3 Up => Vector3.up; + public Vector3 Forward => Vector3.forward; + public Vector3 Right => Vector3.right; + public Quaternion InverseRotation => Quaternion.identity; + public IEscapeDetector EscapeDetector { get; set; } + public int Capacity => _capacity; + public int CapacityPerSubInterceptor => _capacity; + public int CapacityPlannedRemaining => _capacity; + public int CapacityRemaining => _capacity; + public int NumSubInterceptors => 0; + public int NumSubInterceptorsPlannedRemaining => 0; + public int NumSubInterceptorsRemaining => 0; + public bool IsReassignable => true; + + public StubInterceptor(Configs.AgentType agentType, int capacity) { + _capacity = capacity; + StaticConfig = CreateStaticConfig(agentType); + } + + public float MaxForwardAcceleration() => 1f; + public float MaxNormalAcceleration() => 1f; + public void CreateTargetModel(IHierarchical target) {} + public void DestroyTargetModel() {} + public void UpdateTargetModel() {} + public bool EvaluateReassignedTarget(IHierarchical target) => false; + public void AssignSubInterceptor(IInterceptor subInterceptor) { + OnAssignSubInterceptor?.Invoke(subInterceptor); + } + public void ReassignTarget(IHierarchical target) { + OnReassignTarget?.Invoke(target); + } + + public void Terminate() { + IsTerminated = true; + OnTerminated?.Invoke(this); + } + + public Transformation GetRelativeTransformation(IAgent target) => default; + public Transformation GetRelativeTransformation(IHierarchical target) => default; + public Transformation GetRelativeTransformation(in Vector3 waypoint) => default; + } + + private sealed class StubAgent : IAgent { + public event AgentTerminatedEventHandler OnTerminated; + + public HierarchicalAgent HierarchicalAgent { get; set; } + public Configs.StaticConfig StaticConfig { get; set; } + public Configs.AgentConfig AgentConfig { get; set; } + public IMovement Movement { get; set; } + public IController Controller { get; set; } + public ISensor Sensor { get; set; } + public IAgent TargetModel { get; set; } + public Vector3 Position { get; set; } + public Vector3 Velocity { get; set; } + public float Speed => Velocity.magnitude; + public Vector3 Acceleration { get; set; } + public Vector3 AccelerationInput { get; set; } + public bool IsPursuer => false; + public float ElapsedTime => 0f; + public bool IsTerminated { get; private set; } + public GameObject gameObject => null; + public Transform Transform => null; + public Vector3 Up => Vector3.up; + public Vector3 Forward => Vector3.forward; + public Vector3 Right => Vector3.right; + public Quaternion InverseRotation => Quaternion.identity; + + public StubAgent(Configs.AgentType agentType) { + StaticConfig = CreateStaticConfig(agentType); + } + + public float MaxForwardAcceleration() => 0f; + public float MaxNormalAcceleration() => 0f; + public void CreateTargetModel(IHierarchical target) {} + public void DestroyTargetModel() {} + public void UpdateTargetModel() {} + + public void Terminate() { + IsTerminated = true; + OnTerminated?.Invoke(this); + } + + public Transformation GetRelativeTransformation(IAgent target) => default; + public Transformation GetRelativeTransformation(IHierarchical target) => default; + public Transformation GetRelativeTransformation(in Vector3 waypoint) => default; + } +} From 3b3ba7662a0a799338c8ba917a78385d900beb90 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Sun, 7 Jun 2026 16:55:29 -0700 Subject: [PATCH 08/12] [Communication] Update interceptor and IADS mailbox protocol --- Assets/Scripts/IADS/IADS.cs | 3 ++ .../Scripts/Interceptors/InterceptorBase.cs | 18 +++---- Assets/Tests/EditMode/IADSMailboxTests.cs | 30 +++++++++++ .../EditMode/InterceptorBaseMailboxTests.cs | 54 ++++++++++++++++--- 4 files changed, 89 insertions(+), 16 deletions(-) diff --git a/Assets/Scripts/IADS/IADS.cs b/Assets/Scripts/IADS/IADS.cs index 7939cdf15..966fe93ca 100644 --- a/Assets/Scripts/IADS/IADS.cs +++ b/Assets/Scripts/IADS/IADS.cs @@ -121,6 +121,9 @@ private void RegisterSimulationEnded() { } public void RegisterNewAsset(IInterceptor asset) { + if (asset is InterceptorBase interceptorBase) { + interceptorBase.CommsParent = _commsAgent; + } if (asset?.HierarchicalAgent == null || asset.IsPursuer || _assets.Contains(asset.HierarchicalAgent)) { return; diff --git a/Assets/Scripts/Interceptors/InterceptorBase.cs b/Assets/Scripts/Interceptors/InterceptorBase.cs index d777055a5..6ba7614ba 100644 --- a/Assets/Scripts/Interceptors/InterceptorBase.cs +++ b/Assets/Scripts/Interceptors/InterceptorBase.cs @@ -105,7 +105,7 @@ public bool EvaluateReassignedTarget(IHierarchical target) { } public void AssignSubInterceptor(IInterceptor subInterceptor) { - if (subInterceptor == null || subInterceptor.Capacity <= 0) { + if (subInterceptor == null || subInterceptor.CapacityRemaining <= 0) { return; } @@ -367,7 +367,7 @@ private IEnumerator UnassignedTargetsManager(float period) { } } - // AssignSubInterceptorRequest to parent. + // Send an AssignTargetRequest message to the parent mailbox receiver. private void SendAssignRequestToParent(IInterceptor subInterceptor) { IAgent parent = CommsParent; if (subInterceptor == null) { @@ -377,10 +377,10 @@ private void SendAssignRequestToParent(IInterceptor subInterceptor) { OnAssignSubInterceptor?.Invoke(subInterceptor); return; } - SendMailboxMessage(new AssignSubInterceptorRequestMessage(this, parent, subInterceptor)); + SendMailboxMessage(new AssignTargetRequestMessage(this, parent, subInterceptor)); } - // ReassignTargetRequest to parent. + // Send a ReassignTargetRequest message to the parent mailbox receiver. private void SendReassignRequestToParent(IHierarchical target) { IAgent parent = CommsParent; if (target == null) { @@ -393,15 +393,15 @@ private void SendReassignRequestToParent(IHierarchical target) { SendMailboxMessage(new ReassignTargetRequestMessage(this, parent, target)); } - // SendAssignTarget to child. + // Send an AssignTargetResponse message to the child mailbox receiver. private void SendAssignTargetToSub(IInterceptor subInterceptor, IHierarchical target) { if (subInterceptor == null || target == null) { return; } - SendMailboxMessage(new AssignTargetMessage(this, subInterceptor, target)); + SendMailboxMessage(new AssignTargetResponseMessage(this, subInterceptor, target)); } - // Send into Mailbox. + // Send a message through the mailbox. private void SendMailboxMessage(Message message) { if (message == null) { return; @@ -419,13 +419,13 @@ protected override void OnMessage(Message message) { return; } switch (message) { - case AssignSubInterceptorRequestMessage assignRequest: + case AssignTargetRequestMessage assignRequest: AssignSubInterceptor(assignRequest.PayloadData.SubInterceptor); break; case ReassignTargetRequestMessage reassignRequest: ReassignTarget(reassignRequest.PayloadData.Target); break; - case AssignTargetMessage assignTarget: + case AssignTargetResponseMessage assignTarget: IHierarchical assignedTarget = assignTarget.PayloadData.Target; if (assignedTarget != null) { HierarchicalAgent.Target = assignedTarget; diff --git a/Assets/Tests/EditMode/IADSMailboxTests.cs b/Assets/Tests/EditMode/IADSMailboxTests.cs index 59f6dfd43..0b34bcec8 100644 --- a/Assets/Tests/EditMode/IADSMailboxTests.cs +++ b/Assets/Tests/EditMode/IADSMailboxTests.cs @@ -174,6 +174,24 @@ public void MailboxDelivery_ForDifferentReceiver_DoesNotTriggerIadsResponse() { Assert.IsNull(deliveredResponse); } + // Verifies that top-level launchers route mailbox requests to the IADS proxy. + [Test] + public void RegisterNewLauncher_SetsCommsParentToIadsProxy() { + var launcherObject = new GameObject("Launcher"); + try { + var launcher = launcherObject.AddComponent(); + launcherObject.AddComponent(); + launcher.HierarchicalAgent = new HierarchicalAgent(launcher); + launcher.InvokeAwakeForTest(); + + _iads.RegisterNewLauncher(launcher); + + Assert.AreSame(_commsAgent, launcher.CommsParent); + } finally { + Object.DestroyImmediate(launcherObject); + } + } + private static SimManager CreateSimManagerStub() { return (SimManager)FormatterServices.GetUninitializedObject(typeof(SimManager)); } @@ -333,4 +351,16 @@ public void Terminate() { public Transformation GetRelativeTransformation(IHierarchical target) => default; public Transformation GetRelativeTransformation(in Vector3 waypoint) => default; } + + private sealed class TestLauncherInterceptor : InterceptorBase, IAgent { + public void InvokeAwakeForTest() { + base.Awake(); + } + + void IAgent.CreateTargetModel(IHierarchical target) {} + + void IAgent.DestroyTargetModel() {} + + void IAgent.UpdateTargetModel() {} + } } diff --git a/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs index b55066b3e..866b924bc 100644 --- a/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs +++ b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs @@ -43,12 +43,12 @@ public void TearDown() { SetSimManagerInstance(null); } - // Verifies that a mailbox-delivered AssignTarget message updates the interceptor target. + // Verifies that a mailbox-delivered AssignTargetResponse message updates the interceptor target. [Test] - public void MailboxDelivery_AssignTargetMessage_AssignsHierarchicalTarget() { + public void MailboxDelivery_AssignTargetResponseMessage_AssignsHierarchicalTarget() { var target = new FixedHierarchical(position: new Vector3(10f, 0f, 0f)); - var message = - new AssignTargetMessage(new StubAgent(Configs.AgentType.Vessel), _interceptor, target); + var message = new AssignTargetResponseMessage(new StubAgent(Configs.AgentType.Vessel), + _interceptor, target); _mailbox.Configure(null); _mailbox.Send(message); @@ -77,19 +77,51 @@ public void MailboxDelivery_ReassignTargetRequest_QueuesUnassignedTarget() { Assert.True(queuedTargets.Contains(target)); } + // Verifies that a mailbox-delivered AssignTargetRequest message to a parent interceptor produces + // an AssignTargetResponse message for the requesting sub-interceptor. + [Test] + public void MailboxDelivery_AssignTargetRequest_SendsAssignTargetResponseToSubInterceptor() { + var expectedTarget = new FixedHierarchical(position: new Vector3(12f, 0f, 0f)); + _interceptor.HierarchicalAgent.Target = CreateCluster(expectedTarget); + + var subInterceptor = new StubInterceptor(Configs.AgentType.MissileInterceptor, capacity: 1); + subInterceptor.HierarchicalAgent = new HierarchicalAgent(subInterceptor); + + AssignTargetResponseMessage deliveredMessage = null; + _mailbox.OnMessageDelivered += (_, message) => { + if (message is AssignTargetResponseMessage assignTargetResponse && + ReferenceEquals(assignTargetResponse.Receiver, subInterceptor)) { + deliveredMessage = assignTargetResponse; + } + }; + + _mailbox.Configure(null); + _mailbox.Send(new AssignTargetRequestMessage(new StubAgent(Configs.AgentType.Vessel), + _interceptor, subInterceptor)); + + InvokePrivateMethod(_mailbox, "Update"); + Assert.IsNull(deliveredMessage); + + InvokePrivateMethod(_mailbox, "Update"); + Assert.NotNull(deliveredMessage); + Assert.AreSame(_interceptor, deliveredMessage.Sender); + Assert.AreSame(subInterceptor, deliveredMessage.Receiver); + Assert.AreSame(expectedTarget, deliveredMessage.PayloadData.Target); + } + // Verifies that a sub-interceptor assignment request is sent through the mailbox to the parent // receiver when no target is currently available. [Test] - public void AssignSubInterceptor_WithoutTarget_SendsAssignRequestToParentMailboxReceiver() { + public void AssignSubInterceptor_WithoutTarget_SendsAssignTargetRequestToParentMailboxReceiver() { var parent = new StubAgent(Configs.AgentType.Vessel); _interceptor.CommsParent = parent; var subInterceptor = new StubInterceptor(Configs.AgentType.MissileInterceptor, capacity: 1); subInterceptor.HierarchicalAgent = new HierarchicalAgent(subInterceptor); - AssignSubInterceptorRequestMessage deliveredMessage = null; + AssignTargetRequestMessage deliveredMessage = null; _mailbox.OnMessageDelivered += (_, message) => { - if (message is AssignSubInterceptorRequestMessage assignRequest && + if (message is AssignTargetRequestMessage assignRequest && ReferenceEquals(assignRequest.Receiver, parent)) { deliveredMessage = assignRequest; } @@ -146,6 +178,14 @@ private static Configs.StaticConfig CreateStaticConfig(Configs.AgentType agentTy }; } + private static HierarchicalBase CreateCluster(params IHierarchical[] targets) { + var cluster = new HierarchicalBase(); + foreach (IHierarchical target in targets) { + cluster.AddSubHierarchical(target); + } + return cluster; + } + private static void SetMailboxInstance(Mailbox mailbox) { FieldInfo instanceField = typeof(Mailbox).GetField( "k__BackingField", BindingFlags.NonPublic | BindingFlags.Static); From ea4e639eb9691c0b20ed8dab9f4d98d525117a5a Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Sun, 7 Jun 2026 22:18:40 -0700 Subject: [PATCH 09/12] [Communication] Fixed Test Error --- Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs index 866b924bc..67a670e28 100644 --- a/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs +++ b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs @@ -81,6 +81,7 @@ public void MailboxDelivery_ReassignTargetRequest_QueuesUnassignedTarget() { // an AssignTargetResponse message for the requesting sub-interceptor. [Test] public void MailboxDelivery_AssignTargetRequest_SendsAssignTargetResponseToSubInterceptor() { + SetPrivateField(_interceptor, "_capacityPerSubInterceptor", 1); var expectedTarget = new FixedHierarchical(position: new Vector3(12f, 0f, 0f)); _interceptor.HierarchicalAgent.Target = CreateCluster(expectedTarget); From 4876366ecefc417cbfba9cf9409d9bca4c0d274f Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Mon, 8 Jun 2026 21:42:07 -0700 Subject: [PATCH 10/12] [Communication] Fix interceptor mailbox target assertion --- Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs index 67a670e28..99cce955e 100644 --- a/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs +++ b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs @@ -107,7 +107,11 @@ public void MailboxDelivery_AssignTargetRequest_SendsAssignTargetResponseToSubIn Assert.NotNull(deliveredMessage); Assert.AreSame(_interceptor, deliveredMessage.Sender); Assert.AreSame(subInterceptor, deliveredMessage.Receiver); - Assert.AreSame(expectedTarget, deliveredMessage.PayloadData.Target); + + List assignedTargets = deliveredMessage.PayloadData.Target.LeafHierarchicals( + activeOnly: true, withTargetOnly: false); + Assert.AreEqual(1, assignedTargets.Count); + Assert.AreSame(expectedTarget, assignedTargets[0]); } // Verifies that a sub-interceptor assignment request is sent through the mailbox to the parent From 765f9a6edd5a28583d4814d1c9c8229acf8c94ff Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Mon, 8 Jun 2026 22:12:39 -0700 Subject: [PATCH 11/12] [Communication] Fixed CodeRabbit Comments --- Assets/Scripts/IADS/IADS.cs | 6 +++--- Assets/Scripts/Interceptors/InterceptorBase.cs | 2 +- Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta | 2 ++ Assets/Tests/EditMode/IADSMailboxTests.cs.meta | 2 ++ Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs.meta | 2 ++ 5 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta create mode 100644 Assets/Tests/EditMode/IADSMailboxTests.cs.meta create mode 100644 Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs.meta diff --git a/Assets/Scripts/IADS/IADS.cs b/Assets/Scripts/IADS/IADS.cs index 966fe93ca..2a73e03e0 100644 --- a/Assets/Scripts/IADS/IADS.cs +++ b/Assets/Scripts/IADS/IADS.cs @@ -121,14 +121,14 @@ private void RegisterSimulationEnded() { } public void RegisterNewAsset(IInterceptor asset) { - if (asset is InterceptorBase interceptorBase) { - interceptorBase.CommsParent = _commsAgent; - } if (asset?.HierarchicalAgent == null || asset.IsPursuer || _assets.Contains(asset.HierarchicalAgent)) { return; } + if (asset is InterceptorBase interceptorBase) { + interceptorBase.CommsParent = _commsAgent; + } _assets.Add(asset.HierarchicalAgent); } diff --git a/Assets/Scripts/Interceptors/InterceptorBase.cs b/Assets/Scripts/Interceptors/InterceptorBase.cs index 6ba7614ba..c85a8ee9c 100644 --- a/Assets/Scripts/Interceptors/InterceptorBase.cs +++ b/Assets/Scripts/Interceptors/InterceptorBase.cs @@ -428,7 +428,7 @@ protected override void OnMessage(Message message) { case AssignTargetResponseMessage assignTarget: IHierarchical assignedTarget = assignTarget.PayloadData.Target; if (assignedTarget != null) { - HierarchicalAgent.Target = assignedTarget; + EvaluateReassignedTarget(assignedTarget); } break; } diff --git a/Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta b/Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta new file mode 100644 index 000000000..fb0e898ae --- /dev/null +++ b/Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2bcb7fd2c8997460788772dad50fdac3 \ No newline at end of file diff --git a/Assets/Tests/EditMode/IADSMailboxTests.cs.meta b/Assets/Tests/EditMode/IADSMailboxTests.cs.meta new file mode 100644 index 000000000..47e831d52 --- /dev/null +++ b/Assets/Tests/EditMode/IADSMailboxTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 21e3b19c559d14154a7b6884582418ab \ No newline at end of file diff --git a/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs.meta b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs.meta new file mode 100644 index 000000000..9bbbe3433 --- /dev/null +++ b/Assets/Tests/EditMode/InterceptorBaseMailboxTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 645d0f38b2a5d4766933397deb52320c \ No newline at end of file From 9f4dce7e4c7ac5384db4eb9a9a366355e340feb0 Mon Sep 17 00:00:00 2001 From: Joseph Chen Date: Mon, 8 Jun 2026 22:58:54 -0700 Subject: [PATCH 12/12] [Communication] Fixed Failing Test --- Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta | 2 -- Assets/Tests/EditMode/IADSMailboxTests.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta diff --git a/Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta b/Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta deleted file mode 100644 index fb0e898ae..000000000 --- a/Assets/Tests/EditMode/CarrierBaseMailboxTests.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2bcb7fd2c8997460788772dad50fdac3 \ No newline at end of file diff --git a/Assets/Tests/EditMode/IADSMailboxTests.cs b/Assets/Tests/EditMode/IADSMailboxTests.cs index 0b34bcec8..b7c658be5 100644 --- a/Assets/Tests/EditMode/IADSMailboxTests.cs +++ b/Assets/Tests/EditMode/IADSMailboxTests.cs @@ -353,6 +353,8 @@ public void Terminate() { } private sealed class TestLauncherInterceptor : InterceptorBase, IAgent { + public override bool IsPursuer => false; + public void InvokeAwakeForTest() { base.Awake(); }