Reconcile radio-delivered body and status casualties by original roster identity
This commit is contained in:
@@ -10,6 +10,7 @@ class ERR_HQStatusReport
|
||||
int Count;
|
||||
int Fit;
|
||||
int ConfirmedLosses;
|
||||
ref array<int> DeathSlots = {};
|
||||
float ObservedAt = -1;
|
||||
}
|
||||
|
||||
@@ -58,9 +59,11 @@ class ERR_HQWatchEntry
|
||||
int MessageId;
|
||||
vector Position;
|
||||
int Strength;
|
||||
int Count;
|
||||
int Fit;
|
||||
int Count = -1;
|
||||
int Fit = -1;
|
||||
int ConfirmedLosses;
|
||||
ref ERR_HQDeliveredCasualties Casualties = new ERR_HQDeliveredCasualties();
|
||||
float LastBodyDeliveredAt = -1;
|
||||
float ObservedAt = -1;
|
||||
float LastDeliveredAt = -1;
|
||||
float AskedAt = -1;
|
||||
@@ -84,44 +87,71 @@ class ERR_HQWatch
|
||||
}
|
||||
int ConfirmedLosses() { return m_ConfirmedLosses; }
|
||||
int SquadLimit(int baseline) { return ERR_HQWatchRules.SquadLimit(baseline, m_ConfirmedLosses); }
|
||||
protected ERR_HQWatchEntry Create(string sourceId, int strength)
|
||||
{
|
||||
// Closed identities remain tombstones: replays cannot recreate a formation.
|
||||
if (m_Entries.Count() >= 128)
|
||||
{
|
||||
if (!m_CapLogged) Print("Rereintroduced [watch]: 128-formation check-in cap reached.");
|
||||
m_CapLogged = true;
|
||||
return null;
|
||||
}
|
||||
ERR_HQWatchEntry entry = new ERR_HQWatchEntry();
|
||||
entry.SourceId = sourceId; entry.Strength = strength;
|
||||
entry.Casualties.Bind(strength);
|
||||
m_Entries.Insert(entry); m_BySource.Set(sourceId, entry);
|
||||
return entry;
|
||||
}
|
||||
protected void ApplyLosses(ERR_HQWatchEntry entry)
|
||||
{
|
||||
int known = entry.Casualties.Count();
|
||||
m_ConfirmedLosses += known - entry.ConfirmedLosses;
|
||||
entry.ConfirmedLosses = known;
|
||||
entry.Count = entry.Casualties.LivingUpperBound(entry.Count);
|
||||
if (entry.Count >= 0) entry.Fit = Math.Min(entry.Fit, entry.Count);
|
||||
entry.Closed = entry.Casualties.Closed();
|
||||
if (entry.Closed) entry.Fit = 0;
|
||||
}
|
||||
bool Ingest(ERR_HQStatusReport report, float now)
|
||||
{
|
||||
if (!ERR_HQWatchRules.Valid(report, now)) return false;
|
||||
ERR_HQWatchEntry entry = Find(report.SourceId);
|
||||
if (entry)
|
||||
if (entry && (report.MessageId <= entry.MessageId || report.ObservedAt <= entry.ObservedAt
|
||||
|| now < entry.LastDeliveredAt || report.Strength != entry.Strength)) return false;
|
||||
if (!entry)
|
||||
{
|
||||
if (report.MessageId <= entry.MessageId || report.ObservedAt <= entry.ObservedAt
|
||||
|| now < entry.LastDeliveredAt || report.Strength != entry.Strength
|
||||
|| report.ConfirmedLosses < entry.ConfirmedLosses) return false;
|
||||
// Validate slot payload before allocation: malformed snapshots cannot consume the cap.
|
||||
ERR_HQDeliveredCasualties validation = new ERR_HQDeliveredCasualties();
|
||||
if (!validation.Bind(report.Strength)
|
||||
|| !validation.AcceptStatus(report.DeathSlots, report.ConfirmedLosses, report.Count)) return false;
|
||||
entry = Create(report.SourceId, report.Strength);
|
||||
if (!entry) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Keep closed identities as well: replay cannot resurrect a retired patrol
|
||||
// or count the same cumulative casualty total again under its old source.
|
||||
if (m_Entries.Count() >= 128)
|
||||
{
|
||||
if (!m_CapLogged) Print("Rereintroduced [watch]: 128-formation check-in cap reached.");
|
||||
m_CapLogged = true;
|
||||
return false;
|
||||
}
|
||||
entry = new ERR_HQWatchEntry();
|
||||
entry.SourceId = report.SourceId;
|
||||
m_Entries.Insert(entry);
|
||||
m_BySource.Set(entry.SourceId, entry);
|
||||
}
|
||||
m_ConfirmedLosses += report.ConfirmedLosses - entry.ConfirmedLosses;
|
||||
entry.MessageId = report.MessageId;
|
||||
entry.Position = report.Position;
|
||||
entry.Strength = report.Strength;
|
||||
entry.Count = report.Count;
|
||||
entry.Fit = report.Fit;
|
||||
entry.ConfirmedLosses = report.ConfirmedLosses;
|
||||
entry.ObservedAt = report.ObservedAt;
|
||||
entry.LastDeliveredAt = now;
|
||||
entry.AskedAt = -1;
|
||||
entry.NextAttempt = 0;
|
||||
entry.Investigating = false;
|
||||
entry.Closed = entry.ConfirmedLosses == entry.Strength;
|
||||
if (!entry.Casualties.AcceptStatus(report.DeathSlots, report.ConfirmedLosses, report.Count)) return false;
|
||||
entry.MessageId = report.MessageId; entry.Position = report.Position;
|
||||
entry.Count = report.Count; entry.Fit = report.Fit;
|
||||
entry.ObservedAt = report.ObservedAt; entry.LastDeliveredAt = now;
|
||||
entry.AskedAt = -1; entry.NextAttempt = 0; entry.Investigating = false;
|
||||
ApplyLosses(entry);
|
||||
return true;
|
||||
}
|
||||
// Called only from delivered BODY intake; scalar evidence survives native target cleanup.
|
||||
// Corpse coordinates and body message IDs never replace a formation status/check-in.
|
||||
bool IngestBody(ERR_HQReport report, float now)
|
||||
{
|
||||
if (!report || report.TravelMode != "body" || report.Distress || report.MessageId <= 0
|
||||
|| report.CasualtySourceId.IsEmpty() || report.CasualtySourceId.Length() > 96
|
||||
|| report.CasualtyStrength < 1 || report.CasualtyStrength > 32
|
||||
|| report.CasualtySlot < 0 || report.CasualtySlot >= report.CasualtyStrength
|
||||
|| !ERR_HQPursuitRules.Finite(now) || !ERR_HQPursuitRules.Finite(report.ObservedAt)
|
||||
|| !ERR_HQPursuitRules.PositionValid(report.Position) || report.ObservedAt < 0
|
||||
|| now < report.ObservedAt || now - report.ObservedAt > 120000) return false;
|
||||
ERR_HQWatchEntry entry = Find(report.CasualtySourceId);
|
||||
if (entry && (entry.Strength != report.CasualtyStrength || now < entry.LastBodyDeliveredAt)) return false;
|
||||
if (!entry) entry = Create(report.CasualtySourceId, report.CasualtyStrength);
|
||||
if (!entry || !entry.Casualties.AcceptBody(report.CasualtySlot)) return false;
|
||||
entry.LastBodyDeliveredAt = now;
|
||||
ApplyLosses(entry);
|
||||
return true;
|
||||
}
|
||||
bool RequestDue(string sourceId, float now)
|
||||
|
||||
Reference in New Issue
Block a user