Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
116 lines
10 KiB
Python
116 lines
10 KiB
Python
"""Actual startup and pool accounting; terrain and native dispatch boundaries doubled."""
|
|
from policy_harness import ROOT, body, run
|
|
from pathlib import Path
|
|
import os,re
|
|
P=Path(os.environ.get('ERR_SUPPLY_SYSTEM_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoySystem.c'))
|
|
s='\n'.join(body(P,sig) for sig in ('bool StartOne(', 'protected string RefusalReason(',
|
|
'protected string BudgetStatus(', 'protected void NoteFailure(', 'void Tick(', 'string Status('))
|
|
s=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('null','nullptr')
|
|
for name in ('Replication','ERR_GameMode','Math','ERR_Actors','ERR_ConvoyRoadPlan','ERR_SupplyConvoyRules','ERR_HQ','ERR_ForceRules','string'):
|
|
s=s.replace(name+'.',name+'::')
|
|
s=s.replace('string::Empty','string{}').replace('array<vector>','Array<vector>')
|
|
for typ in ('ERR_ForceRoute','ERR_ConvoyReceipt','ERR_ConvoyTask'):
|
|
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
|
for name in ('m_Mode','m_Pool','m_Service','receipt','task'):
|
|
s=s.replace(name+'.',name+'->')
|
|
s=s.replace('GetGame().','GetGame()->').replace('GetWorldEntity().','GetWorldEntity()->').replace('GetWorld().','GetWorld()->')
|
|
s=s.replace('(m_Service->Count() + 1).ToString()', 'std::to_string(m_Service->Count() + 1)')
|
|
B=Path(os.environ.get('ERR_CONVOY_BUDGET',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c'))
|
|
# Execute the real reservation/replay/accounting/consume methods alongside StartOne.
|
|
pool=B.read_text();pool=re.sub(r'//[^\n]*','',pool).replace('protected ','').replace('ref ','').replace('class ','struct ')
|
|
pool=pool.replace('array<int>','Array<int>').replace('array<ERR_ConvoyReceipt>','Array<ERR_ConvoyReceipt*>')
|
|
for typ in ('ERR_GameMode','ERR_ForceLedger','ERR_ForceRecord','ERR_ConvoyReceipt'):
|
|
pool=re.sub(r'\b'+typ+r' ([A-Za-z_]\w*)',typ+r'* \1',pool)
|
|
for obj in ('m_Mode','ledger','record','receipt','known'):pool=pool.replace(obj+'.',obj+'->')
|
|
for cls in ('Math','Replication','ERR_GameMode','ERR_ForceKind'):pool=pool.replace(cls+'.',cls+'::')
|
|
pool=pool.replace('Forces().','Forces()->').replace('foreach (','for (').replace('null','nullptr').replace('void ERR_ConvoyPool(','ERR_ConvoyPool(').replace('vehicle.ToString()','std::to_string(vehicle)').replace('\n}\n','\n};\n')
|
|
pool=re.sub(r'\b(bool|int|[A-Za-z_]\w*\*) ([A-Za-z_]\w*);',r'\1 \2 {};',pool)
|
|
pool=pool.replace('ERR_ConvoyReceipt* Reserve(string sourceId)\n {','ERR_ConvoyReceipt* Reserve(string sourceId)\n {\n calls.reserve++;')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <vector>
|
|
struct string:std::string{using std::string::string;string(std::string s):std::string(s){}bool IsEmpty(){return empty();}int Length(){return size();}
|
|
template<class T>static std::string Text(T v){std::ostringstream o;o<<v;return o.str();}
|
|
template<class...T>static string Format(string format,T... values){int i=0;auto replace=[&](auto v){auto tag="%"+std::to_string(++i);auto at=format.find(tag);if(at!=std::string::npos)format.replace(at,tag.size(),Text(v));};(replace(values),...);return format;}};
|
|
struct vector{float x=0,y=0,z=0;float operator[](int i)const{return i==0?x:i==1?y:z;}};
|
|
std::ostream& operator<<(std::ostream& out,vector p){return out<<p.x<<","<<p.y<<","<<p.z;}
|
|
vector Vector(float x,float y,float z){return {x,y,z};}
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}void Insert(T x){this->push_back(x);}};
|
|
struct Counts{int world=0,bounds=0,bases=0,ground=0,random=0,road=0,reserve=0,dispatch=0;} calls;
|
|
struct World{void GetWorldBounds(vector& low,vector& high){calls.bounds++;low={0,0,0};high={10000,100,10000};}};
|
|
struct Game{World world;bool available=true;float now=0;World* GetWorldEntity(){calls.world++;return available?&world:nullptr;}Game* GetWorld(){return this;}float GetWorldTime(){return now;}} game;
|
|
Game* GetGame(){return &game;}
|
|
enum class ERR_ForceKind{GARRISON,CONVOY};
|
|
struct ERR_ForceRecord{int Id=0,InitialStrength=0,Strength=0;bool Physical=true,Eliminated=false;void* Group=this;ERR_ForceKind Kind=ERR_ForceKind::CONVOY;string SourceId;};
|
|
struct ERR_ForceLedger{Array<ERR_ForceRecord*>records;int Count(){return records.Count();}ERR_ForceRecord* At(int i){return records[i];}ERR_ForceRecord* Find(int id){for(auto* r:records){if(r->Id==id)return r;}return nullptr;}};
|
|
struct ERR_GameMode{static inline ERR_GameMode* current=nullptr;ERR_ForceLedger ledger;bool active=true;static ERR_GameMode* Get(){return current;}bool Active(){return active;}ERR_ForceLedger* Forces(){return &ledger;}};
|
|
using Mode=ERR_GameMode;
|
|
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
|
namespace Math{int Clamp(int v,int low,int high){return std::clamp(v,low,high);}float Min(float a,float b){return std::min(a,b);}float Max(float a,float b){return std::max(a,b);}float RandomFloat(float low,float){calls.random++;return low;}}
|
|
namespace ERR_Actors{vector Ground(vector p){calls.ground++;return p;}}
|
|
namespace ERR_ForceRules{bool Finite(float v){return std::isfinite(v);}}
|
|
namespace ERR_HQ{std::vector<string> events;void Event(string,string detail){events.push_back(detail);}}
|
|
struct ERR_ForceRoute{};
|
|
'''+pool+r'''
|
|
using Pool=ERR_ConvoyPool;
|
|
struct ERR_ConvoyTask{bool Stopped=false;};
|
|
struct Service{Pool* pool=nullptr;int count=0;bool fail=false;ERR_ConvoyTask task;int Count(){return count;}
|
|
ERR_ConvoyTask* Dispatch(ERR_ConvoyReceipt* r,Array<vector>,Array<vector>,ERR_ForceRoute*,float){calls.dispatch++;if(fail||!pool->Consume(r))return nullptr;count++;return &task;}};
|
|
namespace ERR_ConvoyRoadPlan{bool clear=true;ERR_ForceRoute found;
|
|
bool Build(vector,Array<vector>& positions,Array<vector>& angles,ERR_ForceRoute*& route,Array<vector>,float){calls.road++;if(!clear)return false;positions={{100,0,100}};angles={{0,0,0}};route=&found;return true;}}
|
|
namespace ERR_SupplyConvoyRules{bool Separated(vector,Array<vector>,float){return true;}}
|
|
struct Startup{Mode* m_Mode;Pool* m_Pool;Service* m_Service;Array<vector> m_Bases,m_Starts;int m_Target=3,m_Attempts=0;bool m_Stopped=false,m_Automatic=true;
|
|
float m_Began=-1,m_Next=0;string m_Reason,m_LastFailure;void FindBases(){calls.bases++;m_Bases={{0,0,0},{1000,0,0}};}
|
|
'''+s+r'''
|
|
};
|
|
void Reset(){calls={};ERR_HQ::events.clear();ERR_ConvoyRoadPlan::clear=true;game.available=true;}
|
|
void NoSurvey(){assert(calls.bases==0&&calls.world==0&&calls.bounds==0&&calls.ground==0&&calls.random==0&&calls.road==0&&calls.reserve==0&&calls.dispatch==0);}
|
|
struct Fixture{Mode mode;ERR_ForceRecord baseline;Pool pool;Service service;Startup startup;
|
|
Fixture(int bodies):pool(&mode,3,true),startup{&mode,&pool,&service}{baseline.Strength=bodies;mode.ledger.records.Insert(&baseline);ERR_GameMode::current=&mode;service.pool=&pool;Reset();}};
|
|
int main(){
|
|
{Fixture f(126);auto& blocked=f.startup;assert(!blocked.StartOne());NoSurvey();
|
|
assert(blocked.m_Reason=="insufficient shared body budget for another original convoy");
|
|
assert(blocked.m_Starts.empty()&&blocked.m_Attempts==0&&f.service.count==0&&f.pool.m_Receipts.empty());
|
|
blocked.Tick(0,true);blocked.Tick(30000,true);blocked.Tick(34999,true);blocked.Tick(35000,true);
|
|
NoSurvey();assert(blocked.m_Attempts==2&&ERR_HQ::events.size()==1);
|
|
assert(ERR_HQ::events[0].find("126/135")!=string::npos&&ERR_HQ::events[0].find("requires 10")!=string::npos);
|
|
for(int i=2;i<150;i++)blocked.Tick(30000+5000*i,true);
|
|
NoSurvey();assert(blocked.m_Attempts==120&&ERR_HQ::events.size()==2);
|
|
assert(ERR_HQ::events.back().find("attempts exhausted")!=string::npos);}
|
|
{Fixture f(125);assert(f.startup.StartOne());assert(calls.bases==1&&calls.bounds==1&&calls.road==1&&calls.reserve==1&&calls.dispatch==1);
|
|
assert(f.service.count==1&&f.startup.m_Starts.Count()==1&&f.pool.m_Receipts[0]->Consumed&&f.pool.CommittedBodies()==135);
|
|
// A lost task counter must never turn the same consumed source into a replacement.
|
|
f.service.count=0;assert(!f.startup.StartOne());assert(calls.dispatch==1&&f.startup.m_Reason.find("already consumed")!=string::npos);}
|
|
{Fixture f(100);ERR_ConvoyRoadPlan::clear=false;assert(!f.startup.StartOne());assert(calls.road==8&&calls.reserve==0&&calls.dispatch==0);
|
|
assert(f.startup.m_Reason=="no clear separated road formation in this bounded survey");}
|
|
{Fixture f(126);f.startup.Tick(0,true);f.startup.Tick(30000,true);NoSurvey();f.baseline.Strength=125;
|
|
f.startup.Tick(35000,true);assert(f.service.count==1&&calls.dispatch==1&&f.startup.m_Attempts==2&&f.startup.m_LastFailure.empty());}
|
|
// Critical real-pool replay: failed road preflight retains the unconsumed receipt.
|
|
{Fixture f(123);f.service.fail=true;assert(!f.startup.StartOne());
|
|
assert(f.service.count==0&&f.pool.m_Receipts.Count()==1&&f.pool.CommittedBodies()==133);
|
|
auto original=f.pool.m_Receipts[0];assert(!original->Consumed&&original->OutstandingBodies==10);
|
|
assert(f.pool.BodyBudgetAvailable("supply-convoy-1")&&!f.pool.BodyBudgetAvailable("supply-convoy-2"));
|
|
assert(f.pool.Reserve("supply-convoy-1")==original&&!f.pool.Reserve("supply-convoy-2"));
|
|
f.service.fail=false;Reset();assert(f.startup.StartOne());
|
|
assert(calls.road==1&&calls.reserve==1&&calls.dispatch==1&&f.pool.m_Receipts.Count()==1);
|
|
assert(f.pool.m_Receipts[0]==original&&original->Consumed&&f.service.count==1&&f.pool.CommittedBodies()==133);}
|
|
{Fixture f(123);f.service.fail=true;assert(!f.startup.StartOne());auto original=f.pool.m_Receipts[0];
|
|
// Precheck intentionally cannot grant admission: actual Consume rechecks budget growth.
|
|
f.baseline.Strength=126;f.service.fail=false;assert(!f.startup.StartOne()&&!original->Consumed&&f.service.count==0);
|
|
f.baseline.Strength=125;assert(f.startup.StartOne()&&original->Consumed&&f.pool.CommittedBodies()==135);}
|
|
{Fixture f(0);assert(!f.pool.BodyBudgetAvailable("")&&!f.pool.BodyBudgetAvailable(string(65,'x')));
|
|
Mode other;ERR_GameMode::current=&other;assert(!f.pool.BodyBudgetAvailable("supply-convoy-1"));
|
|
ERR_GameMode::current=&f.mode;Replication::server=false;assert(!f.pool.BodyBudgetAvailable("supply-convoy-1"));Replication::server=true;
|
|
f.mode.active=false;assert(!f.pool.BodyBudgetAvailable("supply-convoy-1"));}
|
|
{Fixture f(0);f.startup.m_Pool=nullptr;assert(!f.startup.StartOne());NoSurvey();assert(f.startup.m_Reason=="convoy allocator unavailable");}
|
|
{Fixture f(0);f.startup.m_Target=0;assert(!f.startup.StartOne());NoSurvey();f.startup.m_Target=3;
|
|
f.startup.m_Stopped=true;assert(!f.startup.StartOne());NoSurvey();}
|
|
|
|
}
|
|
''')
|
|
print('PASS: actual StartOne plus actual pool preserve unconsumed receipt retry, reject consumed replay, skip impossible new-budget surveys, retain equality, retry caps and journal dedup; no FPS claim')
|