Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
225 lines
13 KiB
Python
225 lines
13 KiB
Python
"""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/00_Core/ERR_ForceLedger.c'
|
|
S=ROOT/'addons/core/Scripts/Game/ERR/00_Core/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')
|