Fix convoy coordination, AI combat and spectator recovery
This commit is contained in:
@@ -2,23 +2,27 @@
|
||||
import re
|
||||
from policy_harness import ROOT,body,run
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_GeneratedSites.c'
|
||||
s=body(p,'ERR_PrepareResult Tick(')
|
||||
s=body(p,'protected bool SkipOptional(')+'\n'+body(p,'ERR_PrepareResult Tick(')
|
||||
s=s.replace('ERR_SiteDefinition site','Site* site').replace('site.','site->').replace('m_Terrain.Site','m_Terrain.SiteAt')
|
||||
s=s.replace('System.GetTickCount','Clock::Now').replace('Replication.IsServer','Server').replace('Math.RandomFloat','RandomFloat').replace('ERR_PrepareResult.','Result::')
|
||||
s=s.replace('float floor;','float floor=0;')
|
||||
s=s.replace('float floor;','float floor=0;').replace('protected ', '').replace('ref ERR_SiteDefinition retained', 'Site* retained').replace('retained.', 'retained->').replace('m_Definition.', 'm_Definition->').replace('ERR_LocationKind.', 'Kind::')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
using string=std::string;
|
||||
struct vector{float a[3];float& operator[](int i){return a[i];}};vector Vector(float x,float y,float z){return {{x,y,z}};}
|
||||
namespace Result{enum Value{FAILED=-1,PENDING,READY};}using ERR_PrepareResult=Result::Value;
|
||||
namespace Clock {int now=0,step=1;int Now(){int t=now;now+=step;return t;}}
|
||||
bool Server(){return true;}float RandomFloat(float a,float b){return (a+b)/2;}template<class...T>void PrintFormat(T...){}
|
||||
struct Site{vector Position=Vector(10,0,20);float Heading=0;bool PlacementResolved=false;string Id="site";};
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::operator=;int Count(){return this->size();}};
|
||||
struct Terrain{bool clear=true;int queries=0;bool SiteAt(Site*,vector,float,float& height){queries++;height=42;return clear;}};
|
||||
namespace Kind{enum{PRISON,RADIO,EXTRACTION,OUTPOST,DEPOT,MORTAR,HOSPITAL};}
|
||||
struct Site{int Kind=Kind::PRISON;bool required=false;string Label="Test site";vector Position=Vector(10,0,20);float Heading=0;bool PlacementResolved=false;string Id="site";};
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::operator=;int Count(){return this->size();}int Find(T v){auto i=std::find(this->begin(),this->end(),v);return i==this->end()?-1:i-this->begin();}void Remove(int i){this->erase(this->begin()+i);}void InsertAt(T v,int i){this->insert(this->begin()+i,v);}};
|
||||
struct Definition{Array<Site*> Sites;Site* required=nullptr;bool Valid(){return !required||Sites.Find(required)>=0;}};
|
||||
struct Terrain{bool clear=true;Site* refused=nullptr;int queries=0;bool SiteAt(Site* site,vector,float,float& height){queries++;height=42;return clear&&site!=refused;}};
|
||||
struct Scheduler{
|
||||
Definition definition;Definition* m_Definition=&definition;
|
||||
bool m_Started=true;ERR_PrepareResult m_Result=Result::PENDING;int m_Index=0,m_Attempts=0,m_Began=0;
|
||||
vector m_Low=Vector(0,0,0),m_High=Vector(10000,0,10000);Array<Site*>m_Pending;Terrain m_Terrain;string m_Error;
|
||||
bool Separated(Site*,vector,bool){return true;}
|
||||
@@ -31,6 +35,28 @@ int main(){
|
||||
{Clock::now=0;Scheduler s;Site a;s.m_Pending={&a};s.m_Terrain.clear=false;assert(s.Tick()==Result::PENDING);assert(s.m_Terrain.queries<10&&!a.PlacementResolved);Clock::now=30001;assert(s.Tick()==Result::FAILED&&!s.m_Error.empty()&&!a.PlacementResolved);int calls=s.m_Terrain.queries;assert(s.Tick()==Result::FAILED&&s.m_Terrain.queries==calls);}
|
||||
{Clock::now=0;Scheduler s;Site a;s.m_Pending={&a};s.m_Attempts=12000;assert(s.Tick()==Result::READY&&a.Position[0]==10&&a.Position[2]==20&&a.PlacementResolved);}
|
||||
{Clock::now=30001;Scheduler s;Site a;s.m_Pending={&a};assert(s.Tick()==Result::READY&&a.Position[0]==10&&a.Position[2]==20);}
|
||||
// Each optional type can fail after exhausting candidates without blocking the next site.
|
||||
for(int kind : {Kind::OUTPOST,Kind::HOSPITAL,Kind::DEPOT}){
|
||||
Clock::now=0;Scheduler s;Site a,b;a.Kind=kind;a.Id="unplaceable";b.Id="next";
|
||||
s.m_Pending={&a,&b};s.definition.Sites={&a,&b};s.m_Terrain.refused=&a;s.m_Attempts=12000;
|
||||
while(s.Tick()==Result::PENDING){}
|
||||
assert(s.m_Result==Result::READY&&s.m_Error.empty()&&!a.PlacementResolved&&b.PlacementResolved);
|
||||
assert(s.definition.Sites.size()==1&&s.definition.Sites[0]==&b);
|
||||
int calls=s.m_Terrain.queries;assert(s.Tick()==Result::READY&&s.m_Terrain.queries==calls);
|
||||
}
|
||||
// A deadline skip gets a fresh candidate/time budget for the following optional site.
|
||||
{Clock::now=30001;Scheduler s;Site a,b;a.Kind=Kind::OUTPOST;b.Kind=Kind::HOSPITAL;
|
||||
s.m_Pending={&a,&b};s.definition.Sites={&a,&b};s.m_Terrain.refused=&a;
|
||||
while(s.Tick()==Result::PENDING){}assert(s.m_Result==Result::READY&&b.Position[0]!=10&&b.PlacementResolved);}
|
||||
// Required objectives (including mortars) still fail; no unsafe terrain is accepted.
|
||||
for(int kind : {Kind::PRISON,Kind::RADIO,Kind::EXTRACTION,Kind::MORTAR}){
|
||||
Clock::now=0;Scheduler s;Site a;a.Kind=kind;s.m_Pending={&a};s.definition.Sites={&a};s.m_Attempts=12000;s.m_Terrain.clear=false;
|
||||
assert(s.Tick()==Result::FAILED&&s.definition.Sites[0]==&a&&!a.PlacementResolved);
|
||||
}
|
||||
// Optional by type can still be indispensable for minimum accessible radio intel.
|
||||
{Clock::now=0;Scheduler s;Site before,a,after;a.Kind=Kind::HOSPITAL;
|
||||
s.definition.Sites={&before,&a,&after};s.definition.required=&a;s.m_Pending={&a};s.m_Attempts=12000;s.m_Terrain.clear=false;
|
||||
assert(s.Tick()==Result::FAILED&&s.definition.Sites.size()==3&&s.definition.Sites[1]==&a&&!a.PlacementResolved);}
|
||||
}
|
||||
''')
|
||||
print('PASS: actual generation scheduler bounds each tick, enforces deadline/candidate ceiling, validates fallback, commits only accepted positions, and terminates without repeated searches')
|
||||
print('PASS: actual generation scheduler bounds each tick, enforces deadline/candidate ceiling, validates fallback, commits only accepted positions, skips only dispensable sites, preserves required objectives/intel, and resets the next search budget')
|
||||
|
||||
Reference in New Issue
Block a user