Files
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

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')