Improve small-terrain placement and show startup failure causes; record Workshop 0.0.120
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-10-24 03:03:10 +02:00
parent 253dde90cd
commit 3768ce7ed8
25 changed files with 4058 additions and 51 deletions
+4 -3
View File
@@ -87,7 +87,7 @@ assert 'm_Mode.StartMode() != 0' in body(source('ERR_PrisonEscapeService.c'),'vo
survey=body(source('ERR_GeneratedSites.c'),'bool Site(')
assert 'ERR_StartSetup.IsCrash(site)' in survey and 'ERR_StartSetup.Geometry(offset, center, halfSize, height)' in survey
assert 'Clear(position + Rotate(center, heading), heading, halfSize, 0.6, wreckFloor, height)' in survey
assert 'm_Terrain.Site(site, position, heading, floor)' in (ROOT/C/'ERR_TerrainSites.c').read_text()
assert 'm_Terrain.Site(site, position, heading, floor, PrisonAirportBuffer(m_Low, m_High))' in (ROOT/C/'ERR_TerrainSites.c').read_text()
print('PASS: actual crash geometry, detached definition setup, frozen server choice and guarded phase policy; integration/source ordering checked')
# Execute actual GameMode transition admission prefix (effects after acceptance
# are existing mission hooks); callers cannot use selected crash mode before
@@ -110,6 +110,7 @@ for a,b in [('ERR_SiteDefinition site','SiteData* site'),('site.','site->'),('fo
run(r'''
#include <cassert>
#include <cmath>
#include <string>
#include <vector>
#define null nullptr
struct vector{float x=0,y=0,z=0;};vector operator+(vector a,vector b){return{a.x+b.x,a.y+b.y,a.z+b.z};}float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}
@@ -118,7 +119,7 @@ struct SiteData{int Kind=0,ResponseCount=0;bool Guarded=false,crash=false;vector
namespace StartSetup{bool measured=true;bool IsCrash(SiteData*s){return s->crash;}bool Geometry(vector&offset,vector&center,float&half,float&height){offset={16,1,0};center={17,0,0};half=6;height=6;return measured;}}
struct Road{};struct RoadManager{void GetClosestRoad(vector,Road*&,float&,bool){}};struct AIWorld{RoadManager roads;RoadManager*GetRoadNetworkManager(){return &roads;}};
struct Call{vector p;float half,height;};
struct Survey{AIWorld world;std::vector<vector>m_Airfields;std::vector<Call>calls;int refuse=0;vector Rotate(vector p,float h){assert(h==90);return{p.z,p.y,-p.x};}bool Clear(vector p,float,float half,float,float&floor,float height=4){calls.push_back({p,half,height});floor=42;return int(calls.size())!=refuse;}SITE};
int main(){SiteData a;float floor=0;Survey normal;assert(normal.Site(&a,{100,0,200},90,floor)&&normal.calls.size()==1&&normal.calls[0].half==10&&normal.calls[0].height==4);a.crash=true;Survey crash;assert(crash.Site(&a,{100,0,200},90,floor)&&crash.calls.size()==2);assert(crash.calls[1].p.x==100&&crash.calls[1].p.z==183&&crash.calls[1].half==6&&crash.calls[1].height==6);for(int refusal:{1,2}){Survey blocked;blocked.refuse=refusal;assert(!blocked.Site(&a,{100,0,200},90,floor));}StartSetup::measured=false;Survey unknown;assert(!unknown.Site(&a,{100,0,200},90,floor)&&unknown.calls.size()==1);}
struct Survey{std::string m_Rejection;bool Reject(std::string reason){m_Rejection=reason;return false;}AIWorld world;std::vector<vector>m_Airfields;std::vector<Call>calls;int refuse=0;vector Rotate(vector p,float h){assert(h==90);return{p.z,p.y,-p.x};}bool Clear(vector p,float,float half,float,float&floor,float height=4){calls.push_back({p,half,height});floor=42;return int(calls.size())!=refuse;}SITE};
int main(){SiteData a;float floor=0;Survey airport;airport.m_Airfields.push_back({600,0,0});assert(!airport.Site(&a,{0,0,0},90,floor)&&airport.calls.empty());assert(airport.Site(&a,{0,0,0},90,floor,300)&&airport.calls.size()==1);Survey normal;assert(normal.Site(&a,{100,0,200},90,floor)&&normal.calls.size()==1&&normal.calls[0].half==10&&normal.calls[0].height==4);a.crash=true;Survey crash;assert(crash.Site(&a,{100,0,200},90,floor)&&crash.calls.size()==2);assert(crash.calls[1].p.x==100&&crash.calls[1].p.z==183&&crash.calls[1].half==6&&crash.calls[1].height==6);for(int refusal:{1,2}){Survey blocked;blocked.refuse=refusal;assert(!blocked.Site(&a,{100,0,200},90,floor));}StartSetup::measured=false;Survey unknown;assert(!unknown.Site(&a,{100,0,200},90,floor)&&unknown.calls.size()==1);}
'''.replace('SITE',site))
print('PASS: actual GameMode readiness admission and common full-footprint survey require crash authorization and both surveyed areas')
+3 -1
View File
@@ -7,6 +7,7 @@ s=s.replace('ERR_SiteDefinition site','Site* site').replace('site.','site->').re
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;').replace('protected ', '').replace('ref ERR_SiteDefinition retained', 'Site* retained').replace('retained.', 'retained->').replace('m_Definition.', 'm_Definition->').replace('ERR_LocationKind.', 'Kind::')
s=s.replace('foreach (Site* site : m_Definition->Sites)', 'for (Site* site : m_Definition->Sites)')
s=s.replace('bool fits;', 'bool fits=false;')
s=s.replace('ERR_Actors.', 'ERR_Actors::').replace('ERR_CampTerrain.', 'ERR_CampTerrain::').replace('ERR_RescueLanding.', 'ERR_RescueLanding::')
run(r'''
#include <cassert>
@@ -26,8 +27,9 @@ 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),AircraftOffset=Vector(12,0,0);float Heading=0;bool PlacementResolved=false,RandomClear=true;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{bool TerrainGenerated=false;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 Terrain{string Rejection(){return "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{
void RecordRejection(string){}void ReportSearch(Site*,string){}void ReportProgress(Site*){}void ClearDiagnostics(){}string RejectionSummary(){return "terrain";}
Definition definition;Definition* m_Definition=&definition;
bool m_StartupOnly=false;
bool m_Started=true;ERR_PrepareResult m_Result=Result::PENDING;int m_Index=0,m_Attempts=0,m_Began=0;
+67
View File
@@ -0,0 +1,67 @@
"""Execute production placement rejection classification and bounded summaries."""
from policy_harness import ROOT, body, run
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_GeneratedSites.c'
methods='\n'.join(body(p,sig) for sig in ('protected void RecordRejection(', 'protected string RejectionSummary(', 'protected void ReportSearch(', 'protected void ReportProgress(', 'protected void ClearDiagnostics()'))
methods=methods.replace('protected ', '').replace('ERR_SiteDefinition site','Site* site').replace('site.','site->').replace('System.GetTickCount()', 'Clock::now').replace('int count;', 'int count=0;').replace('count.ToString()', 'std::to_string(count)').replace('foreach (string reason, int count : m_Rejections)', 'for (auto [reason,count] : m_Rejections)')
run(r'''
#include <cassert>
#include <string>
#include <map>
#include <vector>
struct string:std::string{using std::string::string;using std::string::operator=;string(std::string s):std::string(s){}bool IsEmpty(){return empty();}};
template<class K,class V>struct Map:std::map<K,V>{void Find(K k,V&out){auto i=this->find(k);if(i!=this->end())out=i->second;}void Set(K k,V v){(*this)[k]=v;}void Clear(){this->clear();}};
namespace Clock{int now=0;}
struct Site{string Id="radio-1",Label="Radio station";};
struct Report{string stage,id,label,summary;int attempts,elapsed;};std::vector<Report> reports;
void PrintFormat(string,string stage,string id,string label,int attempts,int elapsed,string summary){reports.push_back({stage,id,label,summary,attempts,elapsed});}
struct Diagnostics{Map<string,int>m_Rejections;int m_NextDiagnostic=0,m_Began=100,m_Attempts=123;
'''+methods+r'''
};
int main(){
Diagnostics d;Site site;assert(d.RejectionSummary()=="none recorded");
for(int i=0;i<10;i++){d.RecordRejection("water");}d.RecordRejection("uneven ground");d.RecordRejection("");
assert(d.m_Rejections.size()==3&&d.m_Rejections["water"]==10);
assert(d.RejectionSummary().find("water=10")!=string::npos&&d.RejectionSummary().find("uneven ground=1")!=string::npos&&d.RejectionSummary().find("unspecified terrain clearance=1")!=string::npos);
Clock::now=30099;d.ReportProgress(&site);assert(reports.empty());Clock::now=30100;d.ReportProgress(&site);assert(reports.size()==1);
assert(reports.back().stage=="still searching"&&reports.back().id==site.Id&&reports.back().label==site.Label&&reports.back().attempts==123&&reports.back().elapsed==30000);
for(int i=0;i<100;i++){d.ReportProgress(&site);}assert(reports.size()==1);
Clock::now=60099;d.ReportProgress(&site);assert(reports.size()==1);Clock::now=60100;d.ReportProgress(&site);assert(reports.size()==2);
d.ReportSearch(&site,"exhausted");assert(reports.size()==3&&reports.back().stage=="exhausted"&&reports.back().summary.find("water=10")!=string::npos);
d.ClearDiagnostics();assert(d.m_Rejections.empty()&&d.m_NextDiagnostic==0);
d.m_Began=Clock::now;d.ReportProgress(&site);assert(reports.size()==3);d.RecordRejection("obstructed footprint");assert(d.RejectionSummary()=="obstructed footprint=1");
}
''')
# Execute the actual water, elevation, flatness and physical-trace short circuits.
clear=body(p,'protected bool Reject(')+'\n'+body(p,'bool Clear(')
clear=clear.replace('protected ', '').replace('out float lowest','float& lowest').replace('float.MAX','FLT_MAX').replace('Math.','Math::').replace('TraceBox trace','TraceBox* trace').replace('trace.','trace->').replace('TraceFlags.','TraceFlags::').replace('null','nullptr').replace('ChimeraWorldUtils.','ChimeraWorldUtils::')
for a,b in [('"-1.42 0 -1.42"','Vector(-1.42,0,-1.42)'),('"1.42 0.1 1.42"','Vector(1.42,.1,1.42)')]:clear=clear.replace(a,b)
run(r'''
#include <cassert>
#include <cfloat>
#include <string>
#include <algorithm>
using string=std::string;
struct vector{float p[3];float operator[](int i){return p[i];}};vector Vector(float x,float y,float z){return {{x,y,z}};}vector operator+(vector a,vector b){return Vector(a[0]+b[0],a[1]+b[1],a[2]+b[2]);}
namespace Math{float Min(float a,float b){return std::min(a,b);}float Max(float a,float b){return std::max(a,b);}}
namespace TraceFlags{int ENTS=1,WORLD=2;}struct TraceBox{vector Start,End,Mins,Maxs;int Flags;};
struct World{bool water=false,blocked=false,slope=false;float height=42;int traces=0;float GetSurfaceY(float x,float){return height+(slope?x:0);}float TraceMove(TraceBox*,void*){traces++;return blocked?0:1;}};
struct Game{World world;World&GetWorld(){return world;}}game;Game&GetGame(){return game;}
namespace ChimeraWorldUtils{bool TryGetWaterSurfaceSimple(World&w,vector){return w.water;}}
struct Terrain{string m_Rejection;vector Rotate(vector p,float){return p;}
'''+clear+r'''
};
int main(){Terrain t;float floor;auto check=[&](){return t.Clear(Vector(0,0,0),0,10,.6,floor);};
game.world.water=true;assert(!check()&&t.m_Rejection=="water"&&game.world.traces==0);
game.world.water=false;game.world.height=.5;assert(!check()&&t.m_Rejection=="ground below minimum elevation");
game.world.height=42;game.world.slope=true;assert(!check()&&t.m_Rejection=="uneven ground"&&game.world.traces==0);
game.world.slope=false;game.world.blocked=true;assert(!check()&&t.m_Rejection=="obstructed footprint"&&game.world.traces==1);
game.world.blocked=false;assert(check()&&t.m_Rejection.empty()&&floor==42);
}
''')
# Both paths publish counts before resetting, and expose them in startup errors.
for filename in ('ERR_GeneratedSites.c','ERR_TerrainSites.c'):
source=(p.parent/filename).read_text()
assert 'ReportProgress(site);' in source
assert 'ReportSearch(site, "placed"); ClearDiagnostics();' in source
assert 'Checks: ' in source and 'RejectionSummary();' in source
print('PASS: actual terrain refusal reasons, counted summaries, site/attempt/time context, 30-second rate limit and per-site reset')
+7 -1
View File
@@ -32,7 +32,7 @@ struct ERR_GameMode {
int pickup=0;int PickupStage(){return pickup;}
int startMode=0;int StartMode(){return startMode;}
int phase=ERR_Phase::RADIO_DEFENCE, feedback=ERR_RadioFeedback::WAITING, remaining=180;
bool occupied=false;int Phase(){return phase;}string PreparationError(){return "";}
string error;bool occupied=false;int Phase(){return phase;}string PreparationError(){return error;}
void Locations(std::vector<ERR_LocationComponent*>& locations){locations.clear();}
int RadioFeedback(){return feedback;}int DefenceSecondsRemaining(){return remaining;}
bool DefenceOccupied(){return occupied;}float EscapeRadius(){return 9000;}
@@ -45,6 +45,12 @@ struct Actual {
bool has(string text,string needle){return text.find(needle)!=string::npos;}
int main(){
ERR_GameMode mode;
mode.phase=ERR_Phase::PREPARING;
assert(has(Actual::PhaseDescription(&mode,false),"Preparing mission sites"));
mode.error="Terrain has no safe navigable placement for Prison. Checks: uneven ground=123; water=45";
auto failure=Actual::PhaseDescription(&mode,false);
assert(has(failure,mode.error)&&has(failure,"restart the scenario")&&!has(failure,"/esc"));
mode.error.clear();mode.phase=ERR_Phase::RADIO_DEFENCE;
for(int duration : {30,180,1800}) {
mode.remaining=duration;
auto task=Actual::PhaseDescription(&mode,true);
+41 -12
View File
@@ -1,7 +1,7 @@
"""Run production generic placement scheduling with clock/terrain adapters."""
from policy_harness import ROOT, body, run
base = ROOT/'addons/core/Scripts/Game/ERR/00_Core'
s = '\n'.join(body(base/'ERR_GeneratedSites.c', signature) for signature in ('static bool StartupSite(', 'void StartupOnly(', 'bool StartupReady(', 'protected bool SkipOptional(')) + '\n' + body(base/'ERR_TerrainSites.c', 'override ERR_PrepareResult Tick(') + '\n' + body(base/'ERR_TerrainSites.c', 'protected bool PrepareAirports(')
s = '\n'.join(body(base/'ERR_GeneratedSites.c', signature) for signature in ('static bool StartupSite(', 'void StartupOnly(', 'bool StartupReady(', 'protected bool SkipOptional(')) + '\n' + body(base/'ERR_TerrainSites.c', 'override ERR_PrepareResult Tick(') + '\n' + body(base/'ERR_TerrainSites.c', 'protected bool PrepareAirports(') + '\n' + '\n'.join(body(base/'ERR_TerrainSites.c', sig) for sig in ('static int SearchAttempts(', 'static int SearchMilliseconds(', 'protected void ExpandExtractionSearch('))
for before, after in [('override ', ''), ('protected ', ''), ('ref ERR_SiteDefinition retained','Site* retained'),
('ERR_SiteDefinition site','Site* site'), ('foreach (', 'for ('), ('site.', 'site->'), ('retained.','retained->'),
('m_Definition.', 'm_Definition->'), ('m_Airports.', 'm_Airports->'), ('System.GetTickCount','Clock::Now'),
@@ -27,6 +27,7 @@ template<class T>struct Array:std::vector<T>{using std::vector<T>::operator=;boo
struct Definition{bool TerrainGenerated=true;Array<Site*> Sites;Site* required=nullptr;bool valid=true;bool Valid(){return valid&&(!required||Sites.Find(required)>=0);}};
struct Airports{Array<vector> m_Positions;Airports(){m_Positions.push_back(Vector(0,0,0));}bool complete=true;int updates=0,cost=0,finishAfter=0;void Update(){updates++;Clock::now+=cost;if(finishAfter&&updates>=finishAfter)complete=true;}bool Complete(){return complete;}};
struct Scheduler{
void RecordRejection(string){}void ReportSearch(Site*,string){}void ReportProgress(Site*){}void ClearDiagnostics(){}string RejectionSummary(){return "terrain";}
Airports airports;Airports* m_Airports=&airports;bool m_AirportsReady=true;
Definition definition;Definition* m_Definition=&definition;Array<Site*> m_Pending;
ERR_PrepareResult m_Result=Result::PENDING;string m_Error;
@@ -74,16 +75,30 @@ int main(){
// A streaming timeout releases the candidate, but never accepts unloaded geometry.
{Clock::now=0;Scheduler s;Site a;s.m_Pending={&a};s.waiting=100;
assert(s.Tick()==Result::PENDING&&s.m_Attempts==1);Clock::now=5001;s.Tick();assert(!a.PlacementResolved&&s.m_Attempts>=2);}
// The former ceiling no longer ends a difficult prison search.
{Clock::now=30001;Scheduler s;Site a;s.m_Pending={&a};s.definition.Sites={&a};s.m_Attempts=12000;
while(s.Tick()==Result::PENDING){}assert(s.m_Result==Result::READY&&a.PlacementResolved);}
// A recognized but unusable airport broadens once, preserving the total clock
// and attempt counter and discarding any pending airport/navmesh candidate.
for(int byTime:{0,1}){
Clock::now=byTime?30001:1;Scheduler s;Site a;a.Kind=Kind::EXTRACTION;
s.m_Pending={&a};s.definition.Sites={&a};s.m_Attempts=byTime?2:12000;s.m_HaveCandidate=true;
int attempts=s.m_Attempts;s.ExpandExtractionSearch(&a);
assert(!a.RequireAirfield&&!s.m_HaveCandidate&&s.m_Began==0&&s.m_Attempts==attempts&&a.Label=="Extraction landing zone");
s.m_HaveCandidate=true;s.ExpandExtractionSearch(&a);assert(s.m_HaveCandidate); // one transition only
while(s.Tick()==Result::PENDING){}assert(s.m_Result==Result::READY&&a.PlacementResolved);}
{Clock::now=29990;Scheduler s;Site a;a.Kind=Kind::EXTRACTION;s.m_Attempts=11999;
s.ExpandExtractionSearch(&a);assert(a.RequireAirfield);}
// An exhausted budget rejects every mandatory kind WITHOUT trying the authored fallback.
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;
Clock::now=0;Scheduler s;Site a;a.Kind=kind;s.m_Pending={&a};s.definition.Sites={&a};s.m_Attempts=48000;
assert(s.Tick()==Result::FAILED&&!a.PlacementResolved&&s.surveys==0&&a.Position[0]==11&&!s.m_Error.empty());}
{Clock::now=30001;Scheduler s;Site a;s.m_Pending={&a};assert(s.Tick()==Result::FAILED&&s.surveys==0);}
{Clock::now=120001;Scheduler s;Site a;s.m_Pending={&a};assert(s.Tick()==Result::FAILED&&s.surveys==0);}
// Optional sites can be removed only while preserving minimum objective/intel validity.
for(int kind:{Kind::OUTPOST,Kind::HOSPITAL,Kind::DEPOT}){
Clock::now=0;Scheduler s;Site a,b;a.Kind=kind;s.m_Pending={&a,&b};s.definition.Sites={&a,&b};s.m_Attempts=12000;
Clock::now=0;Scheduler s;Site a,b;a.Kind=kind;s.m_Pending={&a,&b};s.definition.Sites={&a,&b};s.m_Attempts=48000;
while(s.Tick()==Result::PENDING){}assert(s.m_Result==Result::READY&&!a.PlacementResolved&&b.PlacementResolved&&s.definition.Sites.Count()==1);}
{Clock::now=0;Scheduler s;Site a;a.Kind=Kind::DEPOT;s.m_Pending={&a};s.definition.Sites={&a};s.definition.required=&a;s.m_Attempts=12000;
{Clock::now=0;Scheduler s;Site a;a.Kind=Kind::DEPOT;s.m_Pending={&a};s.definition.Sites={&a};s.definition.required=&a;s.m_Attempts=48000;
assert(s.Tick()==Result::FAILED&&s.definition.Sites.Count()==1);}
{Clock::now=0;Scheduler s;s.definition.valid=false;assert(s.Tick()==Result::FAILED);}
}
@@ -94,12 +109,13 @@ nav = body(base/'ERR_TerrainSites.c', 'protected bool Navigable(')
nav = nav.replace('protected ', '').replace('NavmeshWorldComponent mesh', 'Mesh* mesh').replace('mesh.', 'mesh->')
run(r'''
#include <cassert>
#include <string>
struct vector{};
struct Mesh{bool loaded=true,loadable=false,reachable=false;int requests=0,queries=0;bool IsTileLoaded(vector){return loaded;}bool LoadTileIn(vector){requests++;return loadable;}bool GetReachablePoint(vector,int,vector&){queries++;return reachable;}} mesh;
struct World{bool available=true;Mesh* GetNavmeshWorldComponent(const char*){return available?&mesh:nullptr;}} world;
struct Game{World& GetAIWorld(){return world;}} game;
Game& GetGame(){return game;}
struct Survey{bool m_WaitingNav=false;
struct Survey{std::string m_NavigationReason;bool m_WaitingNav=false;
'''+nav+r'''
};
int main(){
@@ -115,7 +131,7 @@ print('PASS: actual navigation readiness supports static meshes, yields only for
# Execute sampling and clearance selection: an empty airport list must never be
# indexed, and generic helicopter pads retain geometry/nav/physical validation.
placement = body(base/'ERR_TerrainSites.c', 'protected vector SamplePosition(') + '\n' + body(base/'ERR_TerrainSites.c', 'protected bool Survey(')
placement = body(base/'ERR_TerrainSites.c', 'static bool Bounds(') + '\n' + body(base/'ERR_TerrainSites.c', 'static float PrisonAirportBuffer(') + '\n' + body(base/'ERR_TerrainSites.c', 'protected vector SamplePosition(') + '\n' + body(base/'ERR_TerrainSites.c', 'protected bool Survey(')
for before, after in [
('protected ', ''), ('ERR_SiteDefinition site', 'Site* site'), ('site.', 'site->'),
('out float floor', 'float& floor'), ('m_Airports.', 'm_Airports->'),
@@ -126,6 +142,7 @@ for before, after in [
run(r'''
#include <cassert>
#include <cmath>
#include <string>
#include <vector>
struct vector {float a[3];float& operator[](int i){return a[i];}};
vector Vector(float x,float y,float z){return {{x,y,z}};}
@@ -136,6 +153,7 @@ namespace Math {
int indexed=0;const float PI2=6.283185;
float RandomFloat(float a,float b){return (a+b)/2;}
int RandomInt(int a,int b){assert(b>a);indexed++;return a;}
float Min(float a,float b){return a<b?a:b;}float Clamp(float v,float lo,float hi){return v<lo?lo:(v>hi?hi:v);}
float Cos(float a){return std::cos(a);}float Sin(float a){return std::sin(a);}
}
namespace ERR_Actors {vector Ground(vector p){return p;}}
@@ -149,11 +167,13 @@ namespace ERR_RescueLanding {bool fits=true;int checks=0;bool Fits(vector){check
template<class T>struct Array:std::vector<T>{bool IsEmpty(){return this->empty();}int Count(){return this->size();}};
struct Airports {Array<vector> m_Positions;};
struct Terrain {
bool geometry=true,flat=true;
bool Site(::Site*,vector,float,float& floor){floor=42;return geometry;}
std::string Rejection(){return "terrain";}
bool geometry=true,flat=true;float airportBuffer=0;
bool Site(::Site*,vector,float,float& floor,float buffer){airportBuffer=buffer;floor=42;return geometry;}
bool Clear(vector,float,float,float,float&){return flat;}
};
struct Placement {
std::string m_NavigationReason="navigation";void RecordRejection(std::string){}
Airports airports;Airports* m_Airports=&airports;Terrain m_Terrain;
vector m_Low=Vector(1000,0,-5000),m_High=Vector(9000,0,3000);
int m_GeometryRejected=0,m_NavigationRejected=0,m_PadRejected=0;
@@ -162,12 +182,21 @@ struct Placement {
};
int main(){
Placement s;Site site;float floor=0;
assert(s.Bounds(Vector(-500,0,-500),Vector(500,0,500)));
assert(!s.Bounds(Vector(0,0,0),Vector(999,0,2000)));
assert(!s.Bounds(Vector(0,0,0),Vector(2000,0,999)));
assert(!s.Bounds(Vector(0,0,0),Vector(100001,0,2000)));
assert(s.PrisonAirportBuffer(Vector(0,0,0),Vector(2000,0,2000))==300);
assert(s.PrisonAirportBuffer(Vector(-100,0,-100),Vector(900,0,900))==200);
assert(s.PrisonAirportBuffer(Vector(0,0,0),Vector(20000,0,20000))==1500);
assert(s.PrisonAirportBuffer(Vector(0,0,0),Vector(2000,0,20000))==300);
auto p=s.SamplePosition(&site);assert(p[0]==5000&&p[2]==-1000&&Math::indexed==0);
s.airports.m_Positions.push_back(Vector(2000,0,3000));
s.airports.m_Positions.push_back(Vector(2000,0,3000));site.RequireAirfield=true;
p=s.SamplePosition(&site);assert(Math::indexed==1&&std::abs(p[0]-1775)<.01&&std::abs(p[2]-3000)<.01);
site.Kind=Kind::PRISON;p=s.SamplePosition(&site);assert(p[0]==5000&&p[2]==-1000&&Math::indexed==1);
site.Kind=Kind::EXTRACTION;
assert(s.Survey(&site,p,0,floor)&&floor==42);assert(ERR_AirfieldPlacement::groundChecks==1&&ERR_AirfieldPlacement::airportChecks==0);
site.Kind=Kind::EXTRACTION;site.RequireAirfield=false;
p=s.SamplePosition(&site);assert(p[0]==5000&&p[2]==-1000&&Math::indexed==1);
assert(s.Survey(&site,p,0,floor)&&floor==42);assert(ERR_AirfieldPlacement::groundChecks==1&&ERR_AirfieldPlacement::airportChecks==0&&s.m_Terrain.airportBuffer==1200);
ERR_AirfieldPlacement::ground=false;assert(!s.Survey(&site,p,0,floor)&&s.m_PadRejected==1);
site.RequireAirfield=true;ERR_AirfieldPlacement::ground=true;
assert(!s.Survey(&site,p,0,floor));assert(ERR_AirfieldPlacement::airportChecks==1&&ERR_AirfieldPlacement::groundChecks==2);