Files
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

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')