Integrate faction and random settings menus, generated objectives, scouts, convoys, air support, mortar stations and body-intel recovery. Add identified-body aggression, finite hunter UAZ deployment, dedicated objective defence and maximum AI skill with separate reduced aim accuracy. Include policy tests, native probes, evidence and documentation.
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/core/Scripts/Game/ERR/10_AI/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().Runtime','m_Mode->Settings()->Runtime').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 Runtime(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')
|