Integrate delivered HQ memory, finite physical convoy and RHS WCS dependencies
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
"""Run the actual bounded memory implementation with value-only native adapters.
|
||||
|
||||
No fake world polling: identity stubs deliberately have no position/health API.
|
||||
"""
|
||||
import re
|
||||
from policy_harness import ROOT, run
|
||||
path = ROOT / 'addons/ai/Scripts/Game/ERR/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
|
||||
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'):
|
||||
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)
|
||||
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 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;string TravelMode="unknown";
|
||||
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;}
|
||||
int main(){
|
||||
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);
|
||||
// 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));
|
||||
// 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')
|
||||
Reference in New Issue
Block a user