106 lines
8.1 KiB
Python
106 lines
8.1 KiB
Python
"""Execute production HQ convoy planning/delivery with detached reports and radio-boundary doubles."""
|
|
from policy_harness import ROOT, body, run
|
|
import re
|
|
H = ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQ.c'
|
|
|
|
def cpp(s):
|
|
s=s.replace('protected ', '').replace('null','nullptr').replace('foreach (','for (')
|
|
for x in ('ERR_HQPursuitRules','vector','string'): s=s.replace(x+'.',x+'::')
|
|
for typ in ('ERR_HQUnit','ERR_HQReport','ERR_HQWatchEntry','ERR_HQRoute','ERR_ConvoyCombatOrder','ERR_ConvoyVehicle'):
|
|
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
|
for x in ('entry','unit','best','report','vehicle','contact','route','order','m_ConvoyTask'):
|
|
s=s.replace(x+'.',x+'->')
|
|
s=s.replace('->Combat.', '->Combat->').replace('->Receipt.', '->Receipt->')
|
|
s=s.replace('unit->Downlink.', 'unit->Downlink->')
|
|
s=re.sub(r'(ERR_\w+\* \w+);',r'\1 = nullptr;',s)
|
|
s=s.replace('bool accepted;', 'bool accepted = false;')
|
|
return s
|
|
methods='\n'.join(cpp(body(H,s)) for s in ('protected bool ConvoyPositionKnown(',
|
|
'protected ERR_HQReport ConvoyContact(', 'protected void PlanConvoy(', 'protected void DeliverConvoyOrder('))
|
|
downlink=cpp(body(H,'if (unit.Downlink)\n'))
|
|
cancel=cpp(body(H,'void CancelPending('))
|
|
src=H.read_text()
|
|
tick=body(H,'override void Tick(')
|
|
assert tick.index('PlanConvoy(now);') < tick.index('PlanResupply(now);') < tick.index('Assign(now);')
|
|
assert 'm_Contacts' in body(H,'protected ERR_HQReport ConvoyContact(')
|
|
assert 'GetOrigin' not in methods and 'Target' not in methods
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cmath>
|
|
#include <map>
|
|
struct string:std::string {using std::string::string; template<class...T>static string Format(T...){return "order";}};
|
|
struct vector {float x=0,z=0;static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}};
|
|
struct ERR_HQPursuitRules {static bool Finite(float n){return std::isfinite(n);}};
|
|
struct SCR_AIGroup{};
|
|
struct ERR_HQReport {int MessageId=0;float ObservedAt=-1;vector Position;};
|
|
struct ERR_HQWatchEntry {bool Closed=false;int Count=2;float ObservedAt=-1;vector Position;};
|
|
struct Watch {std::map<string,ERR_HQWatchEntry*> entries;ERR_HQWatchEntry* Find(string key){return entries.count(key)?entries[key]:nullptr;}};
|
|
struct Journal {int seq=100;int Message(){return ++seq;}void Write(string,string,int){}};
|
|
struct ERR_HQRoute {int MessageId=0;string Payload;int result=0;int Advance(float,Journal&){return result;}};
|
|
struct Network {bool available=true;int calls=0;ERR_HQRoute* Route(void*,bool down){assert(down);calls++;return available?new ERR_HQRoute:nullptr;}};
|
|
struct ERR_ConvoyCombatOrder {int CommandId=0,ReportId=0,VehicleIndex=0;string SourceId;SCR_AIGroup* Group=nullptr;bool Hold=false;float ObservedAt=-1,IssuedAt=0,ExpiresAt=0;};
|
|
struct CombatState {bool Held=false;int LastCommandId=0,ReportId=0;float ObservedAt=-1;};
|
|
struct ERR_ConvoyVehicle {int SupplyToken=0,Index=0;SCR_AIGroup* Group=nullptr;bool Released=false,Arrived=false,Registered=true;CombatState state;CombatState* Combat=&state;};
|
|
struct Receipt {string SourceId="convoy-1";};
|
|
struct ConvoyTask {bool Stopped=false,Ready=true;struct Receipt receipt;struct Receipt* Receipt=&receipt;std::vector<ERR_ConvoyVehicle*> Vehicles;};
|
|
struct Service {int calls=0;bool valid=true;ERR_ConvoyCombatOrder* last=nullptr;
|
|
bool ApplyCombatOrder(ConvoyTask* task,ERR_ConvoyCombatOrder* o,float){calls++;last=o;if(!task||!valid)return false;
|
|
auto s=task->Vehicles.at(o->VehicleIndex)->Combat;s->Held=o->Hold;s->LastCommandId=o->CommandId;s->ReportId=o->ReportId;s->ObservedAt=o->ObservedAt;return true;}};
|
|
struct ERR_HQUnit {SCR_AIGroup* Group=nullptr;bool ReservedForSupport=true;void* Radio=this;string ForceSourceId="vehicle-0";
|
|
ERR_HQRoute* Downlink=nullptr;ERR_ConvoyCombatOrder* PendingConvoyOrder=nullptr;void* PendingOrder=nullptr;void* PendingTask=nullptr;
|
|
bool PendingCheckin=false;float NextStatus=0;
|
|
'''+cancel+r'''
|
|
};
|
|
struct HQ {Watch m_Watch;Network m_Network;Journal m_Journal;Service m_Convoys;ConvoyTask* m_ConvoyTask=nullptr;
|
|
std::vector<ERR_HQUnit*> m_Units;std::vector<ERR_HQReport*> m_Contacts;int ordinary=0,lost=0;
|
|
void DeliverOrder(ERR_HQUnit*,float){ordinary++;}void LostOrder(ERR_HQUnit* u,float){lost++;u->CancelPending();}
|
|
'''+methods+r'''
|
|
void TickDownlink(ERR_HQUnit* unit,float now){
|
|
'''+downlink+r'''
|
|
}
|
|
};
|
|
int main(){
|
|
HQ h;SCR_AIGroup g;ERR_HQUnit u;u.Group=&g;h.m_Units={&u};ConvoyTask t;h.m_ConvoyTask=&t;
|
|
ERR_ConvoyVehicle v;v.Group=&g;t.Vehicles={&v};ERR_HQWatchEntry e;e.ObservedAt=1000;e.Position={0,0};
|
|
ERR_HQReport r;r.MessageId=1;r.ObservedAt=2000;r.Position={200,0};
|
|
h.PlanConvoy(3000);assert(!u.Downlink); // no delivered own status or target report
|
|
h.m_Watch.entries[u.ForceSourceId]=&e;h.PlanConvoy(3000);assert(!u.Downlink);
|
|
h.m_Contacts={&r};v.SupplyToken=100001;h.PlanConvoy(3000);assert(!u.Downlink);v.SupplyToken=0;
|
|
h.m_Contacts={&r};h.m_Network.available=false;h.PlanConvoy(3000);assert(!u.Downlink&&!v.Combat->Held);
|
|
h.m_Network.available=true;h.PlanConvoy(3000);assert(u.Downlink&&u.PendingConvoyOrder->Hold);
|
|
auto pending=u.PendingConvoyOrder;int calls=h.m_Network.calls;h.PlanConvoy(4000);assert(h.m_Network.calls==calls&&u.PendingConvoyOrder==pending);
|
|
h.TickDownlink(&u,4000);assert(!v.Combat->Held&&h.m_Convoys.calls==0); // transmitting, no application
|
|
u.Downlink->result=-1;h.TickDownlink(&u,5000);assert(!u.Downlink&&!u.PendingConvoyOrder&&!v.Combat->Held);
|
|
h.PlanConvoy(6000);u.Downlink->result=1;h.TickDownlink(&u,8000);assert(v.Combat->Held&&h.m_Convoys.calls==1&&h.ordinary==0);
|
|
h.PlanConvoy(9000);assert(!u.Downlink); // same delivered contact never duplicates a HOLD
|
|
ERR_HQReport newer=r;newer.MessageId=2;newer.ObservedAt=12000;h.m_Contacts={&newer};h.PlanConvoy(13000);
|
|
assert(u.PendingConvoyOrder->ReportId==2);u.Downlink->result=1;h.TickDownlink(&u,15000);assert(v.Combat->ReportId==2);
|
|
h.PlanConvoy(71000);assert(!u.Downlink); // less than60s quiet
|
|
h.PlanConvoy(72000);assert(u.Downlink&&!u.PendingConvoyOrder->Hold);
|
|
newer.MessageId=3;newer.ObservedAt=73000;u.Downlink->result=1;h.TickDownlink(&u,74000);
|
|
assert(v.Combat->Held&&h.m_Convoys.calls==2&&!u.Downlink); // new report rejects in-flight RESUME
|
|
h.m_Network.available=false;h.PlanConvoy(75000);assert(!u.Downlink&&v.Combat->Held);
|
|
h.m_Network.available=true;h.PlanConvoy(76000);assert(u.PendingConvoyOrder->Hold);u.Downlink->result=1;
|
|
newer.MessageId=4;newer.ObservedAt=77000;h.TickDownlink(&u,78000);assert(h.m_Convoys.calls==3&&v.Combat->ReportId==3); // a newer nearby report cannot starve HOLD delivery
|
|
h.PlanConvoy(79000);u.Downlink->result=1;h.TickDownlink(&u,80000);assert(v.Combat->ReportId==4);
|
|
h.PlanConvoy(137000);assert(u.Downlink&&!u.PendingConvoyOrder->Hold);u.Downlink->result=1;h.TickDownlink(&u,139000);
|
|
assert(!v.Combat->Held&&h.m_Convoys.calls==5);h.PlanConvoy(140000);assert(!u.Downlink);
|
|
// Report proximity uses detached Watch coordinates only; no live entity reads exist.
|
|
newer.ObservedAt=150000;newer.MessageId=5;newer.Position={401,0};h.PlanConvoy(151000);assert(!u.Downlink);
|
|
newer.Position={400,0};h.PlanConvoy(151000);assert(u.Downlink);u.CancelPending();
|
|
e.Closed=true;h.PlanConvoy(151000);assert(!u.Downlink);e.Closed=false;e.Count=0;h.PlanConvoy(151000);assert(!u.Downlink);e.Count=2;
|
|
e.ObservedAt=160000;h.PlanConvoy(151000);assert(!u.Downlink);e.ObservedAt=0;h.PlanConvoy(181000);assert(!u.Downlink);
|
|
e.ObservedAt=150000;v.Released=true;h.PlanConvoy(151000);assert(!u.Downlink);v.Released=false;v.Arrived=true;
|
|
h.PlanConvoy(151000);assert(!u.Downlink);v.Arrived=false;t.Ready=false;h.PlanConvoy(151000);assert(!u.Downlink);t.Ready=true;
|
|
u.ReservedForSupport=false;h.PlanConvoy(151000);assert(!u.Downlink);u.ReservedForSupport=true;
|
|
h.PlanConvoy(151000);assert(u.Downlink);h.m_Convoys.valid=false;u.Downlink->result=1;h.TickDownlink(&u,152000);
|
|
assert(!v.Combat->Held&&!u.Downlink&&h.ordinary==0); // service capture/death/canonical guards are authoritative
|
|
// A routine check-in shares the channel and cannot be displaced by convoy planning.
|
|
u.PendingCheckin=true;u.Downlink=new ERR_HQRoute;calls=h.m_Network.calls;h.PlanConvoy(153000);assert(h.m_Network.calls==calls);
|
|
u.Downlink->result=1;h.TickDownlink(&u,154000);assert(u.NextStatus==154000&&!u.Downlink&&h.ordinary==0);
|
|
}
|
|
''')
|
|
print('HQ convoy delivered-report planning and downlink regressions passed')
|