70 lines
7.0 KiB
Python
70 lines
7.0 KiB
Python
"""Execute actual HQ route choice/visual queue/caller exclusion with radio boundary doubles."""
|
|
import os,re
|
|
from pathlib import Path
|
|
from policy_harness import ROOT,body,run
|
|
p=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQ.c'))
|
|
s='\n'.join(body(p,x) for x in ('protected bool OriginOccupied(', 'protected ERR_HQRoute ObservationRoute(', 'protected void Observe(', 'protected void QueueReports('))
|
|
s=s.replace('protected ','').replace('array<IEntity>','Arr<IEntity>').replace('array<ref ERR_HQVisualContact>','Arr<ERR_HQVisualContact*>').replace('foreach (','for (')
|
|
for name in ('ERR_HQUnit','ERR_HQRoute','ERR_HQReport','ERR_HQVisualContact'):
|
|
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
|
|
for name in ('unit','other','route','own','seen','report'):
|
|
s=s.replace(name+'.',name+'->')
|
|
for name in ('Uplink','Task'):
|
|
s=s.replace(name+'.',name+'->')
|
|
s=s.replace('Nodes[0].','Nodes[0]->').replace('m_Mode.Settings().Get','m_Mode->Settings()->Get').replace('ERR_HQObservation.','ERR_HQObservation::').replace('null','nullptr')
|
|
s=s.replace('ERR_HQRoute* own;','ERR_HQRoute* own=nullptr;').replace('ERR_HQRoute* route;','ERR_HQRoute* route=nullptr;')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <string>
|
|
using IEntity=void*;
|
|
using string=std::string;
|
|
template<class T>struct Arr:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;bool IsEmpty(){return this->empty();}int Count(){return this->size();}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}void Insert(T v){this->push_back(v);}};
|
|
struct Group{};
|
|
struct Node{IEntity Carrier=nullptr;bool valid=true;bool Valid(bool){return valid;}};
|
|
struct ERR_HQRoute{bool Originated=false,Failed=false;Group*LocalSource=nullptr;Arr<Node*> Nodes;int MessageId=0;string Payload;};
|
|
struct ERR_HQVisualContact{IEntity Entity=nullptr;int Position=91;float ObservedAt=900;};
|
|
struct Utility{bool visible=true;ERR_HQVisualContact sample;};
|
|
struct ERR_HQObservation{static void Gather(Utility*u,float,Arr<ERR_HQVisualContact*>&out){if(u->visible)out.Insert(&u->sample);}template<class T,class A>static void Describe(T*,A&,float){} };
|
|
struct Selector{int queues=0;ERR_HQVisualContact*Select(Arr<ERR_HQVisualContact*>&s){return s[0];}void Queued(IEntity){queues++;}};
|
|
struct History{IEntity target=nullptr;float time=-1;bool CanQueue(IEntity t,float at,float){return t&&!(t==target&&at<=time);}void Queued(IEntity t,float at,float){target=t;time=at;}};
|
|
struct ERR_HQReport{int MessageId=0,Position=0;IEntity Target=nullptr;Group*Source=nullptr;float ObservedAt=0;string Description(float){return "unaltered source sighting";}};
|
|
struct Settings{int Get(string){return 5;}};
|
|
struct Mode{struct Settings values;struct Settings*Settings(){return &values;}};
|
|
struct Task{float ArrivedAt=0;};
|
|
struct ERR_HQUnit{struct Group*Group=nullptr;struct Utility*Utility=nullptr;IEntity Radio=nullptr;float NextReport=0;ERR_HQRoute*Uplink=nullptr;ERR_HQReport*PendingReport=nullptr;Selector ReportSelection;History SentObservations;int StatusSampler=1;struct Task*Task=nullptr;};
|
|
struct Network{ERR_HQRoute*own=nullptr,*local=nullptr;IEntity ownCarrier=nullptr;int attempts=0;Arr<IEntity>lastBusy;
|
|
ERR_HQRoute*Route(IEntity caller){return caller==ownCarrier?own:nullptr;}
|
|
ERR_HQRoute*LocalRoute(Group*source,int,Arr<IEntity>&busy){attempts++;lastBusy=busy;if(!local||busy.Contains(local->Nodes[0]->Carrier))return nullptr;local->LocalSource=source;return local;}};
|
|
struct Journal{int next=0,reports=0;int Message(){return ++next;}void Write(string kind,string,int){if(kind=="REPORT")reports++;}};
|
|
struct HQ{Mode mode;Mode*m_Mode=&mode;Network m_Network;Journal m_Journal;Arr<ERR_HQUnit*>m_Units;int statuses=0,bodies=0,sweeps=0;
|
|
void StatusReport(ERR_HQUnit*,float){statuses++;}void ObserveBody(ERR_HQUnit*,float){bodies++;}void Sweep(ERR_HQUnit*,float){sweeps++;}
|
|
''' + s.replace('static void Describe(auto*,auto&,float)','template<class T,class A>static void Describe(T*,A&,float)') + r'''
|
|
};
|
|
int main(){
|
|
HQ h;Group sourceGroup,receiverGroup;int witness,carrier,target;Utility utility;utility.sample.Entity=⌖ERR_HQUnit source,receiver;source.Group=&sourceGroup;source.Utility=&utility;receiver.Group=&receiverGroup;receiver.Radio=&carrier;Node node;node.Carrier=&carrier;ERR_HQRoute local;local.Nodes={&node};h.m_Network.local=&local;h.m_Units={&source,&receiver};
|
|
Utility quietUtility;quietUtility.visible=false;ERR_HQUnit quiet;quiet.Group=&sourceGroup;quiet.Utility=&quietUtility;
|
|
h.Observe(&quiet,0);assert(h.m_Network.attempts==0&&quiet.NextReport==5000);
|
|
h.Observe(&source,1000);assert(source.Uplink==&local&&source.PendingReport&&h.m_Journal.reports==1&&source.ReportSelection.queues==1);
|
|
auto report=source.PendingReport;assert(report->Source==&sourceGroup&&report->Target==&target&&report->ObservedAt==900&&report->Position==91&&report->MessageId==local.MessageId);
|
|
assert(receiver.PendingReport==nullptr&&receiver.Uplink==nullptr); // No recipient-owned rewrite or direct HQ intake.
|
|
h.Observe(&source,1001);assert(source.PendingReport==report&&h.m_Journal.reports==1);
|
|
// Receiver cannot originate its own reports during the source's physical origin hold.
|
|
Task task;task.ArrivedAt=500;receiver.Task=&task;assert(h.OriginOccupied(&carrier,&receiver));h.QueueReports(&receiver,1001);assert(task.ArrivedAt==-1&&h.statuses==0&&h.sweeps==0);
|
|
ERR_HQUnit contender;contender.Group=&sourceGroup;contender.Utility=&utility;assert(!h.ObservationRoute(&contender));
|
|
local.Originated=true;assert(!h.OriginOccupied(&carrier,&receiver));
|
|
// Explicit own pending uplink also excludes a candidate even after its origin phase.
|
|
source.Uplink=nullptr;receiver.Uplink=&local;assert(!h.ObservationRoute(&contender));receiver.Uplink=nullptr;
|
|
// Identical observed lineage never receives a second message id even after failure/slot release.
|
|
source.NextReport=0;h.Observe(&source,1100);assert(!source.Uplink&&h.m_Journal.reports==1&&source.ReportSelection.queues==1);
|
|
utility.sample.ObservedAt=1101;source.NextReport=0;h.Observe(&source,1101);assert(source.Uplink==&local&&h.m_Journal.reports==2&&source.PendingReport->ObservedAt==1101);
|
|
// Prefer an actual own route; a currently invalid own caller falls back to the nearby usable route.
|
|
source.Uplink=nullptr;Node ownNode;ownNode.Carrier=&witness;ERR_HQRoute own;own.Nodes={&ownNode};source.Radio=&witness;h.m_Network.ownCarrier=&witness;h.m_Network.own=&own;
|
|
assert(h.ObservationRoute(&source)==&own);ownNode.valid=false;assert(h.ObservationRoute(&source)==&local);
|
|
// A blocked fallback throttles the next no-radio attempt and creates no report.
|
|
source.Radio=nullptr;h.m_Network.local=nullptr;source.NextReport=0;utility.sample.ObservedAt=2000;int messages=h.m_Journal.next;h.Observe(&source,2000);assert(source.NextReport==7000&&!source.Uplink&&h.m_Journal.next==messages);
|
|
}
|
|
'''.replace('static void Describe(auto*,auto&,float)','template<class T,class A>static void Describe(T*,A&,float)'))
|
|
print('PASS: actual visual intake retains source/time/position/id, rejects replay, prefers usable own route, excludes simultaneous callers and conservatively resets negative-sweep dwell')
|