Build realistic roadblocks with staggered barriers and guarded positions

This commit is contained in:
2026-10-15 12:39:04 +02:00
parent 7b13231f29
commit 3b920e0715
6 changed files with 267 additions and 40 deletions
+98
View File
@@ -0,0 +1,98 @@
"""Execute the shipped checkpoint layout across road widths and rotations."""
from policy_harness import ROOT, body, run
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('))
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')
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;
string COVER="cover",BARRIER="barrier",OBSTACLE="obstacle",SIGN="sign";
'''+s+r'''
};
int main(){
for(double width:{4.,6.,10.,16.})for(int degrees=0;degrees<360;degrees+=15){
Checkpoint l;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=std::max(width*.5+1.5,4.);Array<Prop*>props;l.Layout(props);assert(props.size()==10);
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);
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:{-1,1}){
Vec guard=l.m_Centre+l.m_Right*(side*(l.m_Shoulder+2.5))+l.m_Forward*(side*(row-1.5));
Vec rel=guard-p->Position;
assert(std::abs(dot(rel,p->Right))>p->HalfWidth+.45 || std::abs(dot(rel,p->Forward))>p->HalfDepth+.45);
}
}
assert(counts["cover"]==4&&counts["barrier"]==2&&counts["obstacle"]==2&&counts["sign"]==2);
assert(barriers.size()==2&&std::abs(barriers[0]-barriers[1])>15.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: shipped checkpoint layout preserves 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')