Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
119 lines
11 KiB
Python
119 lines
11 KiB
Python
"""Actual shared-service handoff, atomic HQ claims, and stopped-owner proof polling."""
|
|
import os,re
|
|
from pathlib import Path
|
|
from policy_harness import ROOT,body,run
|
|
S=Path(os.environ.get('ERR_INFANTRY_RESUPPLY_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c'))
|
|
H=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
|
|
methods='\n'.join(body(S,x) for x in ('int State()', 'ERR_MountedResupply Mounted()', 'bool Unresolved()', 'protected void PollTransfers(', 'bool TryPlanMounted('))
|
|
methods=methods.replace('protected ','').replace('null','nullptr').replace('ERR_MountedResupply Mounted()', 'ERR_MountedResupply* Mounted()')
|
|
for n in ('m_Move','m_MineMove','m_Mounted'):methods=methods.replace(n+'.',n+'->')
|
|
for n in ('ERR_HQUnit','ERR_HQWatchEntry'):methods=re.sub(r'\b'+n+r' (\w+)',n+r'* \1',methods)
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <map>
|
|
struct ERR_HQUnit{};struct ERR_HQWatchEntry{};struct HQ{};struct Mode{};struct Convoy{};
|
|
struct Item{};using IEntity=Item*;
|
|
struct Leaf{bool unresolved=true;bool Unresolved(){return unresolved;}};
|
|
using Stock=std::vector<IEntity>;using Consumed=std::map<int,int>;using Cooldown=std::map<int,float>;
|
|
struct ERR_MountedResupply{
|
|
inline static int made=0;bool busy=false,unresolved=false,allow=true;int begins=0,ticks=0;HQ*h;Mode*m;Convoy*c;int slot;Stock*stock,*spent;Consumed*consumed;Cooldown*next;
|
|
ERR_MountedResupply(HQ*a,Mode*b,Convoy*d,int i,Stock*x,Stock*y,Consumed*z,Cooldown*w):h(a),m(b),c(d),slot(i),stock(x),spent(y),consumed(z),next(w){made++;}
|
|
bool Begin(ERR_HQUnit*,ERR_HQUnit*,ERR_HQWatchEntry*,ERR_HQWatchEntry*,float){begins++;busy=allow;return allow;}
|
|
bool Busy(){return busy;}bool Unresolved(){return unresolved;}void Tick(float){ticks++;}
|
|
};
|
|
struct Service{bool m_LauncherTrip=false;HQ*m_HQ=nullptr;Mode*m_Mode=nullptr;Convoy*m_Convoy=nullptr;int m_ConvoySlot=0,m_State=1;
|
|
Stock*m_Stock=nullptr,*m_Spent=nullptr;Consumed*m_Consumed=nullptr;Cooldown*m_NextAttempt=nullptr;Leaf*m_Move=nullptr,*m_MineMove=nullptr;ERR_MountedResupply*m_Mounted=nullptr;
|
|
void Finish(const char*){m_State=1;}bool current=true,valid=true,arrived=false;bool Current(){return current;}bool TruckValid(bool allowArrived=false){return valid&&(!arrived||allowArrived);}
|
|
'''+methods+r'''
|
|
};
|
|
int main(){
|
|
HQ h;Mode m;Convoy c;Stock stock,spent;Consumed consumed;Cooldown next;ERR_HQUnit d,r;ERR_HQWatchEntry demand,status;
|
|
Service s;s.m_HQ=&h;s.m_Mode=&m;s.m_Convoy=&c;s.m_Stock=&stock;s.m_Spent=&spent;s.m_Consumed=&consumed;s.m_NextAttempt=&next;s.m_ConvoySlot=7;
|
|
s.m_State=0;assert(!s.TryPlanMounted(&r,&d,&demand,&status,0)&&!s.Mounted());s.m_State=1;
|
|
Leaf mine;s.m_MineMove=&mine;assert(!s.TryPlanMounted(&r,&d,&demand,&status,0)&&!s.Mounted()&&s.Unresolved());mine.unresolved=false;s.PollTransfers(0);assert(!s.m_MineMove);
|
|
Leaf foot;s.m_Move=&foot;assert(!s.TryPlanMounted(&r,&d,&demand,&status,0)&&!s.Mounted()&&s.Unresolved());
|
|
foot.unresolved=false;s.PollTransfers(0);assert(!s.m_Move);s.arrived=true;
|
|
assert(s.TryPlanMounted(&r,&d,&demand,&status,0));auto*mounted=s.Mounted();
|
|
assert(mounted->stock==&stock&&mounted->spent==&spent&&mounted->consumed==&consumed&&mounted->next==&next&&mounted->slot==7&&mounted->c==&c);
|
|
assert(s.State()==6&&!s.TryPlanMounted(&r,&d,&demand,&status,1)&&mounted->begins==1);
|
|
mounted->busy=false;mounted->allow=false;assert(!s.TryPlanMounted(&r,&d,&demand,&status,2)&&s.Mounted()==mounted&&ERR_MountedResupply::made==1);
|
|
mounted->unresolved=true;assert(s.Unresolved());s.current=false;s.PollTransfers(10);assert(mounted->ticks==1&&mounted->begins==2);
|
|
mounted->unresolved=false;assert(!s.Unresolved()&&s.State()==1);
|
|
s.current=true;s.m_ConvoySlot=-1;assert(!s.TryPlanMounted(&r,&d,&demand,&status,20));
|
|
}
|
|
''')
|
|
claims='\n'.join(body(H,x) for x in ('protected bool MountedLogisticsEligible(', 'bool ReserveMountedLogistics('))
|
|
claims=claims.replace('protected ','').replace('ERR_HQUnit ','Unit* ').replace('ERR_ForceRecord ','Record* ')
|
|
for n in ('unit','m_Mode','record','donor','recipient'):claims=claims.replace(n+'.',n+'->')
|
|
for n in ('Group','Utility','StatusSampler','m_Perception'):claims=claims.replace(n+'.',n+'->')
|
|
claims=claims.replace('Forces().','Forces()->').replace('ERR_GameMode.Get()','Mode::Get()').replace('Replication.','Replication::').replace('ERR_ForceKind.','ERR_ForceKind::')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <algorithm>
|
|
#include <string>
|
|
using string=std::string;
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}bool IsEmpty(){return this->empty();}};
|
|
struct Group{bool slave=false;int players=0,faction=1;bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}int GetFaction(){return faction;}};
|
|
struct Perception{Array<int>m_aTargets;};struct Utility{Perception*m_Perception=nullptr;};
|
|
struct Sampler{Group*group=nullptr;string source;bool valid=true;bool OwnsRoster(Group*g){return valid&&group==g;}string InitialSourceId(){return source;}};
|
|
struct Unit{struct Group*Group=nullptr;struct Utility*Utility=nullptr;void*Radio=this;Sampler*StatusSampler=nullptr;bool ReservedForSupport=true,HomeGuard=false,PendingCheckin=false;int LogisticsReservation=0;string ForceSourceId;
|
|
void*Assignment=nullptr,*Task=nullptr,*PendingTask=nullptr,*PendingOrder=nullptr,*PendingConvoyOrder=nullptr,*Downlink=nullptr,*Waypoint=nullptr;};
|
|
struct Record{struct Group*Group=nullptr;string SourceId;int Kind=1;bool Physical=true,Eliminated=false;};
|
|
struct Ledger{std::map<Group*,Record*>records;Record*FindGroup(Group*g){return records.count(g)?records[g]:nullptr;}};
|
|
struct Mode{inline static Mode*current=nullptr;Ledger*ledger=nullptr;bool active=true;static Mode*Get(){return current;}bool Active(){return active;}Ledger*Forces(){return ledger;}int EnemyFaction(){return 1;}};
|
|
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};struct ERR_ForceKind{static const int CONVOY=1;};
|
|
struct HQ{Mode*m_Mode=nullptr;bool m_Stopping=false;Array<Unit*>m_Units;
|
|
'''+claims+r'''};
|
|
struct Fixture{Mode mode;Ledger ledger;HQ hq;Group dg,rg;Unit donor,recipient;Utility du,ru;Sampler ds,rs;Record dr,rr;Perception perception;
|
|
Fixture(){Mode::current=&mode;mode.ledger=&ledger;hq.m_Mode=&mode;hq.m_Units={&donor,&recipient};
|
|
donor.Group=&dg;recipient.Group=&rg;donor.Utility=&du;recipient.Utility=&ru;donor.StatusSampler=&ds;recipient.StatusSampler=&rs;
|
|
donor.ForceSourceId=ds.source=dr.SourceId="donor";recipient.ForceSourceId=rs.source=rr.SourceId="recipient";ds.group=dr.Group=&dg;rs.group=rr.Group=&rg;
|
|
ledger.records={{&dg,&dr},{&rg,&rr}};}
|
|
};
|
|
int main(){
|
|
{Fixture f;assert(f.hq.ReserveMountedLogistics(&f.donor,&f.recipient,200001));assert(f.donor.LogisticsReservation==200001&&f.recipient.LogisticsReservation==200001);
|
|
assert(f.donor.ReservedForSupport&&f.recipient.ReservedForSupport&&!f.hq.ReserveMountedLogistics(&f.donor,&f.recipient,200002));}
|
|
for(int side=0;side<2;side++)for(int k=0;k<22;k++){Fixture f;Unit*u=side?&f.recipient:&f.donor;Record*record=side?&f.rr:&f.dr;
|
|
switch(k){case 0:u->HomeGuard=true;break;case 1:u->ReservedForSupport=false;break;case 2:u->LogisticsReservation=7;break;case 3:u->Radio=nullptr;break;
|
|
case 4:u->Assignment=u;break;case 5:u->Task=u;break;case 6:u->PendingTask=u;break;case 7:u->PendingOrder=u;break;case 8:u->PendingConvoyOrder=u;break;
|
|
case 9:u->PendingCheckin=true;break;case 10:u->Downlink=u;break;case 11:u->Waypoint=u;break;case 12:u->Group->slave=true;break;case 13:u->Group->players=1;break;
|
|
case 14:u->Group->faction=2;break;case 15:u->StatusSampler->valid=false;break;case 16:u->StatusSampler->source="foreign";break;
|
|
case 17:record->Physical=false;break;case 18:record->Eliminated=true;break;case 19:record->Kind=2;break;case 20:u->Utility->m_Perception=&f.perception;f.perception.m_aTargets={1};break;
|
|
case 21:f.hq.m_Units={side?&f.donor:&f.recipient};break;}
|
|
assert(!f.hq.ReserveMountedLogistics(&f.donor,&f.recipient,200001));
|
|
assert((side?f.donor:f.recipient).LogisticsReservation==0);assert(u->LogisticsReservation==(k==2?7:0));}
|
|
for(int k=0;k<6;k++){Fixture f;Mode other;if(k==0)f.hq.m_Stopping=true;if(k==1)f.mode.active=false;if(k==2)Mode::current=&other;if(k==3)Replication::server=false;
|
|
int token=k==4?200000:k==5?202561:200001;assert(!f.hq.ReserveMountedLogistics(&f.donor,&f.recipient,token)&&!f.donor.LogisticsReservation&&!f.recipient.LogisticsReservation);Replication::server=true;}
|
|
{Fixture f;assert(!f.hq.ReserveMountedLogistics(&f.donor,&f.donor,200001));}
|
|
}
|
|
''')
|
|
poll='\n'.join(body(H,x) for x in ('protected void CancelStoppedResupply(', 'protected void PollStoppedResupply('))
|
|
poll=poll.replace('protected ','').replace('ERR_GameMode.Get()','Mode::Get()').replace('Replication.','Replication::')
|
|
poll=poll.replace('m_Mode.','m_Mode->').replace('m_Resupply.','m_Resupply->').replace(').',')->')
|
|
poll=poll.replace('Remove(PollStoppedResupply)','Remove(1)').replace('CallLater(PollStoppedResupply,','CallLater(1,')
|
|
run(r'''
|
|
#include <cassert>
|
|
struct HQ;
|
|
struct Mode{inline static Mode*current=nullptr;HQ*command=nullptr;static Mode*Get(){return current;}HQ*Command(){return command;}};
|
|
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
|
|
struct Pool{int polls=0;bool unresolved=true;void Tick(float){polls++;}bool Unresolved(){return unresolved;}};
|
|
struct World{float GetWorldTime(){return 1000;}};
|
|
struct Queue{int pending=0,removed=0,scheduled=0;void Remove(int id){assert(id==1);pending=0;removed++;}void CallLater(int id,int ms,bool repeat){assert(id==1&&ms==1000&&!repeat);pending++;scheduled++;}};
|
|
struct Game{World*world=nullptr;Queue*queue=nullptr;World*GetWorld(){return world;}Queue*GetCallqueue(){return queue;}};Game*game=nullptr;Game*GetGame(){return game;}
|
|
struct HQ{Mode*m_Mode=nullptr;Pool*m_Resupply=nullptr;bool m_Stopping=true;
|
|
'''+poll+r'''};
|
|
int main(){
|
|
HQ h,other;Mode m,next;Pool p;Queue q;World w;Game g;g.world=&w;g.queue=&q;game=&g;Mode::current=&m;m.command=&h;h.m_Mode=&m;h.m_Resupply=&p;
|
|
h.PollStoppedResupply();assert(p.polls==1&&q.pending==1);h.PollStoppedResupply();assert(p.polls==2&&q.pending==1);
|
|
p.unresolved=false;h.PollStoppedResupply();assert(p.polls==3&&q.pending==0);p.unresolved=true;
|
|
m.command=&other;h.PollStoppedResupply();assert(p.polls==3&&q.pending==0);m.command=&h;Mode::current=&next;h.PollStoppedResupply();assert(p.polls==3&&q.pending==0);
|
|
Mode::current=&m;h.m_Stopping=false;h.PollStoppedResupply();assert(p.polls==3&&q.pending==0);h.m_Stopping=true;
|
|
h.CancelStoppedResupply();h.CancelStoppedResupply();assert(q.pending==0);
|
|
g.world=nullptr;h.PollStoppedResupply();assert(p.polls==3&&q.pending==0);
|
|
}
|
|
''')
|
|
print('PASS: actual shared mounted stock/history ownership, atomic exact HQ pair claim, and bounded stopped-owner proof polling')
|