Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
109 lines
8.2 KiB
Python
109 lines
8.2 KiB
Python
"""Execute the actual local evidence cache and priority/radio intake methods."""
|
|
import re
|
|
from policy_harness import ROOT,body,run
|
|
base=ROOT/'addons/core/Scripts/Game/ERR/10_AI'
|
|
s=(base/'ERR_HQLocalAwareness.c').read_text().split('modded class')[0]
|
|
h=(base/'ERR_HQLocalHandoff.c').read_text().split('class ERR_HQObservationHistory')[1]
|
|
h='class ERR_HQObservationHistory'+h
|
|
q='\n'.join(body(base/'ERR_HQ.c',sig) for sig in ['protected void ContactReport(', 'protected void QueueReports('])
|
|
def trans(s):
|
|
s=s.replace('protected ','').replace('ref ','').replace('class ','struct ').replace('\n}\n','\n};\n')
|
|
s=s.replace('array<ERR_HQReport>', 'Array<ERR_HQReport*>').replace('array<IEntity>','Array<IEntity>').replace('array<float>','Array<float>')
|
|
for t in ['ERR_HQReport','ERR_HQObservationHistory','ERR_HQUnit','ERR_HQRoute','SCR_AIGroup']:
|
|
s=re.sub(r'\b'+t+r' (\w+)',t+r'* \1',s)
|
|
s=s.replace('foreach (','for (').replace('null','nullptr')
|
|
for c in ['ERR_ForceRules','ERR_HQMemoryRules','ERR_HQLocalAwareness','Math']:
|
|
s=s.replace(c+'.',c+'::')
|
|
for c in ['known','report','source','history','unit','route']:
|
|
s=re.sub(r'\b'+c+r'\.',c+'->',s)
|
|
s=s.replace('m_Alarm.', 'm_Alarm->').replace('m_Sound.', 'm_Sound->').replace('m_Visual[i].','m_Visual[i]->')
|
|
s=s.replace('unit->LocalAwareness.','unit->LocalAwareness->').replace('unit->SentObservations.','unit->SentObservations->')
|
|
s=s.replace('m_Mode.Settings().Runtime','m_Mode.Settings().Runtime')
|
|
s=s.replace('ERR_HQReport* m_Alarm;', 'ERR_HQReport* m_Alarm=nullptr;').replace('float m_NextAlarm;', 'float m_NextAlarm=0;')
|
|
s=s.replace('ERR_HQReport* m_Sound;', 'ERR_HQReport* m_Sound=nullptr;').replace('float m_NextSound;', 'float m_NextSound=0;')
|
|
return s.replace('unit->Task.', 'unit->Task->')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <vector>
|
|
using string=std::string;
|
|
struct Actor{};using IEntity=Actor*;
|
|
struct SCR_AIGroup:Actor{};
|
|
struct vector{float v[3]={0,0,0};float& operator[](int i){return v[i];}};
|
|
vector Vector(float x,float y,float z){return {{x,y,z}};}
|
|
namespace Math{float Floor(float f){return std::floor(f);}}
|
|
namespace ERR_ForceRules{bool Finite(float f){return std::isfinite(f);}}
|
|
namespace ERR_HQMemoryRules{bool PositionValid(vector p){return std::isfinite(p[0])&&std::isfinite(p[1])&&std::isfinite(p[2]);}}
|
|
template<class T>struct Array:std::vector<T>{void Clear(){this->clear();}int Count(){return this->size();}
|
|
void Insert(T x){this->push_back(x);}void Remove(int i){this->erase(this->begin()+i);}int Find(T x){auto p=std::find(this->begin(),this->end(),x);return p==this->end()?-1:p-this->begin();}};
|
|
struct ERR_HQReport{IEntity Target=nullptr;SCR_AIGroup* Source=nullptr;vector Position;float ObservedAt=0;
|
|
int MessageId=0;string TravelMode="unknown";string Weapon="unknown";
|
|
ERR_HQReport* Snapshot(){return new ERR_HQReport(*this);}string Description(float){return TravelMode;}};
|
|
'''+trans(h)+trans(s)+r'''
|
|
struct ERR_HQRoute{int MessageId=0;string Payload;};
|
|
struct Task{float ArrivedAt=0;};
|
|
struct ERR_HQUnit{bool ExecutionTurn=true;SCR_AIGroup* Group=nullptr;IEntity Radio=nullptr;ERR_HQLocalAwareness own;
|
|
ERR_HQLocalAwareness* LocalAwareness=&own;ERR_HQObservationHistory hist;ERR_HQObservationHistory* SentObservations=&hist;
|
|
ERR_HQRoute* Uplink=nullptr;ERR_HQReport* PendingReport=nullptr;struct Task* Task=nullptr;
|
|
float NextContact=0,NextReport=0,NextStatus=0;bool status=false;};
|
|
struct Settings{int Runtime(string){return 10;}};
|
|
struct Mode{struct Settings Settings(){return {};}};
|
|
struct Journal{int id=0;int Message(){return ++id;}void Write(string,string,int){}};
|
|
struct HQ{bool m_Coordinated=false;void QueueExecution(ERR_HQUnit*,float){}Mode m_Mode;Journal m_Journal;bool network=true,occupied=false;
|
|
ERR_HQRoute* ObservationRoute(ERR_HQUnit*){return network?new ERR_HQRoute:nullptr;}
|
|
bool OriginOccupied(IEntity,ERR_HQUnit*){return occupied;}
|
|
void StatusReport(ERR_HQUnit* u,float now){if(!u->Uplink&&now>=u->NextStatus){u->Uplink=new ERR_HQRoute;u->status=true;u->NextStatus=now+180000;}}
|
|
void Observe(ERR_HQUnit*,float){} void ObserveBody(ERR_HQUnit*,float){} void Sweep(ERR_HQUnit*,float){}
|
|
'''+trans(q)+r'''
|
|
};
|
|
int main(){
|
|
SCR_AIGroup group;Actor enemy;ERR_HQLocalAwareness cache;ERR_HQObservationHistory history;
|
|
ERR_HQReport r;r.Target=&enemy;r.Source=&group;r.Position=Vector(20,0,40);r.ObservedAt=100;r.Weapon="rifle";
|
|
cache.Remember(&r,100);r.Position[0]=900;r.Weapon="launcher";
|
|
auto held=cache.Peek(1200,&history);assert(held&&held->Position[0]==20&&held->Weapon=="rifle"&&held->ObservedAt==100);
|
|
assert(cache.Peek(30100,&history)&&!cache.Peek(30101,&history)); // original time, never refresh on reads
|
|
r.ObservedAt=32000;cache.Remember(&r,32000);assert(!cache.CaptureDue(&enemy,32999)&&cache.CaptureDue(&enemy,33000));
|
|
held=cache.Peek(33000,&history);history.Queued(held->Target,held->ObservedAt,33000);cache.Queued(held,33000);
|
|
cache.Remember(&r,33000);assert(!cache.Peek(33000,&history)); // same observation cannot replay
|
|
cache.Hear(&group,Vector(12,8,-1),34000,34000);auto sound=cache.Peek(34000,&history);
|
|
assert(sound&&!sound->Target&&sound->Source==&group&&sound->Position[0]==75&&sound->Position[2]==-75);
|
|
assert(sound->TravelMode=="heard gunfire"&&sound->Weapon=="unknown");
|
|
cache.Queued(sound,34000);cache.Hear(&group,Vector(800,0,0),35000,35000);
|
|
assert(!cache.Peek(63999,&history)&&cache.Peek(64000,&history));
|
|
cache.Clear();assert(!cache.Peek(64000,&history));
|
|
// Alarm retains its own slot and timestamp; it cannot replace gunfire/visual
|
|
// evidence or use multiple squad members to replay an already queued event.
|
|
cache.HearAlarm(&group,Vector(4,7,9),65000,65001);
|
|
auto alarm=cache.Peek(65001,&history);assert(alarm&&!alarm->Target&&alarm->TravelMode=="heard vehicle alarm");
|
|
assert(alarm->Position[0]==75&&alarm->Position[2]==75&&alarm->ObservedAt==65000);
|
|
cache.HearAlarm(&group,Vector(900,0,900),64000,65001);assert(cache.Peek(65001,&history)->Position[0]==75);
|
|
cache.Hear(&group,Vector(250,0,250),65001,65001);assert(cache.Peek(65001,&history)->TravelMode=="heard gunfire");
|
|
r.ObservedAt=65002;cache.Remember(&r,65002);assert(cache.Peek(65002,&history)->Target==&enemy);
|
|
auto visual=cache.Peek(65002,&history);cache.Queued(visual,65002);
|
|
auto gunfire=cache.Peek(65002,&history);assert(gunfire->TravelMode=="heard gunfire");cache.Queued(gunfire,65002);
|
|
assert(cache.Peek(65002,&history)->TravelMode=="heard vehicle alarm");cache.Queued(alarm,65002);
|
|
cache.HearAlarm(&group,Vector(4,7,9),65000,65003);assert(!cache.m_Alarm); // duplicate native receiver
|
|
cache.HearAlarm(&group,Vector(4,7,9),66000,66000);assert(!cache.Peek(95001,&history));
|
|
assert(cache.Peek(95002,&history)->ObservedAt==66000);
|
|
assert(!cache.Peek(96001,&history)); // receipt never renews observation age
|
|
cache.Clear();cache.HearAlarm(nullptr,Vector(0,0,0),1,1);cache.HearAlarm(&group,Vector(0,0,0),2,1);
|
|
cache.HearAlarm(&group,Vector(NAN,0,0),1,1);assert(!cache.Peek(1,&history));
|
|
cache.HearAlarm(&group,Vector(4,7,9),1,1);assert(cache.Peek(1,&history));cache.Clear();
|
|
Actor targets[40];for(int i=0;i<40;i++){r.Target=&targets[i];r.ObservedAt=70000+i;cache.Remember(&r,70000+i);}
|
|
assert(cache.m_Visual.Count()==16);cache.Clear();
|
|
HQ h;ERR_HQUnit u;u.Group=&group;r.Target=&enemy;r.ObservedAt=1000;
|
|
u.own.Remember(&r,1000);h.QueueReports(&u,2000);assert(u.PendingReport&&!u.status&&u.PendingReport->ObservedAt==1000);
|
|
auto inFlight=u.Uplink;r.ObservedAt=3000;u.own.Remember(&r,3000);h.QueueReports(&u,4000);assert(u.Uplink==inFlight);
|
|
u.Uplink=nullptr;u.PendingReport=nullptr;r.ObservedAt=16000;u.own.Remember(&r,16000);
|
|
h.QueueReports(&u,16000);assert(u.status&&!u.PendingReport); // bounded status delay under continuous contacts
|
|
u.Uplink=nullptr;u.status=false;h.QueueReports(&u,17000);assert(u.PendingReport&&u.PendingReport->ObservedAt==16000);
|
|
u.Uplink=nullptr;u.PendingReport=nullptr;r.ObservedAt=23000;u.own.Remember(&r,23000);
|
|
h.network=false;h.ContactReport(&u,23000);assert(!u.Uplink&&u.own.Peek(24000,&u.hist));
|
|
h.network=true;h.ContactReport(&u,24000);assert(!u.Uplink); // failed-route retry bounded
|
|
h.ContactReport(&u,28000);assert(u.PendingReport&&u.PendingReport->ObservedAt==23000);
|
|
}
|
|
''')
|
|
print('PASS: captured sighting retention, immutable details, expiry, replay rejection, bounded sound uncertainty/cooldown, radio loss, priority and overdue-status fairness')
|