Point Defense Controller

From NEBULOUS: Fleet Command Official Wiki

The game's point defense controller prioritizes targets based on a variety of factors. Analysis based on Nebulous.Ships.PointDefenseController from the ilspy decompilation of game version 0.6.2.6:260903-1430.

Working loop

Every two seconds, or when the PD target list changes, Ships.PointDefenseController.SortPointDefensePriorities is called.[1]

SortPointDefensePriorities does a whole lot of things but primarily first sorts the list of all point defense targets then tasks turrets and AMMs down the list of targets, then launches decoys.[2]

Sorting the list

First, SortPointDefensePriorities calculates properties of each PD target and assigns them to it.

foreach (PDTarget target in _allTargets)
{
	Vector3 fromTargetToShip = (_ship.Position - target.Track.KnownPosition).normalized;
	Vector3 targetVelocity = target.Track.AbsoluteVelocity.normalized;
	target.Angle = Vector3.Angle(targetVelocity, fromTargetToShip);
	target.Range = Vector3.Distance(target.Track.KnownPosition, _ship.Position);
	target.TimeToImpact = target.Range.SafeDivideMaxVal(Vector3.Project(target.Track.AbsoluteVelocity, fromTargetToShip).magnitude);
	target.WeaponsAssigned = 0;
	target.SensorsAssigned = 0;
}

[2]

It then sorts the list of _allTargets; PDTarget implements System.IComparable so it is sorted normally.

Comparing PDTargets

Sorting requires an object have some way of being distinguished as higher or lower than another object; this is the CompareTo function that is defined by IComparable. Ships.PointDefenseController+PDTarget.CompareTo(PDTarget other) is as follows:

if (this == other)
{
	return 0;
}

If both this and other point at the same memory location (i.e. are exactly the same), then both are sorted equally (e.g. sorting two equal numbers).

Lemma 1: sortAsPriority

The compareTo function is structured in such a way that priority targets will always take precedence over (be sorted in front of) non-priority targets. What defines a priority target is the boolean SortAsPriority:

public bool SortAsPriority => PlayerPriority || (IsDecoy && IsDirectThreat);

All priority targets are either manually prioritized (i.e. Z-prioed), or are both a decoy and a direct threat.

Lemma 2: IsDirectThreat

A direct threat is defined by the boolean IsDirectThreat:

public bool IsDirectThreat
{
	get
	{
		if (IsCraft)
		{
			return false;
		}
		bool threat = Angle <= 10f;
		WasEverDirectThreat |= threat;
		return threat;
	}
}

If a target is a craft, it cannot ever be a direct threat. Otherwise, if its velocity vector is within ten degrees of its direction towards the ship, it is a direct threat.

if (SortAsPriority)
{
	if (other.SortAsPriority)
	{
		int sizeCompare = -MissileSize.CompareTo(other.MissileSize);
		if (sizeCompare != 0)
		{
			return sizeCompare;
		}
		return Range.CompareTo(other.Range);
	}
	return -1;
}
if (other.SortAsPriority)
{
	return 1;
}

If this is a priority target, and other is also a priority target, then whichever target has a larger size takes precedence. If both targets have the same size, then whichever is closer takes precendence. If other is not a priority target, then this takes precedence. If this is not a priority target, and other is, then other take precedence.

Lemma 3: Time to Impact

PDTarget.TimeToImpact is set by SortPointDefensePriorities as seen above.

target.TimeToImpact = target.Range.SafeDivideMaxVal(Vector3.Project(target.Track.AbsoluteVelocity, fromTargetToShip).magnitude);

It is calculated as the range to the target divided by the projected velocity towards the ship, and zero if the velocity is zero.

if (IsDirectThreat && other.IsDirectThreat)
{
	float ttiDiff = Mathf.Abs(TimeToImpact - other.TimeToImpact);
	if (ttiDiff <= 5f)
	{
		int sizeCompare2 = -MissileSize.CompareTo(other.MissileSize);
		if (sizeCompare2 != 0)
		{
			return sizeCompare2;
		}
		return invertRange ? (-TimeToImpact.CompareTo(other.TimeToImpact)) : TimeToImpact.CompareTo(other.TimeToImpact);
	}
	return invertRange ? (-TimeToImpact.CompareTo(other.TimeToImpact)) : TimeToImpact.CompareTo(other.TimeToImpact);
}
if (IsDirectThreat)
{
	return -1;
}
if (other.IsDirectThreat)
{
	return 1;
}
return invertRange ? (-TimeToImpact.CompareTo(other.TimeToImpact)) : TimeToImpact.CompareTo(other.TimeToImpact);

If both this and other are direct threats, the code first calculates the difference between the time to impacts. If this is less than five seconds, the larger target takes precedence. If both targets are the same size, then whichever impacts first takes precedence. However, if invertRange is true , then whichever impacts last takes precedence. If the time to impact difference is greater than five seconds, whichever impacts first (or last) takes precedence.

If this is a direct threat, and other is not, then this takes precedence. If other is a direct threat, and this is not, then other takes precedence.

Queuing point defense fire

Queuing point defense missile launches

Launching decoys

References

  1. ↑ Nebulous.Ships.PointDefenseController.Update
  2. ↑ 2.0 2.1 Ships.PointDefenseController.SortPointDefensePriorities