Integrate delivered HQ memory, finite physical convoy and RHS WCS dependencies
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
// Negative sightings have the same ownership as visual reports: HQ may ingest
|
||||
// this snapshot only after its physical source's uplink completes successfully.
|
||||
class ERR_HQSearchReport
|
||||
{
|
||||
string WatchSourceId;
|
||||
int WatchMessageId;
|
||||
int WatchRequestId;
|
||||
int SourceId;
|
||||
int MessageId;
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
vector Position;
|
||||
float ObservedAt = -1;
|
||||
bool SweepCompleted;
|
||||
bool ContactFound;
|
||||
}
|
||||
|
||||
class ERR_HQCoverageRules
|
||||
{
|
||||
static bool PositionValid(vector position)
|
||||
{
|
||||
return ERR_HQPursuitRules.PositionValid(position)
|
||||
&& Math.AbsFloat(position[0]) < 1000000 && Math.AbsFloat(position[2]) < 1000000;
|
||||
}
|
||||
static int Cell(float coordinate) { return Math.Floor(coordinate / 500); }
|
||||
// Bounded coordinates make this integer grid key collision-free, including
|
||||
// negative positions. No per-candidate string or collection allocation.
|
||||
static int Key(vector position)
|
||||
{
|
||||
return (Cell(position[0]) + 2000) * 4096 + Cell(position[2]) + 2000;
|
||||
}
|
||||
static bool Recent(float searchedAt, float now)
|
||||
{
|
||||
return ERR_HQPursuitRules.Finite(searchedAt) && ERR_HQPursuitRules.Finite(now)
|
||||
&& searchedAt >= 0 && now >= searchedAt && now - searchedAt <= 900000;
|
||||
}
|
||||
static bool ValidReport(int sourceId, int messageId, int beliefId, int revision,
|
||||
bool completed, bool found, float observedAt, float now)
|
||||
{
|
||||
return sourceId > 0 && messageId > 0 && beliefId > 0 && revision > 0 && completed && !found
|
||||
&& Recent(observedAt, now) && now - observedAt <= 120000;
|
||||
}
|
||||
static bool Point(int x, int z, vector center, float radius, out vector point)
|
||||
{
|
||||
point = Vector(x * 500 + 250, center[1], z * 500 + 250);
|
||||
if (vector.DistanceXZ(point, center) > radius)
|
||||
{
|
||||
// Small uncertainty circles need not include a grid center. Choose a point
|
||||
// in their intersecting cell; inset edges keep its key in the selected cell.
|
||||
point[0] = Math.Clamp(center[0], x * 500, x * 500 + 499.99);
|
||||
point[2] = Math.Clamp(center[2], z * 500, z * 500 + 499.99);
|
||||
}
|
||||
return PositionValid(point) && vector.DistanceXZ(point, center) <= radius;
|
||||
}
|
||||
}
|
||||
|
||||
// A cell records a reported sweep visit, not proof that every building or hiding
|
||||
// place in its 500m square was visible. New positive sightings invalidate that
|
||||
// coarse exclusion; replay watermarks survive so old negatives cannot restore it.
|
||||
class ERR_HQCoverage
|
||||
{
|
||||
protected ref map<int, float> m_Cells = new map<int, float>();
|
||||
protected ref map<int, int> m_Messages = new map<int, int>();
|
||||
int Count() { return m_Cells.Count(); }
|
||||
void Forget(vector position)
|
||||
{
|
||||
if (ERR_HQCoverageRules.PositionValid(position)) m_Cells.Remove(ERR_HQCoverageRules.Key(position));
|
||||
}
|
||||
bool Recent(vector position, float now)
|
||||
{
|
||||
if (!ERR_HQCoverageRules.PositionValid(position)) return false;
|
||||
int key = ERR_HQCoverageRules.Key(position);
|
||||
return m_Cells.Contains(key) && ERR_HQCoverageRules.Recent(m_Cells.Get(key), now);
|
||||
}
|
||||
bool Ingest(ERR_HQSearchReport report, float now)
|
||||
{
|
||||
if (!report || !ERR_HQCoverageRules.PositionValid(report.Position)) return false;
|
||||
if (!ERR_HQCoverageRules.ValidReport(report.SourceId, report.MessageId, report.BeliefId,
|
||||
report.BeliefRevision, report.SweepCompleted, report.ContactFound, report.ObservedAt, now)) return false;
|
||||
if (m_Messages.Contains(report.SourceId) && report.MessageId <= m_Messages.Get(report.SourceId)) return false;
|
||||
// One uplink per source gives monotonic message order. Never evict replay
|
||||
// watermarks; the mission already caps its total physical formations at 128.
|
||||
if (!m_Messages.Contains(report.SourceId) && m_Messages.Count() >= 128) return false;
|
||||
m_Messages.Set(report.SourceId, report.MessageId);
|
||||
int key = ERR_HQCoverageRules.Key(report.Position);
|
||||
if (m_Cells.Contains(key) && m_Cells.Get(key) >= report.ObservedAt) return false;
|
||||
if (!m_Cells.Contains(key) && m_Cells.Count() >= 128)
|
||||
{
|
||||
int oldestKey;
|
||||
float oldest = 1e20;
|
||||
foreach (int known, float searched : m_Cells)
|
||||
{
|
||||
if (searched >= oldest) continue;
|
||||
oldest = searched;
|
||||
oldestKey = known;
|
||||
}
|
||||
if (oldest >= report.ObservedAt) return false;
|
||||
m_Cells.Remove(oldestKey);
|
||||
}
|
||||
// Store the observation time, not delivery time: radio delay never makes a
|
||||
// searched area newer than the physical sweep that justified it.
|
||||
m_Cells.Set(key, report.ObservedAt);
|
||||
return true;
|
||||
}
|
||||
protected bool Reserved(vector point, array<vector> reserved)
|
||||
{
|
||||
if (!reserved) return false;
|
||||
int key = ERR_HQCoverageRules.Key(point);
|
||||
foreach (vector position : reserved)
|
||||
if (ERR_HQCoverageRules.PositionValid(position) && ERR_HQCoverageRules.Key(position) == key) return true;
|
||||
return false;
|
||||
}
|
||||
bool Select(vector center, float radius, vector fromPosition, float now,
|
||||
out vector destination, array<vector> reserved = null)
|
||||
{
|
||||
destination = center;
|
||||
if (!ERR_HQCoverageRules.PositionValid(center) || !ERR_HQCoverageRules.PositionValid(fromPosition))
|
||||
return false;
|
||||
if (!ERR_HQPursuitRules.Finite(now) || now < 0 || !ERR_HQPursuitRules.Finite(radius)
|
||||
|| radius < 0 || radius > 2000 || (reserved && reserved.Count() > 8)) return false;
|
||||
int lowX = ERR_HQCoverageRules.Cell(center[0] - radius);
|
||||
int highX = ERR_HQCoverageRules.Cell(center[0] + radius);
|
||||
int lowZ = ERR_HQCoverageRules.Cell(center[2] - radius);
|
||||
int highZ = ERR_HQCoverageRules.Cell(center[2] + radius);
|
||||
float best = 1e20;
|
||||
bool found;
|
||||
vector point;
|
||||
// Radius is bounded to 2 km: at most nine by nine candidate grid cells.
|
||||
for (int x = lowX; x <= highX; x++)
|
||||
{
|
||||
for (int z = lowZ; z <= highZ; z++)
|
||||
{
|
||||
if (!ERR_HQCoverageRules.Point(x, z, center, radius, point)) continue;
|
||||
if (Recent(point, now) || Reserved(point, reserved)) continue;
|
||||
float distance = vector.DistanceXZ(point, fromPosition);
|
||||
if (distance >= best) continue;
|
||||
best = distance;
|
||||
destination = point;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
void Clear() { m_Cells.Clear(); m_Messages.Clear(); }
|
||||
}
|
||||
Reference in New Issue
Block a user