Skip to content

[IADS] Clean up IADS and simulation manager - #124

Merged
tryuan99 merged 126 commits into
titan/hierarchicalfrom
titan/iads
Dec 10, 2025
Merged

tryuan99 merged 126 commits into
titan/hierarchicalfrom
titan/iads

Conversation

@tryuan99

Copy link
Copy Markdown
Member

This PR cleans up the IADS and simulation manager to manage the hierarchical strategy. Some important changes to note:

  1. The IADS is responsible for clustering the threats into clusters and clustering the clusters into swarms. It then assigns one swarm to each launcher present.
  2. All interceptors and threats now define events for hitting their targets or missing their intended target, either because they hit the ground or collided with another agent.
  3. There is no longer a notion of swarms, which previously kept track of interceptor swarms launched to counter a swarm of threats. Similarly, all released missile interceptors formed a swarm. This is no longer the case, so I've removed everything related to this. In any case, the IADS should not keep track of swarms if it's used by the camera - the camera controller should be responsible for clustering all threats into "visual" swarms.

@daniellovell daniellovell left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes since last review LGTM :)

Base automatically changed from titan/launcher_type to titan/hierarchical December 10, 2025 06:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
Assets/Scripts/UI/CameraController.cs (2)

326-337: TODO placeholders fall back to GetAllAgentsCenter() which may be misleading.

The FOLLOW_INTERCEPTOR_SWARM and FOLLOW_THREAT_SWARM modes currently fall back to GetAllAgentsCenter(), making them behave identically to FOLLOW_ALL_AGENTS. This could confuse users who expect swarm-specific tracking. Consider returning early or logging when these modes are selected but not yet implemented.


279-290: Fix centroid initialization logic in SetCameraMode—should initialize when entering follow modes, not exiting.

The condition is backwards. When transitioning from FREE to a follow mode, centroids should be initialized. Currently they're only initialized when exiting a follow mode. Change the condition to initialize centroids when mode != CameraMode.FREE:

public void SetCameraMode(CameraMode mode) {
  if (mode != CameraMode.FREE) {
    _currentCentroid = target.position;
    _targetCentroid = target.position;
  } else {
    if (_centroidUpdateCoroutine != null) {
      StopCoroutine(_centroidUpdateCoroutine);
      _centroidUpdateCoroutine = null;
    }
  }
  cameraMode = mode;
}

This ensures centroids are properly initialized before the update coroutine begins using them.

Assets/Scripts/Managers/ParticleManager.cs (1)

145-150: Hit marker is added to list twice.

SpawnHitMarker adds the marker to _hitMarkerList at line 148, and the callers (RegisterInterceptorHit at line 117 and RegisterInterceptorMiss at line 125) also add the returned object to _hitMarkerList. This results in duplicate entries.

Remove the duplicate add inside SpawnHitMarker:

 private GameObject SpawnHitMarker(in Vector3 position) {
   GameObject hitMarker = Instantiate(Resources.Load<GameObject>("Prefabs/HitMarkerPrefab"),
                                      position, Quaternion.identity);
-  _hitMarkerList.Add(hitMarker);
   return hitMarker;
 }
♻️ Duplicate comments (5)
Assets/Scripts/Agents/AgentBase.cs (1)

147-158: Terminate() implementation follows correct event ordering.

State is updated (IsTerminated = true) before firing OnTerminated, ensuring subscribers see consistent state. The null-checks on HierarchicalAgent and Movement pattern-match appropriately.

The Position = Vector3.zero before deactivation was flagged in a previous review regarding object pooling concerns.

Assets/Scripts/Interceptors/IInterceptor.cs (1)

5-5: Delegate naming has been previously discussed.

The delegate name InterceptHitMissEventHandler was discussed in a previous review, with a suggestion to use InterceptorHitMissEventHandler for clarity. While the current name is consistent with the codebase pattern, consider the earlier feedback if refactoring for improved readability.

Assets/Scripts/Threats/IThreat.cs (1)

7-7: Delegate naming may have been previously addressed.

There's a past review comment about delegate naming, but the current name ThreatHitMissEventHandler appears to be the suggested form. If the previous comment was proposing this name, it has been implemented.

Assets/Scripts/Hierarchical/FixedHierarchical.cs (1)

5-9: Acknowledged design: new keyword with explicit interface implementation.

The new keyword shadowing combined with explicit IHierarchical implementation is a known workaround (per prior discussion). This ensures correct behavior when accessed through the interface, though accessing through a HierarchicalBase reference will still invoke the base class properties. If all consumers use IHierarchical, this is acceptable.

The IsTerminated => false override is correct for a fixed test object that never terminates.

Assets/Scripts/Threats/ThreatBase.cs (1)

160-161: Unsafe cast may cause NullReferenceException.

If HierarchicalAgent.Target is not a HierarchicalAgent (e.g., it's a FixedHierarchical as created in Start()), the cast will return null, and accessing targetAgent.Agent will throw. Use pattern matching for safe access.

Apply this diff:

-    } else if (HierarchicalAgent.Target is HierarchicalAgent targetAgent &&
-               otherAgent == targetAgent.Agent) {
+    } else if (HierarchicalAgent?.Target is HierarchicalAgent targetAgent &&
+               otherAgent == targetAgent.Agent) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a8f47e and 4f4e9e6.

⛔ Files ignored due to path filters (3)
  • Assets/Resources/Prefabs/DummyAgent.prefab is excluded by !**/*.prefab
  • Assets/Scenes/MainScene.unity is excluded by !**/*.unity
  • Assets/Scripts/Generated/Proto/FlightPhase.cs is excluded by !**/generated/**
📒 Files selected for processing (33)
  • Assets/Proto/Simulation/flight_phase.proto (1 hunks)
  • Assets/Scripts/Agents/AgentBase.cs (7 hunks)
  • Assets/Scripts/Agents/IAgent.cs (2 hunks)
  • Assets/Scripts/Algorithms/Clustering/Cluster.cs (1 hunks)
  • Assets/Scripts/Behavior/AttackBehaviorFactory.cs (0 hunks)
  • Assets/Scripts/Behavior/AttackBehaviorFactory.cs.meta (0 hunks)
  • Assets/Scripts/Config/ConfigLoader.cs (1 hunks)
  • Assets/Scripts/Hierarchical/FixedHierarchical.cs (1 hunks)
  • Assets/Scripts/Hierarchical/HierarchicalAgent.cs (1 hunks)
  • Assets/Scripts/Hierarchical/HierarchicalBase.cs (3 hunks)
  • Assets/Scripts/Hierarchical/IHierarchical.cs (1 hunks)
  • Assets/Scripts/IADS/IADS.cs (2 hunks)
  • Assets/Scripts/Interceptors/CarrierBase.cs (1 hunks)
  • Assets/Scripts/Interceptors/CarrierInterceptor.cs (1 hunks)
  • Assets/Scripts/Interceptors/IInterceptor.cs (1 hunks)
  • Assets/Scripts/Interceptors/InterceptorBase.cs (2 hunks)
  • Assets/Scripts/Managers/InputManager.cs (1 hunks)
  • Assets/Scripts/Managers/ParticleManager.cs (8 hunks)
  • Assets/Scripts/Managers/SimManager.cs (5 hunks)
  • Assets/Scripts/Monitors/SimMonitor.cs (4 hunks)
  • Assets/Scripts/Movements/MissileMovement.cs (3 hunks)
  • Assets/Scripts/Movements/MovementBase.cs (1 hunks)
  • Assets/Scripts/Release/MassReleaseStrategyBase.cs (1 hunks)
  • Assets/Scripts/Release/SingleReleaseStrategyBase.cs (2 hunks)
  • Assets/Scripts/Threats/IThreat.cs (1 hunks)
  • Assets/Scripts/Threats/ThreatBase.cs (3 hunks)
  • Assets/Scripts/UI/CameraController.cs (7 hunks)
  • Assets/Scripts/UI/UIManager.cs (5 hunks)
  • Assets/StreamingAssets/Configs/Simulations/5_swarms_500_ucav.pbtxt (1 hunks)
  • Assets/Tests/EditMode/AgentBaseTests.cs (3 hunks)
  • Assets/Tests/EditMode/FixedHierarchicalTests.cs (1 hunks)
  • Assets/Tests/EditMode/FixedHierarchicalTests.cs.meta (1 hunks)
  • Assets/Tests/PlayMode/ConfigsTests.cs (2 hunks)
💤 Files with no reviewable changes (2)
  • Assets/Scripts/Behavior/AttackBehaviorFactory.cs.meta
  • Assets/Scripts/Behavior/AttackBehaviorFactory.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-17T05:39:03.474Z
Learnt from: tryuan99
Repo: PisterLab/micromissiles-unity PR: 110
File: Assets/Scripts/Algorithms/Clustering/Cluster.cs:123-126
Timestamp: 2025-10-17T05:39:03.474Z
Learning: In `Assets/Scripts/Algorithms/Clustering/Cluster.cs`, the `IsFullyTerminated()` method intentionally treats null agents as terminated via `?? true`, which differs from the null-handling in `Centroid()` and `Velocity()` methods that skip null agents entirely. This semantic difference is by design.

Applied to files:

  • Assets/Scripts/Hierarchical/IHierarchical.cs
  • Assets/Scripts/Hierarchical/HierarchicalAgent.cs
  • Assets/Scripts/Agents/AgentBase.cs
  • Assets/Scripts/Agents/IAgent.cs
  • Assets/Scripts/Algorithms/Clustering/Cluster.cs
  • Assets/Scripts/Threats/ThreatBase.cs
🧬 Code graph analysis (18)
Assets/Scripts/Interceptors/CarrierInterceptor.cs (3)
Assets/Scripts/Agents/AgentBase.cs (1)
  • LateUpdate (181-181)
Assets/Scripts/Movements/MissileMovement.cs (2)
  • MissileMovement (14-120)
  • MissileMovement (31-33)
Assets/Scripts/Agents/Agent.cs (1)
  • FlightPhase (73-75)
Assets/Scripts/Release/MassReleaseStrategyBase.cs (3)
Assets/Scripts/Interceptors/CarrierBase.cs (1)
  • CarrierBase (9-73)
Assets/Scripts/Release/SingleReleaseStrategyBase.cs (1)
  • List (15-64)
Assets/Scripts/Release/IReleaseStrategy.cs (1)
  • List (13-13)
Assets/Scripts/Movements/MovementBase.cs (3)
Assets/Scripts/Movements/IMovement.cs (1)
  • Vector3 (12-12)
Assets/Scripts/Movements/GroundMovement.cs (1)
  • Vector3 (12-17)
Assets/Scripts/Movements/IdealMovement.cs (1)
  • Vector3 (12-14)
Assets/Scripts/Hierarchical/IHierarchical.cs (2)
Assets/Scripts/Agents/Agent.cs (1)
  • IsTerminated (81-83)
Assets/Scripts/Hierarchical/HierarchicalBase.cs (5)
  • AddSubHierarchical (42-46)
  • RemoveSubHierarchical (48-50)
  • AddPursuer (56-60)
  • RemovePursuer (62-64)
  • IsEscapingPursuers (66-87)
Assets/Scripts/Interceptors/IInterceptor.cs (1)
Assets/Scripts/Managers/SimManager.cs (1)
  • IInterceptor (134-167)
Assets/Scripts/Managers/InputManager.cs (1)
Assets/Scripts/Managers/SimManager.cs (1)
  • SimManager (7-308)
Assets/Scripts/Threats/IThreat.cs (3)
Assets/Scripts/Managers/SimManager.cs (2)
  • IThreat (171-202)
  • IAgent (204-211)
Assets/Scripts/Threats/ThreatBase.cs (3)
  • IAgent (123-140)
  • LookupPowerTable (32-35)
  • HandleIntercept (37-40)
Assets/Scripts/Config/ConfigLoader.cs (4)
  • Configs (41-46)
  • Configs (48-57)
  • Configs (59-63)
  • Configs (65-70)
Assets/Scripts/Agents/AgentBase.cs (2)
Assets/Scripts/Agents/Agent.cs (6)
  • IsTerminated (81-83)
  • FlightPhase (73-75)
  • Vector3 (187-189)
  • Vector3 (199-201)
  • Vector3 (207-209)
  • Vector3 (405-416)
Assets/Scripts/Hierarchical/HierarchicalAgent.cs (2)
  • HierarchicalAgent (7-33)
  • HierarchicalAgent (30-32)
Assets/Scripts/Config/ConfigLoader.cs (1)
Assets/Scripts/Plugins/Protobuf.cs (1)
  • Protobuf (4-52)
Assets/Scripts/Release/SingleReleaseStrategyBase.cs (6)
Assets/Scripts/Interceptors/CarrierBase.cs (3)
  • CarrierBase (9-73)
  • RegisterSubInterceptorHit (22-35)
  • RegisterSubInterceptorMiss (38-42)
Assets/Scripts/Release/ReleaseStrategyBase.cs (1)
  • List (12-12)
Assets/Scripts/Hierarchical/HierarchicalBase.cs (1)
  • IsEscapingPursuers (66-87)
Assets/Scripts/Utils/Coordinates3.cs (2)
  • Simulation (64-70)
  • Coordinates3 (11-71)
Assets/Scripts/Hierarchical/HierarchicalAgent.cs (2)
  • HierarchicalAgent (7-33)
  • HierarchicalAgent (30-32)
Assets/Scripts/UI/UIManager.cs (2)
  • UIManager (9-233)
  • LogActionMessage (89-91)
Assets/Tests/EditMode/FixedHierarchicalTests.cs (1)
Assets/Scripts/Hierarchical/HierarchicalBase.cs (1)
  • Vector3 (89-102)
Assets/Scripts/Agents/IAgent.cs (2)
Assets/Scripts/Agents/Agent.cs (1)
  • IsTerminated (81-83)
Assets/Scripts/Agents/AgentBase.cs (4)
  • MaxForwardAcceleration (106-108)
  • MaxNormalAcceleration (110-115)
  • UpdateTargetModel (117-133)
  • Terminate (147-158)
Assets/Scripts/Algorithms/Clustering/Cluster.cs (2)
Assets/Scripts/Agents/Agent.cs (1)
  • IsTerminated (81-83)
Assets/Scripts/Hierarchical/HierarchicalBase.cs (1)
  • Vector3 (89-102)
Assets/Scripts/Hierarchical/HierarchicalBase.cs (1)
Assets/Scripts/Hierarchical/IHierarchical.cs (2)
  • AddPursuer (25-25)
  • IsEscapingPursuers (28-28)
Assets/Scripts/Movements/MissileMovement.cs (3)
Assets/Scripts/Movements/MovementBase.cs (2)
  • Vector3 (15-15)
  • Vector3 (18-29)
Assets/Scripts/Movements/IMovement.cs (1)
  • Vector3 (12-12)
Assets/Scripts/Movements/AerialMovement.cs (2)
  • CalculateDrag (18-26)
  • CalculateLiftInducedDrag (30-36)
Assets/Scripts/Threats/ThreatBase.cs (3)
Assets/Scripts/Threats/IThreat.cs (2)
  • LookupPowerTable (22-22)
  • HandleIntercept (25-25)
Assets/Scripts/Agents/AgentBase.cs (3)
  • Terminate (147-158)
  • Start (166-166)
  • CheckFloorCollision (201-204)
Assets/Scripts/Agents/IAgent.cs (1)
  • Terminate (40-40)
Assets/Scripts/Managers/SimManager.cs (3)
Assets/Scripts/Config/ConfigLoader.cs (5)
  • Configs (41-46)
  • Configs (48-57)
  • Configs (59-63)
  • Configs (65-70)
  • ConfigLoader (6-96)
Assets/Scripts/Release/SingleReleaseStrategyBase.cs (1)
  • List (15-64)
Assets/Scripts/Hierarchical/HierarchicalAgent.cs (2)
  • HierarchicalAgent (7-33)
  • HierarchicalAgent (30-32)
Assets/Scripts/Hierarchical/FixedHierarchical.cs (1)
Assets/Scripts/Hierarchical/HierarchicalBase.cs (2)
  • HierarchicalBase (9-103)
  • Vector3 (89-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build plugins for windows-latest
  • GitHub Check: Test plugins on windows-latest
  • GitHub Check: Test plugins on macos-latest
  • GitHub Check: Test plugins on ubuntu-latest
🔇 Additional comments (62)
Assets/Scripts/Movements/MovementBase.cs (1)

20-21: LGTM! Clearer intent with ProjectOnPlane.

Using Vector3.ProjectOnPlane to compute the normal (perpendicular) component is more explicit and readable than the likely previous approach of subtracting the forward component. Both are mathematically equivalent, but this refactor improves code clarity.

Assets/Scripts/Algorithms/Clustering/Cluster.cs (1)

21-25: LGTM! Properly handles empty sequences and filters terminated hierarchicals.

The implementation correctly uses DefaultIfEmpty() and a null-check to prevent InvalidOperationException when all subHierarchicals are terminated. This aligns with the termination-aware logic in HierarchicalBase.GetMean() which similarly skips terminated entries.

Assets/Scripts/Agents/IAgent.cs (1)

7-8: Clean termination API design.

The interface additions provide a complete termination lifecycle:

  • IsTerminated for state queries
  • OnTerminated event for reactive subscribers
  • Terminate() for explicit termination

This enables event-driven architecture for agent lifecycle management as described in the PR objectives.

Also applies to: 28-28, 30-30, 33-33, 40-40

Assets/Scripts/Agents/AgentBase.cs (1)

177-184: Virtual lifecycle stubs provide extension points for subclasses.

Empty Update, LateUpdate, and OnDestroy stubs follow Unity's common pattern for allowing derived classes to override without requiring a base call.

Assets/Scripts/Config/ConfigLoader.cs (1)

59-63: No change needed: LoadSimulatorConfig correctly reflects the file location.

The simulator.pbtxt file is intentionally stored at Assets/StreamingAssets/simulator.pbtxt (the root level), not in a Configs/Simulators subdirectory like other config types. The method's implementation is correct—it passes configFile directly without prepending a directory path because the file's location differs from other config types. The apparent API inconsistency is by design and should not be changed.

Likely an incorrect or invalid review comment.

Assets/Proto/Simulation/flight_phase.proto (1)

12-13: Verify proto enum value renumbering context.

The change to BALLISTIC = 5 and TERMINATED = 6 is a wire-breaking change only if previously serialized data exists with TERMINATED at value 5. While the codebase uses symbolic names (mitigating immediate risk), unable to confirm the prior enum state. If this proto is consumed by external systems or backward compatibility is required, the enum values should remain fixed. If this is a new feature branch with no legacy data, the change is acceptable.

Assets/Tests/EditMode/AgentBaseTests.cs (1)

25-71: LGTM! Improved NUnit assertion order.

The assertion argument order has been corrected to follow NUnit conventions (expected before actual). This improves test readability and ensures proper error messages when assertions fail.

Assets/Scripts/Release/MassReleaseStrategyBase.cs (1)

21-23: LGTM! Proper guard clause for carrier validation.

The early-return guard correctly prevents release operations when the agent is not a CarrierBase or when no sub-interceptors remain. This aligns with the carrier lifecycle management introduced in CarrierBase.

Assets/Scripts/Hierarchical/IHierarchical.cs (1)

20-28: LGTM! Well-designed interface additions.

The IsTerminated property and IsEscapingPursuers() method extend the IHierarchical interface consistently with the broader termination and pursuit-tracking architecture. The additions integrate well with implementations in HierarchicalBase and FixedHierarchical.

Assets/Scripts/Interceptors/IInterceptor.cs (1)

7-15: LGTM! Well-structured interface evolution.

The interface now properly inherits from IAgent and introduces consistent event-based hit/miss signaling. This aligns well with the broader termination architecture and provides a clean event-driven pattern for interceptor lifecycle management.

Assets/Scripts/Managers/InputManager.cs (1)

185-185: LGTM! Proper migration to property-based API.

The change from method call to property access aligns with SimManager's refactored API. Using properties for simple state queries follows C# best practices and improves code readability.

Assets/Scripts/Threats/IThreat.cs (1)

9-25: LGTM! Comprehensive interface evolution.

The interface properly inherits from IAgent and introduces consistent event-based lifecycle management. The OnHit/OnMiss events and HandleIntercept() method provide clear semantics for threat termination scenarios, aligning well with the broader architecture refactoring.

Assets/Scripts/Movements/MissileMovement.cs (3)

62-64: LGTM!

The ballistic phase case correctly routes to the new ActBallistic method, maintaining consistency with the existing flight phase handling pattern.


107-110: LGTM!

The ballistic phase correctly applies zero control acceleration while still accounting for drag and gravity through ActMidCourse. The named parameter syntax makes the intent clear.


112-119: LGTM!

Including gravity in the lift-induced drag calculation is physically correct—the lift force must counteract gravity in addition to any commanded normal acceleration, so the total should be considered when computing induced drag.

Assets/Scripts/Release/SingleReleaseStrategyBase.cs (5)

11-11: LGTM!

Increasing _initialSpeed from 1e-6f to 1e-3f provides a more stable initial velocity for rotation calculation while remaining negligibly small relative to actual missile speeds.


20-23: LGTM!

The carrier cast with null check is appropriate defensive programming. This ensures the release strategy gracefully handles cases where it's attached to a non-carrier agent.


27-35: LGTM!

The guard conditions are well-structured:

  • Prevents over-allocation by tracking remaining capacity minus already-released count
  • Skips terminated targets
  • Avoids redundant launches on targets already effectively pursued

50-53: LGTM!

The event subscriptions correctly wire OnHit to RegisterSubInterceptorHit and OnMiss to RegisterSubInterceptorMiss, properly handling both hit and miss outcomes for sub-interceptors.


55-59: LGTM!

The logging provides useful observability for interceptor launches, with both debug console and UI output for different consumers.

Assets/Scripts/Interceptors/InterceptorBase.cs (3)

10-11: LGTM!

The OnHit and OnMiss events align with the IInterceptor interface, enabling event-driven interception handling.


44-50: LGTM!

Downgrading to LogWarning is appropriate since a missing controller type is handled gracefully (sets Controller = null) rather than causing a failure.


68-80: LGTM!

The threat collision logic correctly implements probabilistic kill handling:

  • Invokes OnHit and calls HandleIntercept on successful kill
  • Invokes OnMiss when probability check fails
  • Terminates the interceptor in both cases (consumed on collision)
Assets/Scripts/Hierarchical/HierarchicalBase.cs (4)

28-30: LGTM!

The null check before AddPursuer prevents NRE when target is set to null.


36-40: LGTM!

The virtual properties correctly expose aggregated state from sub-hierarchicals. IsTerminated using All() correctly returns true for empty collections (no active items means effectively terminated).


66-87: LGTM!

The IsEscapingPursuers implementation properly addresses the edge cases raised in past reviews:

  • Returns false when Target == null or Speed <= 0
  • Guards against pursuer.Speed <= 0 to prevent division by zero
  • Returns true (vacuously) when there are no pursuers

89-102: LGTM!

The refactored GetMean addresses the past review concern about memory allocation by using a single-pass manual iteration instead of ToList(). Correctly handles the all-terminated case by returning Vector3.zero.

Assets/Tests/EditMode/FixedHierarchicalTests.cs (1)

4-14: LGTM!

The test setup correctly initializes FixedHierarchical with test values and stores it as IHierarchical to verify polymorphic access.

Assets/Scripts/Interceptors/CarrierBase.cs (2)

44-55: LGTM!

Proper lifecycle management with coroutine cleanup in OnDestroy.


62-72: Good implementation of the suggested improvement.

The coroutine now correctly terminates when all sub-interceptors are released, and resets the reference to allow restart. This addresses the previous review feedback.

Assets/Tests/PlayMode/ConfigsTests.cs (2)

28-30: API migration looks correct.

The method calls have been updated to match the new SimManager API (LoadNewSimulationConfig and ElapsedSimulationTime property).


52-53: LGTM!

Property access updated consistently with the API changes.

Assets/Scripts/Hierarchical/HierarchicalAgent.cs (2)

11-14: LGTM!

Properly overriding base class virtual properties to proxy to the underlying Agent. This maintains correct polymorphic behavior.


16-28: Target lifecycle management looks correct.

The setter properly manages dummy agent creation/destruction. One edge case to verify: if base.Target is set to a terminated/destroyed hierarchical, accessing Position and Velocity on line 25 might yield stale or invalid data. Ensure terminated targets are filtered upstream before assignment.

Assets/Scripts/IADS/IADS.cs (4)

7-24: LGTM!

Clean singleton implementation with proper encapsulation of mutable collections via AsReadOnly().


68-75: LGTM!

Periodic clustering with proper guard for empty threat list.


106-119: Verify recursive target assignment is intentional.

The local AssignTarget function assigns the same target to all sub-hierarchicals recursively (line 109). This means every threat in a swarm will target the same launcher. If sub-hierarchicals should target different entities (e.g., specific carrier interceptors), this needs adjustment.

Also, consider removing Debug.Log statements (lines 86, 97) in favor of the existing UIManager logging to reduce console noise in production.


91-95: Good fix for zero launchers edge case.

The guard at lines 92-94 properly handles the case when no launchers are registered, preventing issues with KMeansClusterer. This addresses the previous review feedback.

Assets/Scripts/Threats/ThreatBase.cs (5)

7-8: LGTM!

The speed error threshold constant is well-defined for controlling forward acceleration behavior.


29-30: LGTM!

Events for hit/miss align with the PR's goal of exposing lifecycle events for threats.


37-40: LGTM!

HandleIntercept() properly invokes OnMiss and terminates the threat, fulfilling the interface contract.


42-61: LGTM!

Target selection logic correctly finds the nearest launcher and falls back to a fixed position at origin when no launchers exist.


63-91: LGTM!

The speed-aware acceleration logic properly decomposes acceleration into forward and normal components, applying speed-based scaling to regulate thrust according to the desired waypoint power.

Assets/Scripts/UI/UIManager.cs (6)

29-43: LGTM!

The new UI text handles and counter variables are well-organized for tracking interceptor hits, misses, and remaining counts for both interceptors and threats.


59-61: LGTM!

Event subscriptions correctly wire up to the new interface-based events from SimManager.


125-125: LGTM!

Config loading correctly delegates to the new SimManager.LoadNewSimulationConfig API.


143-145: LGTM!

Time display correctly uses the new ElapsedSimulationTime property.


181-186: LGTM!

Counters are properly reset when the simulation ends.


196-227: LGTM!

Event handlers correctly use interface-based types (IInterceptor, IThreat, IAgent) and properly manage counter state. The type checks in RegisterAgentTerminated correctly distinguish between interceptors and threats.

Assets/Scripts/Monitors/SimMonitor.cs (3)

93-124: LGTM!

Telemetry recording correctly uses interface-based agent access and filters terminated agents. The binary format writes position, velocity, and agent type consistently.


127-158: LGTM!

Binary-to-CSV conversion correctly reads the updated telemetry format with position and velocity components.


200-245: LGTM!

Event registration methods correctly use interface types (IThreat, IInterceptor, IAgent). The interceptor hit/miss handlers properly wire up to the new OnHit/OnMiss events.

Assets/Scripts/Managers/ParticleManager.cs (4)

93-103: LGTM!

Event registration correctly uses interface types and wires up to the new OnHit/OnMiss events for both interceptors and threats.


105-130: LGTM!

Handler methods correctly use interface types (IAgent, IInterceptor, IThreat). The empty handlers for threat hit/miss are placeholders for future functionality.


132-143: LGTM!

Trail renderer commandeering correctly uses interface-based type checking to determine material assignment.


161-161: LGTM!

Using in parameter for Vector3 is appropriate for avoiding unnecessary copies of the struct.

Assets/Scripts/Managers/SimManager.cs (6)

133-167: LGTM!

The interceptor creation flow with config loading, hierarchical agent assignment, cost tracking, and event invocation is well-structured.


169-202: LGTM!

The threat creation logic correctly loads config, assigns the hierarchical agent, subscribes to the miss event for cost tracking, and invokes the new threat event.


204-216: LGTM!

Dummy agent creation and destruction are cleanly implemented with proper list management.


218-252: LGTM!

The agent creation helpers properly handle prefab loading, coordinate conversion, and randomization with configurable noise.


254-272: LGTM!

The singleton pattern implementation with DontDestroyOnLoad and conditional startup logic is correct.


283-307: LGTM!

Initialization methods correctly iterate over swarm configs and fire appropriate events. Cost tracking via RegisterThreatMiss aligns with the event-based architecture for tracking neutralized threats.

Comment thread Assets/Scripts/Agents/AgentBase.cs Resolved
Comment thread Assets/Scripts/IADS/IADS.cs Resolved
Comment thread Assets/Scripts/IADS/IADS.cs Resolved
Comment on lines +151 to +159
IAgent otherAgent = other.gameObject.GetComponentInParent<IAgent>();
// Dummy agents are virtual targets and should not trigger collisions.
if (otherAgent == null || otherAgent is DummyAgent) {
return;
}
// Check if the collision is with another threat or with the intended target.
if (otherAgent is IThreat) {
OnMiss?.Invoke(this);
Terminate();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Early return missing after floor collision terminates the agent.

When floor collision is detected at lines 146-149, the method invokes OnMiss and Terminate() but continues execution. Subsequent code may access the now-terminated agent or trigger additional events. Add a return after termination.

Apply this diff:

     if (CheckFloorCollision(other)) {
       OnMiss?.Invoke(this);
       Terminate();
+      return;
     }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Assets/Scripts/Threats/ThreatBase.cs around lines 151 to 159, the
floor-collision branch calls OnMiss?.Invoke(this) and Terminate() but does not
return, so execution continues after the agent has been terminated; add an
immediate return statement right after Terminate() to stop further processing in
that method and avoid accessing the terminated agent or firing additional
events.

Comment on lines +256 to 268
public void SnapToSwarm(List<IAgent> swarm, bool forceFreeMode = true) {
// TODO(titan): To be implemented.
}

public void SnapToNextInterceptorSwarm(bool forceFreeMode = true) {
if (SimManager.Instance.GetInterceptorSwarms().Count == 0) {
UIManager.Instance.LogActionWarning("[CAM] No interceptor swarms to follow");
return;
}

// Set pre-set view 1
_selectedInterceptorSwarmIndex += 1;
_selectedThreatSwarmIndex = -1;
if (_selectedInterceptorSwarmIndex >= SimManager.Instance.GetInterceptorSwarms().Count) {
_selectedInterceptorSwarmIndex = 0;
}
List<(Agent, bool)> swarm =
SimManager.Instance.GetInterceptorSwarms()[_selectedInterceptorSwarmIndex];
string swarmTitle = SimManager.Instance.GenerateInterceptorSwarmTitle(swarm);

// Filter out inactive agents
List<(Agent, bool)> activeAgents = swarm.FindAll(tuple => tuple.Item2);
List<Agent> activeAgentsList = activeAgents.ConvertAll(tuple => tuple.Item1);
Vector3 swarmCenter = SimManager.Instance.GetSwarmCenter(activeAgentsList);
SetCameraTargetPosition(swarmCenter);
if (forceFreeMode) {
SetCameraMode(CameraMode.FREE);
}
UIManager.Instance.LogActionMessage($"[CAM] Snap to interceptor swarm: {swarmTitle}");
// TODO(titan): To be implemented.
return;
}

public void SnapToNextThreatSwarm(bool forceFreeMode = true) {
if (SimManager.Instance.GetThreatSwarms().Count == 0) {
return;
}
_selectedInterceptorSwarmIndex = -1;
_selectedThreatSwarmIndex += 1;
if (_selectedThreatSwarmIndex >= SimManager.Instance.GetThreatSwarms().Count) {
_selectedThreatSwarmIndex = 0;
}
List<(Agent, bool)> swarm = SimManager.Instance.GetThreatSwarms()[_selectedThreatSwarmIndex];
string swarmTitle = SimManager.Instance.GenerateThreatSwarmTitle(swarm);
// Filter out inactive agents
List<(Agent, bool)> activeAgents = swarm.FindAll(tuple => tuple.Item2);
List<Agent> activeAgentsList = activeAgents.ConvertAll(tuple => tuple.Item1);
Vector3 swarmCenter = SimManager.Instance.GetSwarmCenter(activeAgentsList);
SetCameraTargetPosition(swarmCenter);
if (forceFreeMode) {
SetCameraMode(CameraMode.FREE);
}
UIManager.Instance.LogActionMessage($"[CAM] Snap to threat swarm: {swarmTitle}");
// TODO(titan): To be implemented.
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "CameraController.cs" -type f

Repository: PisterLab/micromissiles-unity

Length of output: 112


🏁 Script executed:

wc -l ./Assets/Scripts/UI/CameraController.cs

Repository: PisterLab/micromissiles-unity

Length of output: 116


🏁 Script executed:

sed -n '250,310p' ./Assets/Scripts/UI/CameraController.cs

Repository: PisterLab/micromissiles-unity

Length of output: 1888


🏁 Script executed:

sed -n '220,250p' ./Assets/Scripts/UI/CameraController.cs

Repository: PisterLab/micromissiles-unity

Length of output: 1056


TODO stubs in follow methods may create silent failures.

SnapToNextInterceptorSwarm and SnapToNextThreatSwarm are stubbed with early returns, but callers like FollowNextInterceptorSwarm() and FollowNextThreatSwarm() continue execution after these methods return. This causes the camera mode to switch and the centroid update coroutine to start without actually setting any swarm target position, resulting in undefined follow behavior.

Consider either disabling the follow functionality entirely until implemented, or logging a warning when these methods are called.

🤖 Prompt for AI Agents
In Assets/Scripts/UI/CameraController.cs around lines 256-268, the three TODO
stub methods (SnapToSwarm, SnapToNextInterceptorSwarm, SnapToNextThreatSwarm)
return or do nothing which lets callers proceed and start follow behavior with
no target; change these stubs so they either (a) log a clear warning/error and
early-return before any camera mode changes or coroutine starts, or (b)
implement the actual snapping behavior; minimally, add a warning log in each
method and ensure they do NOT trigger camera mode switching or start the
centroid-follow coroutine when no valid swarm target is set, and update callers
or method return semantics if needed so callers can detect the failure and avoid
continuing follow logic.

Comment on lines +479 to +490
private Vector3 GetAllAgentsCenter() {
var agentPositions = SimManager.Instance.Agents.Select(agent => agent.Position).ToList();
if (agentPositions.Count == 0) {
return Vector3.zero;
}

var sum = Vector3.zero;
foreach (var position in agentPositions) {
sum += position;
}
return sum / agentPositions.Count;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using LINQ for center calculation.

The helper can be simplified using LINQ's Aggregate or a simple average pattern.

 private Vector3 GetAllAgentsCenter() {
-    var agentPositions = SimManager.Instance.Agents.Select(agent => agent.Position).ToList();
-    if (agentPositions.Count == 0) {
+    var agents = SimManager.Instance.Agents;
+    if (!agents.Any()) {
       return Vector3.zero;
     }
-
-    var sum = Vector3.zero;
-    foreach (var position in agentPositions) {
-      sum += position;
-    }
-    return sum / agentPositions.Count;
+    return agents.Aggregate(Vector3.zero, (sum, agent) => sum + agent.Position) / agents.Count;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private Vector3 GetAllAgentsCenter() {
var agentPositions = SimManager.Instance.Agents.Select(agent => agent.Position).ToList();
if (agentPositions.Count == 0) {
return Vector3.zero;
}
var sum = Vector3.zero;
foreach (var position in agentPositions) {
sum += position;
}
return sum / agentPositions.Count;
}
private Vector3 GetAllAgentsCenter() {
var agents = SimManager.Instance.Agents;
if (!agents.Any()) {
return Vector3.zero;
}
return agents.Aggregate(Vector3.zero, (sum, agent) => sum + agent.Position) / agents.Count();
}
🤖 Prompt for AI Agents
In Assets/Scripts/UI/CameraController.cs around lines 479 to 490, the
GetAllAgentsCenter method manually builds a list and loops to sum positions;
replace that with a concise LINQ-based calculation (e.g., project Positions and
use Aggregate or compute component-wise Average) and handle the empty case by
returning Vector3.zero. Ensure you avoid creating an intermediate list if
unnecessary (operate on the IEnumerable directly), and keep the result as a
Vector3 average of agent positions.

@tryuan99
tryuan99 merged commit 4ae12a8 into titan/hierarchical Dec 10, 2025
11 checks passed
@tryuan99
tryuan99 deleted the titan/iads branch December 10, 2025 07:23
tryuan99 added a commit that referenced this pull request Dec 18, 2025
[All] Implement fully hierarchical defense strategy

* [Agent] Add agent-related interfaces and base implementations (#95)
* [Interceptor/Threat] Add interceptor and threat interfaces (#96)
* [Algorithm] Add algorithm interfaces and base implementations (#97)
* [Movements] Implement all aerial and ground-based movements (#99)
* [Gemini] Disable Gemini pull request summary (#142)
* [Sensor] Add sensor interface and ideal sensor implementation (#100)
* [Chore] Clean up private properties (#101)
* [Evasion] Implement evasion behaviors (#102)
* [Agent] Add agent-related interfaces and base implementations (#95)
* [Interceptor/Threat] Add interceptor and threat interfaces (#96)
* [Algorithm] Add algorithm interfaces and base implementations (#97)
* [Movements] Implement all aerial and ground-based movements (#99)
* [Sensor] Add sensor interface and ideal sensor implementation (#100)
* [Chore] Clean up private properties (#101)
* [Evasion] Implement evasion behaviors (#102)
* [Attack Behavior] Implement direct attack behavior (#103)
* [Controller] Add controller interface and implementations (#105)
* [Predictor/Planner] Clean up predictor and planner classes (#108)
* [Clustering] Change clustering algorithms to act on hierarchical objects (#111)
* [Utils] Clean up coordinates classes and tests (#112)
* [Assignment] Refactor assignment algorithms (#113)
* [Accessor] Use init accessor for read-only fields (#116)
* [Interceptors/Threats] Add Legacy prefix to existing interceptor and threat class names (#121)
* [Interceptors/Threats] Implement all interceptor and threat types (#122)
* [Proto/Configs] Add launcher types to proto definition and simulation configs (#123)
* [IADS] Clean up IADS and simulation manager (#124)
* [UI] Remove swarm status dialog and tactical panel (#125)
* [All] Remove all legacy code (#127)
* [Prefabs] Add prefabs for launchers (#128)
* [Release] Adjust minimum distance in proximity release (#129)
* [Configs] Add simulation configuration with 2 launchers (#130)
* [Simulation] Destroy terminated game objects (#132)
* [UI] Draw gizmos for agents (#133)
* [UI] Fix summary text label sizes (#134)
* [Clustering] Implement recursive clustering within hierarchy (#139)
* [UI] Clean up UI scripts (#140)
* [Reassignment] Assign new targets to excess interceptors (#141)
* [Evasion] Fix threat evasion of interceptors (#143)
* [Reassignment] Use capacity remaining to reassign interceptors (#144)
* [Agent] Add IsPursuable property (#145)
* [Reassignment] Implement threat reassignment (#146)
* [Escape] Add escape detectors (#147)
* [Escape] Check for escaping targets (#148)
* [Movement] Fix missile movement to avoid the ground (#149)
* [Reassignment] Fix relaunching multiple carrier interceptors (#150)
* [Unity] Fix serializable fields and references (#152)
* [Docs] Update docs with hierarchical strategy (#153)
* [Utils] Sample from normal distribution (#154)
* [Proto] Add run configuration proto (#155)
* [Visualization] Fix visualizing simulation monitor logs (#157)
* [Tools] Build standalone player (#158)
* [Manager] Add run manager to handle run configurations (#159)
* [Tools] Refactor common simulation-related constants and utilities (#160)
* [Tools] Add metric and aggregator classes (#161)
* [Tools] Process the aggregated logs of a simulation run (#162)
* [Docs] Update documentation with run configuration and logging (#163)
* [Tests] Remove unused Rigidbody variables from unit tests (#166)
* [Agent] Add target model API in Agent interface (#167)
* [Logging] Emit more detailed events (#168)
* [Metrics] Consider only missile interceptors in metrics (#169)
* [Logging] Remove logging from CP-SAT solver (#171)
* [Tools] Add script to animate telemetry log (#172)
* [Docs] Remove unnecessary docs image files (#174)
* [Interceptor] Rename IsPursuable to IsPursuer (#175)
* [UI] Reimplement tactical panel (#176)
* [UI] Add camera to follow and rotate around interceptors or threats (#177)
* [Configs] Add configuration for 5 swarms and 1000 UCAVs (#179)
* [Agent] Cache the agent transform (#180)
* [Agent] Clean up finding relative transformations (#181)
* [Configs] Exclude 5_swarms_1000_ucav configuration from CI (#182)
* [Agents] Fix collisions with terminated agents (#184)
* [Controls] Implement vector proportional navigation (#186)
* [Reassignment] Find subhierarchicals with at least one active target (#187)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants