Files
EscapistsReintroduced/tests/test_native_mortar_refresh.py
T

106 lines
9.2 KiB
Python

"""Actual WORKBENCH refresh-fixture observation/acceptance policy, not native proof."""
import re
from policy_harness import ROOT, body, run
p=ROOT/'addons/ai/Scripts/Game/ERR/ERR_NativeMortarSalvoProbe.c'
base=ROOT/'addons/ai/Scripts/Game/ERR/ERR_NativeMortarFiringProbe.c'
def adapt(s):
s=s.replace('protected ','').replace('null','nullptr').replace('string kind','std::string kind').replace('string reason, string failure','std::string reason, std::string failure')
for typ in ['ERR_HQReport','ERR_MortarBattery','ERR_NativeMortarRefreshRecord','ERR_MortarRefresh']:
s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
for name in ['battery','report','observation','record','initial','update','m_Battery']:
s=s.replace(name+'.',name+'->')
for name in ['Report','Route','Delivered']:
s=s.replace('.'+name+'.','.'+name+'->').replace('->'+name+'.','->'+name+'->')
s=s.replace('ERR_MortarSalvo.','ERR_MortarSalvo::').replace('ERR_MortarPrediction.','ERR_MortarPrediction::').replace('ERR_NativeMortarEvidenceRules.','ERR_NativeMortarEvidenceRules::').replace('vector.Distance','vector::Distance')
s=re.sub(r'foreach \(ERR_NativeMortarRefreshRecord\* (\w+) : m_Observations\)',r'for (ERR_NativeMortarRefreshRecord* \1 : m_Observations.values)',s)
s=s.replace('].ObservedAt',']->ObservedAt')
return s
rules=adapt(body(p,'static bool QueueUpdate('))
evidence='\n'.join(adapt(body(base,s)) for s in ['static bool CommitFresh(', 'static bool AimFresh('])
matching=adapt(body(p,'bool Matches('))
methods='\n'.join(adapt(body(p,s)) for s in ['protected ERR_NativeMortarRefreshRecord FindObservation(', 'protected bool AddObservation(', 'protected bool ReportKnown(', 'protected bool AllAccepted(', 'protected bool NoReportReplay(', 'void NativeAudit(', 'void NativeCommit(', 'void NativeClosing('])
# A return type is a reference in Enforce, a pointer in this policy adapter.
methods=methods.replace('ERR_NativeMortarRefreshRecord FindObservation(', 'ERR_NativeMortarRefreshRecord* FindObservation(')
run(r'''
#include <cassert>
#include <cmath>
#include <set>
#include <string>
#include <vector>
using IEntity=void*;using SCR_AIGroup=void*;
struct vector {float x=0,y=0,z=0;float Length(){return std::sqrt(x*x+y*y+z*z);}static float Distance(vector a,vector b){return vector{a.x-b.x,a.y-b.y,a.z-b.z}.Length();}};
struct ERR_MortarSalvo {static constexpr int ROUNDS=8;};
struct ERR_MortarPrediction {static constexpr float MAX_REPORT_AGE_MS=60000;};
struct Rules {'''+rules+r'''};
struct ERR_NativeMortarEvidenceRules {'''+evidence+r'''};
template<class T>struct Items {std::vector<T> values;int Count(){return values.size();}bool IsEmpty(){return values.empty();}void Insert(T t){values.push_back(t);}T operator[](int n){return values[n];}};
struct ERR_HQReport {int MessageId=1;float ObservedAt=0;IEntity Target=nullptr;SCR_AIGroup Source=nullptr;vector Position{},Velocity{};bool HasVelocity=true,Distress=false;ERR_HQReport* Snapshot(){return new ERR_HQReport(*this);}};
struct Route {bool Downlink=true;int MessageId=3;Items<int> Nodes;Route(){Nodes.Insert(1);}};
struct ERR_MortarBattery;
struct ERR_MortarRefresh {IEntity Gun=nullptr,Group=nullptr;int OrderId=1,RadioId=3;ERR_HQReport* Report=nullptr;struct Route* Route=nullptr;bool Owns(ERR_MortarBattery*);};
struct ERR_MortarBattery {IEntity Gun=this,Group=this,Activity=this;int SourceId=1,OrderId=1,OrderShots=0;bool Released=false,CommandOpen=true,RefreshRejected=false,stopped=false;float AimedAt=1000,AimValidatedAt=24000,FlightTime=20;ERR_HQReport* Report=nullptr;ERR_MortarRefresh* Refresh=nullptr;std::set<int> claimed;bool Stopped(){return stopped;}bool CanRequest(ERR_HQReport* r,float){return !claimed.count(r->MessageId);}};
bool ERR_MortarRefresh::Owns(ERR_MortarBattery* b){return b&&Gun==b->Gun&&Group==b->Group&&OrderId==b->OrderId;}
struct ERR_NativeMortarRefreshRecord {int MessageId=0;float ObservedAt=0;int DownlinkId=0;bool Accepted=false;ERR_HQReport* Delivered=nullptr;
'''+matching+r'''};
template<class...Args>void PrintFormat(Args... args){}
struct Probe {
bool m_RefreshCase=true,m_Stopped=false,m_SawDownlink=true,m_SawActivity=false,m_BadProjectile=false,m_CommitPending=false,m_ClosureSeen=false,m_ClosureComplete=false;
ERR_MortarBattery* m_Battery=nullptr;IEntity m_Target=this,m_Group=this,m_OriginalActivity=nullptr,m_OriginalRTO=this;vector m_Impact{};
int m_Source=1,m_Order=1,m_UpdatesAccepted=0,m_UpdatedCommits=0,m_Commits=0,m_Step=1,m_CommitOrder=0,m_CommitShot=0,m_CommitSource=0,m_Events=0;float m_ObservedAt=0,m_CommittedAt=0,m_CommitObservedAt=0;std::string m_BadReason;ERR_HQReport* m_Delivered=nullptr;
Items<ERR_NativeMortarRefreshRecord*> m_Observations;
'''+methods+r'''};
struct F {
Probe p;ERR_MortarBattery b;ERR_HQReport first,next;ERR_MortarRefresh update;Route route;
F(){p.m_Battery=&b;first.Target=p.m_Target;first.Source=p.m_Group;next=first;next.MessageId=2;next.ObservedAt=20000;b.Report=&first;b.claimed.insert(1);update.Gun=b.Gun;update.Group=b.Group;update.Report=&next;update.Route=&route;}
void initial(){assert(p.AddObservation(1,0));p.FindObservation(1)->DownlinkId=2;p.NativeAudit(&b,"AIM");assert(!p.m_BadProjectile);}
void queued(){assert(p.AddObservation(2,20000));b.Refresh=&update;b.claimed.insert(2);p.NativeAudit(&b,"UPDATE QUEUED");}
void accepted(){update.Route=nullptr;b.Report=&next;p.NativeAudit(&b,"UPDATE RECEIVED");b.Refresh=nullptr;}
};
int main(){
assert(Rules::QueueUpdate(true,false,0,0,1000,20000));assert(!Rules::QueueUpdate(true,false,0,0,1000,19999));
assert(!Rules::QueueUpdate(false,false,0,0,1000,20000));assert(!Rules::QueueUpdate(true,true,0,0,1000,20000));
assert(!Rules::QueueUpdate(true,false,4,0,1000,20000));assert(!Rules::QueueUpdate(true,false,0,0,1000,91000));
assert(!Rules::QueueUpdate(true,false,0,0,22000,20000));
F f;f.initial();assert(f.p.ReportKnown(&f.first));assert(!f.p.ReportKnown(&f.next));assert(!f.p.AllAccepted());
assert(!f.p.AddObservation(1,1));assert(!f.p.AddObservation(2,0));assert(!f.p.AddObservation(0,100));
f.queued();assert(!f.p.ReportKnown(&f.next)&&f.p.ReportKnown(&f.next,false));assert(!f.p.AllAccepted());
f.accepted();assert(!f.p.m_BadProjectile&&f.p.ReportKnown(&f.next)&&f.p.m_UpdatesAccepted==1);
f.b.Released=true;f.p.NativeCommit(&f.b,25000);assert(!f.p.m_BadProjectile&&f.p.m_UpdatedCommits==1&&f.p.m_CommitSource==2&&f.p.m_CommitObservedAt==20000);
assert(f.p.AllAccepted()&&f.p.NoReportReplay(26000));f.b.claimed.erase(2);assert(!f.p.NoReportReplay(26000));f.b.claimed.insert(2);
f.b.OrderShots=8;f.p.m_Events=8;f.p.m_Commits=8;f.p.m_CommitPending=false;f.b.Released=false;
f.p.NativeClosing(&f.b,"native artillery activity COMPLETED (shot count is separate)","");assert(f.p.m_ClosureComplete&&!f.p.m_BadProjectile);
for(int fault=0;fault<7;fault++){
F a;a.initial();a.queued();
if(fault==0)a.update.OrderId=2;
if(fault==1)a.b.Activity=&a.next;
if(fault==2)a.next.ObservedAt=20001;
if(fault==3)a.next.Position.x=.02f;
if(fault==4)a.next.Target=&a.next;
if(fault==5)a.next.Source=&a.next;
if(fault==6)a.next.Velocity.x=1;
a.accepted();assert(a.p.m_BadProjectile&&!a.p.FindObservation(2)->Accepted);
}
F beforeRoute;beforeRoute.initial();beforeRoute.queued();beforeRoute.b.Report=&beforeRoute.next;beforeRoute.p.NativeAudit(&beforeRoute.b,"UPDATE RECEIVED");assert(beforeRoute.p.m_BadProjectile);
F committed;committed.initial();committed.queued();committed.b.Released=true;committed.accepted();assert(committed.p.m_BadProjectile);
F duplicate;duplicate.initial();duplicate.queued();duplicate.accepted();duplicate.b.Refresh=&duplicate.update;duplicate.p.NativeAudit(&duplicate.b,"UPDATE RECEIVED");assert(duplicate.p.m_BadProjectile&&duplicate.p.m_UpdatesAccepted==1);
F recreated;recreated.initial();recreated.p.NativeAudit(&recreated.b,"AIM");assert(recreated.p.m_BadProjectile);
F unaccepted;unaccepted.initial();unaccepted.queued();unaccepted.b.Report=&unaccepted.next;unaccepted.b.Released=true;unaccepted.p.NativeCommit(&unaccepted.b,25000);assert(unaccepted.p.m_BadProjectile&&unaccepted.p.m_Commits==0);
F extra;extra.initial();extra.queued();extra.accepted();extra.p.m_UpdatedCommits=1;assert(extra.p.AddObservation(3,40000));assert(!extra.p.AllAccepted());
F ignored;ignored.p.m_RefreshCase=false;ignored.p.NativeAudit(&ignored.b,"AIM");assert(!ignored.p.m_BadProjectile);assert(ignored.p.ReportKnown(&ignored.first));
}
''')
source=p.read_text();queue=body(p,'int ERR_QueueMortarRefreshReport(')
assert 'observedAt = GetGame().GetWorld().GetWorldTime()' in queue
assert 'm_Network.Route(radio)' in queue and 'unit.Radio != radio' in queue
assert 'unit.PendingReport = report; unit.Uplink = route;' in queue
assert 'm_Contacts.' not in queue and '.Order(' not in queue and 'BeginRefresh(' not in queue
assert 'report.ObservedAt = observedAt' in queue
assert 'outcome == "mortar-salvo" || outcome == "mortar-refresh"' in source
assert 'm_Battery.RadioOperator != m_OriginalRTO' in source
assert 'm_Battery.Report.MessageId != m_CommitSource' in body(p,'void NativeProjectile(')
assert 'ERR_NativeMortarSalvoRules.QueueUpdate(' in body(p,'int Tick(')
assert 'm_Battery.Report =' not in body(p,'int Tick(')
print('PASS: actual refresh-fixture gates pin immutable observations/actual downlinks/original activity, reject unaccepted or relabelled commits, stop queueing early and require all queued contacts claimed before replay')