Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
91 lines
9.0 KiB
Python
91 lines
9.0 KiB
Python
"""Actual delivered-memory road selector, geometry and native-query boundaries."""
|
|
from pathlib import Path
|
|
import os,re
|
|
from policy_harness import ROOT,body,run
|
|
p=Path(os.environ.get('ERR_ROAD_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'))
|
|
source=p.read_text()
|
|
for forbidden in ('GetPlayer','GetControlledEntity','GetOrigin','Spawn(', 'AddWaypoint','SetOrigin','CallLater'):
|
|
assert forbidden not in source,forbidden
|
|
rules='\n'.join(body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c', x) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role(', 'static vector Direction(', 'static vector Objective('))
|
|
rules=rules.replace('Math.','Math::').replace('vector.','vector::').replace('ERR_HQPursuitRole.','ERR_HQPursuitRole::').replace('vector offset;','vector offset{};')
|
|
s=re.sub(r'//[^\n]*','',source).replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept')
|
|
s=s.replace('out vector','vector&').replace('out float','float&').replace('array<vector>','Array<vector>').replace('array<BaseRoad>','Array<BaseRoad*>').replace('foreach (','for (')
|
|
s=s.replace('SCR_AIWorld.Cast(GetGame().GetAIWorld())','GetGame()->GetAIWorld()')
|
|
for name in ('ERR_HQBelief','SCR_AIWorld','BaseRoad','TraceBox'):
|
|
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
|
|
for name in ('belief','world','road','body'):
|
|
s=s.replace(name+'.',name+'->')
|
|
s=s.replace('GetGame().','GetGame()->').replace('GetRoadNetworkManager().','GetRoadNetworkManager()->').replace('GetWorld().','GetWorld()->')
|
|
for name in ('ERR_ForceRules','ERR_HQPursuitRules','ERR_HQPursuitRole','ERR_HQMemoryKind','vector','Math','ERR_Actors','ChimeraWorldUtils','TraceFlags','Replication'):
|
|
s=s.replace(name+'.',name+'::')
|
|
s=s.replace('null','nullptr')
|
|
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('\n}\n','\n};\n')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <limits>
|
|
#include <vector>
|
|
#include <string>
|
|
enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};enum class ERR_HQMemoryKind{SIGHTING,DISTRESS,BODY};
|
|
struct Math{static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}static float AbsFloat(float x){return std::abs(x);}};
|
|
struct vector{float x=0,y=0,z=0;vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}vector(const char*s){sscanf(s,"%f %f %f",&x,&y,&z);}float&operator[](int i){return i==0?x:i==1?y:z;}float operator[](int i)const{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};}bool operator==(vector b)const{return x==b.x&&y==b.y&&z==b.z;}
|
|
float Length()const{return std::sqrt(x*x+y*y+z*z);}vector Normalized()const{float n=Length();return{x/n,y/n,z/n};}static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}static vector Lerp(vector a,vector b,float n){return a+(b-a)*n;}static const vector Zero;};
|
|
const vector vector::Zero{};vector Vector(float a,float b,float c){return{a,b,c};}
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}void Insert(T v){this->push_back(v);}explicit operator bool(){return true;}};
|
|
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector p){return Finite(p.x)&&Finite(p.y)&&Finite(p.z);}};
|
|
struct ERR_HQPursuitRules{''' + rules + r'''};
|
|
struct ERR_HQBelief{ERR_HQMemoryKind Kind=ERR_HQMemoryKind::SIGHTING;int SampleCount=2,MessageId=1;float ObservedAt=5000,LastDeliveredAt=7000,Confidence=.9,Radius=40;std::string TravelMode="on foot";vector PredictedPosition={1000,10,1000},Velocity={0,0,4};bool HasVelocity=true;};
|
|
struct TraceFlags{static constexpr int WORLD=1,ENTS=2;};struct TraceBox{vector Start,End,Mins,Maxs;int Flags=0;};
|
|
struct NativeWorld{int traces=0;float fraction=1;float TraceMove(TraceBox* t,void*){traces++;assert(t->Flags==3&&t->Mins.y==-.9f);return fraction;}};
|
|
struct BaseRoad{float width=4;Array<vector>points={{900,10,1060},{1100,10,1060}};float GetWidth(){return width;}void GetPoints(Array<vector>&p){p=points;}};
|
|
struct RoadManager{Array<BaseRoad*>roads;int queries=0;void GetRoadsInAABB(vector low,vector high,Array<BaseRoad*>&out){queries++;assert(high.x-low.x==200&&high.z-low.z==200&&high.y-low.y==2000);out=roads;}};
|
|
struct SCR_AIWorld{RoadManager manager;bool available=true;RoadManager*GetRoadNetworkManager(){return available?&manager:nullptr;}};
|
|
struct Game{NativeWorld world;SCR_AIWorld ai;NativeWorld*GetWorld(){return &world;}SCR_AIWorld*GetAIWorld(){return &ai;}}game;Game*GetGame(){return &game;}
|
|
struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
|
|
struct ERR_Actors{static inline bool steep=false;static vector Ground(vector p){p.y=10;if(steep&&p.x>1000)p.y=11;return p;}};
|
|
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
|
|
''' + s + r'''
|
|
int main(){
|
|
using Road=ERR_HQRoadIntercept;ERR_HQBelief good;
|
|
assert(Road::Eligible(&good,10000));
|
|
auto vehicle=good;vehicle.TravelMode="vehicle";assert(Road::Eligible(&vehicle,10000));
|
|
vehicle.TravelMode="in vehicle";assert(Road::Eligible(&vehicle,10000));
|
|
for(int invalid=0;invalid<12;invalid++){
|
|
auto bad=good;
|
|
if(invalid==0){bad.Kind=ERR_HQMemoryKind::DISTRESS;}if(invalid==1){bad.SampleCount=1;}if(invalid==2){bad.MessageId=0;}
|
|
if(invalid==3){bad.LastDeliveredAt=10001;}if(invalid==4){bad.LastDeliveredAt=4999;}if(invalid==5){bad.TravelMode="aircraft";}
|
|
if(invalid==6){bad.HasVelocity=false;}if(invalid==7){bad.Velocity={0,0,1.99f};}if(invalid==8){bad.Confidence=.649f;}
|
|
if(invalid==9){bad.Radius=151;}if(invalid==10){bad.ObservedAt=-35001;}if(invalid==11){bad.Velocity.x=NAN;}
|
|
assert(!Road::Eligible(&bad,10000));
|
|
}
|
|
Array<vector> reserved;vector origin={1000,10,1000},velocity={0,0,4},seed={1000,10,1060},result;float score;
|
|
assert(Road::FromPolyline({{900,0,1060},{1100,0,1060}},origin,velocity,seed,reserved,result,score)&&result.x==1000&&result.z==1060&&score==0);
|
|
assert(!Road::Ahead(origin,velocity,{1000,10,1039.99f}));assert(Road::Ahead(origin,velocity,{1000,10,1040}));assert(!Road::Ahead(origin,velocity,{1000,10,1250.01f}));assert(!Road::Ahead(origin,velocity,{1000,10,940}));
|
|
reserved.Insert({1000,10,1060});assert(!Road::FromPolyline({{900,0,1060},{1100,0,1060}},origin,velocity,seed,reserved,result,score)&&result==vector::Zero);reserved.clear();
|
|
// Hairpin gaps are never joined by an offroad chord toward the desired center.
|
|
assert(Road::FromPolyline({{900,0,1040},{1100,0,1040},{1100,0,1160},{900,0,1160}},origin,velocity,{1000,10,1100},reserved,result,score));assert(result.z==1040||result.z==1160||result.x==1100);
|
|
assert(!Road::FromPolyline({{900,0,1060},{NAN,0,1060}},origin,velocity,seed,reserved,result,score)&&result==vector::Zero);
|
|
Array<vector> oversized(1025,seed);assert(!Road::FromPolyline(oversized,origin,velocity,seed,reserved,result,score));
|
|
BaseRoad road;game.ai.manager.roads={&road};assert(Road::Select(&good,10000,reserved,result)&&result==vector(1000,10,1060)&&game.world.traces==1);
|
|
// An elevated bridge must not be silently projected onto terrain beneath the actual road.
|
|
road.points={{900,20,1060},{1100,20,1060}};int groundTraces=game.world.traces;
|
|
assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero&&game.world.traces==groundTraces);
|
|
road.points={{900,12,1060},{1100,12,1060}};assert(Road::Select(&good,10000,reserved,result));
|
|
road.points={{900,10,1060},{1100,10,1060}};
|
|
// Refusal never leaves a seemingly usable destination from an earlier successful call.
|
|
game.world.fraction=.5;assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero);game.world.fraction=1;
|
|
ChimeraWorldUtils::water=true;assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero);ChimeraWorldUtils::water=false;
|
|
ERR_Actors::steep=true;assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero);ERR_Actors::steep=false;
|
|
road.width=3.99;assert(!Road::Select(&good,10000,reserved,result));road.width=4;
|
|
game.ai.manager.roads.resize(129,&road);assert(!Road::Select(&good,10000,reserved,result));game.ai.manager.roads={&road};
|
|
BaseRoad dense;for(int i=0;i<1024;i++)dense.points.push_back({float(i),0,1060});dense.points.erase(dense.points.begin(),dense.points.begin()+2);
|
|
game.ai.manager.roads={&dense,&dense,&dense,&dense,&dense};int before=game.world.traces;assert(!Road::Select(&good,10000,reserved,result)&&game.world.traces==before&&result==vector::Zero);
|
|
game.ai.manager.roads={&road};auto stale=good;stale.ObservedAt=-35001;before=game.ai.manager.queries;assert(!Road::Select(&stale,10000,reserved,result)&&game.ai.manager.queries==before);
|
|
game.world.fraction=NAN;assert(!Road::Select(&good,10000,reserved,result));
|
|
}
|
|
''')
|
|
print('PASS: actual delivered road eligibility, ahead projection without chords, reservations, stock road query bounds, terrain/water/obstruction refusal and clean outputs')
|