Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
83 lines
8.8 KiB
Python
83 lines
8.8 KiB
Python
"""Execute actual footprint sampling and native query callback policy with API doubles."""
|
|
import re,os
|
|
from policy_harness import ROOT,body,run
|
|
from pathlib import Path
|
|
p=Path(os.environ.get('ERR_CONVOY_PLACEMENT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyPlacement.c'))
|
|
code='\n'.join(body(p,s) for s in ('protected bool Occupancy(', 'static bool Terrain(', 'bool Clear(', 'bool ClearFootprint('))
|
|
code=code.replace('null','nullptr').replace('protected ','').replace('IEntity entity','IEntity* entity').replace('entity.GetVObject()', 'entity->GetVObject()')
|
|
code=code.replace('ERR_InterceptVehicleFootprint footprint','ERR_InterceptVehicleFootprint* footprint').replace('footprint.','footprint->')
|
|
code=code.replace('TraceBox volume', 'TraceBox* volume').replace('volume.', 'volume->').replace('\"0 0.01 0\"', 'Vector(0,.01,0)')
|
|
for cls in ('Math','ERR_ForceRules','ERR_Actors','ChimeraWorldUtils','TraceFlags'):code=code.replace(cls+'.',cls+'::')
|
|
code=code.replace('GetGame().GetWorld().','GetGame()->GetWorld()->').replace('GetGame().GetWorld()', 'GetGame()->GetWorld()')
|
|
code=code.replace('GetGame().', 'GetGame()->')
|
|
code=code.replace('low, high, Occupancy','low, high, [this](IEntity* entity){return Occupancy(entity);}')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <vector>
|
|
#include <string>
|
|
struct vector{float x=0,y=0,z=0;float& operator[](int i){return i==0?x:i==1?y:z;}vector operator+(vector b)const{return{x+b.x,y+b.y,z+b.z};}vector operator-(vector b)const{return{x-b.x,y-b.y,z-b.z};}vector operator*(float n)const{return{x*n,y*n,z*n};}};
|
|
vector Vector(float x,float y,float z){return{x,y,z};}
|
|
namespace Math{float Ceil(float n){return std::ceil(n);}float DEG2RAD=3.141592653589793/180;float Sin(float n){return std::sin(n);}float Cos(float n){return std::cos(n);}float Min(float a,float b){return std::min(a,b);}float Max(float a,float b){return std::max(a,b);}}
|
|
namespace ERR_ForceRules{bool Finite(float n){return std::isfinite(n);}bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.y)&&Finite(v.z);}}
|
|
struct ERR_InterceptVehicleFootprint{bool valid=true;float radius=4,height=5;bool Valid(){return valid;}float Radius(){return radius;}float Height(){return height;}};
|
|
struct IEntity{bool visible=false;bool GetVObject(){return visible;}};
|
|
struct TraceBox {vector Start,End,Mins,Maxs;int Flags=0;IEntity* TraceEnt=nullptr;};
|
|
namespace TraceFlags{constexpr int ENTS=1,OCEAN=2,WORLD=4;}
|
|
struct World{int traces=0;float fraction=1;IEntity* hit=nullptr;TraceBox last;float TraceMove(TraceBox* volume,void*){traces++;last=*volume;volume->TraceEnt=hit;return fraction;}int queries=0,visits=0;vector low,high;std::vector<IEntity*> entities;template<class C>void QueryEntitiesByAABB(vector a,vector b,C callback){queries++;low=a;high=b;for(auto e:entities){visits++;if(!callback(e))break;}}};
|
|
struct Game{World world;World* GetWorld(){return &world;}}game;bool gameAvailable=true;Game* GetGame(){return gameAvailable?&game:nullptr;}
|
|
namespace ERR_Actors{int grounds=0;float elevation=10,slope=0;bool invalid=false;vector Ground(vector v){grounds++;v.y=invalid?NAN:elevation+slope*v.x;return v;}}
|
|
namespace ChimeraWorldUtils{int waters=0;bool water=false;bool TryGetWaterSurfaceSimple(World*,vector){waters++;return water;}}
|
|
struct Placement{float m_LastTrace=-1;std::string m_LastReason;float m_LastLowest=0,m_LastHighest=0;IEntity* m_FirstObstruction=nullptr;bool m_Obstructed=false;int m_Visited=0;'''+code+r'''};
|
|
void reset(){game.world=World();ERR_Actors::grounds=0;ERR_Actors::elevation=10;ERR_Actors::slope=0;ERR_Actors::invalid=false;ChimeraWorldUtils::waters=0;ChimeraWorldUtils::water=false;}
|
|
bool near(float a,float b){return std::abs(a-b)<.001;}
|
|
int main(){
|
|
Placement p;reset();assert(p.Clear({0,10,0},0));assert(ERR_Actors::grounds==9&&ChimeraWorldUtils::waters==9&&game.world.queries==1);
|
|
assert(near(game.world.low.x,-2)&&near(game.world.high.x,2)&&near(game.world.low.z,-4.5)&&near(game.world.high.z,4.5));assert(near(game.world.low.y,10.05)&&near(game.world.high.y,13.5));
|
|
assert(game.world.traces==1&&game.world.last.Flags==(TraceFlags::ENTS|TraceFlags::OCEAN));
|
|
assert(near(game.world.last.Start.y+game.world.last.Mins.y,10.05)&&near(game.world.last.Start.y+game.world.last.Maxs.y,13.5));
|
|
assert(near(game.world.last.Mins.x,-2)&&near(game.world.last.Maxs.x,2)&&near(game.world.last.Mins.z,-4.5)&&near(game.world.last.Maxs.z,4.5));
|
|
assert(game.world.last.End.y>game.world.last.Start.y&&near(game.world.last.End.y-game.world.last.Start.y,.01));
|
|
reset();assert(p.Clear({0,10,0},90));assert(near(game.world.low.x,-4.5)&&near(game.world.high.x,4.5)&&near(game.world.low.z,-2)&&near(game.world.high.z,2));
|
|
reset();assert(!p.Clear({NAN,10,0},0)&&game.world.queries==0&&ERR_Actors::grounds==0);assert(!p.Clear({0,10,0},NAN));
|
|
reset();ERR_Actors::slope=.2;assert(!p.Clear({0,10,0},0)&&game.world.queries==0); // 0.8m cross-footprint rise.
|
|
reset();ERR_Actors::slope=.2;assert(p.Clear({0,10,0},0,2.5)); // already physical driver may stage on this gentle slope
|
|
reset();ERR_Actors::slope=.7;assert(!p.Clear({0,10,0},0,2.5)); // 2.8 m relief still rejected
|
|
reset();assert(!p.Clear({0,10,0},0,NAN));assert(!p.Clear({0,10,0},0,3));
|
|
reset();ChimeraWorldUtils::water=true;assert(!p.Clear({0,10,0},0,2.5));
|
|
reset();game.world.fraction=0;assert(!p.Clear({0,10,0},0,2.5));
|
|
reset();ERR_Actors::elevation=.29;assert(!p.Clear({0,.29,0},0)&&game.world.queries==0);
|
|
reset();assert(!p.Clear({0,10.31,0},0)&&game.world.queries==0);
|
|
reset();ChimeraWorldUtils::water=true;assert(!p.Clear({0,10,0},0)&&game.world.queries==0&&ERR_Actors::grounds==1);
|
|
reset();ERR_Actors::invalid=true;assert(!p.Clear({0,10,0},0)&&game.world.queries==0);
|
|
IEntity abstract,obstacle;obstacle.visible=true;
|
|
reset();game.world.entities={&abstract,&obstacle,&abstract};assert(p.Clear({0,10,0},0)&&game.world.visits==2&&!p.m_Obstructed&&!p.m_FirstObstruction&&p.m_LastReason=="clear");
|
|
// Broad tree/cable bounds overlap, but their actual low geometry does not: accept.
|
|
assert(game.world.traces==1&&p.m_LastTrace==1);
|
|
// Actual geometry refuses even when the broad diagnostic callback found only abstract entities.
|
|
reset();game.world.entities={&abstract};game.world.fraction=.4;game.world.hit=&obstacle;
|
|
assert(!p.Clear({0,10,0},0)&&p.m_Obstructed&&p.m_FirstObstruction==&obstacle&&p.m_LastReason=="native filled volume collision");
|
|
for(float fraction:{0.f,.999f,NAN,INFINITY}){reset();game.world.fraction=fraction;assert(!p.Clear({0,10,0},0));}
|
|
// New query resets previous obstruction. Abstract logic entities do not block.
|
|
reset();game.world.entities={nullptr,&abstract};assert(p.Clear({0,10,0},0)&&game.world.visits==2&&!p.m_Obstructed&&!p.m_FirstObstruction&&p.m_LastReason=="clear");
|
|
reset();game.world.entities.assign(128,&abstract);assert(p.Clear({0,10,0},0)&&game.world.visits==128);
|
|
reset();game.world.entities.assign(130,&abstract);assert(p.Clear({0,10,0},0)&&game.world.visits==129&&game.world.traces==1&&p.m_LastReason=="clear");
|
|
assert(Placement::Terrain(10,9.7,10.4));assert(!Placement::Terrain(10,9.7,10.451));assert(!Placement::Terrain(10,10.1,10));assert(!Placement::Terrain(10,NAN,10));
|
|
// Execute new measured square while all legacy Clear assertions above remain.
|
|
ERR_InterceptVehicleFootprint footprint;reset();gameAvailable=false;assert(!p.ClearFootprint({0,10,0},0,&footprint));gameAvailable=true;assert(p.ClearFootprint({0,10,0},0,&footprint));
|
|
assert(ERR_Actors::grounds==25&&ChimeraWorldUtils::waters==25&&game.world.traces==1);
|
|
assert(near(game.world.low.x,-4)&&near(game.world.high.x,4)&&near(game.world.low.z,-4)&&near(game.world.high.z,4));
|
|
assert(near(game.world.low.y,10.05)&&near(game.world.high.y,15));
|
|
reset();assert(p.ClearFootprint({0,10,0},45,&footprint));assert(near(game.world.high.x,4*std::sqrt(2.f))&&near(game.world.high.z,4*std::sqrt(2.f)));
|
|
reset();footprint.radius=19.5;footprint.height=14;assert(p.ClearFootprint({0,10,0},0,&footprint));assert(ERR_Actors::grounds==441&&near(game.world.high.y,24));
|
|
reset();footprint.radius=4;footprint.height=5;footprint.valid=false;assert(!p.ClearFootprint({0,10,0},0,&footprint)&&ERR_Actors::grounds==0);footprint.valid=true;assert(!p.ClearFootprint({0,10,0},0,nullptr));
|
|
reset();ChimeraWorldUtils::water=true;assert(!p.ClearFootprint({0,10,0},0,&footprint)&&game.world.traces==0);
|
|
reset();ERR_Actors::invalid=true;assert(!p.ClearFootprint({0,10,0},0,&footprint)&&game.world.traces==0);
|
|
reset();ERR_Actors::slope=.1;assert(!p.ClearFootprint({0,10,0},0,&footprint)&&game.world.traces==0);
|
|
for(float fraction:{0.f,.99f,NAN,INFINITY}){reset();game.world.fraction=fraction;assert(!p.ClearFootprint({0,10,0},0,&footprint));}
|
|
|
|
}
|
|
''')
|
|
print('PASS: actual new measured ClearFootprint 25/441 sample grids, measured extents/height, water/invalid world/terrain/trace refusal plus legacy filled rotated-footprint TraceBox and nine terrain/water samples; broad tree/cable bounds alone accepted, actual collision/nonfinite trace refused, nonzero sweep and ENTS/OCEAN flags, bounded diagnostic query and reset')
|