Files
EscapistsReintroduced/tests/test_hq_local_coverage.py
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

115 lines
7.6 KiB
Python

"""Execute delivered physical-patch coverage, without treating entire cells as searched."""
from policy_harness import ROOT, body, run
import re
P=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQCoverage.c'
section=P.read_text().split('class ERR_HQLocalCoverage\n')[1]
def method(signature):
start=section.index(signature); opening=section.index('{',start); end=opening+1; depth=1
while depth:
depth+=(section[end]=='{')-(section[end]=='}');end+=1
return section[start:end]
def cpp(s):
s=s.replace('Math.','Math::').replace('vector.','vector::')
for cls in ('ERR_HQCoverageRules','ERR_HQLocalCoverageRules','ERR_HQPursuitRules'):
s=s.replace(cls+'.',cls+'::')
for cls in ('ERR_HQSearchReport','ERR_HQSearchPatch'):
s=re.sub(r'\b'+cls+r' (\w+)',cls+r'* \1',s)
for name in ('report','patch','positive'):
s=s.replace(name+'.',name+'->')
s=re.sub(r'(m_(?:Patches|Positives)\[[^]]+\])\.',r'\1->',s)
s=s.replace('foreach (ERR_HQSearchPatch* positive : m_Positives)','for (ERR_HQSearchPatch* positive : m_Positives)')
s=s.replace('foreach (ERR_HQSearchPatch* patch : m_Patches)','for (ERR_HQSearchPatch* patch : m_Patches)')
s=s.replace('array<vector> reserved','Array<vector>* reserved').replace('out vector ','vector& ')
s=s.replace('reserved.Count()','reserved->Count()').replace('foreach (vector other : reserved)','for (vector other : *reserved)')
s=s.replace('= null','= nullptr').replace('bool occupied;','bool occupied=false;').replace('bool found;','bool found=false;')
s=s.replace('int oldest;','int oldest=0;').replace('float strongest;','float strongest=0;')
return s
rules='\n'.join(cpp(body(P,s)) for s in ('static bool RadiusValid(', 'static float Fraction('))
methods='\n'.join(cpp(method(s)) for s in ('int Count(', 'void Forget(', 'bool Ingest(', 'float Multiplier(', 'bool Select(', 'void Clear('))
run(r'''
#include <cassert>
#include <cmath>
#include <algorithm>
#include <limits>
#include <map>
#include <vector>
struct vector{float x=0,y=0,z=0;float&operator[](int i){return i==0?x:i==1?y:z;}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}};
struct Math{static float Min(float a,float b){return std::min(a,b);}static float Max(float a,float b){return std::max(a,b);}};
template<class T>struct Array:std::vector<T>{int Count(){return this->size();}void Insert(T p){this->push_back(p);}void Remove(int i){this->erase(this->begin()+i);}void Clear(){this->clear();}};
template<class K,class V>struct Map:std::map<K,V>{int Count(){return this->size();}bool Contains(K k){return this->count(k);}V Get(K k){return this->at(k);}void Set(K k,V v){(*this)[k]=v;}void Clear(){this->clear();}};
struct ERR_HQPursuitRules{static bool Finite(float v){return std::isfinite(v);}};
struct ERR_HQCoverageRules{
static bool PositionValid(vector p){return std::isfinite(p.x)&&std::isfinite(p.y)&&std::isfinite(p.z)&&std::abs(p.x)<1000000&&std::abs(p.z)<1000000;}
static bool Recent(float at,float now){return std::isfinite(at)&&std::isfinite(now)&&at>=0&&now>=at&&now-at<=900000;}
static bool ValidReport(int s,int m,int b,int r,bool complete,bool contact,float at,float now){return s>0&&m>0&&b>0&&r>0&&complete&&!contact&&Recent(at,now)&&now-at<=120000;}
};
struct ERR_HQLocalCoverageRules{
'''+rules+r'''
};
struct ERR_HQSearchPatch{vector Position;float Radius=0,ObservedAt=0;};
struct ERR_HQSearchReport{int SourceId=1,MessageId=1,BeliefId=1,BeliefRevision=1;vector Position{250,0,250};float ObservedAt=1000;bool SweepCompleted=true,ContactFound=false;};
struct Coverage{
Array<ERR_HQSearchPatch*> m_Patches,m_Positives;Map<int,int>m_Replay;float m_Floor=-1;
'''+methods+r'''
};
int main(){
using R=ERR_HQLocalCoverageRules;float nan=std::numeric_limits<float>::quiet_NaN();
assert(R::RadiusValid(10)&&R::RadiusValid(40)&&!R::RadiusValid(40.01)&&!R::RadiusValid(0)&&!R::RadiusValid(nan));
assert(R::Fraction({0,0,0},10,{0,0,0},10)==1);
assert(std::abs(R::Fraction({0,0,0},100,{0,0,0},10)-.01)<.0001);
assert(R::Fraction({0,0,0},10,{20,0,0},10)==0);
assert(R::Fraction({0,0,0},10,{15,0,0},10)<.1);
assert(R::Fraction({nan,0,0},10,{0,0,0},10)==0);
Coverage c;ERR_HQSearchReport r;
assert(c.Multiplier(r.Position,10,1000)==1);
assert(c.Ingest(&r,10,2000)&&c.Count()==1);
assert(c.Multiplier(r.Position,10,2000)==.25);
assert(c.Multiplier({350,0,250},10,2000)==1); // same legacy500m cell, unseen patch stays intact
assert(c.Multiplier(r.Position,100,2000)>.99); // small patch cannot clear large hypothesis
assert(!c.Ingest(&r,10,2000));
r.MessageId=2;assert(c.Ingest(&r,10,2000));assert(c.Multiplier(r.Position,10,2000)==.25);
assert(c.Multiplier(r.Position,10,901001)==1); // observation-time expiry
r.MessageId=3;r.ContactFound=true;assert(!c.Ingest(&r,10,2000));
r.ContactFound=false;r.SweepCompleted=false;assert(!c.Ingest(&r,10,2000));
r.SweepCompleted=true;assert(!c.Ingest(&r,41,2000)&&!c.Ingest(&r,0,2000));
assert(!c.Ingest(&r,10,999)&&!c.Ingest(&r,10,121001));
assert(!c.Ingest(nullptr,10,2000));
c.Forget(r.Position,2000);assert(c.Count()==0&&c.Multiplier(r.Position,10,2000)==1);
r.ObservedAt=1500;assert(!c.Ingest(&r,10,3000)); // newer positive beats delayed negative
r.MessageId=4;r.ObservedAt=2000;assert(!c.Ingest(&r,10,3000)); // simultaneous positive wins
r.MessageId=5;r.ObservedAt=2001;assert(c.Ingest(&r,10,3000));
c.Forget(r.Position,1999);assert(c.Count()==1); // old positive cannot erase newer inspection
c.Forget({400,0,400},4000);assert(c.Count()==1); // unrelated patch stays intact
c.Forget({nan,0,0},4000);assert(c.m_Positives.Count()==3);
Coverage cap;
for(int i=0;i<140;i++){r.MessageId=i+1;r.Position={float(i*50),0,0};r.ObservedAt=1000+i;assert(cap.Ingest(&r,10,2000));}
assert(cap.Count()==128&&cap.Multiplier({0,0,0},10,2000)==1);
for(int i=0;i<140;i++)cap.Forget({float(i*50),0,0},2000+i);
assert(cap.m_Positives.Count()==128&&cap.m_Floor==2011);
r.SourceId=2;r.MessageId=1;r.Position={9000,0,9000};r.ObservedAt=2000;
assert(!cap.Ingest(&r,10,3000)); // eviction watermark protects chronology
r.MessageId=2;r.ObservedAt=2200;assert(cap.Ingest(&r,10,3000));
Coverage sources;
for(int i=1;i<=256;i++){r.SourceId=i;r.MessageId=1;r.ObservedAt=1000+i;assert(sources.Ingest(&r,10,2000));}
r.SourceId=257;assert(!sources.Ingest(&r,10,2000)&&sources.m_Replay.Count()==256);
sources.Clear();assert(sources.Count()==0&&sources.m_Replay.empty()&&sources.m_Floor==-1);
assert(sources.Ingest(&r,10,2000));
// Search destinations move to uninspected local ground while broad uncertainty survives.
Coverage selection;ERR_HQSearchReport done;vector chosen;
assert(selection.Select(done.Position,90,done.Position,2000,chosen));assert(chosen.x==250&&chosen.z==250);
assert(selection.Ingest(&done,10,2000));
assert(selection.Select(done.Position,90,done.Position,2000,chosen));
assert(vector::DistanceXZ(chosen,done.Position)==30&&selection.Multiplier(chosen,10,2000)==1);
Array<vector> reserved;reserved.Insert(chosen);
assert(selection.Select(done.Position,90,done.Position,2000,chosen,&reserved));
assert(vector::DistanceXZ(chosen,reserved[0])>=60&&vector::DistanceXZ(chosen,done.Position)<=90);
assert(!selection.Select(done.Position,10,done.Position,2000,chosen));
assert(selection.Select(done.Position,10,done.Position,901001,chosen)); // expired visit reopens
assert(!selection.Select(done.Position,111,done.Position,2000,chosen));
assert(!selection.Select(done.Position,nan,done.Position,2000,chosen));
for(int i=0;i<32;i++)reserved.Insert(done.Position);
assert(!selection.Select(done.Position,90,done.Position,2000,chosen,&reserved));
}
''')
print('PASS: actual10/40m local negative patches, same-cell hidden-ground preservation, proportional residual belief, replay, chronology, physical-report guards, caps and cleanup')