Integrate delivered HQ memory, finite physical convoy and RHS WCS dependencies
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
"""Execute actual bounded force ledger and dormant route bodies without engine claims."""
|
||||
from policy_harness import ROOT, body, run
|
||||
import re
|
||||
L=ROOT/'addons/core/Scripts/Game/ERR/ERR_ForceLedger.c'
|
||||
S=ROOT/'addons/core/Scripts/Game/ERR/ERR_ForceSimulation.c'
|
||||
def cpp(s):
|
||||
for old,new in [('protected ',''),('static ', 'static '),('Math.','Math::'),('vector.','vector::'),
|
||||
('ERR_ForceRules.','ERR_ForceRules::'),('ERR_ForceKind.','ERR_ForceKind::'),
|
||||
('ERR_ForceDuty.','ERR_ForceDuty::'),('Replication.','Replication::'),('out bool ','bool& '),
|
||||
('sourceId.IsEmpty()','sourceId.empty()'),('sourceId.Length()','sourceId.size()'),
|
||||
('string.Format(', 'Format('),('null','nullptr')]: s=s.replace(old,new)
|
||||
for typ in ('ERR_ForceRecord','ERR_ForceRoute','ERR_ForceLedger','SCR_AIGroup'):
|
||||
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
for name in ('record','known','route','m_Ledger'):
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('foreach (ERR_ForceRecord* record : m_Records)','for (ERR_ForceRecord* record : m_Records)')
|
||||
s=s.replace('float length;','float length=0;').replace('int visited;','int visited=0;')
|
||||
s=s.replace('int physical, dormant, eliminated, living, casualties;',
|
||||
'int physical=0, dormant=0, eliminated=0, living=0, casualties=0;')
|
||||
return s
|
||||
rules='\n'.join(cpp(body(S,s)) for s in ('static bool Finite(', 'static bool FiniteVector(', 'static float Speed(',
|
||||
'static int Advance(', 'static vector Step(', 'static bool Due(', 'static float Heading('))
|
||||
route='\n'.join(cpp(body(L,s)) for s in ('int Count()', 'vector At(', 'bool Looped(', 'float Length(', 'bool Add(', 'bool Seal('))
|
||||
ledger_source=L.read_text().split('class ERR_ForceLedger')[1]
|
||||
# body() locates repeated Count/At unambiguously by extracting the ledger tail.
|
||||
import tempfile
|
||||
with tempfile.NamedTemporaryFile(mode='w',suffix='.c') as tail:
|
||||
tail.write(ledger_source);tail.flush()
|
||||
from pathlib import Path
|
||||
ledger='\n'.join(cpp(body(Path(tail.name),s)) for s in ('int Count()', 'ERR_ForceRecord Find(',
|
||||
'ERR_ForceRecord FindGroup(', 'ERR_ForceRecord At(', 'ERR_ForceRecord Register(', 'bool Unregister(',
|
||||
'bool Eliminate(', 'bool RecordPhysicalState(', 'string Status(', 'void Clear('))
|
||||
simulation='\n'.join(cpp(body(S,s)) for s in ('protected void Route(', 'void Tick('))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
using string=std::string;
|
||||
struct vector{
|
||||
float x=0,y=0,z=0;
|
||||
vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}
|
||||
vector(const char* value){std::istringstream(value)>>x>>y>>z;}
|
||||
float& operator[](int i){return i==0?x:i==1?y:z;}
|
||||
float operator[](int i)const{return i==0?x:i==1?y:z;}
|
||||
vector operator+(vector b){return {x+b.x,y+b.y,z+b.z};}
|
||||
vector operator-(vector b){return {x-b.x,y-b.y,z-b.z};}
|
||||
vector operator*(float n){return {x*n,y*n,z*n};}
|
||||
static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}
|
||||
};
|
||||
struct Math{static float Floor(float n){return std::floor(n);}
|
||||
static float Atan2(float a,float b){return std::atan2(a,b);}
|
||||
static constexpr float RAD2DEG=180/3.14159265358979323846;
|
||||
};
|
||||
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
|
||||
int logs=0;void Print(const char*){logs++;}
|
||||
string Format(const char*,int a,int b,int c,int d,int e,int f){
|
||||
return std::to_string(a)+","+std::to_string(b)+","+std::to_string(c)+","+
|
||||
std::to_string(d)+","+std::to_string(e)+","+std::to_string(f);
|
||||
}
|
||||
template<class T>struct Array:std::vector<T>{
|
||||
int Count(){return this->size();}bool IsEmpty(){return this->empty();}
|
||||
void Insert(T n){this->push_back(n);}void Clear(){this->clear();}
|
||||
void RemoveItem(T n){this->erase(std::remove(this->begin(),this->end(),n),this->end());}
|
||||
};
|
||||
template<class K,class V>struct Map:std::map<K,V>{
|
||||
bool Contains(K k){return this->count(k);}V Get(K k){return this->count(k)?this->at(k):V{};}
|
||||
int Count(){return this->size();}void Set(K k,V v){(*this)[k]=v;}
|
||||
void Remove(K k){this->erase(k);}void Clear(){this->clear();}
|
||||
};
|
||||
enum class ERR_ForceKind { GARRISON, PATROL, CHECKPOINT, CONVOY };
|
||||
enum class ERR_ForceDuty { HOLD, PATROL_ROUTE, MOVE_TO, SEARCH, RETURN };
|
||||
struct ERR_ForceRules{
|
||||
'''+rules+r'''
|
||||
};
|
||||
struct ERR_ForceRoute{
|
||||
Array<vector> m_Points;bool m_Loop=false,m_Sealed=false;float m_Length=0;
|
||||
ERR_ForceRoute(bool loop=false):m_Loop(loop){}
|
||||
'''+route+r'''
|
||||
};
|
||||
struct SCR_AIGroup{};
|
||||
struct ERR_ForceRecord{
|
||||
int Id=0;string SourceId;ERR_ForceKind Kind=ERR_ForceKind::GARRISON;
|
||||
vector Position,HomePosition,DutyTarget;float Heading=0;
|
||||
ERR_ForceDuty HomeDuty=ERR_ForceDuty::HOLD,Duty=ERR_ForceDuty::HOLD;
|
||||
ERR_ForceRoute* Route=nullptr;int RouteIndex=0;float RouteProgress=0;bool RouteFinished=false;
|
||||
int InitialStrength=0,Strength=0,Casualties=0,Importance=1,RadioCapability=0;
|
||||
SCR_AIGroup* Group=nullptr;bool Physical=false,Pinned=false,Eliminated=false;
|
||||
float LastSimulatedAt=0,StateSince=0;
|
||||
};
|
||||
struct ERR_ForceLedger{
|
||||
Array<ERR_ForceRecord*>m_Records;Map<int,ERR_ForceRecord*>m_ById;
|
||||
Map<string,int>m_Sources;Map<SCR_AIGroup*,int>m_Groups;
|
||||
int m_NextId=0;bool m_CapLogged=false;
|
||||
'''+ledger+r'''
|
||||
};
|
||||
struct ERR_ForceSimulation{
|
||||
ERR_ForceLedger* m_Ledger;ERR_ForceSimulation(ERR_ForceLedger* ledger):m_Ledger(ledger){}
|
||||
'''+simulation+r'''
|
||||
};
|
||||
bool near(float a,float b,float e=.005){return std::abs(a-b)<=e;}
|
||||
ERR_ForceRoute* square(bool loop){
|
||||
auto r=new ERR_ForceRoute(loop);
|
||||
assert(r->Add({0,10,0})&&r->Add({14,12,0})&&r->Add({14,14,14})&&r->Add({0,16,14}));
|
||||
return r;
|
||||
}
|
||||
int main(){
|
||||
using K=ERR_ForceKind;using D=ERR_ForceDuty;using R=ERR_ForceRules;
|
||||
const float nan=std::numeric_limits<float>::quiet_NaN();
|
||||
assert(R::Speed(K::GARRISON,D::HOLD)==0);
|
||||
assert(R::Speed(K::CONVOY,D::MOVE_TO)==11&&near(R::Speed(K::PATROL,D::PATROL_ROUTE),1.4));
|
||||
assert(R::Speed(static_cast<K>(-1),D::SEARCH)==0);
|
||||
assert(R::Advance(3,4,false,true)==4&&R::Advance(4,4,false,true)==4);
|
||||
assert(R::Advance(3,4,true,true)==0&&R::Advance(2,4,true,false)==2);
|
||||
assert(R::Advance(-1,4,true,true)==-1&&R::Advance(0,65,true,true)==-1);
|
||||
bool arrived=false;auto p=R::Step({0,10,0},{14,20,0},1.4,5,arrived);
|
||||
assert(!arrived&&near(p.x,7)&&near(p.y,15));
|
||||
p=R::Step(p,{14,20,0},1.4,999,arrived);assert(arrived&&p.x==14&&p.y==20);
|
||||
p=R::Step({0,10,0},{14,20,0},-1,2,arrived);assert(!arrived&&p.x==0);
|
||||
p=R::Step({0,10,0},{14,20,0},nan,2,arrived);assert(!arrived&&p.x==0);
|
||||
int due=0;for(int id=0;id<8;id++)due+=R::Due(id,1000,0,2000);
|
||||
assert(due>0&&due<8&&!R::Due(0,1000,2000,2000)&&!R::Due(0,nan,0,2000));
|
||||
assert(near(R::Heading({0,0,0},{1,0,0},0),90));
|
||||
assert(near(R::Heading({0,0,0},{-1,0,0},0),270));
|
||||
ERR_ForceRoute empty;assert(!empty.Seal());
|
||||
ERR_ForceRoute zero(true);zero.Add({0,0,0});zero.Add({0,0,0});assert(!zero.Seal());
|
||||
ERR_ForceRoute cap;for(int i=0;i<64;i++)assert(cap.Add({float(i),0,0}));
|
||||
assert(!cap.Add({0,0,0})&&cap.Seal()&&!cap.Add({0,0,0}));
|
||||
ERR_ForceRoute invalid;assert(!invalid.Add({nan,0,0}));
|
||||
ERR_ForceLedger ledger;
|
||||
Replication::server=false;assert(!ledger.Register("client",K::PATROL,{0,0,0},4));
|
||||
Replication::server=true;
|
||||
assert(!ledger.Register("",K::PATROL,{0,0,0},4));
|
||||
assert(!ledger.Register("bad",K::PATROL,{nan,0,0},4));
|
||||
assert(!ledger.Register("bad",K::PATROL,{0,0,0},0));
|
||||
assert(!ledger.Register("bad",K::PATROL,{0,0,0},33));
|
||||
assert(!ledger.Register("bad",K::PATROL,{0,0,0},4,nullptr,nullptr,-1));
|
||||
auto loop=square(true);auto force=ledger.Register("loop",K::PATROL,{0,10,0},4,loop);
|
||||
assert(force&&loop->Length()==56&&loop->m_Sealed&&force->HomeDuty==D::PATROL_ROUTE);
|
||||
assert(ledger.Register("loop",K::PATROL,{999,0,0},32,loop)==force&&force->Strength==4);
|
||||
assert(!ledger.Register("loop",K::CONVOY,{0,0,0},4,loop));
|
||||
ERR_ForceSimulation sim(&ledger);
|
||||
// Four legs at 1.4 m/s: return after 40 seconds, with authored elevation along each segment.
|
||||
for(int t=2000;t<=40000;t+=2000){
|
||||
sim.Tick(t);
|
||||
if(t==10000)assert(near(force->Position.x,14)&&near(force->Position.z,0));
|
||||
if(t==20000)assert(near(force->Position.x,14)&&near(force->Position.z,14));
|
||||
if(t==30000)assert(near(force->Position.x,0)&&near(force->Position.z,14));
|
||||
}
|
||||
assert(vector::DistanceXZ(force->Position,{0,10,0})<.01&&near(force->Position.y,10));
|
||||
// Huge elapsed distance skips complete laps instead of looping per travelled metre.
|
||||
sim.Tick(40004000); // total 1,000 laps plus one 4-second fragment
|
||||
assert(near(force->Position.x,5.6,.02)&&near(force->Position.z,0));
|
||||
auto line=square(false);auto terminal=ledger.Register("line",K::PATROL,{0,10,0},4,line,nullptr,40004000);
|
||||
sim.Tick(40044000);assert(terminal->RouteFinished&&terminal->RouteIndex==4&&terminal->Duty==D::HOLD);
|
||||
assert(near(terminal->Position.x,0)&&near(terminal->Position.z,14)&&terminal->Position.y==16);
|
||||
sim.Tick(40046000);assert(terminal->RouteIndex==4&&terminal->Position.z==14);
|
||||
// Movement orders clamp to destination; HOLD does not continue extrapolating.
|
||||
auto mover=ledger.Register("move",K::CONVOY,{0,0,0},4,nullptr,nullptr,40046000);
|
||||
mover->Duty=D::MOVE_TO;mover->DutyTarget={22,4,0};sim.Tick(40048000);
|
||||
assert(mover->Position.x==22&&mover->Position.y==4&&mover->Duty==D::HOLD);
|
||||
// Physical snapshots preserve losses and body position, including native dormant transitions.
|
||||
SCR_AIGroup group;
|
||||
auto physical=ledger.Register("physical",K::GARRISON,{10,0,10},4,nullptr,&group,40048000);
|
||||
assert(physical&&ledger.FindGroup(&group)==physical);
|
||||
assert(!ledger.Register("duplicate-group",K::GARRISON,{10,0,10},4,nullptr,&group));
|
||||
physical->Duty=D::MOVE_TO;physical->DutyTarget={100,0,100};sim.Tick(40050000);
|
||||
assert(physical->Position.x==10&&physical->LastSimulatedAt==40048000);
|
||||
assert(ledger.RecordPhysicalState(physical->Id,{20,3,20},45,3,40050000));
|
||||
assert(physical->Strength==3&&physical->Casualties==1&&physical->Position.x==20);
|
||||
assert(!ledger.RecordPhysicalState(physical->Id,{30,3,30},45,4,40052000));
|
||||
assert(!ledger.RecordPhysicalState(physical->Id,{30,3,30},45,3,40049990));
|
||||
assert(ledger.RecordPhysicalState(physical->Id,{20,3,20},45,3,40052000,false));
|
||||
assert(!physical->Physical&&physical->Group==&group&&physical->StateSince==40052000);
|
||||
sim.Tick(40054000);assert(physical->Position.x>20&&physical->Casualties==1);
|
||||
assert(ledger.RecordPhysicalState(physical->Id,physical->Position,45,3,40054000,true));
|
||||
assert(physical->Strength==3&&physical->Physical);
|
||||
assert(ledger.RecordPhysicalState(physical->Id,{30,0,30},0,0,40056000));
|
||||
assert(physical->Eliminated&&physical->Strength==0&&physical->Casualties==4);
|
||||
assert(!ledger.RecordPhysicalState(physical->Id,{30,0,30},0,3,40058000));
|
||||
assert(ledger.Eliminate(physical->Id,40058000));sim.Tick(40060000);assert(physical->Position.x==30);
|
||||
Replication::server=false;
|
||||
assert(!ledger.Eliminate(force->Id,40060000)&&!ledger.Unregister(force->Id));
|
||||
auto before=force->Position;sim.Tick(40062000);assert(force->Position.x==before.x);
|
||||
Replication::server=true;
|
||||
assert(ledger.Unregister(physical->Id)&&!ledger.FindGroup(&group));
|
||||
assert(!ledger.Register("physical",K::GARRISON,{0,0,0},4));
|
||||
ledger.Clear();assert(ledger.Count()==0&&ledger.m_Sources.Count()==0&&ledger.m_ById.Count()==0);
|
||||
// All 128 formations remain bounded through a simulated hour; stagger only affects snapshot age.
|
||||
ERR_ForceLedger hour;ERR_ForceSimulation hourSim(&hour);auto circuit=square(true);
|
||||
for(int i=0;i<128;i++)assert(hour.Register(std::to_string(i),K::PATROL,{0,10,0},4,circuit));
|
||||
assert(!hour.Register("overflow",K::PATROL,{0,0,0},4));
|
||||
assert(!hour.Register("overflow2",K::PATROL,{0,0,0},4)&&logs==1);
|
||||
for(int t=1000;t<=3600000;t+=1000)hourSim.Tick(t);
|
||||
assert(hour.Count()==128&&hour.m_Sources.Count()==128&&hour.m_ById.Count()==128);
|
||||
for(int i=0;i<128;i++){
|
||||
auto r=hour.At(i);assert(!r->Eliminated&&r->Strength==4&&r->Casualties==0);
|
||||
assert(3600000-r->LastSimulatedAt<=1000);
|
||||
float distance=std::fmod(1.4*r->LastSimulatedAt/1000,56);
|
||||
float actual=0;
|
||||
if(r->Position.z<.01)actual=r->Position.x;
|
||||
else if(r->Position.x>13.99)actual=14+r->Position.z;
|
||||
else if(r->Position.z>13.99)actual=42-r->Position.x;
|
||||
else actual=56-r->Position.z;
|
||||
assert(std::min(std::abs(actual-distance),56-std::abs(actual-distance))<.1);
|
||||
}
|
||||
assert(hour.Status()=="128,0,128,0,512,0");
|
||||
assert(hour.Unregister(hour.At(0)->Id));
|
||||
assert(!hour.Register("replacement",K::PATROL,{0,0,0},4)); // retired identity consumes finite pool
|
||||
}
|
||||
''')
|
||||
for path in (L,S):
|
||||
text=path.read_text()
|
||||
for forbidden in ('ERR_HQ', 'GetGame(', 'GetOrigin(', 'GetPlayers(', 'GetControlledEntity(',
|
||||
'Ground(', 'Spawn(', 'CallLater(', 'EOnFrame(', 'GetWorld(', 'GetAgents('):
|
||||
assert forbidden not in text,(path,forbidden)
|
||||
assert all(len(line)<120 for line in text.splitlines()),path
|
||||
for signature in ('protected void Route(', 'void Tick('):
|
||||
assert 'new ' not in body(S,signature)
|
||||
print('PASS: actual force ledger, finite casualties/identity/cap, routes/arrival/elevation/overshoot, server and physical guards, staggered 128-record simulated hour without world queries')
|
||||
Reference in New Issue
Block a user