RIM-116 & Phalanx
Radar coverage
One radar covers every mount in range. Shoot it and they fall back to their own sensor: much shorter range and worse accuracy. It is a target worth protecting – and worth taking out first.
Why a separate radar at all
A mount that carries its own full-performance sensor is a self-contained problem: kill it or do not. Splitting the sensor off gives the attacker a cheaper first objective and the defender something to put behind cover. It also matches the real systems – a close-in launcher is cued by the ship's radar.
| State | Range against aircraft (example) | Accuracy |
|---|---|---|
| Full radar coverage | 100 % | as configured |
| No radar in range (emergency mode) | about 30 % | degraded |
Terrain blocks it – sample, do not raycast
The first version traced a physics ray. A ray ends at the target position, and that position is the helicopter's own origin – a point inside its hull. On the last metre the ray hit the target itself and reported “occluded”: a helicopter parked in the open blocked its own line of sight.
Sampling the terrain height along the line has no such problem, is cheaper, and models the thing better. A radar beam does not care about a fence, but it very much cares about a ridge.
bool MyAD_Radar_Sees(vector target)
{
vector eye = GetOwner().GetOrigin();
eye[1] = eye[1] + ANTENNA_HEIGHT; // the beam starts at the antenna, not at the ground
if (vector.Distance(eye, target) > m_fRange)
return false;
vector line = target - eye;
int steps = Math.ClampInt(line.Length() / 40, 8, 100);
for (int i = 1; i < steps; i++)
{
vector p = eye + line * ((float)i / steps);
// only terrain that clearly rises above the line blocks it - a grazing crest does not
if (GetGame().GetWorld().GetSurfaceY(p[0], p[2]) - p[1] > 5)
return false;
}
return true;
}
Ground clutter, with hysteresis
Seeing a target is not the same as holding a track on it. Below a set height above ground a radar detects but cannot track. Without a margin that threshold is a knife edge: whoever flies along it flips the condition every second. In testing the lock dropped many times a minute with clear line of sight, and the log named the reason: a target at 29.99 m against a threshold of 30.
The cure is the thermostat principle: acquire above one value, lose only below a lower one. A track, once built, holds longer than it forms.
bool MyAD_Radar_InClutter(vector pos, bool alreadyTracking)
{
float limit = m_fClutterHeight;
if (alreadyTracking)
limit = limit - 8; // hysteresis: lose it only well below where it was gained
float height = pos[1] - GetGame().GetWorld().GetSurfaceY(pos[0], pos[2]);
return height < limit;
}
The rotation is a formula, not a state
If the dish turns at a fixed rate, its bearing at any moment follows from the time of day. Every machine computes the same answer and nothing has to be sent. That is what makes the client-side ping on the next page possible. The rate also has to match the visible animation of the radar prefab, or the audible ping and the dish the pilot can see drift apart.
float MyAD_Radar_Bearing()
{
float seconds = SecondsSinceMidnight();
float phase = SCR_Math.fmod(m_fDegreesPerSecond * seconds, 360.0);
return SCR_Math.fmod(phase + GetOwner().GetYawPitchRoll()[0] + m_fModelOffset, 360.0);
}
float SecondsSinceMidnight()
{
ChimeraWorld world = ChimeraWorld.CastFrom(GetGame().GetWorld());
TimeAndWeatherManagerEntity mgr = world.GetTimeAndWeatherManager();
// time of day scaled by day length - accelerated time cancels out of the rotation
return mgr.GetTimeOfTheDay() / 24.0 * mgr.GetDayDuration();
}
SCR_Math.fmod and not Math.Mod: the built-in returns a negative result for negative input (Math.Mod(-500, 360) is -140). The bearing adds a mast orientation and a model offset, and either may be negative.