Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
76 lines
7.0 KiB
Python
76 lines
7.0 KiB
Python
"""Run actual canonical convoy receipt and shared ledger-cap methods."""
|
|
import os,re
|
|
from pathlib import Path
|
|
from policy_harness import ROOT,run
|
|
p=Path(os.environ.get('ERR_CONVOY_BUDGET',str(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyBudget.c')))
|
|
s=p.read_text();s=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('ref ','').replace('class ','struct ')
|
|
s=s.replace('array<int>','Array<int>').replace('array<ERR_ConvoyReceipt>','Array<ERR_ConvoyReceipt*>')
|
|
for typ in ['ERR_GameMode','ERR_ForceLedger','ERR_ForceRecord','ERR_ConvoyReceipt']:
|
|
s=re.sub(r'\b'+typ+r' ([A-Za-z_]\w*)',typ+r'* \1',s)
|
|
for obj in ['m_Mode','ledger','record','receipt','known']:s=s.replace(obj+'.',obj+'->')
|
|
for cls in ['Math','Replication','ERR_GameMode','ERR_ForceKind']:s=s.replace(cls+'.',cls+'::')
|
|
s=s.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')
|
|
s=re.sub(r'\b(bool|int|[A-Za-z_]\w*\*) ([A-Za-z_]\w*);',r'\1 \2 {};',s)
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <string>
|
|
#include <algorithm>
|
|
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>struct Array:std::vector<T>{using std::vector<T>::vector;bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}void Insert(T v){this->push_back(v);}int Count(){return this->size();}};
|
|
namespace Math{int Clamp(int a,int low,int high){return std::clamp(a,low,high);}}
|
|
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
|
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;}};
|
|
''' + s + r'''
|
|
int main(){
|
|
ERR_GameMode mode;ERR_GameMode::current=&mode;ERR_ForceRecord guard;guard.Id=1;guard.Strength=50;mode.ledger.records.Insert(&guard);
|
|
ERR_ConvoyPool pool(&mode,3);assert(pool.BodyLimit()==60);auto receipt=pool.Reserve("road-a");assert(receipt&&pool.CommittedBodies()==60);assert(pool.Reserve("road-a")==receipt);assert(!pool.Reserve("road-b"));
|
|
ERR_ConvoyReceipt forged;assert(!pool.Consume(&forged));assert(pool.Consume(receipt)&&!pool.Consume(receipt));
|
|
for(int i=0;i<6;i++){
|
|
if(i==2)continue;
|
|
auto* r=new ERR_ForceRecord();r->Id=i+2;r->InitialStrength=ERR_ConvoyPool::CrewCount(i);r->Strength=r->InitialStrength;r->SourceId="road-a-vehicle-"+std::to_string(i);mode.ledger.records.Insert(r);
|
|
assert(pool.BindRecord(receipt,i,r));assert(pool.BindRecord(receipt,i,r));assert(pool.CommittedBodies()==60);
|
|
}
|
|
ERR_ForceRecord retired;assert(!pool.BindRecord(receipt,2,&retired));
|
|
assert(receipt->OutstandingBodies==0&&receipt->OutstandingGroups==0);assert(!pool.Consume(receipt));
|
|
// Dead/captured/released forces never refund a consumed receipt's convoy slot.
|
|
guard.Strength=0;guard.Eliminated=true;auto second=pool.Reserve("road-b");assert(second);assert(pool.Consume(second));assert(!pool.Consume(receipt));
|
|
ERR_GameMode other;ERR_GameMode::current=&other;assert(!pool.Reserve("wrong-world")&&!pool.Consume(second));ERR_GameMode::current=&mode;
|
|
ERR_GameMode crowded;ERR_GameMode::current=&crowded;ERR_ForceRecord many;many.Strength=51;crowded.ledger.records.Insert(&many);ERR_ConvoyPool limited(&crowded,8);assert(!limited.Reserve("too-many"));
|
|
many.Strength=50;auto reserved=limited.Reserve("exact");assert(reserved);many.Strength=51;assert(!limited.Consume(reserved));many.Strength=50;assert(limited.Consume(reserved));
|
|
ERR_ForceRecord invalid;invalid.Id=2;invalid.InitialStrength=2;invalid.Strength=2;invalid.SourceId="exact-vehicle-0";
|
|
assert(!limited.BindRecord(reserved,0,&invalid));crowded.ledger.records.Insert(&invalid);invalid.Kind=ERR_ForceKind::GARRISON;assert(!limited.BindRecord(reserved,0,&invalid));invalid.Kind=ERR_ForceKind::CONVOY;
|
|
invalid.Strength=1;assert(!limited.BindRecord(reserved,0,&invalid));invalid.Strength=2;invalid.Physical=false;assert(!limited.BindRecord(reserved,0,&invalid));invalid.Physical=true;assert(limited.BindRecord(reserved,0,&invalid));
|
|
Replication::server=false;assert(!limited.Reserve("client"));Replication::server=true;
|
|
ERR_GameMode formations;ERR_GameMode::current=&formations;for(int i=0;i<124;i++){auto* r=new ERR_ForceRecord();r->Strength=0;r->Physical=false;formations.ledger.records.Insert(r);}ERR_ConvoyPool capped(&formations,8);assert(!capped.Reserve("formation-cap"));
|
|
// Five convoy vehicles reserve ten crew; reassigned hunter infantry retains shared headroom.
|
|
for(int target:{0,1,3,20}){
|
|
ERR_GameMode supply;ERR_GameMode::current=&supply;ERR_ForceRecord baseline;baseline.Strength=60;supply.ledger.records.Insert(&baseline);ERR_ConvoyPool full(&supply,target,true);assert(full.BodyLimit()==60+25*target);
|
|
for(int i=0;i<target;i++){
|
|
auto ticket=full.Reserve("supply-"+std::to_string(i));assert(ticket&&ticket->OutstandingBodies==10&&ticket->OutstandingGroups==5&&ticket->Records.Count()==6);assert(full.Consume(ticket));
|
|
for(int group=0;group<6;group++){
|
|
if(group==2)continue;
|
|
auto r=new ERR_ForceRecord();r->Id=1+supply.ledger.records.Count();r->SourceId=ticket->SourceId+"-vehicle-"+std::to_string(group);r->InitialStrength=full.PlannedCount(group);r->Strength=r->InitialStrength;supply.ledger.records.Insert(r);assert(full.BindRecord(ticket,group,r));
|
|
}
|
|
assert(ticket->OutstandingBodies==0&&ticket->OutstandingGroups==0&&full.CommittedBodies()==60+10*(i+1));
|
|
}
|
|
assert(!full.Reserve("excess"));
|
|
for(auto r:supply.ledger.records)r->Strength=0;
|
|
assert(!full.Reserve("replacement"));
|
|
}
|
|
// Reconstructed normal-roster budget boundaries: defaults remain finite, never forced.
|
|
for(int baseline:{123,126}){
|
|
ERR_GameMode observed;ERR_GameMode::current=&observed;ERR_ForceRecord base;base.Strength=baseline;observed.ledger.records.Insert(&base);
|
|
ERR_ConvoyPool normal(&observed,3,true);assert(normal.BodyLimit()==135&&normal.PlannedBodies()==10);
|
|
auto first=normal.Reserve("observed-first");assert(bool(first)==(baseline==123));
|
|
if(first){assert(normal.Consume(first));assert(normal.CommittedBodies()==133);assert(!normal.Reserve("observed-second"));}
|
|
else assert(normal.CommittedBodies()==126);
|
|
}
|
|
ERR_GameMode finite;ERR_GameMode::current=&finite;ERR_ConvoyPool one(&finite,1);assert(one.Reserve("first"));assert(!one.Reserve("second"));
|
|
}
|
|
''')
|
|
print('PASS: actual convoy receipt identity/one-use, shared60-body and128-formation caps, outstanding/native-ledger accounting, exact10-body composition, source reuse, mode/client guards and no consumed receipt refunds')
|