107 lines
7.0 KiB
Python
107 lines
7.0 KiB
Python
"""Execute the shipped checkpoint layout across road widths and rotations."""
|
||
from policy_harness import ROOT, body, run
|
||
import re
|
||
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_Roadblocks.c'
|
||
s='\n'.join(body(p,x) for x in ('protected void Layout(', 'protected void AddProp(', 'protected vector GuardPosition('))
|
||
s=s.replace('protected ', '').replace('out ', '').replace('array<ref ERR_RoadblockProp> covers', 'Array<Prop*>& covers')
|
||
s=s.replace('ResourceName ', 'string ').replace('vector ', 'Vec ').replace('ERR_Actors.Ground','Ground').replace('new ERR_RoadblockProp','new Prop')
|
||
shoulder = re.search(r'm_Shoulder = ([^;]+);', body(p, 'protected bool Candidate('))[1]
|
||
shoulder = shoulder.replace('road.GetWidth()', 'width').replace('Math.Max', 'std::max')
|
||
run(r'''
|
||
#include <cassert>
|
||
#include <cmath>
|
||
#include <string>
|
||
#include <vector>
|
||
#include <map>
|
||
#include <algorithm>
|
||
using string=std::string;
|
||
struct Vec{double x=0,y=0,z=0;Vec operator+(Vec b)const{return{x+b.x,y+b.y,z+b.z};}Vec operator-(Vec b)const{return{x-b.x,y-b.y,z-b.z};}Vec operator*(double k)const{return{x*k,y*k,z*k};}};
|
||
double dot(Vec a,Vec b){return a.x*b.x+a.y*b.y+a.z*b.z;}
|
||
Vec Ground(Vec p){p.y=50;return p;}
|
||
template<class T>struct Array:std::vector<T>{void Clear(){for(auto*p:*this)delete p;this->clear();}void Insert(T p){this->push_back(p);}};
|
||
struct Prop{string Prefab;Vec Position,Forward,Right;double HalfWidth,HalfDepth,Height;
|
||
Prop(string p,Vec v,Vec f,double w,double d,double h):Prefab(p),Position(v),Forward(f),Right({f.z,0,-f.x}),HalfWidth(w),HalfDepth(d),Height(h){}
|
||
};
|
||
struct Checkpoint{Vec m_Centre={1000,50,2000},m_Forward,m_Right;double m_Shoulder;bool m_Large=false;
|
||
string COVER="cover",BARRIER="barrier",OBSTACLE="obstacle",SIGN="sign",TABLE="table",SHELTER="shelter";
|
||
'''+s+r'''
|
||
};
|
||
int main(){
|
||
for(bool large:{false,true})for(double width:{4.,6.,10.,16.})for(int degrees=0;degrees<360;degrees+=15){
|
||
Checkpoint l;l.m_Large=large;double angle=degrees*3.141592653589793/180;
|
||
l.m_Forward={std::sin(angle),0,std::cos(angle)};l.m_Right={std::cos(angle),0,-std::sin(angle)};
|
||
l.m_Shoulder='''+shoulder+r''';Array<Prop*>props;l.Layout(props);assert(props.size()==(large?14:8));
|
||
std::map<string,int>counts;std::vector<double>barriers;
|
||
for(auto*p:props){counts[p->Prefab]++;
|
||
Vec delta=p->Position-l.m_Centre;double across=dot(delta,l.m_Right),along=dot(delta,l.m_Forward);
|
||
// Whole footprints (not just origins) preserve a four-metre straight passage.
|
||
double reach=std::abs(dot(p->Right,l.m_Right))*p->HalfWidth+std::abs(dot(p->Forward,l.m_Right))*p->HalfDepth;
|
||
assert(std::abs(across)-reach>=2.-1e-8);
|
||
// Bound the compact footprint, including both road shoulders.
|
||
assert(std::abs(across)+reach <= std::max(width*.5+1.2,4.5)+1e-6);
|
||
double depth=std::abs(dot(p->Right,l.m_Forward))*p->HalfWidth+std::abs(dot(p->Forward,l.m_Forward))*p->HalfDepth;
|
||
assert(std::abs(along)+depth <= (large?21.4:8.9)+1e-6);
|
||
if(p->Prefab=="barrier")barriers.push_back(along);
|
||
if(p->Prefab=="sign")assert(dot(p->Forward,l.m_Forward)*along>0); // both approaches face outward
|
||
// Guard starting volumes cannot intersect any prop in the planned post.
|
||
for(int side:{-1,1})for(int row=0;row<(large?4:2);row++){
|
||
Vec guard=l.GuardPosition(side,row);
|
||
Vec rel=guard-p->Position;
|
||
assert(std::abs(dot(rel,p->Right))>p->HalfWidth+.6 || std::abs(dot(rel,p->Forward))>p->HalfDepth+.6);
|
||
}
|
||
}
|
||
assert(counts["cover"]==(large?8:4)&&counts["barrier"]==(large?4:2)&&counts["sign"]==2&&counts.size()==3);
|
||
assert(barriers.size()==(large?4:2));
|
||
if(!large)assert(std::abs(barriers[0]-barriers[1])>11.9);
|
||
// No physical prop footprint intersects another (separating axes).
|
||
for(size_t i=0;i<props.size();i++)for(size_t j=i+1;j<props.size();j++){
|
||
auto*a=props[i];auto*b=props[j];bool separate=false;
|
||
for(Vec axis:{a->Right,a->Forward,b->Right,b->Forward}){
|
||
double ar=std::abs(dot(a->Right,axis))*a->HalfWidth+std::abs(dot(a->Forward,axis))*a->HalfDepth;
|
||
double br=std::abs(dot(b->Right,axis))*b->HalfWidth+std::abs(dot(b->Forward,axis))*b->HalfDepth;
|
||
if(std::abs(dot(a->Position-b->Position,axis))>=ar+br)separate=true;
|
||
}
|
||
assert(separate);
|
||
}
|
||
props.Clear();
|
||
}
|
||
}
|
||
''')
|
||
print('PASS: both shipped checkpoint layouts preserve vehicle lane, opposing signs, staggered barriers, clear guard slots and separate props across 4–16m roads and all tested headings')
|
||
|
||
# Execute the actual clearance path: owned pieces are excluded only after
|
||
# checking guards; terrain and foreign obstructions remain authoritative.
|
||
s=body(p,'protected bool CoverClear(').replace('protected ', '').replace('ERR_RoadblockProp prop','Prop prop')
|
||
s=s.replace('vector ', 'Vec ').replace('foreach (IEntity builtEntity : m_BuildSpawned)', 'for (Node* builtEntity : m_BuildSpawned)')
|
||
s=s.replace('ChimeraCharacter.Cast(builtEntity)', 'builtEntity->character').replace('builtEntity.GetOrigin()', 'builtEntity->point')
|
||
s=s.replace('ERR_Actors.Ground','Ground').replace('Vector(', 'Vec(').replace('Math.AbsFloat', 'std::abs').replace('vector.Dot','dot')
|
||
s=s.replace('ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), point)', 'water').replace('GetGame().GetWorld().TraceMove(trace, null)', 'TraceMove(trace)')
|
||
s=s.replace('TraceBox trace = new TraceBox();','TraceBox trace;').replace('"0 0.15 0"','Vec(0,.15,0)').replace('"0 0.01 0"','Vec(0,.01,0)').replace('TraceFlags.', 'TraceFlags::')
|
||
run(r'''
|
||
#include <cassert>
|
||
#include <cmath>
|
||
#include <vector>
|
||
struct Vec {double v[3];Vec(double x=0,double y=0,double z=0):v{x,y,z}{}double& operator[](int i){return v[i];}Vec operator+(Vec b){return {v[0]+b[0],v[1]+b[1],v[2]+b[2]};}Vec operator-(Vec b){return {v[0]-b[0],v[1]-b[1],v[2]-b[2]};}Vec operator*(double n){return {v[0]*n,v[1]*n,v[2]*n};}};
|
||
double dot(Vec a,Vec b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];}
|
||
struct Node{bool character;Vec point;};
|
||
struct Prop{Vec Position,Right={1,0,0},Forward={0,0,1};double HalfWidth=1.9,HalfDepth=.7,Height=1;};
|
||
namespace TraceFlags{int WORLD=1,ENTS=2;}
|
||
struct TraceBox{Vec Start,End,Mins,Maxs;int Flags=0;std::vector<Node*>ExcludeArray;};
|
||
struct Clearance{
|
||
bool water=false,blocked=false;double slope=0;int traces=0;std::vector<Node*>m_BuildSpawned;
|
||
Vec Ground(Vec p){p[1]=slope*p[0];return p;}
|
||
double TraceMove(TraceBox trace){traces++;assert(trace.Flags==3&&trace.ExcludeArray==m_BuildSpawned);assert(std::abs(trace.Mins[0]+1.9)<1e-5&&std::abs(trace.Maxs[2]-.7)<1e-5);return blocked?0:1;}
|
||
'''+s+r'''
|
||
};
|
||
int main(){
|
||
Prop prop;Clearance c;assert(c.CoverClear(prop));
|
||
c.water=true;assert(!c.CoverClear(prop));c.water=false;
|
||
c.slope=.2;assert(!c.CoverClear(prop));c.slope=0;
|
||
c.blocked=true;assert(!c.CoverClear(prop));c.blocked=false;
|
||
Node ownProp{false,{0,0,0}},guard{true,{3,0,0}};c.m_BuildSpawned={&ownProp,&guard};assert(c.CoverClear(prop));
|
||
int traces=c.traces;guard.point={2,0,0};assert(!c.CoverClear(prop)&&c.traces==traces);
|
||
guard.point={0,0,1.4};assert(c.CoverClear(prop));guard.point={0,0,1};assert(!c.CoverClear(prop));
|
||
}
|
||
''')
|
||
print('PASS: actual prop clearance excludes owned geometry while retaining moved-guard, foreign-obstruction, water and terrain rejection')
|