Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
159 lines
17 KiB
Python
159 lines
17 KiB
Python
"""Actual HQ adapter intake, ownership and radio arbitration with explicit native boundary doubles."""
|
|
from policy_harness import ROOT, body, run
|
|
import re
|
|
P=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQCoordinated.c'
|
|
HQ=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'
|
|
def cpp(s):
|
|
s=s.replace('override ','').replace('protected ','').replace('ref ','').replace('string.Format','Format')
|
|
s=s.replace('array<ERR_HQOperationForce> forces','Array<ERR_HQOperationForce*>& forces')
|
|
for typ in ('ERR_HQUnit','ERR_HQTask','ERR_HQExecutionReport','ERR_HQSearchReport','ERR_HQRoute',
|
|
'ERR_HQOperationSlot','ERR_HQOperationForce','ERR_HQWatchEntry'):
|
|
s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
|
|
for typ in ('ERR_HQMemoryRules','ERR_HQExecutionRules','ERR_HQOperationRules','ERR_HQOperationRole','ERR_HQLocalAwareness','Math'):
|
|
s=s.replace(typ+'.',typ+'::')
|
|
for name in ('unit','report','searched','task','slot','entry','force','route','accepted','casualty','expirySlot','expiryReport','Task','Report','PreviousTask','PreviousReport','AmmoState'):
|
|
s=s.replace(name+'.',name+'->')
|
|
s=s.replace('foreach (ERR_HQUnit* unit : m_Units)','for (ERR_HQUnit* unit : m_Units)')
|
|
s=re.sub(r'\bnull\b','nullptr',s).replace('int contactFlag;', 'int contactFlag=0;').replace('int supportFlag;', 'int supportFlag=0;').replace('int arrivedFlag;', 'int arrivedFlag=0;').replace('ERR_HQExecutionReport* expiryReport;', 'ERR_HQExecutionReport* expiryReport=nullptr;')
|
|
return s
|
|
methods='\n'.join(cpp(body(P,s)) for s in ('protected void QueueExecution(', 'protected void AcceptExecution(',
|
|
'protected bool CoordinatedDelivery(', 'protected void CoordinatedForces(', 'protected int CoordinatedResponseAssigned('))
|
|
queue=cpp(body(HQ,'protected void QueueReports('))
|
|
assigned=cpp(body(HQ,'protected int ResponseAssigned('))
|
|
closure='void ReportedClosures(){'+cpp(body(P,'for (int watched = 0; watched < m_Watch.Count(); watched++)'))+'}'
|
|
expiry='void Expire(ERR_HQUnit* unit,float now){'+cpp(body(P,'if (unit.Task && now >= unit.Task.ExpiresAt)'))+'}'
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <vector>
|
|
#include <string>
|
|
using string=std::string;
|
|
template<class...T>string Format(T...){return "report";}
|
|
struct vector{float x=0,y=0,z=0;};
|
|
struct Math{static float Round(float x){return std::round(x);}};
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Clear(){this->clear();}void Insert(T t){this->push_back(t);}};
|
|
enum class ERR_HQOperationRole{OBSERVE,FIX,FLANK,BLOCK,SEARCH,RESERVE,WITHDRAW};
|
|
struct ERR_HQTask{int OperationId=1,BeliefId=1,OperationRevision=2,BeliefRevision=2,ExecutionId=10,SupportSourceId=2;float SupportUntil=9000,ArrivedAt=0,ExpiresAt=120000;ERR_HQOperationRole OperationRole=ERR_HQOperationRole::SEARCH;};
|
|
struct ERR_HQExecutionReport{int OperationRevision=2,ReportSequence=1;float SupportUntil=0;int SourceId=1,MessageId=1,OperationId=1,ExecutionId=10,State=0,BeliefId=1,BeliefRevision=2;float ObservedAt=1000,ContactObservedAt=1000,SearchedRadius=0,DistanceRemaining=10;bool SupportReady=false,HasContact=false,PhysicallyArrived=false;string Detail;vector ContactPosition{9,0,8},Position{1,0,2};};
|
|
struct ERR_HQSearchReport{int SourceId=0,MessageId=0,BeliefId=0,BeliefRevision=0;vector Position;float ObservedAt=0;bool SweepCompleted=false,ContactFound=true;};
|
|
struct ERR_HQMemoryRules{static bool DeliveryFresh(float a,float n){return std::isfinite(a)&&std::isfinite(n)&&a>=0&&n>=a&&n-a<=120000;}};
|
|
struct ERR_HQOperationRules{static bool Terminal(int state){return state==5||state==7||state==8;}};
|
|
struct ERR_HQExecutionRules{static bool SupportFresh(float a,float n){return std::isfinite(a)&&a>n&&a<=n+30000;}};
|
|
struct ERR_HQLocalAwareness{static bool Priority(float n,float next,float status){return n>=next&&n<status+15000;}};
|
|
struct ERR_HQRoute{int MessageId=0;string Payload;};
|
|
struct Execution{bool needs=true;int acks=0;ERR_HQExecutionReport report;bool NeedsReport(){return needs;}ERR_HQExecutionReport*Snapshot(int source,int message,float){report.SourceId=source;report.MessageId=message;return &report;}void Reported(ERR_HQExecutionReport*){acks++;}};
|
|
struct Ammo{bool Complete=true;int Members=4,SmallArmsRounds=150,SmallArmsChambers=4;};
|
|
struct ERR_HQWatchEntry{string SourceId="one";bool Closed=false;int Fit=4,Count=4,Strength=4;vector Position{100,0,200};float ObservedAt=1000;Ammo*AmmoState=nullptr;};
|
|
struct ERR_HQOperationForce{int Id=0,Strength=0,Fit=0,Count=-1,Rounds=0,OperationId=0,ExecutionId=0;vector Position;float ObservedAt=0,StatusObservedAt=-1;bool AmmoKnown=false;};
|
|
struct ERR_HQOperationSlot{ERR_HQTask*Task=nullptr;ERR_HQExecutionReport*Report=nullptr;ERR_HQTask*PreviousTask=nullptr;ERR_HQExecutionReport*PreviousReport=nullptr;};
|
|
struct ERR_HQUnit{int Id=1;void*Group=this;void*Radio=this;bool HomeGuard=false,ReservedForSupport=false,eligible=true,PendingCheckin=false,ExecutionTurn=true,security=false;int LogisticsReservation=0;float NextContact=0,NextStatus=60000;string ForceSourceId="one";void*PendingOrder=nullptr,*PendingConvoyOrder=nullptr,*Assignment=nullptr;ERR_HQRoute*Uplink=nullptr,*Downlink=nullptr;ERR_HQTask*Task=nullptr,*PendingTask=nullptr;ERR_HQExecutionReport*PendingExecution=nullptr;struct Execution Execution;int releases=0;void ReleaseAssignment(){releases++;Task=nullptr;Assignment=nullptr;}bool Eligible(void*){return eligible;}};
|
|
struct Network{ERR_HQRoute route;bool available=true;int calls=0;ERR_HQRoute*Route(void*){calls++;return available?&route:nullptr;}};
|
|
struct Journal{std::vector<string>kinds;int next=0,writes=0;int Message(){return ++next;}void Write(string kind,string,int=0){writes++;kinds.push_back(kind);}};
|
|
struct Operations{ERR_HQOperationSlot*slot=nullptr;std::vector<int>active;int CountAll(float){return active.size();}bool Active(int id,float){for(int a:active)if(a==id)return true;return false;}std::vector<string>closed;void CloseReported(string source){closed.push_back(source);}bool accepted=true;int accepts=0,releases=0;void Release(int,int,float){releases++;}bool Accept(ERR_HQExecutionReport*,float){accepts++;return accepted;}ERR_HQOperationSlot*Find(int){return slot;}};
|
|
struct Coverage{int positives=0,negatives=0;ERR_HQSearchReport latest;float radius=0;void Forget(vector,float){positives++;}bool Ingest(ERR_HQSearchReport*r,float size,float){negatives++;latest=*r;radius=size;return true;}};
|
|
struct Watch{std::vector<ERR_HQWatchEntry*>entries;int Count(){return entries.size();}ERR_HQWatchEntry*At(int index){return entries[index];}ERR_HQWatchEntry*entry=nullptr;ERR_HQWatchEntry*Find(string){return entry;}};
|
|
struct Adapter{bool m_Coordinated=true,occupied=false;void*m_Mode=nullptr;Network m_Network;Journal m_Journal;Operations m_Operations;Coverage m_LocalCoverage;Watch m_Watch;Array<ERR_HQUnit*>m_Units;int contacts=0,statuses=0,observations=0,bodies=0,sweeps=0,finished=0;void FinishWatch(ERR_HQTask*,float){finished++;}
|
|
int mounted=0;int Assigned(){int count=mounted;for(auto unit:m_Units)if(unit->Assignment||unit->PendingOrder)count++;return count;}bool SecurityAssigned(ERR_HQUnit*unit){return unit->security;}
|
|
bool OriginOccupied(void*,ERR_HQUnit*){return occupied;}
|
|
void StatusReport(ERR_HQUnit*u,float n){if(u->Uplink||n<u->NextStatus)return;u->Uplink=&m_Network.route;u->NextStatus=n+60000;statuses++;}
|
|
void ContactReport(ERR_HQUnit*u,float n){if(u->Uplink||n<u->NextContact)return;u->Uplink=&m_Network.route;u->NextContact=n+5000;u->ExecutionTurn=true;contacts++;}
|
|
void Observe(ERR_HQUnit*,float){observations++;}void ObserveBody(ERR_HQUnit*,float){bodies++;}void Sweep(ERR_HQUnit*,float){sweeps++;}
|
|
'''+methods+queue+assigned+expiry+closure+r'''
|
|
};
|
|
int main(){
|
|
Adapter a;ERR_HQUnit u;ERR_HQTask task;ERR_HQOperationSlot initial;initial.Task=&task;a.m_Operations.slot=&initial;
|
|
// Physical/pending and delivered-ledger commitments form a union. Lost
|
|
// local units, ordinary orders and existing mounted work retain their share.
|
|
{Adapter cap;ERR_HQUnit active,pending,ordinary,security;
|
|
active.Id=1;active.Assignment=&active;pending.Id=2;pending.Assignment=&pending;pending.PendingOrder=&pending;
|
|
ordinary.Id=3;ordinary.Assignment=&ordinary;security.Id=4;security.Assignment=&security;security.security=true;
|
|
cap.m_Units={&active,&pending,&ordinary,&security};cap.mounted=1;cap.m_Operations.active={1,2,99};
|
|
assert(cap.ResponseAssigned()==4&&cap.CoordinatedResponseAssigned(1000)==5);
|
|
active.Assignment=nullptr;active.Group=nullptr;assert(cap.ResponseAssigned()==3&&cap.CoordinatedResponseAssigned(1000)==5);
|
|
cap.m_Units={&pending,&ordinary,&security};assert(cap.CoordinatedResponseAssigned(1000)==5);
|
|
cap.m_Operations.active={2};assert(cap.CoordinatedResponseAssigned(2000)==3); // delivered closures/deadlines release silent reservations
|
|
cap.m_Operations.active.clear();assert(cap.CoordinatedResponseAssigned(2000)==3); // physical ordinary/pending/mounted work still counts
|
|
cap.m_Operations.active={4};assert(cap.CoordinatedResponseAssigned(2000)==4); // site defence was excluded physically, so don't subtract its retained old pursuit ledger
|
|
}
|
|
a.m_Coordinated=false;a.QueueExecution(&u,2000);assert(!u.Uplink&&a.m_Network.calls==0);
|
|
a.m_Coordinated=true;u.Execution.needs=false;a.QueueExecution(&u,2000);assert(!u.Uplink);
|
|
u.Execution.needs=true;a.m_Network.available=false;a.QueueExecution(&u,2000);assert(!u.Uplink&&u.ExecutionTurn);
|
|
a.m_Network.available=true;u.Execution.report.ObservedAt=3000;a.QueueExecution(&u,2000);assert(!u.Uplink&&u.ExecutionTurn);
|
|
u.Execution.report.ObservedAt=1000;a.QueueExecution(&u,2000);assert(u.Uplink&&u.PendingExecution&&!u.ExecutionTurn&&u.Execution.acks==0);
|
|
int calls=a.m_Network.calls;a.QueueExecution(&u,2000);assert(calls==a.m_Network.calls);
|
|
a.m_Operations.accepted=false;u.PendingExecution->SearchedRadius=10;a.AcceptExecution(&u,3000);
|
|
assert(u.Execution.acks==1&&a.m_LocalCoverage.negatives==0&&a.m_Journal.kinds.back()=="EXECUTION NOT APPLIED"); // rejected/superseded sequence acknowledged, never evidence
|
|
a.m_Operations.accepted=true;a.AcceptExecution(&u,3000);assert(a.m_LocalCoverage.negatives==1&&a.m_LocalCoverage.radius==10);
|
|
assert(a.m_LocalCoverage.latest.Position.x==1&&a.m_LocalCoverage.latest.BeliefRevision==2&&a.m_LocalCoverage.latest.SweepCompleted);
|
|
u.PendingExecution->HasContact=true;u.PendingExecution->SearchedRadius=0;a.AcceptExecution(&u,3000);assert(a.m_LocalCoverage.positives==1&&a.m_LocalCoverage.negatives==1);
|
|
ERR_HQOperationSlot slot;slot.Task=&task;a.m_Operations.slot=&slot;
|
|
assert(a.CoordinatedDelivery(&u,&task,3000));
|
|
u.HomeGuard=true;assert(!a.CoordinatedDelivery(&u,&task,3000));u.HomeGuard=false;
|
|
u.ReservedForSupport=true;assert(!a.CoordinatedDelivery(&u,&task,3000));u.ReservedForSupport=false;
|
|
task.OperationRole=ERR_HQOperationRole::FLANK;task.SupportUntil=3000;assert(!a.CoordinatedDelivery(&u,&task,3000));
|
|
task.SupportUntil=9000;task.SupportSourceId=1;assert(!a.CoordinatedDelivery(&u,&task,3000));task.SupportSourceId=2;assert(a.CoordinatedDelivery(&u,&task,3000));
|
|
ERR_HQTask foreign=task;assert(!a.CoordinatedDelivery(&u,&foreign,3000));
|
|
// Friendly planner positions come from delivered reports, never native group positions.
|
|
Adapter b;ERR_HQUnit own;ERR_HQWatchEntry entry;Ammo ammo;entry.AmmoState=&ammo;b.m_Watch.entry=&entry;b.m_Units={&own};Array<ERR_HQOperationForce*> forces;
|
|
b.CoordinatedForces(2000,forces);assert(forces.size()==1&&forces[0]->Position.x==100&&forces[0]->Rounds==154);
|
|
assert(forces[0]->Count==4&&forces[0]->StatusObservedAt==1000);
|
|
own.PendingOrder=&own;b.CoordinatedForces(2000,forces);assert(forces.empty());own.PendingOrder=nullptr;
|
|
own.HomeGuard=true;b.CoordinatedForces(2000,forces);assert(forces.empty());own.HomeGuard=false;
|
|
own.LogisticsReservation=1;b.CoordinatedForces(2000,forces);assert(forces.empty());own.LogisticsReservation=0;
|
|
entry.ObservedAt=0;b.CoordinatedForces(120001,forces);assert(forces.empty());entry.ObservedAt=1000;
|
|
own.Task=&task;own.Assignment=&own;ERR_HQExecutionReport fresh;fresh.ObservedAt=2000;fresh.Position={300,0,400};slot.Report=&fresh;b.m_Operations.slot=&slot;
|
|
b.CoordinatedForces(3000,forces);assert(forces.size()==1&&forces[0]->Position.x==300&&forces[0]->ExecutionId==10);
|
|
assert(forces[0]->ObservedAt==2000&&forces[0]->StatusObservedAt==1000); // movement heartbeat cannot count as casualty/ammo recovery
|
|
fresh.ExecutionId=99;b.CoordinatedForces(3000,forces);assert(forces[0]->Position.x==100);
|
|
// Continuous contacts and execution changes alternate; status can still take its due slot.
|
|
Adapter radio;ERR_HQUnit sender;sender.Task=&task;
|
|
for(int i=0;i<10;i++){float now=1000+i*6000;sender.Execution.report.ObservedAt=now;sender.Uplink=nullptr;sender.PendingExecution=nullptr;radio.QueueReports(&sender,now);assert(sender.Uplink);}
|
|
assert(radio.contacts==5&&radio.m_Network.calls==5&&radio.sweeps==0);
|
|
sender.Uplink=nullptr;sender.NextStatus=61000;sender.Execution.report.ObservedAt=61000;radio.QueueReports(&sender,61000);assert(radio.statuses==1);
|
|
int count=radio.contacts+radio.m_Network.calls;sender.Uplink=nullptr;radio.occupied=true;radio.QueueReports(&sender,67000);assert(count==radio.contacts+radio.m_Network.calls&&task.ArrivedAt==-1);
|
|
|
|
// Casualty release reads only accepted watch records; missing/silent or
|
|
// physically disappeared formations are not inferred to have been killed.
|
|
Adapter casualties;ERR_HQWatchEntry closed,zero,silent;
|
|
closed.SourceId="closed";closed.Closed=true;zero.SourceId="zero-fit";zero.Fit=0;
|
|
silent.SourceId="silent";silent.ObservedAt=0;ERR_HQUnit disappeared;disappeared.Group=nullptr;
|
|
casualties.m_Units={&disappeared};casualties.m_Watch.entries={nullptr,&silent};casualties.ReportedClosures();
|
|
assert(casualties.m_Operations.closed.empty());
|
|
casualties.m_Watch.entries={&closed,nullptr,&zero,&silent};casualties.ReportedClosures();
|
|
assert(casualties.m_Operations.closed.size()==2&&casualties.m_Operations.closed[0]=="closed"&&casualties.m_Operations.closed[1]=="zero-fit");
|
|
// Deadline telemetry is an HQ scheduling fact, never accepted physical evidence.
|
|
Adapter expired;ERR_HQUnit overdue;ERR_HQTask deadline;overdue.Task=&deadline;
|
|
expired.Expire(&overdue,119999);assert(overdue.Task&&expired.m_Journal.writes==0);
|
|
expired.Expire(&overdue,120000);assert(!overdue.Task&&overdue.releases==1&&expired.m_Operations.releases==1&&expired.finished==1);
|
|
assert(expired.m_Journal.kinds.back()=="OPERATION EXPIRED"&&expired.m_LocalCoverage.negatives==0);
|
|
expired.Expire(&overdue,130000);assert(expired.m_Journal.writes==1&&overdue.releases==1);
|
|
|
|
for(int terminal : {5,7,8}){
|
|
Adapter known;ERR_HQUnit owner;ERR_HQTask finishedTask;owner.Task=&finishedTask;
|
|
ERR_HQExecutionReport outcome;outcome.State=terminal;ERR_HQOperationSlot recorded;
|
|
recorded.Task=&finishedTask;recorded.Report=&outcome;known.m_Operations.slot=&recorded;
|
|
known.Expire(&owner,120000);assert(!owner.Task&&known.m_Operations.releases==1&&known.m_Journal.writes==0);
|
|
owner.Task=&finishedTask;recorded.Task=nullptr;recorded.Report=nullptr;recorded.PreviousTask=&finishedTask;recorded.PreviousReport=&outcome;
|
|
known.Expire(&owner,120001);assert(!owner.Task&&known.m_Operations.releases==2&&known.m_Journal.writes==0);
|
|
owner.Task=&finishedTask;outcome.ExecutionId=99;known.Expire(&owner,120002);
|
|
assert(!owner.Task&&known.m_Journal.kinds.back()=="OPERATION EXPIRED"); // other execution is not proof
|
|
}
|
|
ERR_HQTask ordinaryDeadline;ordinaryDeadline.OperationId=0;overdue.Task=&ordinaryDeadline;
|
|
expired.Expire(&overdue,130000);assert(expired.m_Journal.writes==1&&expired.m_Operations.releases==1&&overdue.releases==2);
|
|
Adapter legacy;legacy.m_Coordinated=false;ERR_HQUnit ordinary;legacy.QueueReports(&ordinary,1000);assert(legacy.m_Network.calls==0&&legacy.sweeps==1);
|
|
}
|
|
''')
|
|
assert 'unit.ExecutionTurn = true' in body(HQ,'protected void ContactReport(')
|
|
assert 'if (m_Coordinated) unit.NextStatus = now + 60000' in body(HQ,'protected void StatusReport(')
|
|
assert 'm_LocalCoverage.Forget(report.Position, report.ObservedAt)' in body(HQ,'protected void Accept(')
|
|
assert 'globalLimit - CoordinatedResponseAssigned(now) - CoordinatedHunterCount(now)' in body(P,'protected void CoordinatedChoices(')
|
|
assert 'if (CoordinatedResponseAssigned(now) < infantryLimit) Investigate(now, infantryLimit)' in body(P,'protected void AssignCoordinated(')
|
|
hunters=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQHunterOperations.c'
|
|
assert 'CoordinatedResponseAssigned(now) + count >= globalLimit' in body(hunters,'override bool CoordinatedHunterCandidate(')
|
|
mounted=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c'
|
|
assert mounted.read_text().count('CoordinatedResponseAssigned(now)') == 3
|
|
shadow=body(P,'protected void CoordinatedChoices(')
|
|
shadow=shadow[shadow.index(' if (shadow)'):shadow.index(' else if (QueueCoordinated')]
|
|
assert 'm_Operations.Queue(' not in shadow and 'm_AttackSpace.Claim(' not in shadow and 'CoordinatedGround(' not in shadow
|
|
print('PASS: actual coordinated adapter report gates, local evidence, delivered-force snapshots, ownership/lease checks, continuous-contact arbitration, legacy isolation and shadow no-native-order boundary')
|