107 lines
9.3 KiB
Python
107 lines
9.3 KiB
Python
"""Actual WORKBENCH refresh-fixture observation/acceptance policy, not native proof."""
|
|
import re
|
|
from policy_harness import ROOT, body, run
|
|
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_NativeMortarSalvoProbe.c'
|
|
base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/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,5,0,1000,20000));
|
|
assert(!Rules::QueueUpdate(true,false,6,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')
|