Files
EscapistsReintroduced/tests/test_hq_road_intercept.py
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

89 lines
8.9 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));
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')