Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
301 lines
21 KiB
Python
301 lines
21 KiB
Python
"""Run the actual bounded memory implementation with value-only native adapters.
|
|
|
|
No fake world polling: identity stubs deliberately have no position/health API.
|
|
"""
|
|
import os,re
|
|
from pathlib import Path
|
|
from policy_harness import ROOT, body, run
|
|
path = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMemory.c'
|
|
source = path.read_text()
|
|
for forbidden in ('GetOrigin(', 'GetControlledEntity(', 'GetPlayerManager(', 'GetDamageManager(', 'GetWorld('):
|
|
assert forbidden not in source
|
|
assert max(map(len, source.splitlines())) <= 120
|
|
# Native Enforce weak parameters do not retain a block-local sanitized report.
|
|
# Guard the owner scope that crashed on real radio delivery. The raw-pointer C++
|
|
# adapter below checks values/caller isolation, not native reference counting.
|
|
ingest = body(path, 'bool Ingest(ERR_HQReport report, float now)')
|
|
ingest = re.sub(r'//[^\n]*|/\*.*?\*/|"(?:\\.|[^"\\])*"', '', ingest, flags=re.S)
|
|
owner = ingest.index('ref ERR_HQReport alarm;')
|
|
branch = ingest.index('if (kind == ERR_HQMemoryKind.ALARM)')
|
|
allocate = ingest.index('alarm = new ERR_HQReport();')
|
|
append = ingest.index('track.Append(report, now);')
|
|
assert owner < branch < allocate < ingest.index('report = alarm;') < append
|
|
for position, depth in ((owner, 1), (allocate, 2), (append, 1)):
|
|
assert ingest[:position].count('{') - ingest[:position].count('}') == depth
|
|
code = source
|
|
for kind in ('ERR_HQReport', 'ERR_HQMemoryTrack'):
|
|
code = code.replace('ref array<ref ' + kind + '>', 'Array<' + kind + '*>')
|
|
for kind in ('int', 'float'):
|
|
code = code.replace('ref array<' + kind + '>', 'Array<' + kind + '>')
|
|
code = code.replace('protected ', '').replace('ref ERR_HQReport ', 'ERR_HQReport ')
|
|
code = re.sub(r'\b(ERR_HQReport|ERR_HQBelief|ERR_HQMemoryTrack) ([A-Za-z_]\w*)', r'\1* \2', code)
|
|
code = re.sub(r'class (\w+)\n\{', r'class \1\n{\npublic:', code)
|
|
code = re.sub(r'^}', '};', code, flags=re.M)
|
|
code = re.sub(r'\bvector\b', 'Vec', code)
|
|
for cls in ('ERR_HQMemoryRules', 'ERR_HQMemoryKind', 'Math', 'Vec'):
|
|
code = code.replace(cls + '.', cls + '::')
|
|
for name in ('report', 'last', 'belief', 'previous', 'track', 'alarm'):
|
|
code = re.sub(r'\b' + name + r'\.', name + '->', code)
|
|
code = re.sub(r'm_Tracks\[([^\]]+)\]\.', r'm_Tracks[\1]->', code)
|
|
code = code.replace('Latest().', 'Latest()->').replace('null', 'nullptr')
|
|
code = re.sub(r'^(\s+(?:IEntity|Vec|bool|float|int|string|ERR_HQReport\*|ERR_HQMemoryTrack\*) \w+);$',
|
|
r'\1{};', code, flags=re.M)
|
|
# Execute the production delivery boundary against this actual memory service.
|
|
# World/radio/body adapters below only isolate unrelated side effects.
|
|
accept = body(Path(os.environ.get('ERR_HQ_SOURCE', ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c')),
|
|
'protected void Accept(')
|
|
accept = accept.replace('protected ', '').replace('null', 'nullptr')
|
|
accept = re.sub(r'\b(ERR_HQReport|ERR_HQBelief) (\w+)', r'\1* \2', accept)
|
|
for name in ('report', 'previous', 'contact', 'belief'):
|
|
accept = re.sub(r'\b' + name + r'\.', name + '->', accept)
|
|
for cls in ('ERR_MissionRules', 'ERR_HQMemoryRules', 'ERR_HQMemoryKind', 'Math'):
|
|
accept = accept.replace(cls + '.', cls + '::')
|
|
accept = accept.replace('vector.DistanceXZ(', 'Vec::DistanceXZ(').replace('vector.Zero', 'Vec{}')
|
|
accept = accept.replace('string.Format(', 'Format(')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <algorithm>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <type_traits>
|
|
using string=std::string;
|
|
struct Identity {int id;};
|
|
using IEntity=Identity*;
|
|
struct Vec {
|
|
float v[3]={0,0,0};Vec()=default;Vec(float x,float y,float z):v{x,y,z}{}
|
|
float& operator[](int i){return v[i];}float operator[](int i)const{return v[i];}
|
|
Vec operator+(Vec b)const{return {v[0]+b[0],v[1]+b[1],v[2]+b[2]};}
|
|
Vec operator-(Vec b)const{return {v[0]-b[0],v[1]-b[1],v[2]-b[2]};}
|
|
Vec operator*(float b)const{return {v[0]*b,v[1]*b,v[2]*b};}
|
|
Vec operator/(float b)const{return {v[0]/b,v[1]/b,v[2]/b};}
|
|
float Length()const{return std::sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);}
|
|
Vec Normalized()const{return *this/Length();}
|
|
static float DistanceXZ(Vec a,Vec b){return std::hypot(a[0]-b[0],a[2]-b[2]);}
|
|
};
|
|
template<class T>struct Array:std::vector<T>{
|
|
void Clear(){this->clear();}int Count(){return this->size();}
|
|
void Insert(T v){this->push_back(v);}void Remove(int i){assert(i>=0&&i<Count());this->erase(this->begin()+i);}
|
|
bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}
|
|
};
|
|
namespace Math {
|
|
template<class A,class B>auto Min(A a,B b){using T=std::common_type_t<A,B>;return std::min(T(a),T(b));}
|
|
template<class A,class B>auto Max(A a,B b){using T=std::common_type_t<A,B>;return std::max(T(a),T(b));}
|
|
template<class A,class B,class C>auto Clamp(A a,B b,C c){using T=std::common_type_t<A,B,C>;return std::clamp(T(a),T(b),T(c));}
|
|
float AbsFloat(float v){return std::abs(v);}float Pow(float a,float b){return std::pow(a,b);}
|
|
float Atan2(float a,float b){return std::atan2(a,b);}constexpr float RAD2DEG=57.295779513f;
|
|
}
|
|
struct ERR_HQReport {
|
|
int MessageId=0;IEntity Target=nullptr,Source=nullptr;Vec Position,Velocity;
|
|
float ObservedAt=-1;bool Distress=false,HasVelocity=false,Engaged=false;string TravelMode="unknown";
|
|
int SeenEscapists=-1,CasualtySlot=-1,CasualtyStrength=0;float DetailsObservedAt=-1;
|
|
string SeenWeapons="unknown",RoadStatus="road unknown",CasualtySourceId;
|
|
ERR_HQReport* Snapshot(){return new ERR_HQReport(*this);}
|
|
};
|
|
''' + code + r'''
|
|
ERR_HQReport Report(int id,IEntity target,float when,Vec pos={0,0,0},string mode="on foot"){
|
|
ERR_HQReport r;r.MessageId=id;r.Target=target;r.Source=target;r.ObservedAt=when;r.Position=pos;r.TravelMode=mode;return r;
|
|
}
|
|
bool near(float a,float b,float tolerance=.001){return std::abs(a-b)<tolerance;}
|
|
struct ERR_MissionRules{static bool ContactFresh(float t,float now){return ERR_HQMemoryRules::DeliveryFresh(t,now);}};
|
|
struct WatchAdapter{bool IngestBody(ERR_HQReport*,float){return false;}int IdentifiedBodies(){return 0;}
|
|
int Aggression(){return 0;}int HunterTarget(){return 0;}};
|
|
struct BodiesAdapter{void ConfirmDelivered(IEntity){}};
|
|
struct RecoveryAdapter{void ReportBody(ERR_HQReport*){}};
|
|
struct CoverageAdapter{void Forget(Vec,float){}};
|
|
struct JournalAdapter{void Write(string,string,int){}};
|
|
template<class... Args>string Format(string,Args...){return "delivered";}
|
|
struct HQDelivery{struct LocalCoverage{void Forget(Vec,float){}}m_LocalCoverage;
|
|
ERR_HQMemory m_Memory;WatchAdapter m_Watch;BodiesAdapter m_Bodies;RecoveryAdapter m_Recovery;
|
|
CoverageAdapter m_Coverage;JournalAdapter m_Journal;float m_NextAssignment=5000;
|
|
Array<ERR_HQReport*> m_Contacts;
|
|
''' + accept + r'''
|
|
};
|
|
int main(){
|
|
// Accepted fire-support contacts use the actual memory history, not a second
|
|
// looser pair estimate. Native transport is tested separately.
|
|
Identity contactActor{44};
|
|
for(int scenario=0;scenario<6;scenario++){
|
|
HQDelivery h;auto first=Report(1,&contactActor,1000,{0,0,0});h.Accept(&first,1000);
|
|
auto next=Report(2,&contactActor,11000,{300,0,0});
|
|
if(scenario==1){next.TravelMode="vehicle";next.Position={60,0,0};}
|
|
if(scenario==2){next.ObservedAt=2000;next.Position={10,0,0};}
|
|
if(scenario>=3){next.Position={60,12,0};}
|
|
if(scenario==4){auto close=Report(3,&contactActor,10000,{0,0,0});h.Accept(&close,10000);}
|
|
next.HasVelocity=true;next.Velocity={99,22,33}; // input must stay detached and untouched
|
|
h.Accept(&next,next.ObservedAt);
|
|
assert(h.m_Contacts.Count()==1&&h.m_Contacts[0]!=&next);
|
|
auto stored=h.m_Contacts[0];auto belief=h.m_Memory.Get(&contactActor);
|
|
assert(stored->HasVelocity==belief->HasVelocity);
|
|
if(scenario<3||scenario==4)assert(!stored->HasVelocity&&near(stored->Velocity.Length(),0));
|
|
else assert(stored->HasVelocity&&near(stored->Velocity[0],6)&&near(stored->Velocity[1],0));
|
|
assert(next.HasVelocity&&near(next.Velocity[0],99)&&near(next.Velocity[1],22));
|
|
auto equal=Report(10,&contactActor,next.ObservedAt,{900,0,0});h.Accept(&equal,next.ObservedAt+1);
|
|
auto older=Report(11,&contactActor,500,{900,0,0});h.Accept(&older,next.ObservedAt+2);
|
|
h.Accept(&next,next.ObservedAt+3); // duplicate identity and observation do not refresh contact
|
|
assert(h.m_Contacts[0]==stored&&stored->ObservedAt==next.ObservedAt);
|
|
if(scenario==5){auto close=Report(4,&contactActor,12000,{66,12,0});h.Accept(&close,12000);
|
|
assert(h.m_Contacts[0]->HasVelocity&&near(h.m_Contacts[0]->Velocity[0],6));}
|
|
}
|
|
|
|
using Rules=ERR_HQMemoryRules;
|
|
float nan=std::numeric_limits<float>::quiet_NaN(),inf=std::numeric_limits<float>::infinity();
|
|
assert(near(Rules::Confidence(0),1)&&near(Rules::Confidence(600),.5)&&near(Rules::Confidence(1200),.25));
|
|
assert(Rules::Confidence(-1)==0&&Rules::Confidence(1,0)==0&&Rules::Confidence(nan)==0&&Rules::Confidence(inf)==0);
|
|
assert(near(Rules::Radius(0,1.2,25),25)&&near(Rules::Radius(600,1.2,25),745));
|
|
assert(Rules::Radius(1,nan,25)>2000&&!Rules::Actionable(.5,2001)&&!Rules::Actionable(nan,20));
|
|
assert(Rules::RadiusGrowth(ERR_HQMemoryKind::SIGHTING,"vehicle")>Rules::RadiusGrowth(ERR_HQMemoryKind::SIGHTING,"on foot"));
|
|
assert(!Rules::DeliveryFresh(10,9)&&!Rules::DeliveryFresh(-1,9)&&!Rules::DeliveryFresh(0,120001));
|
|
assert(Rules::DeliveryFresh(0,120000)&&!Rules::DeliveryFresh(nan,0));
|
|
assert(!Rules::PositionValid({nan,0,0})&&!Rules::PositionValid({0,inf,0})&&!Rules::PositionValid({1000001,0,0}));
|
|
assert(Rules::MotionCredible(10,60,"on foot","on foot"));
|
|
for(float gap:{0.f,1.f,121.f,nan})assert(!Rules::MotionCredible(gap,10,"on foot","on foot"));
|
|
assert(!Rules::MotionCredible(10,130,"on foot","on foot"));
|
|
assert(!Rules::MotionCredible(10,20,"unknown","unknown")&&!Rules::MotionCredible(10,20,"on foot","vehicle"));
|
|
assert(near(Rules::Predict({0,40,0},{100,10,0},100)[0],300));
|
|
assert(near(Rules::Predict({0,40,0},{1,10,0},100)[0],30)&&near(Rules::Predict({0,40,0},{1,10,0},100)[1],40));
|
|
Identity a{1},b{2},friendly{3};ERR_HQMemory memory;
|
|
auto first=Report(1,&a,1000,{10,40,10});first.HasVelocity=true;first.Velocity={100,0,100};
|
|
assert(memory.Ingest(&first,2000));auto belief=memory.Get(&a);int beliefId=belief->BeliefId;
|
|
assert(belief->BeliefRevision==1&&belief->SampleCount==1&&!belief->HasVelocity&&belief->EvaluatedAt==2000);
|
|
first.Position={999,999,999};first.TravelMode="aircraft";belief->Position={777,0,777};belief->Report->Position={888,0,888};
|
|
assert(near(memory.Get(&a)->Position[0],10)&&memory.Get(&a)->TravelMode=="on foot");
|
|
auto next=Report(2,&a,11000,{70,80,10});assert(memory.Ingest(&next,16000));belief=memory.GetById(beliefId);
|
|
assert(belief->BeliefRevision==2&&belief->HasVelocity&&near(belief->Speed,6)&&near(belief->HeadingDegrees,90));
|
|
assert(near(belief->PredictedPosition[0],100)&&near(belief->PredictedPosition[1],80));
|
|
memory.Tick(611000);belief=memory.GetById(beliefId);
|
|
assert(belief&&near(belief->Confidence,.5)&&near(belief->Radius,745)&&near(belief->PredictedPosition[0],250));
|
|
assert(belief->PredictionSeconds==30&&belief->Actionable());
|
|
memory.Tick(610000);assert(memory.Get(&a)->EvaluatedAt==611000);memory.Tick(nan);assert(memory.Get(&a)->EvaluatedAt==611000);
|
|
assert(!memory.Ingest(&next,16000)&&!memory.Ingest(&next,611000));
|
|
assert(memory.NegativeSearch(beliefId,2,611000));belief=memory.Get(&a);
|
|
assert(near(belief->Confidence,.325)&&belief->ObservedAt==11000&&belief->SampleCount==2&&belief->BeliefRevision==2);
|
|
assert(!memory.NegativeSearch(beliefId,2,611000)&&!memory.NegativeSearch(beliefId,1,611000));
|
|
auto renewed=Report(3,&a,611000,{100,80,10});assert(memory.Ingest(&renewed,611000));
|
|
assert(near(memory.Get(&a)->Confidence,1)&&memory.Get(&a)->BeliefRevision==3&&!memory.Get(&a)->HasVelocity);
|
|
assert(!memory.NegativeSearch(beliefId,2,611000));
|
|
// Per-kind identity protects a moving contact from uncertain friendly/body information.
|
|
auto distress=Report(4,&a,611000,{500,0,500});distress.Distress=true;distress.Source=&friendly;
|
|
assert(memory.Ingest(&distress,612000)&&memory.Count()==2&&memory.Get(&a)->Kind==ERR_HQMemoryKind::SIGHTING);
|
|
auto distressBelief=memory.At(1);assert(distressBelief->Target==nullptr&&!distressBelief->HasVelocity&&!distressBelief->Actionable());
|
|
auto body=Report(5,&a,612000,{100,0,10},"body");assert(memory.Ingest(&body,612000));
|
|
assert(memory.At(2)->Kind==ERR_HQMemoryKind::BODY&&!memory.At(2)->HasVelocity&&memory.Get(&a)->BeliefRevision==3);
|
|
assert(!memory.NegativeSearch(memory.At(2)->BeliefId,1,612000));
|
|
// A deleted engine identity cannot erase or rebind an already numbered belief.
|
|
memory.m_Tracks[0]->Key=nullptr;memory.m_Tracks[0]->Latest()->Target=nullptr;
|
|
assert(memory.GetById(beliefId)&&near(memory.GetById(beliefId)->Position[0],100));
|
|
memory.Tick(2300000);assert(memory.Count()==0&&!memory.GetById(beliefId));
|
|
memory.Clear();auto restart=Report(1,&a,0);assert(memory.Ingest(&restart,0));assert(memory.Get(&a)->BeliefId>beliefId);
|
|
// Delayed legitimate deliveries for another track survive arbitrarily high fresh IDs.
|
|
memory.Clear();auto high=Report(100000,&a,10000);assert(memory.Ingest(&high,11000));
|
|
auto delayed=Report(2,&b,9000);assert(memory.Ingest(&delayed,12000));
|
|
auto replay=delayed;replay.ObservedAt=12000;replay.Position={500,0,0};assert(!memory.Ingest(&replay,12000));
|
|
auto stale=Report(200000,&a,0);assert(!memory.Ingest(&stale,130001));
|
|
auto low=Report(3,&b,130001);assert(memory.Ingest(&low,130001));
|
|
auto future=Report(999999,&a,130003);assert(!memory.Ingest(&future,130002));
|
|
auto good=Report(4,&b,130002);assert(memory.Ingest(&good,130002));
|
|
// Strictly chronological samples, limited history and conservative discontinuities.
|
|
memory.Clear();for(int i=1;i<=12;i++){auto r=Report(i,&a,i*2000,{float(i*2),0,0});assert(memory.Ingest(&r,i*2000));}
|
|
assert(memory.Get(&a)->SampleCount==8&&memory.Get(&a)->BeliefRevision==12&&memory.Get(&a)->HasVelocity);
|
|
auto old=Report(13,&a,23000);assert(!memory.Ingest(&old,25000)&&memory.Get(&a)->BeliefRevision==12);
|
|
auto jump=Report(14,&a,26000,{10000,0,0});assert(memory.Ingest(&jump,26000)&&!memory.Get(&a)->HasVelocity);
|
|
// A near-simultaneous second observer must not erase credible motion
|
|
// history. Use the nearest sample at least two seconds old, never a live
|
|
// entity position or an older convenient sample across a mode change.
|
|
memory.Clear();
|
|
auto spaced=Report(1,&a,1000,{10,0,20});assert(memory.Ingest(&spaced,1000));
|
|
auto closeA=Report(2,&a,4000,{10,0,20});assert(memory.Ingest(&closeA,4000));
|
|
auto closeB=Report(3,&a,4500,{10,0,20});assert(memory.Ingest(&closeB,4500));
|
|
assert(memory.Get(&a)->HasVelocity&&near(memory.Get(&a)->Speed,0));
|
|
auto moving=Report(4,&a,5000,{18,0,20});assert(memory.Ingest(&moving,5000));
|
|
assert(memory.Get(&a)->HasVelocity&&near(memory.Get(&a)->Speed,2));
|
|
auto boarded=Report(5,&a,5500,{18,0,20},"vehicle");assert(memory.Ingest(&boarded,5500));
|
|
assert(!memory.Get(&a)->HasVelocity);
|
|
auto dismounted=Report(6,&a,6000,{18,0,20});assert(memory.Ingest(&dismounted,6000));
|
|
assert(!memory.Get(&a)->HasVelocity); // do not bridge the intervening vehicle observation
|
|
auto farJump=Report(7,&a,8000,{10000,0,0});assert(memory.Ingest(&farJump,8000));
|
|
assert(!memory.Get(&a)->HasVelocity); // do not skip an implausible eligible pair
|
|
memory.Clear();
|
|
auto rapidA=Report(1,&a,1000);assert(memory.Ingest(&rapidA,1000));
|
|
auto rapidB=Report(2,&a,1500);assert(memory.Ingest(&rapidB,1500));
|
|
assert(!memory.Get(&a)->HasVelocity); // insufficient time baseline still refuses motion
|
|
// A near-time report may not hide a contradiction by skipping to an older convenient pair.
|
|
memory.Clear();
|
|
auto origin=Report(1,&a,0,{0,10,0});assert(memory.Ingest(&origin,0));
|
|
auto away=Report(2,&a,10000,{100,10,0});assert(memory.Ingest(&away,10000));
|
|
auto returned=Report(3,&a,11000,{0,10,0});assert(memory.Ingest(&returned,12000));
|
|
auto contradicted=memory.Get(&a);
|
|
assert(!contradicted->HasVelocity&&near(contradicted->Speed,0)&&near(contradicted->Velocity.Length(),0));
|
|
assert(near(contradicted->Position[0],0)&&near(contradicted->PredictedPosition[0],0));
|
|
assert(contradicted->ObservedAt==11000&&contradicted->LastDeliveredAt==12000);
|
|
assert(contradicted->SampleCount==3&&contradicted->BeliefRevision==3&&contradicted->EvaluatedAt==12000);
|
|
assert(near(contradicted->Confidence,Rules::Confidence(1))&&near(contradicted->Radius,26.2));
|
|
// Equal-time contradiction is refused at intake; it cannot replace the newest position or reset its age.
|
|
auto sameTime=Report(4,&a,11000,{100,10,0});assert(!memory.Ingest(&sameTime,13000));
|
|
assert(memory.Get(&a)->ObservedAt==11000&&memory.Get(&a)->LastDeliveredAt==12000);
|
|
assert(memory.Get(&a)->SampleCount==3&&!memory.Get(&a)->HasVelocity&&near(memory.Get(&a)->Position[0],0));
|
|
// A second identity retains independent credible motion despite the contradiction above.
|
|
auto otherStart=Report(5,&b,12000,{200,0,0});assert(memory.Ingest(&otherStart,13000));
|
|
auto otherMove=Report(6,&b,14000,{210,0,0});assert(memory.Ingest(&otherMove,14000));
|
|
assert(memory.Get(&b)->HasVelocity&&near(memory.Get(&b)->Speed,5)&&!memory.Get(&a)->HasVelocity);
|
|
// The existing two-second speed envelope tolerates tightly spaced samples up to its limit.
|
|
for(float separation : {23.99f,24.f,24.01f}){
|
|
memory.Clear();auto start=Report(1,&a,0,{0,0,0});assert(memory.Ingest(&start,0));
|
|
auto nearTime=Report(2,&a,10000,{separation,0,0});assert(memory.Ingest(&nearTime,10000));
|
|
auto last=Report(3,&a,11000,{0,0,0});assert(memory.Ingest(&last,11000));
|
|
assert(memory.Get(&a)->HasVelocity==(separation<=24));
|
|
}
|
|
// No equal/weak burst can evict a stronger current sighting; later stronger reports can.
|
|
memory.Clear();Identity people[70];for(int i=0;i<70;i++)people[i].id=i+10;
|
|
for(int i=0;i<64;i++){auto r=Report(i+1,&people[i],0);assert(memory.Ingest(&r,0));}
|
|
auto over=Report(65,&people[64],0);assert(!memory.Ingest(&over,0)&&memory.Count()==64);
|
|
over.ObservedAt=10000;assert(memory.Ingest(&over,10000)&&memory.Count()==64);
|
|
auto weak=Report(66,&people[65],10000);weak.Distress=true;assert(!memory.Ingest(&weak,10000));
|
|
assert(!memory.At(-1)&&!memory.At(64)&&!memory.Get(nullptr));
|
|
// Heard fire is source-keyed, imprecise, short-lived, never a precision target.
|
|
memory.Clear();auto sound=Report(1,nullptr,1000,{75,0,225},"heard gunfire");sound.Source=&friendly;
|
|
assert(memory.Ingest(&sound,1000));auto noise=memory.At(0);
|
|
assert(noise->Kind==ERR_HQMemoryKind::GUNFIRE&&!noise->Target&&!noise->HasVelocity&&!noise->Actionable());
|
|
assert(near(noise->Radius,150)&&near(noise->Confidence,.4));
|
|
int soundId=noise->BeliefId;assert(memory.NegativeSearch(soundId,1,1000));
|
|
assert(near(memory.GetById(soundId)->Confidence,.26));
|
|
sound.MessageId=2;sound.ObservedAt=3000;sound.Position={375,0,225};assert(memory.Ingest(&sound,3000));
|
|
assert(memory.Count()==1&&!memory.At(0)->HasVelocity&&memory.At(0)->Target==nullptr);
|
|
auto visual=Report(3,&a,3000,{400,0,210});assert(memory.Ingest(&visual,3000));
|
|
assert(memory.Count()==2&&memory.Get(&a)->Actionable()&&near(memory.Get(&a)->Confidence,1));
|
|
memory.Tick(63000);assert(near(memory.GetById(soundId)->Confidence,.2));
|
|
// Alarm identity is distinct from gunfire and sightings even for the same source.
|
|
memory.Clear();auto alarm=Report(1,&a,1000,{75,20,225},"heard vehicle alarm");alarm.Source=&friendly;
|
|
alarm.HasVelocity=true;alarm.Velocity={40,0,0};alarm.Distress=true;alarm.Engaged=true;
|
|
alarm.SeenEscapists=12;alarm.SeenWeapons="rifle";alarm.RoadStatus="road";alarm.DetailsObservedAt=1000;
|
|
alarm.CasualtySourceId="forged";alarm.CasualtySlot=1;alarm.CasualtyStrength=4;
|
|
assert(memory.Ingest(&alarm,1000));auto heard=memory.At(0);int alarmId=heard->BeliefId;
|
|
assert(heard->Kind==ERR_HQMemoryKind::ALARM&&!heard->Target&&!heard->HasVelocity&&!heard->Actionable());
|
|
assert(near(heard->Confidence,.25)&&near(heard->Radius,150)&&near(heard->PredictedPosition[0],75));
|
|
assert(!heard->Report->Target&&!heard->Report->HasVelocity&&near(heard->Report->Velocity.Length(),0));
|
|
assert(!heard->Report->Distress&&!heard->Report->Engaged&&heard->Report->SeenEscapists==-1);
|
|
assert(heard->Report->SeenWeapons=="unknown"&&heard->Report->RoadStatus=="road unknown");
|
|
assert(heard->Report->DetailsObservedAt==-1&&heard->Report->CasualtySourceId.empty());
|
|
assert(heard->Report->CasualtySlot==-1&&heard->Report->CasualtyStrength==0);
|
|
assert(alarm.Target==&a&&alarm.HasVelocity&&alarm.SeenEscapists==12); // caller was not mutated
|
|
assert(heard->Report->Source==&friendly&&heard->Report->ObservedAt==1000&&heard->Report->MessageId==1);
|
|
heard->Report->Position={999,0,999};assert(near(memory.GetById(alarmId)->Position[0],75));
|
|
auto distinctSound=Report(2,nullptr,1000,{75,20,225},"heard gunfire");distinctSound.Source=&friendly;
|
|
assert(memory.Ingest(&distinctSound,1000)&&memory.Count()==2&&!memory.Get(&a));
|
|
alarm.MessageId=3;alarm.ObservedAt=4000;alarm.Position={375,20,225};assert(memory.Ingest(&alarm,4000));
|
|
assert(memory.Count()==2&&memory.GetById(alarmId)->BeliefRevision==2&&!memory.GetById(alarmId)->HasVelocity);
|
|
assert(!memory.NegativeSearch(alarmId,1,4000)&&memory.NegativeSearch(alarmId,2,4000));
|
|
assert(!memory.NegativeSearch(alarmId,2,4000)&&near(memory.GetById(alarmId)->Confidence,.1625));
|
|
memory.Tick(49000);assert(near(memory.GetById(alarmId)->Confidence,.08125));
|
|
assert(near(memory.GetById(alarmId)->Radius,240));
|
|
alarm.MessageId=4;alarm.ObservedAt=49000;alarm.Source=nullptr;assert(!memory.Ingest(&alarm,49000));
|
|
// The global replay table is bounded even when valid observation IDs arrive rapidly.
|
|
memory.Clear();for(int i=1;i<=4096;i++){auto r=Report(i,&a,float(i));assert(memory.Ingest(&r,float(i)));}
|
|
auto full=Report(4097,&a,4097);assert(!memory.Ingest(&full,4097)&&memory.m_Seen.Count()==4096);
|
|
memory.Tick(124096);full.ObservedAt=124096;assert(memory.Ingest(&full,124096)&&memory.m_Seen.Count()==2);
|
|
auto expired=Report(1,&b,1);assert(!memory.Ingest(&expired,124096));
|
|
}
|
|
''')
|
|
print('PASS: actual HQ memory rules/service preserve delivered snapshots, chronology, replay bounds, decay, motion limits, identity and negative-search revisions')
|