// Memory accepts radio-delivered reports at one boundary. Entity references are // opaque identity keys: this module never reads a target's current world state. enum ERR_HQMemoryKind { SIGHTING, DISTRESS, BODY } class ERR_HQMemoryRules { static bool Finite(float value) { return value == value && Math.AbsFloat(value) <= 1000000000000.0; } static bool PositionValid(vector value) { for (int i = 0; i < 3; i++) if (!Finite(value[i]) || Math.AbsFloat(value[i]) > 1000000) return false; return true; } static bool DeliveryFresh(float observedAt, float now) { return Finite(observedAt) && Finite(now) && observedAt >= 0 && now >= observedAt && now - observedAt <= 120000; } static int Kind(ERR_HQReport report) { if (report.Distress) return ERR_HQMemoryKind.DISTRESS; if (report.TravelMode == "body" || report.TravelMode == "dead body") return ERR_HQMemoryKind.BODY; return ERR_HQMemoryKind.SIGHTING; } static float Confidence(float ageSeconds, float halfLife = 600) { if (!Finite(ageSeconds) || !Finite(halfLife) || ageSeconds < 0 || halfLife <= 0) return 0; return Math.Pow(0.5, ageSeconds / halfLife); } static float BaseConfidence(int kind) { if (kind == ERR_HQMemoryKind.DISTRESS) return 0.3; if (kind == ERR_HQMemoryKind.BODY) return 0.2; return 1; } static float HalfLife(int kind) { if (kind == ERR_HQMemoryKind.DISTRESS) return 180; if (kind == ERR_HQMemoryKind.BODY) return 300; return 600; } static float RadiusFloor(int kind, string travel) { if (kind == ERR_HQMemoryKind.DISTRESS) return 150; if (kind == ERR_HQMemoryKind.BODY) return 75; if (travel == "aircraft") return 120; if (travel == "vehicle" || travel == "in vehicle") return 60; return 25; } static float RadiusGrowth(int kind, string travel) { if (kind == ERR_HQMemoryKind.DISTRESS) return 2; if (kind == ERR_HQMemoryKind.BODY) return 1.2; if (travel == "aircraft") return 8; if (travel == "vehicle" || travel == "in vehicle") return 4; return 1.2; } static float Radius(float ageSeconds, float growth, float floor) { if (!Finite(ageSeconds) || !Finite(growth) || !Finite(floor)) return 2001; if (ageSeconds < 0 || growth < 0 || floor < 0) return 2001; return floor + ageSeconds * growth; } static float SpeedLimit(string travel) { if (travel == "on foot") return 12; if (travel == "vehicle" || travel == "in vehicle") return 55; if (travel == "aircraft") return 120; return 0; } static bool MotionCredible(float seconds, float distance, string previousMode, string currentMode) { if (!Finite(seconds) || !Finite(distance) || seconds < 2 || seconds > 120 || distance < 0) return false; float limit = SpeedLimit(currentMode); return previousMode == currentMode && limit > 0 && distance / seconds <= limit; } static vector Predict(vector position, vector velocity, float ageSeconds) { if (!PositionValid(position) || !PositionValid(velocity) || !Finite(ageSeconds) || ageSeconds < 0) return position; vector offset = velocity * Math.Clamp(ageSeconds, 0, 30); offset[1] = 0; if (offset.Length() > 300) offset = offset.Normalized() * 300; return position + offset; } static bool Actionable(float confidence, float radius) { return Finite(confidence) && Finite(radius) && confidence >= 0.1 && confidence <= 1 && radius <= 2000 && radius >= 0; } static bool NewMessage(int id, bool alreadySeen) { if (id <= 0 || alreadySeen) return false; return true; } static float Value(float confidence, float radius) { return confidence / (1 + radius / 500); } } // Detached read model. Mutating it (or Report) cannot alter the stored history. class ERR_HQBelief { int BeliefId; int BeliefRevision; float EvaluatedAt; IEntity Target; vector Position; vector PredictedPosition; vector Velocity; bool HasVelocity; float Speed; float HeadingDegrees; float Confidence; float Radius; float ObservedAt; float LastDeliveredAt; float PredictionSeconds; int MessageId; int SampleCount; int Kind; string PredictionSource; string TravelMode; ref ERR_HQReport Report; bool Actionable() { return Kind == ERR_HQMemoryKind.SIGHTING && ERR_HQMemoryRules.Actionable(Confidence, Radius); } } class ERR_HQMemoryTrack { int BeliefId; int BeliefRevision; int NegativeRevision = -1; float SearchConfidence = 1; IEntity Key; int Kind; float DeliveredAt; protected ref array m_Samples = {}; ERR_HQReport Latest() { return m_Samples[m_Samples.Count() - 1]; } int Count() { return m_Samples.Count(); } void Append(ERR_HQReport report, float now) { if (m_Samples.Count() >= 8) m_Samples.Remove(0); m_Samples.Insert(report.Snapshot()); DeliveredAt = now; BeliefRevision++; SearchConfidence = 1; } float ConfidenceAt(float now) { float seconds = Math.Max(0, (now - Latest().ObservedAt) / 1000); return SearchConfidence * ERR_HQMemoryRules.BaseConfidence(Kind) * ERR_HQMemoryRules.Confidence(seconds, ERR_HQMemoryRules.HalfLife(Kind)); } float RadiusAt(float now) { ERR_HQReport last = Latest(); return ERR_HQMemoryRules.Radius(Math.Max(0, (now - last.ObservedAt) / 1000), ERR_HQMemoryRules.RadiusGrowth(Kind, last.TravelMode), ERR_HQMemoryRules.RadiusFloor(Kind, last.TravelMode)); } ERR_HQBelief Evaluate(float now) { ERR_HQReport last = Latest(); ERR_HQBelief belief = new ERR_HQBelief(); belief.BeliefId = BeliefId; belief.BeliefRevision = BeliefRevision; belief.EvaluatedAt = now; belief.Report = last.Snapshot(); belief.Target = last.Target; belief.Position = last.Position; belief.PredictedPosition = last.Position; belief.ObservedAt = last.ObservedAt; belief.LastDeliveredAt = DeliveredAt; belief.MessageId = last.MessageId; belief.SampleCount = Count(); belief.Kind = Kind; belief.TravelMode = last.TravelMode; belief.Confidence = ConfidenceAt(now); belief.Radius = RadiusAt(now); belief.PredictionSource = "delivered sighting; motion unknown"; if (Kind == ERR_HQMemoryKind.DISTRESS) { belief.Target = null; belief.PredictionSource = "delivered distress; enemy position and motion unknown"; return belief; } if (Kind == ERR_HQMemoryKind.BODY) { belief.PredictionSource = "reported body location; no moving-target inference"; return belief; } if (Count() < 2) return belief; ERR_HQReport previous = m_Samples[Count() - 2]; float seconds = (last.ObservedAt - previous.ObservedAt) / 1000; float distance = vector.DistanceXZ(previous.Position, last.Position); if (!ERR_HQMemoryRules.MotionCredible(seconds, distance, previous.TravelMode, last.TravelMode)) { belief.PredictionSource = "delivered sighting; motion history not credible"; return belief; } belief.Velocity = (last.Position - previous.Position) / seconds; belief.Velocity[1] = 0; belief.Speed = belief.Velocity.Length(); belief.HasVelocity = true; float age = Math.Max(0, (now - last.ObservedAt) / 1000); belief.PredictionSeconds = Math.Clamp(age, 0, 30); belief.PredictedPosition = ERR_HQMemoryRules.Predict(last.Position, belief.Velocity, age); belief.HeadingDegrees = Math.Atan2(belief.Velocity[0], belief.Velocity[2]) * Math.RAD2DEG; if (belief.HeadingDegrees < 0) belief.HeadingDegrees += 360; belief.PredictionSource = "successive delivered sightings; 30s/300m prediction limit"; if (belief.Speed < 0.5) belief.PredictionSource = "successive delivered sightings; approximately stationary"; return belief; } } class ERR_HQMemory { protected ref array m_Tracks = {}; protected ref array m_Seen = {}; protected ref array m_SeenAt = {}; protected int m_NextBeliefId; protected float m_Now = -1; protected string m_LastRefusal; string LastRefusal() { return m_LastRefusal; } int Count() { return m_Tracks.Count(); } protected bool Refuse(string reason) { m_LastRefusal = reason; return false; } protected int Find(IEntity key, int kind) { for (int i = 0; i < m_Tracks.Count(); i++) if (m_Tracks[i].Key == key && m_Tracks[i].Kind == kind) return i; return -1; } void Clear() { m_Tracks.Clear(); m_Seen.Clear(); m_SeenAt.Clear(); m_Now = -1; m_LastRefusal = ""; // Do not recycle belief IDs: an order retained across Clear cannot bind to a new track. } // A slow HQ tick advances only the observation clock and bounded retirement. // Confidence/radius are calculated on read; there is no per-frame decay work. void Tick(float now) { if (!ERR_HQMemoryRules.Finite(now) || now < 0 || now < m_Now) return; m_Now = now; for (int message = m_Seen.Count() - 1; message >= 0; message--) { if (now - m_SeenAt[message] <= 120000) continue; m_Seen.Remove(message); m_SeenAt.Remove(message); } for (int i = m_Tracks.Count() - 1; i >= 0; i--) if (m_Tracks[i].RadiusAt(now) > 2000) m_Tracks.Remove(i); } // Call exclusively after the radio network accepts delivery. No other method // can inject a report, and no stored report object is exposed to a caller. bool Ingest(ERR_HQReport report, float now) { if (!ERR_HQMemoryRules.Finite(now) || now < m_Now || now < 0) return Refuse("invalid memory clock"); Tick(now); if (!report || !ERR_HQMemoryRules.DeliveryFresh(report.ObservedAt, now)) return Refuse("stale or future report"); if (!ERR_HQMemoryRules.PositionValid(report.Position)) return Refuse("invalid observed position"); if (!ERR_HQMemoryRules.NewMessage(report.MessageId, m_Seen.Contains(report.MessageId))) return Refuse("replayed or expired message"); if (m_Seen.Count() >= 4096) return Refuse("delivered-report replay guard full"); int kind = ERR_HQMemoryRules.Kind(report); IEntity key = report.Target; if (kind == ERR_HQMemoryKind.DISTRESS) key = report.Source; if (!key) return Refuse("report has no observation identity"); int index = Find(key, kind); if (index >= 0 && report.ObservedAt <= m_Tracks[index].Latest().ObservedAt) return Refuse("observation does not advance trajectory"); if (index < 0 && m_NextBeliefId >= 2147483646) return Refuse("belief identity space exhausted"); if (index < 0 && m_Tracks.Count() >= 64) { int weakest = -1; float least = 2; for (int i = 0; i < m_Tracks.Count(); i++) { float value = ERR_HQMemoryRules.Value(m_Tracks[i].ConfidenceAt(now), m_Tracks[i].RadiusAt(now)); if (value < least) { least = value; weakest = i; } } float age = (now - report.ObservedAt) / 1000; float incoming = ERR_HQMemoryRules.BaseConfidence(kind); incoming *= ERR_HQMemoryRules.Confidence(age, ERR_HQMemoryRules.HalfLife(kind)); float radius = ERR_HQMemoryRules.Radius(age, ERR_HQMemoryRules.RadiusGrowth(kind, report.TravelMode), ERR_HQMemoryRules.RadiusFloor(kind, report.TravelMode)); if (weakest < 0 || ERR_HQMemoryRules.Value(incoming, radius) <= least) return Refuse("memory full; stronger observations retained"); m_Tracks.Remove(weakest); } ERR_HQMemoryTrack track; if (index >= 0) track = m_Tracks[index]; else { track = new ERR_HQMemoryTrack(); track.BeliefId = ++m_NextBeliefId; track.Key = key; track.Kind = kind; m_Tracks.Insert(track); } track.Append(report, now); m_Seen.Insert(report.MessageId); m_SeenAt.Insert(report.ObservedAt); m_LastRefusal = ""; return true; } ERR_HQBelief Get(IEntity target) { if (!target) return null; int index = Find(target, ERR_HQMemoryKind.SIGHTING); if (index < 0) return null; return m_Tracks[index].Evaluate(m_Now); } ERR_HQBelief GetById(int beliefId) { for (int i = 0; i < m_Tracks.Count(); i++) if (m_Tracks[i].BeliefId == beliefId) return m_Tracks[i].Evaluate(m_Now); return null; } // This is negative evidence from an actually delivered sweep report, not a // synthetic sighting. A duplicate cannot compound the same observation's loss. bool NegativeSearch(int beliefId, int revision, float now) { if (!ERR_HQMemoryRules.Finite(now) || now < m_Now || now < 0) return false; Tick(now); for (int i = 0; i < m_Tracks.Count(); i++) { ERR_HQMemoryTrack track = m_Tracks[i]; if (track.BeliefId != beliefId || track.Kind != ERR_HQMemoryKind.SIGHTING) continue; if (track.BeliefRevision != revision || track.NegativeRevision == revision) return false; track.SearchConfidence = 0.65; track.NegativeRevision = revision; return true; } return false; } ERR_HQBelief At(int index) { if (index < 0 || index >= m_Tracks.Count()) return null; return m_Tracks[index].Evaluate(m_Now); } }