Capture current mission, AI, map and administration development

Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
2026-10-13 22:52:53 +02:00
parent a951a96edc
commit 6a667d8deb
8104 changed files with 2124945 additions and 2061 deletions
@@ -0,0 +1,89 @@
// Conservative native volume check; no scenery removal or alternate placement.
class ERR_ConvoyPlacement
{
protected bool m_Obstructed;
protected int m_Visited;
protected string m_LastReason;
protected float m_LastLowest;
protected float m_LastHighest;
protected IEntity m_FirstObstruction;
protected float m_LastTrace = -1;
float LastTraceFraction() { return m_LastTrace; }
string Diagnostic()
{
string prefab = "none";
vector position;
if (m_FirstObstruction)
{
prefab = "no prefab";
if (m_FirstObstruction.GetPrefabData()) prefab = m_FirstObstruction.GetPrefabData().GetPrefabName();
position = m_FirstObstruction.GetOrigin();
}
return string.Format("%1; terrainY=%2..%3 span=%4; visited=%5; first=%6 at %7; trace=%8",
m_LastReason, m_LastLowest, m_LastHighest, m_LastHighest - m_LastLowest, m_Visited, prefab, position, m_LastTrace);
}
protected bool Occupancy(IEntity entity)
{
m_Visited++;
if (m_Visited > 128) { m_Obstructed = true; m_LastReason = "native query visit cap"; }
else if (entity && entity.GetVObject())
{
m_Obstructed = true; m_LastReason = "native VObject volume overlap"; m_FirstObstruction = entity;
}
return !m_Obstructed;
}
static bool Terrain(float origin, float lowest, float highest)
{
return ERR_ForceRules.Finite(origin) && ERR_ForceRules.Finite(lowest) && ERR_ForceRules.Finite(highest)
&& lowest > 0.3 && highest >= lowest && highest - lowest <= 0.75 && origin >= lowest - 0.3 && origin <= highest + 0.3;
}
bool Clear(vector position, float heading)
{
m_LastReason = "invalid origin or heading"; m_LastLowest = 1e20; m_LastHighest = -1e20;
m_FirstObstruction = null; m_Visited = 0; m_LastTrace = -1;
if (!ERR_ForceRules.FiniteVector(position) || !ERR_ForceRules.Finite(heading)) return false;
float radians = heading * Math.DEG2RAD;
vector forward = Vector(Math.Sin(radians), 0, Math.Cos(radians));
vector right = Vector(forward[2], 0, -forward[0]);
float lowest = 1e20, highest = -1e20;
vector low = position, high = position;
for (int x = -1; x <= 1; x++)
{
for (int z = -1; z <= 1; z++)
{
vector sample = ERR_Actors.Ground(position + right * (x * 2.0) + forward * (z * 4.5));
if (!ERR_ForceRules.FiniteVector(sample)) { m_LastReason = "nonfinite terrain sample"; return false; }
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), sample))
{ m_LastReason = "water at footprint sample"; return false; }
lowest = Math.Min(lowest, sample[1]); highest = Math.Max(highest, sample[1]);
low[0] = Math.Min(low[0], sample[0]); low[2] = Math.Min(low[2], sample[2]);
high[0] = Math.Max(high[0], sample[0]); high[2] = Math.Max(high[2], sample[2]);
}
}
m_LastLowest = lowest; m_LastHighest = highest;
if (!Terrain(position[1], lowest, highest))
{ m_LastReason = "terrain origin/height/span check"; return false; }
low[1] = lowest + 0.05; high[1] = highest + 3.5;
m_Obstructed = false; m_Visited = 0;
GetGame().GetWorld().QueryEntitiesByAABB(low, high, Occupancy);
// Entity bounding boxes can include distant branches or overhead wires.
// Their overlap is diagnostic only; the filled native volume decides collision.
vector center = (low + high) * 0.5;
TraceBox volume = new TraceBox();
volume.Start = center; volume.End = center + "0 0.01 0";
volume.Mins = low - center; volume.Maxs = high - center;
// Nine terrain/water samples already qualify the supporting ground below us.
volume.Flags = TraceFlags.ENTS | TraceFlags.OCEAN;
m_LastTrace = GetGame().GetWorld().TraceMove(volume, null);
m_Obstructed = !ERR_ForceRules.Finite(m_LastTrace) || m_LastTrace < 1;
m_FirstObstruction = null;
if (m_Obstructed)
{
m_FirstObstruction = volume.TraceEnt;
m_LastReason = "native filled volume collision";
if (!ERR_ForceRules.Finite(m_LastTrace)) m_LastReason = "invalid native trace fraction";
}
else m_LastReason = "clear";
return !m_Obstructed;
}
}
@@ -0,0 +1,60 @@
"""Execute actual footprint sampling and native query callback policy with API doubles."""
import re
from policy_harness import ROOT,body,run
p=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('))
code=code.replace('null','nullptr').replace('protected ','').replace('IEntity entity','IEntity* entity').replace('entity.GetVObject()', 'entity->GetVObject()')
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('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 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 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;Game* GetGame(){return &game;}
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::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));
}
''')
print('PASS: actual 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')
@@ -0,0 +1,89 @@
// Conservative native volume check; no scenery removal or alternate placement.
class ERR_ConvoyPlacement
{
protected bool m_Obstructed;
protected int m_Visited;
protected string m_LastReason;
protected float m_LastLowest;
protected float m_LastHighest;
protected IEntity m_FirstObstruction;
protected float m_LastTrace = -1;
float LastTraceFraction() { return m_LastTrace; }
string Diagnostic()
{
string prefab = "none";
vector position;
if (m_FirstObstruction)
{
prefab = "no prefab";
if (m_FirstObstruction.GetPrefabData()) prefab = m_FirstObstruction.GetPrefabData().GetPrefabName();
position = m_FirstObstruction.GetOrigin();
}
return string.Format("%1; terrainY=%2..%3 span=%4; visited=%5; first=%6 at %7; trace=%8",
m_LastReason, m_LastLowest, m_LastHighest, m_LastHighest - m_LastLowest, m_Visited, prefab, position, m_LastTrace);
}
protected bool Occupancy(IEntity entity)
{
m_Visited++;
if (m_Visited > 128) { m_Obstructed = true; m_LastReason = "native query visit cap"; }
else if (entity && entity.GetVObject())
{
m_Obstructed = true; m_LastReason = "native VObject volume overlap"; m_FirstObstruction = entity;
}
return !m_Obstructed;
}
static bool Terrain(float origin, float lowest, float highest)
{
return ERR_ForceRules.Finite(origin) && ERR_ForceRules.Finite(lowest) && ERR_ForceRules.Finite(highest)
&& lowest > 0.3 && highest >= lowest && highest - lowest <= 0.75 && origin >= lowest - 0.3 && origin <= highest + 0.3;
}
bool Clear(vector position, float heading)
{
m_LastReason = "invalid origin or heading"; m_LastLowest = 1e20; m_LastHighest = -1e20;
m_FirstObstruction = null; m_Visited = 0; m_LastTrace = -1;
if (!ERR_ForceRules.FiniteVector(position) || !ERR_ForceRules.Finite(heading)) return false;
float radians = heading * Math.DEG2RAD;
vector forward = Vector(Math.Sin(radians), 0, Math.Cos(radians));
vector right = Vector(forward[2], 0, -forward[0]);
float lowest = 1e20, highest = -1e20;
vector low = position, high = position;
for (int x = -1; x <= 1; x++)
{
for (int z = -1; z <= 1; z++)
{
vector sample = ERR_Actors.Ground(position + right * (x * 2.0) + forward * (z * 4.5));
if (!ERR_ForceRules.FiniteVector(sample)) { m_LastReason = "nonfinite terrain sample"; return false; }
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), sample))
{ m_LastReason = "water at footprint sample"; return false; }
lowest = Math.Min(lowest, sample[1]); highest = Math.Max(highest, sample[1]);
low[0] = Math.Min(low[0], sample[0]); low[2] = Math.Min(low[2], sample[2]);
high[0] = Math.Max(high[0], sample[0]); high[2] = Math.Max(high[2], sample[2]);
}
}
m_LastLowest = lowest; m_LastHighest = highest;
if (!Terrain(position[1], lowest, highest))
{ m_LastReason = "terrain origin/height/span check"; return false; }
low[1] = lowest + 0.05; high[1] = highest + 3.5;
m_Obstructed = false; m_Visited = 0;
GetGame().GetWorld().QueryEntitiesByAABB(low, high, Occupancy);
// Entity bounding boxes can include distant branches or overhead wires.
// Their overlap is diagnostic only; the filled native volume decides collision.
vector center = (low + high) * 0.5;
TraceBox volume = new TraceBox();
volume.Start = center; volume.End = center + "0 0.01 0";
volume.Mins = low - center; volume.Maxs = high - center;
// Nine terrain/water samples already qualify the supporting ground below us.
volume.Flags = TraceFlags.ENTS | TraceFlags.OCEAN;
m_LastTrace = GetGame().GetWorld().TraceMove(volume, null);
m_Obstructed = !ERR_ForceRules.Finite(m_LastTrace) || m_LastTrace < 1;
m_FirstObstruction = null;
if (m_Obstructed)
{
m_FirstObstruction = volume.TraceEnt;
m_LastReason = "native filled volume collision";
if (!ERR_ForceRules.Finite(m_LastTrace)) m_LastReason = "invalid native trace fraction";
}
else m_LastReason = "clear";
return !m_Obstructed;
}
}
@@ -0,0 +1,229 @@
// Detached radio order. Only its issuer chooses Position and the fixed deadline.
class ERR_ConvoyInterceptOrder
{
int Token;
SCR_AIGroup Group;
string SourceId;
int VehicleIndex;
vector Position;
float IssuedAt;
float ExpiresAt;
float ObservedAt;
int BeliefId;
int BeliefRevision;
int ReportId;
}
// Delivered ground history only; no target entity reads, force creation or orders.
class ERR_HQMountedInterceptRules
{
static bool Eligible(ERR_HQBelief belief, float now)
{
if (!ERR_HQRoadIntercept.Eligible(belief, now)) return false;
if (belief.BeliefId <= 0 || belief.BeliefRevision <= 0 || belief.ObservedAt < 0) return false;
return ERR_HQPursuitRules.PositionValid(belief.Position) && ERR_HQPursuitRules.PositionValid(belief.PredictedPosition);
}
static bool Current(ERR_ConvoyInterceptOrder order, ERR_HQBelief belief, float now)
{
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
if (order.Token < 400001 || order.Token > 400256) return false;
if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
if (order.IssuedAt < belief.LastDeliveredAt || now < order.IssuedAt || now - order.IssuedAt > 30000) return false;
if (order.ExpiresAt <= order.IssuedAt || order.ExpiresAt > order.ObservedAt + 120000 || now >= order.ExpiresAt) return false;
return ERR_HQPursuitRules.PositionValid(order.Position);
}
static bool Ahead(vector origin, vector velocity, vector point)
{
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
float distance = delta.Length();
return distance >= 300 && distance <= 900 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
}
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score, out float heading)
{
result = vector.Zero; score = 1e20; heading = 0;
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
bool found;
for (int i = 1; i < points.Count(); i++)
{
vector delta = points[i] - points[i - 1]; delta[1] = 0;
float length = delta.Length();
if (length < 0.01) continue;
vector offset = seed - points[i - 1]; offset[1] = 0;
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
float distance = vector.DistanceXZ(seed, candidate);
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result)) { result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true; }
}
return found;
}
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
{
result = vector.Zero;
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
if (!GetGame() || !GetGame().GetWorld()) return false;
vector velocity = belief.Velocity; velocity[1] = 0;
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
if (!world || !world.GetRoadNetworkManager()) return false;
array<BaseRoad> roads = {};
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
if (roads.Count() > 128) return false;
int segments;
bool found;
vector best;
float bestScore = 1e20;
float bestHeading;
foreach (BaseRoad road : roads)
{
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
array<vector> points = {}; road.GetPoints(points);
if (points.Count() < 2 || points.Count() > 1024) continue;
segments += points.Count() - 1;
if (segments > 4096) return false;
vector candidate;
float score, heading;
if (!FromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, candidate, score, heading)) continue;
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
{ best = candidate; bestScore = score; bestHeading = heading; found = true; }
}
if (!found) return false;
vector grounded = ERR_Actors.Ground(best);
// Never convert an elevated bridge point into terrain beneath the road.
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
if (!settlements.OutsideSettlements(grounded)) return false;
// Existing conservative vehicle footprint: nine terrain/water samples,
// filled collision volume and segment-aligned heading; no scenery changes.
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
if (!placement.Clear(grounded, bestHeading)) return false;
result = grounded;
return true;
}
// Keep both escorts on one real polyline. Horizontal arc length determines
// placement; interpolation retains each road segment's height and heading.
static bool PairFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector first, out vector second, out float score, out float firstHeading, out float secondHeading)
{
first = vector.Zero; second = vector.Zero; score = 1e20; firstHeading = 0; secondHeading = 0;
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_HQPursuitRules.Heading(velocity, true) || !reserved || reserved.Count() > 128) return false;
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
velocity[1] = 0;
float total; float anchor; float nearest = 1e20;
float along;
vector projected;
bool found;
for (int i = 1; i < points.Count(); i++)
{
vector delta = points[i] - points[i - 1]; delta[1] = 0;
float length = delta.Length();
if (!ERR_ForceRules.Finite(length)) return false;
if (length < 0.01) continue;
vector offset = seed - points[i - 1]; offset[1] = 0;
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
float distance = vector.DistanceXZ(seed, candidate);
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, nearest, projected))
{
projected = candidate; nearest = distance; anchor = total + length * fraction;
along = vector.Dot(delta.Normalized(), velocity.Normalized()); found = true;
}
total += length;
if (!ERR_ForceRules.Finite(total)) return false;
}
if (!found || nearest > 100 || total < 40) return false;
float span = Math.Clamp(total, 40, 60);
float start = anchor - span * 0.5;
// Longitudinal roads extend the pair ahead of the projection, so the first
// point at the 300m planning boundary is not shifted behind that boundary.
if (along >= 0.25) start = anchor;
else if (along <= -0.25) start = anchor - span;
start = Math.Clamp(start, 0, total - span);
float finish = start + span;
float travelled;
vector a, b;
float aHeading; float bHeading;
bool haveFirst; bool haveSecond;
for (int j = 1; j < points.Count(); j++)
{
vector segment = points[j] - points[j - 1]; segment[1] = 0;
float segmentLength = segment.Length();
if (segmentLength < 0.01) continue;
if (!haveFirst && start <= travelled + segmentLength)
{
a = vector.Lerp(points[j - 1], points[j], Math.Clamp((start - travelled) / segmentLength, 0, 1));
aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
}
if (!haveSecond && finish <= travelled + segmentLength)
{
b = vector.Lerp(points[j - 1], points[j], Math.Clamp((finish - travelled) / segmentLength, 0, 1));
bHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveSecond = true;
}
if (haveFirst && haveSecond) break;
travelled += segmentLength;
}
if (!haveFirst || !haveSecond || vector.DistanceXZ(a, b) < 40) return false;
if (vector.DistanceXZ(seed, a) > 100 || vector.DistanceXZ(seed, b) > 100) return false;
if (!Ahead(origin, velocity, a) || !Ahead(origin, velocity, b)) return false;
if (!ERR_HQRoadIntercept.Unreserved(a, reserved) || !ERR_HQRoadIntercept.Unreserved(b, reserved)) return false;
// Canonical order does not depend on native road enumeration direction.
if (ERR_HQRoadIntercept.Better(0, b, 0, a))
{
vector swap = a; a = b; b = swap;
float swapHeading = aHeading; aHeading = bHeading; bHeading = swapHeading;
}
first = a; second = b; score = nearest; firstHeading = aHeading; secondHeading = bHeading;
return true;
}
static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)
{
first = vector.Zero; second = vector.Zero;
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
if (!GetGame() || !GetGame().GetWorld()) return false;
vector velocity = belief.Velocity; velocity[1] = 0;
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
if (!world || !world.GetRoadNetworkManager()) return false;
array<BaseRoad> roads = {};
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
if (roads.Count() > 128) return false;
int segments;
bool found;
vector bestFirst, bestSecond;
float bestScore = 1e20;
float bestFirstHeading; float bestSecondHeading;
foreach (BaseRoad road : roads)
{
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
array<vector> points = {}; road.GetPoints(points);
if (points.Count() < 2 || points.Count() > 1024) continue;
segments += points.Count() - 1;
if (segments > 4096) return false;
vector a, b;
float score; float aHeading; float bHeading;
if (!PairFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, a, b, score, aHeading, bHeading)) continue;
if (!found || ERR_HQRoadIntercept.Better(score, a, bestScore, bestFirst))
{ bestFirst = a; bestSecond = b; bestScore = score; bestFirstHeading = aHeading; bestSecondHeading = bHeading; found = true; }
}
if (!found) return false;
vector groundedFirst = ERR_Actors.Ground(bestFirst);
vector groundedSecond = ERR_Actors.Ground(bestSecond);
if (!ERR_HQPursuitRules.PositionValid(groundedFirst) || !ERR_HQPursuitRules.PositionValid(groundedSecond)) return false;
if (Math.AbsFloat(groundedFirst[1] - bestFirst[1]) > 2 || Math.AbsFloat(groundedSecond[1] - bestSecond[1]) > 2) return false;
if (vector.DistanceXZ(groundedFirst, groundedSecond) < 40) return false;
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
if (!settlements.OutsideSettlements(groundedFirst) || !settlements.OutsideSettlements(groundedSecond)) return false;
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
if (!placement.Clear(groundedFirst, bestFirstHeading) || !placement.Clear(groundedSecond, bestSecondHeading)) return false;
first = groundedFirst; second = groundedSecond;
return true;
}
}
@@ -0,0 +1,60 @@
"""Execute actual footprint sampling and native query callback policy with API doubles."""
import re
from policy_harness import ROOT,body,run
p=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('))
code=code.replace('null','nullptr').replace('protected ','').replace('IEntity entity','IEntity* entity').replace('entity.GetVObject()', 'entity->GetVObject()')
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('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 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 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;Game* GetGame(){return &game;}
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::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));
}
''')
print('PASS: actual 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')
@@ -0,0 +1,102 @@
"""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_MOUNTED_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c'))
source=p.read_text()
base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
source='class ERR_HQRoadIntercept\n{\n'+'\n'.join(body(base,x) for x in ('static bool Eligible(', 'static bool Unreserved(', 'static bool Better('))+'\n}\n'+source
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').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
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','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
for name in ('belief','world','road','body','order','settlements','placement'):
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','ERR_HQRoadIntercept'):
s=s.replace(name+'.',name+'::')
s=s.replace('null','nullptr').replace('string SourceId','std::string SourceId').replace('.IsEmpty()', '.empty()')
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('float score, heading;', 'float score=0, heading=0;').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 SCR_AIGroup{};
struct Math{static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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,BeliefId=3,BeliefRevision=2;vector Position={1000,10,1000};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=6;Array<vector>points={{900,10,1300},{1100,10,1300}};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;}};
struct ERR_SettlementSafety{static inline bool clear=true;static inline int calls=0;bool OutsideSettlements(vector){calls++;return clear;}};
struct ERR_ConvoyPlacement{static inline bool clear=true;static inline int calls=0;static inline float heading=0;bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear;}};
''' + s + r'''
int main(){
using Rules=ERR_HQMountedInterceptRules;ERR_HQBelief belief;SCR_AIGroup group;
assert(Rules::Eligible(&belief,10000));
ERR_ConvoyInterceptOrder order;order.Token=400001;order.Group=&group;order.SourceId="original-convoy";order.VehicleIndex=0;order.Position={1000,10,1300};order.IssuedAt=8000;order.ExpiresAt=125000;order.ObservedAt=5000;order.BeliefId=3;order.BeliefRevision=2;order.ReportId=1;
vector frozen=order.Position;assert(Rules::Current(&order,&belief,10000));belief.PredictedPosition={1050,10,1000};assert(Rules::Current(&order,&belief,11000)&&order.Position==frozen);belief.PredictedPosition={1000,10,1000};
for(int fault=0;fault<21;fault++){auto b=belief;auto o=order;float now=10000;
if(fault==0)o.Token=400000;
if(fault==1)o.Token=400257;
if(fault==2)o.Group=nullptr;
if(fault==3)o.SourceId="";
if(fault==4)o.VehicleIndex=1;
if(fault==5)o.BeliefId++;
if(fault==6)o.BeliefRevision++;
if(fault==7)o.ReportId++;
if(fault==8)o.ObservedAt++;
if(fault==9)o.IssuedAt=6000;
if(fault==10)o.IssuedAt=11000;
if(fault==11)o.ExpiresAt=125001;
if(fault==12)o.ExpiresAt=10000;
if(fault==13)o.IssuedAt=NAN;
if(fault==14)o.Position.x=NAN;
if(fault==15)b.HasVelocity=false;
if(fault==16)b.SampleCount=1;
if(fault==17)b.LastDeliveredAt=10001;
if(fault==18)b.TravelMode="aircraft";
if(fault==19)now=38001;
if(fault==20)b.ObservedAt=-1;
assert(!Rules::Current(&o,&b,now));}
order.VehicleIndex=3;assert(Rules::Current(&order,&belief,38000));order.VehicleIndex=5;assert(Rules::Current(&order,&belief,38000));
assert(!Rules::Eligible(&belief,50001));assert(!Rules::Eligible(nullptr,10000));
Array<vector> reserved;vector result;BaseRoad road;game.ai.manager.roads={&road};
assert(Rules::Select(&belief,10000,reserved,result)&&result==vector(1000,10,1300));assert(ERR_ConvoyPlacement::calls==1&&ERR_SettlementSafety::calls==1);assert(std::abs(ERR_ConvoyPlacement::heading-90)<.001);
road.width=5.99;assert(!Rules::Select(&belief,10000,reserved,result)&&result==vector::Zero);road.width=6;
int placementCalls=ERR_ConvoyPlacement::calls;ERR_SettlementSafety::clear=false;assert(!Rules::Select(&belief,10000,reserved,result)&&result==vector::Zero&&ERR_ConvoyPlacement::calls==placementCalls);ERR_SettlementSafety::clear=true;
ERR_ConvoyPlacement::clear=false;assert(!Rules::Select(&belief,10000,reserved,result)&&result==vector::Zero);ERR_ConvoyPlacement::clear=true;
road.points={{900,20,1300},{1100,20,1300}};placementCalls=ERR_ConvoyPlacement::calls;assert(!Rules::Select(&belief,10000,reserved,result)&&ERR_ConvoyPlacement::calls==placementCalls);road.points={{900,10,1300},{1100,10,1300}};
reserved.Insert({1000,10,1300});assert(!Rules::Select(&belief,10000,reserved,result));reserved.clear();
game.ai.manager.roads.resize(129,&road);assert(!Rules::Select(&belief,10000,reserved,result));game.ai.manager.roads={&road};
auto unknown=belief;unknown.HasVelocity=false;int queries=game.ai.manager.queries;assert(!Rules::Select(&unknown,10000,reserved,result)&&game.ai.manager.queries==queries);
Replication::server=false;assert(!Rules::Select(&belief,10000,reserved,result)&&game.ai.manager.queries==queries);Replication::server=true;
float score,heading;assert(Rules::FromPolyline({{900,10,1300},{1100,10,1300}},belief.Position,belief.Velocity,{1000,10,1350},reserved,result,score,heading)&&result.z==1300);
assert(!Rules::Ahead(belief.Position,belief.Velocity,{1000,10,1299}));assert(!Rules::Ahead(belief.Position,belief.Velocity,{1000,10,1901}));
Array<vector> oversized(1025,{1000,10,1300});assert(!Rules::FromPolyline(oversized,belief.Position,belief.Velocity,{1000,10,1350},reserved,result,score,heading));
}
''')
print('PASS: actual mounted intercept eligibility/order chronology, fixed destinations, native road selection bounds, width, segment heading, clearance and settlement delegation; no native driving claim')
@@ -0,0 +1,133 @@
"""Actual paired road geometry and bounded native survey; native boundaries are doubled."""
from pathlib import Path
import os,re
from policy_harness import ROOT,body,run
p=Path(os.environ.get('ERR_MOUNTED_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c'))
source=p.read_text()
base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
source='class ERR_HQRoadIntercept\n{\n'+'\n'.join(body(base,x) for x in ('static bool Eligible(', 'static bool Unreserved(', 'static bool Better('))+'\n}\n'+source
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').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
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','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
for name in ('belief','world','road','body','order','settlements','placement'):
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','ERR_HQRoadIntercept'):
s=s.replace(name+'.',name+'::')
s=s.replace('null','nullptr').replace('string SourceId','std::string SourceId').replace('.IsEmpty()', '.empty()')
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('float score, heading;', 'float score=0, heading=0;').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 SCR_AIGroup{};
struct Math{static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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,BeliefId=3,BeliefRevision=2;vector Position={1000,10,1000};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{static inline int reads=0;float width=6;Array<vector>points={{900,10,1300},{1100,10,1300}};float GetWidth(){return width;}void GetPoints(Array<vector>&p){reads++;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 float rightHeight=10;static vector Ground(vector p){p.y=p.x>1000?rightHeight:10;return p;}};
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
struct ERR_SettlementSafety{static inline bool clear=true,rightClear=true;static inline int calls=0;bool OutsideSettlements(vector p){calls++;return clear&&(p.x<=1000||rightClear);}};
struct ERR_ConvoyPlacement{static inline bool clear=true,rightClear=true,water=false;static inline int calls=0;static inline float heading=0;bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear&&!water&&(p.x<=1000||rightClear);}};
''' + s + r'''
int main(){
using Rules=ERR_HQMountedInterceptRules;
ERR_HQBelief belief;Array<vector> reserved;vector a={1,2,3},b={4,5,6};float score=0,ha=0,hb=0;
vector origin={1000,10,1000},velocity={0,0,4},seed={1000,10,1300};
auto pair=[&](Array<vector> p){return Rules::PairFromPolyline(p,origin,velocity,seed,reserved,a,b,score,ha,hb);};
auto empty=[&](){assert(a==vector::Zero&&b==vector::Zero);};
// Real arc interpolation over a90-degree bend, not straight interpolation
// between the road's endpoints (which would put both escorts off the road).
assert(pair({{900,10,1300},{1000,10,1300},{1000,10,1400}}));
assert(a==vector(970,10,1300)&&b==vector(1000,10,1330));
assert(std::abs(ha-90)<.001&&std::abs(hb)<.001);
assert(vector::DistanceXZ(a,b)>42&&vector::DistanceXZ(a,b)<43);
// Arc span alone is insufficient on a bend-back/hairpin.
assert(!pair({{970,10,1300},{1000,10,1300},{1000,10,1301},{970,10,1301}}));empty();
assert(!pair({{980,10,1300},{1019.9f,10,1300}}));empty();
assert(pair({{980,10,1300},{1020,10,1300}})&&vector::DistanceXZ(a,b)==40);
// A road aligned with travel must not push a partner behind the300m floor.
assert(pair({{1000,10,1250},{1000,10,1450}}));
assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
assert(pair({{1000,10,1450},{1000,10,1250}}));
assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
// Actual sloped segment retains height for subsequent bridge validation.
assert(pair({{900,5,1300},{1100,15,1300}}));
assert(a==vector(970,8.5,1300)&&b==vector(1030,11.5,1300));
// Degenerate segments are harmless but a wholly degenerate road has no span.
assert(pair({{900,10,1300},{900,10,1300},{1100,10,1300}}));
assert(!pair({{1000,10,1300},{1000,10,1300}}));empty();
assert(!pair({{900,10,1300},{NAN,10,1300},{1100,10,1300}}));empty();
assert(!pair(Array<vector>(1025,{1000,10,1300})));empty();
assert(!pair({}));empty();assert(!pair({{1000,10,1300}}));empty();
auto good=Array<vector>{{900,10,1300},{1100,10,1300}};
velocity={0,0,0};assert(!pair(good));empty();velocity={0,0,4};
seed.x=NAN;assert(!pair(good));empty();seed={1000,10,1300};
origin.x=NAN;assert(!pair(good));empty();origin={1000,10,1000};
reserved={{970,10,1380}};assert(pair(good));
reserved={{970,10,1379.99f}};assert(!pair(good));empty();
reserved={{NAN,10,1380}};assert(!pair(good));empty();
reserved=Array<vector>(129,{10000,10,10000});assert(!pair(good));empty();reserved.clear();
for(float z:{1290.f,1901.f,700.f}){seed.z=z;assert(!pair({{900,10,z},{1100,10,z}}));empty();}seed.z=1300;
BaseRoad road;road.points=good;game.ai.manager.roads={&road};
int q=game.ai.manager.queries;
assert(Rules::SelectPair(&belief,10000,reserved,a,b));assert(game.ai.manager.queries==q+1);
assert(a==vector(970,10,1300)&&b==vector(1030,10,1300));
assert(ERR_SettlementSafety::calls==2&&ERR_ConvoyPlacement::calls==2);
auto select=[&](){a={1,2,3};b={4,5,6};bool ok=Rules::SelectPair(&belief,10000,reserved,a,b);if(!ok)empty();return ok;};
// Second position cannot escape settlement, water/collision or bridge checks.
int pc=ERR_ConvoyPlacement::calls;ERR_SettlementSafety::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_SettlementSafety::rightClear=true;
pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+2);ERR_ConvoyPlacement::rightClear=true;
pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::water=true;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+1);ERR_ConvoyPlacement::water=false;
pc=ERR_ConvoyPlacement::calls;ERR_Actors::rightHeight=7;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_Actors::rightHeight=10;
road.points={{900,20,1300},{1100,20,1300}};assert(!select());road.points=good;
// Reject a pair assembled from two short unrelated roads, even when their
// endpoints could have formed a geometrically spaced chord.
BaseRoad left,right;left.points={{960,10,1300},{980,10,1300}};right.points={{1020,10,1300},{1040,10,1300}};game.ai.manager.roads={&left,&right};assert(!select());
game.ai.manager.roads={&road};road.width=5.99;assert(!select());road.width=NAN;assert(!select());road.width=6;
// All pre-query gates remain cheap; one permitted pair call is one query.
q=game.ai.manager.queries;belief.HasVelocity=false;assert(!select());belief.HasVelocity=true;
Replication::server=false;assert(!select());Replication::server=true;
reserved.resize(129);assert(!select());reserved.clear();
assert(game.ai.manager.queries==q);
game.ai.available=false;assert(!select());game.ai.available=true;assert(game.ai.manager.queries==q);
// Road count bound refuses before any point arrays are read.
game.ai.manager.roads=Array<BaseRoad*>(129,&road);int reads=BaseRoad::reads;assert(!select());assert(BaseRoad::reads==reads);
game.ai.manager.roads=Array<BaseRoad*>(128,&road);assert(select());assert(BaseRoad::reads==reads+128);
// Total4096 segment allowance includes irrelevant/degenerate vertices; an
// earlier valid pair cannot leak through once later roads exhaust the cap.
BaseRoad big,tail;big.points=Array<vector>(1024,{900,10,1300});big.points.back()={1100,10,1300};tail.points=Array<vector>(5,{900,10,1300});tail.points.back()={1100,10,1300};
game.ai.manager.roads={&big,&big,&big,&big,&tail};assert(select());
tail.points.insert(tail.points.begin(),{900,10,1300});pc=ERR_ConvoyPlacement::calls;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);
// Pair failure still permits unchanged singleton selection in a separate
// call; this explicitly consumes the caller's second survey budget.
road.points={{995,10,1300},{1005,10,1300}};game.ai.manager.roads={&road};q=game.ai.manager.queries;
assert(!select());vector singleton;assert(Rules::Select(&belief,10000,reserved,singleton));assert(singleton==vector(1000,10,1300)&&game.ai.manager.queries==q+2);
}
''')
print('PASS: actual paired arc geometry,40m/80m spacing, separate original polylines, both-position validation and128road/4096segment budget; no native driving proof')
@@ -0,0 +1,19 @@
{
"scope": "Measured, immutable original vehicle footprint and conservative singleton armored destination geometry. Existing UAZ singleton/pair and old Clear behavior retained. No spawning or changes to target/route/force claims.",
"tests": "Five focused actual-method programs green, six runtime-failing mutations. Final footprint re-run passes with current-storage-slot guard. Existing native boundaries are doubles; no Enforce/native/physical movement qualification.",
"bounds": {
"footprint_nodes": 128,
"footprint_parent_depth": 32,
"road_queries": 1,
"roads": 128,
"segments": 4096,
"clearance_grid_points_max": 441
},
"limitations": [
"Capture/Matches can conservatively refuse real native vehicles with unavailable/invalid bounds or too many nodes; actual BMP capture remains unverified.",
"Cargo node identities remain retained; insertion/removal can invalidate Matches. Seated character body measured, exact reciprocal carried subtree excluded. Exact reciprocal native deposit cargo excluded only with current storage.GetSlot identity.",
"Orientation-independent padded circle enclosed by square requires wide roads; short or bent support is conservatively refused. Endpoint/center terrain and destination collision checks do not guarantee path reachability, arrival heading or tactics.",
"No predecessor red is claimed for newly introduced APIs; mutation red controls prove checked behavioral boundaries.",
"prepare.py/adapt_tests.py/adapt_placement.py are historical work scripts, not authoritative regenerators. Only files in source-manifest.json are proposed for integration."
]
}
@@ -0,0 +1,563 @@
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyPlacement.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyPlacement.c
@@ -86,4 +86,59 @@
else m_LastReason = "clear";
return !m_Obstructed;
}
+ // Measured padded square enclosing the immutable, yaw-independent circle.
+ bool ClearFootprint(vector position, float heading, ERR_InterceptVehicleFootprint footprint)
+ {
+ m_LastReason = "invalid origin or heading"; m_LastLowest = 1e20; m_LastHighest = -1e20;
+ m_FirstObstruction = null; m_Visited = 0; m_LastTrace = -1;
+ if (!footprint || !footprint.Valid() || !GetGame() || !GetGame().GetWorld()) return false;
+ if (!ERR_ForceRules.FiniteVector(position) || !ERR_ForceRules.Finite(heading)) return false;
+ float radians = heading * Math.DEG2RAD;
+ vector forward = Vector(Math.Sin(radians), 0, Math.Cos(radians));
+ vector right = Vector(forward[2], 0, -forward[0]);
+ float lowest = 1e20, highest = -1e20;
+ vector low = position, high = position;
+ float radius = footprint.Radius();
+ int steps = Math.Ceil(radius);
+ if (steps % 2 != 0) steps++;
+ if (steps < 1 || steps > 20) return false;
+ for (int x = 0; x <= steps; x++)
+ {
+ for (int z = 0; z <= steps; z++)
+ {
+ vector sample = ERR_Actors.Ground(position + right * (-radius + 2 * radius * x / steps) + forward * (-radius + 2 * radius * z / steps));
+ if (!ERR_ForceRules.FiniteVector(sample)) { m_LastReason = "nonfinite terrain sample"; return false; }
+ if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), sample))
+ { m_LastReason = "water at footprint sample"; return false; }
+ lowest = Math.Min(lowest, sample[1]); highest = Math.Max(highest, sample[1]);
+ low[0] = Math.Min(low[0], sample[0]); low[2] = Math.Min(low[2], sample[2]);
+ high[0] = Math.Max(high[0], sample[0]); high[2] = Math.Max(high[2], sample[2]);
+ }
+ }
+ m_LastLowest = lowest; m_LastHighest = highest;
+ if (!Terrain(position[1], lowest, highest))
+ { m_LastReason = "terrain origin/height/span check"; return false; }
+ low[1] = lowest + 0.05; high[1] = highest + footprint.Height();
+ m_Obstructed = false; m_Visited = 0;
+ GetGame().GetWorld().QueryEntitiesByAABB(low, high, Occupancy);
+ // Entity bounding boxes can include distant branches or overhead wires.
+ // Their overlap is diagnostic only; the filled native volume decides collision.
+ vector center = (low + high) * 0.5;
+ TraceBox volume = new TraceBox();
+ volume.Start = center; volume.End = center + "0 0.01 0";
+ volume.Mins = low - center; volume.Maxs = high - center;
+ // At most441 terrain/water samples with spacing <=2m; destination only.
+ volume.Flags = TraceFlags.ENTS | TraceFlags.OCEAN;
+ m_LastTrace = GetGame().GetWorld().TraceMove(volume, null);
+ m_Obstructed = !ERR_ForceRules.Finite(m_LastTrace) || m_LastTrace < 1;
+ m_FirstObstruction = null;
+ if (m_Obstructed)
+ {
+ m_FirstObstruction = volume.TraceEnt;
+ m_LastReason = "native filled volume collision";
+ if (!ERR_ForceRules.Finite(m_LastTrace)) m_LastReason = "invalid native trace fraction";
+ }
+ else m_LastReason = "clear";
+ return !m_Obstructed;
+ }
}
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c
@@ -5,6 +5,9 @@
SCR_AIGroup Group;
string SourceId;
int VehicleIndex;
+ bool Armored;
+ ref ERR_InterceptVehicleFootprint Footprint;
+ int ConfirmedLosses;
vector Position;
float IssuedAt;
float ExpiresAt;
@@ -27,7 +30,11 @@
{
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
if (order.Token < 400001 || order.Token > 400256) return false;
- if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
+ if (order.Armored)
+ {
+ if (order.VehicleIndex != 4 || order.ConfirmedLosses < 6 || !order.Footprint || !order.Footprint.Valid()) return false;
+ }
+ else if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
@@ -226,4 +233,82 @@
first = groundedFirst; second = groundedSecond;
return true;
}
+ // One actual straight segment must support the full retained square. No arc
+ // interpolation over bends or short adjoining segments; conservative refusal.
+ static bool ArmoredFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, ERR_InterceptVehicleFootprint footprint, out vector result, out float score, out float heading, out vector supportA, out vector supportB)
+ {
+ result = vector.Zero; supportA = vector.Zero; supportB = vector.Zero; score = 1e20; heading = 0;
+ if (!footprint || !footprint.Valid() || !points || points.Count() < 2 || points.Count() > 1024) return false;
+ if (!ERR_ForceRules.FiniteVector(seed) || !reserved || reserved.Count() > 128) return false;
+ foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
+ float radius = footprint.Radius();
+ bool found;
+ for (int i = 1; i < points.Count(); i++)
+ {
+ vector delta = points[i] - points[i - 1]; delta[1] = 0;
+ float length = delta.Length();
+ if (!ERR_ForceRules.Finite(length) || length < 2 * radius) continue;
+ vector offset = seed - points[i - 1]; offset[1] = 0;
+ float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), radius / length, 1 - radius / length);
+ vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
+ float distance = vector.DistanceXZ(seed, candidate);
+ if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
+ if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result))
+ {
+ result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true;
+ supportA = vector.Lerp(points[i - 1], points[i], fraction - radius / length);
+ supportB = vector.Lerp(points[i - 1], points[i], fraction + radius / length);
+ }
+ }
+ return found;
+ }
+ static bool SelectArmored(ERR_HQBelief belief, float now, array<vector> reserved, ERR_InterceptVehicleFootprint footprint, out vector result)
+ {
+ result = vector.Zero;
+ if (!Replication.IsServer() || !Eligible(belief, now) || !footprint || !footprint.Valid() || !reserved || reserved.Count() > 128) return false;
+ if (!GetGame() || !GetGame().GetWorld()) return false;
+ vector velocity = belief.Velocity; velocity[1] = 0;
+ vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
+ if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
+ SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
+ if (!world || !world.GetRoadNetworkManager()) return false;
+ array<BaseRoad> roads = {};
+ world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
+ if (roads.Count() > 128) return false;
+ int segments;
+ bool found;
+ vector best, bestSupportA, bestSupportB;
+ float bestScore = 1e20;
+ float bestHeading;
+ foreach (BaseRoad road : roads)
+ {
+ if (!road) return false;
+ float width = road.GetWidth();
+ if (!ERR_ForceRules.Finite(width)) return false;
+ array<vector> points = {}; road.GetPoints(points);
+ if (points.Count() < 2 || points.Count() > 1024) return false;
+ segments += points.Count() - 1;
+ if (segments > 4096) return false;
+ foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
+ if (width < Math.Max(6, 2 * footprint.Radius())) continue;
+ vector candidate, supportA, supportB;
+ float score, heading;
+ if (!ArmoredFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, footprint, candidate, score, heading, supportA, supportB)) continue;
+ if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
+ { best = candidate; bestScore = score; bestHeading = heading; bestSupportA = supportA; bestSupportB = supportB; found = true; }
+ }
+ if (!found) return false;
+ vector grounded = ERR_Actors.Ground(best);
+ if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
+ vector groundedA = ERR_Actors.Ground(bestSupportA);
+ vector groundedB = ERR_Actors.Ground(bestSupportB);
+ if (!ERR_HQPursuitRules.PositionValid(groundedA) || !ERR_HQPursuitRules.PositionValid(groundedB)) return false;
+ if (Math.AbsFloat(groundedA[1] - bestSupportA[1]) > 2 || Math.AbsFloat(groundedB[1] - bestSupportB[1]) > 2) return false;
+ ERR_SettlementSafety settlements = new ERR_SettlementSafety();
+ if (!settlements.OutsideSettlements(grounded)) return false;
+ ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
+ if (!placement.ClearFootprint(grounded, bestHeading, footprint)) return false;
+ result = grounded;
+ return true;
+ }
}
--- /dev/null
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_InterceptVehicleFootprint.c
@@ -0,0 +1,116 @@
+// Exact original vehicle geometry, measured once; no prefab dimension guesses.
+// A horizontal circle is conservative for any arrival yaw. The enclosing square
+// used by road/terrain checks deliberately refuses narrower roads.
+class ERR_InterceptVehicleFootprint
+{
+ private IEntity m_Root;
+ private ref array<IEntity> m_Nodes = {};
+ private float m_Radius;
+ private float m_Low;
+ private float m_High;
+ float Radius() { return m_Radius; }
+ float Height() { return m_High - Math.Min(m_Low, 0); }
+ bool Valid()
+ {
+ return m_Root && m_Nodes && m_Nodes.Count() > 0 && m_Nodes.Count() <= 128 && m_Nodes[0] == m_Root
+ && ERR_ForceRules.Finite(m_Radius) && m_Radius > 0.5 && m_Radius <= 20
+ && ERR_ForceRules.Finite(m_Low) && ERR_ForceRules.Finite(m_High)
+ && m_High > m_Low && Height() > 0 && Height() <= 15;
+ }
+ protected static bool PartOf(IEntity entity, IEntity root)
+ {
+ for (int depth = 0; entity && depth < 32; depth++)
+ {
+ if (entity == root) return true;
+ entity = entity.GetParent();
+ }
+ return false;
+ }
+ protected static bool StoredCargo(IEntity entity, IEntity root)
+ {
+ InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
+ if (!item || !item.GetParentSlot()) return false;
+ InventoryStorageSlot slot = item.GetParentSlot();
+ BaseInventoryStorageComponent storage = slot.GetStorage();
+ if (!storage || storage.GetSlot(slot.GetID()) != slot || !PartOf(storage.GetOwner(), root) || slot.GetParentContainer() != storage || slot.GetAttachedEntity() != entity) return false;
+ int purpose = storage.GetPurpose();
+ return purpose != EStoragePurpose.PURPOSE_UNSET && purpose != EStoragePurpose.PURPOSE_ANY && (purpose & EStoragePurpose.PURPOSE_DEPOSIT) != 0;
+ }
+ protected static bool Measure(IEntity root, array<IEntity> nodes, out float radius, out float low, out float high)
+ {
+ radius = 0; low = 1e20; high = -1e20;
+ if (!root || !root.GetVObject() || !nodes || !nodes.IsEmpty()) return false;
+ nodes.Insert(root);
+ for (int i = 0; i < nodes.Count(); i++)
+ {
+ IEntity entity = nodes[i];
+ if (!entity) return false;
+ bool character = ChimeraCharacter.Cast(entity) != null;
+ if (character)
+ {
+ CompartmentAccessComponent access = CompartmentAccessComponent.Cast(entity.FindComponent(CompartmentAccessComponent));
+ if (!access || !access.IsInCompartment() || !access.GetCompartment() || access.GetCompartment().GetOccupant() != entity
+ || !PartOf(access.GetVehicleCompartmentManagerOwner(), root)) return false;
+ }
+ else if (entity != root && StoredCargo(entity, root)) continue;
+ if (entity.GetVObject())
+ {
+ vector minimum, maximum; entity.GetBounds(minimum, maximum);
+ if (!ERR_ForceRules.FiniteVector(minimum) || !ERR_ForceRules.FiniteVector(maximum)) return false;
+ for (int axis = 0; axis < 3; axis++)
+ if (maximum[axis] < minimum[axis] || (entity == root && maximum[axis] <= minimum[axis]) || maximum[axis] - minimum[axis] > 40) return false;
+ for (int x = 0; x < 2; x++)
+ {
+ for (int y = 0; y < 2; y++)
+ {
+ for (int z = 0; z < 2; z++)
+ {
+ vector corner = Vector(minimum[0], minimum[1], minimum[2]);
+ if (x) corner[0] = maximum[0];
+ if (y) corner[1] = maximum[1];
+ if (z) corner[2] = maximum[2];
+ vector worldPoint = entity.CoordToParent(corner);
+ if (!ERR_ForceRules.FiniteVector(worldPoint)) return false;
+ vector localPoint = root.CoordToLocal(worldPoint);
+ if (!ERR_ForceRules.FiniteVector(localPoint)) return false;
+ float reach = Math.Sqrt(localPoint[0] * localPoint[0] + localPoint[2] * localPoint[2]);
+ if (!ERR_ForceRules.Finite(reach)) return false;
+ radius = Math.Max(radius, reach);
+ low = Math.Min(low, localPoint[1]); high = Math.Max(high, localPoint[1]);
+ }
+ }
+ }
+ }
+ // Retain/measure the seated body, but never its carried inventory subtree.
+ if (character) continue;
+ IEntity child = entity.GetChildren();
+ while (child)
+ {
+ if (nodes.Count() >= 128 || nodes.Contains(child)) return false;
+ nodes.Insert(child); child = child.GetSibling();
+ }
+ }
+ return radius > 0 && radius <= 19.5 && high > low && high - Math.Min(low, 0) <= 14;
+ }
+ static ERR_InterceptVehicleFootprint Capture(IEntity root)
+ {
+ ERR_InterceptVehicleFootprint footprint = new ERR_InterceptVehicleFootprint();
+ float radius, low, high;
+ if (!Measure(root, footprint.m_Nodes, radius, low, high)) return null;
+ footprint.m_Root = root; footprint.m_Radius = radius + 0.5;
+ footprint.m_Low = low - 0.5; footprint.m_High = high + 0.5;
+ if (!footprint.Valid()) return null;
+ return footprint;
+ }
+ bool Matches(IEntity root)
+ {
+ if (!Valid() || root != m_Root) return false;
+ array<IEntity> nodes = {};
+ float radius, low, high;
+ if (!Measure(root, nodes, radius, low, high) || nodes.Count() != m_Nodes.Count()) return false;
+ foreach (IEntity entity : nodes) if (!m_Nodes.Contains(entity)) return false;
+ // Moving turret/attachment bounds may change within this immutable envelope.
+ // Keep at least 25cm horizontal clearance; never enlarge a pending order.
+ return radius <= m_Radius - 0.25 && low >= m_Low && high <= m_High;
+ }
+}
--- /dev/null
+++ b/tests/test_bmp_intercept_geometry.py
@@ -0,0 +1,84 @@
+"""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_MOUNTED_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c'))
+source=p.read_text()
+base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
+source='class ERR_HQRoadIntercept\n{\n'+'\n'.join(body(base,x) for x in ('static bool Eligible(', 'static bool Unreserved(', 'static bool Better('))+'\n}\n'+source
+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('ref ','').replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
+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','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement','ERR_InterceptVehicleFootprint'):
+ s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
+for name in ('belief','world','road','body','order','settlements','placement','footprint'):
+ s=s.replace(name+'.',name+'->')
+s=s.replace('Footprint.', 'Footprint->')
+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','ERR_HQRoadIntercept'):
+ s=s.replace(name+'.',name+'::')
+s=s.replace('null','nullptr').replace('string SourceId','std::string SourceId').replace('.IsEmpty()', '.empty()')
+s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('float score, heading;', 'float score=0, heading=0;').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 SCR_AIGroup{};struct ERR_InterceptVehicleFootprint{bool valid=true;float radius=4;bool Valid(){return valid;}float Radius(){return radius;}};
+struct Math{static float Max(float a,float b){return std::max(a,b);}static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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,BeliefId=3,BeliefRevision=2;vector Position={1000,10,1000};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=6;Array<vector>points={{900,10,1300},{1100,10,1300}};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;}};
+
+struct ERR_SettlementSafety{static inline bool clear=true;static inline int calls=0;bool OutsideSettlements(vector){calls++;return clear;}};
+struct ERR_ConvoyPlacement{static inline bool clear=true;static inline int calls=0;static inline float heading=0;bool ClearFootprint(vector p,float h,ERR_InterceptVehicleFootprint*){return Clear(p,h);}bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear;}};
+''' + s + r'''
+int main(){
+ using Rules=ERR_HQMountedInterceptRules;ERR_HQBelief belief;ERR_InterceptVehicleFootprint footprint;SCR_AIGroup group;
+ Array<vector> reserved;BaseRoad road;road.width=8;game.ai.manager.roads={&road};vector result={1,2,3};
+ auto select=[&](){int q=game.ai.manager.queries;bool ok=Rules::SelectArmored(&belief,10000,reserved,&footprint,result);assert(game.ai.manager.queries-q==1);if(!ok)assert(result==vector::Zero);return ok;};
+ assert(select()&&result==vector(1000,10,1300));
+ road.width=7.999;assert(!select());road.width=8;
+ road.points={{997,10,1300},{1000,10,1300},{1000,10,1303}};assert(!select());
+ road.points={{996,10,1300},{1004,10,1300}};assert(select());
+ road.points={{996,20,1300},{1004,20,1300}};assert(!select());
+ road.points={{996,7,1300},{1004,13,1300}};assert(!select()); // Center matches terrain but footprint ends do not.
+ road.points={{900,10,1300},{1100,10,1300}};
+ ERR_ConvoyPlacement::clear=false;assert(!select());ERR_ConvoyPlacement::clear=true;
+ ERR_SettlementSafety::clear=false;assert(!select());ERR_SettlementSafety::clear=true;
+ reserved={{1000,10,1379.99f}};assert(!select());reserved={{1000,10,1380}};assert(select());reserved.clear();
+ footprint.valid=false;int q=game.ai.manager.queries;assert(!Rules::SelectArmored(&belief,10000,reserved,&footprint,result)&&q==game.ai.manager.queries);footprint.valid=true;
+ auto saved=belief;belief.HasVelocity=false;assert(!Rules::SelectArmored(&belief,10000,reserved,&footprint,result)&&q==game.ai.manager.queries);belief=saved;
+ road.width=NAN;assert(!select());road.width=8;road.points[0].x=NAN;assert(!select());road.points[0].x=900;
+ game.ai.manager.roads.assign(129,&road);assert(!select());game.ai.manager.roads={&road};
+ BaseRoad many;many.width=1;many.points.assign(1024,{1000,10,1300});game.ai.manager.roads={&road,&many,&many,&many,&many,&many};assert(!select());
+ game.ai.manager.roads={&road};ERR_ConvoyInterceptOrder order;order.Token=400001;order.Group=&group;order.SourceId="original-convoy";order.VehicleIndex=4;order.Armored=true;order.Footprint=&footprint;order.ConfirmedLosses=6;order.Position={1000,10,1300};order.IssuedAt=8000;order.ExpiresAt=125000;order.ObservedAt=5000;order.BeliefId=3;order.BeliefRevision=2;order.ReportId=1;
+ assert(Rules::Current(&order,&belief,10000));order.ConfirmedLosses=5;assert(!Rules::Current(&order,&belief,10000));order.ConfirmedLosses=6;
+ order.Armored=false;assert(!Rules::Current(&order,&belief,10000));order.Armored=true;
+ order.VehicleIndex=0;assert(!Rules::Current(&order,&belief,10000));order.VehicleIndex=4;
+ footprint.valid=false;assert(!Rules::Current(&order,&belief,10000));footprint.valid=true;order.Footprint=nullptr;assert(!Rules::Current(&order,&belief,10000));
+}
+''')
+print('PASS: actual BMP singleton geometry and typed current order; one survey, full straight segment, width, known history, reservations, settlement/clearance and work caps; placement/footprint tested separately')
--- a/tests/test_convoy_placement.py
+++ b/tests/test_convoy_placement.py
@@ -1,12 +1,15 @@
"""Execute actual footprint sampling and native query callback policy with API doubles."""
-import re
+import re,os
from policy_harness import ROOT,body,run
-p=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('))
+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>
@@ -16,13 +19,14 @@
#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 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 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;Game* GetGame(){return &game;}
+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'''};
@@ -55,6 +59,19 @@
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 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')
+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')
--- a/tests/test_hq_mounted_intercept.py
+++ b/tests/test_hq_mounted_intercept.py
@@ -10,13 +10,14 @@
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').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
+s=re.sub(r'//[^\n]*','',source).replace('ref ','').replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
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','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement'):
+for name in ('ERR_HQBelief','SCR_AIWorld','BaseRoad','TraceBox','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement','ERR_InterceptVehicleFootprint'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
-for name in ('belief','world','road','body','order','settlements','placement'):
+for name in ('belief','world','road','body','order','settlements','placement','footprint'):
s=s.replace(name+'.',name+'->')
+s=s.replace('Footprint.', 'Footprint->')
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','ERR_HQRoadIntercept'):
s=s.replace(name+'.',name+'::')
@@ -31,8 +32,8 @@
#include <vector>
#include <string>
enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};enum class ERR_HQMemoryKind{SIGHTING,DISTRESS,BODY};
-struct SCR_AIGroup{};
-struct Math{static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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 SCR_AIGroup{};struct ERR_InterceptVehicleFootprint{bool valid=true;float radius=4;bool Valid(){return valid;}float Radius(){return radius;}};
+struct Math{static float Max(float a,float b){return std::max(a,b);}static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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;};
@@ -52,7 +53,7 @@
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
struct ERR_SettlementSafety{static inline bool clear=true;static inline int calls=0;bool OutsideSettlements(vector){calls++;return clear;}};
-struct ERR_ConvoyPlacement{static inline bool clear=true;static inline int calls=0;static inline float heading=0;bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear;}};
+struct ERR_ConvoyPlacement{static inline bool clear=true;static inline int calls=0;static inline float heading=0;bool ClearFootprint(vector p,float h,ERR_InterceptVehicleFootprint*){return Clear(p,h);}bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear;}};
''' + s + r'''
int main(){
using Rules=ERR_HQMountedInterceptRules;ERR_HQBelief belief;SCR_AIGroup group;
--- a/tests/test_hq_mounted_intercept_pair.py
+++ b/tests/test_hq_mounted_intercept_pair.py
@@ -10,13 +10,14 @@
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').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
+s=re.sub(r'//[^\n]*','',source).replace('ref ','').replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
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','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement'):
+for name in ('ERR_HQBelief','SCR_AIWorld','BaseRoad','TraceBox','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement','ERR_InterceptVehicleFootprint'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
-for name in ('belief','world','road','body','order','settlements','placement'):
+for name in ('belief','world','road','body','order','settlements','placement','footprint'):
s=s.replace(name+'.',name+'->')
+s=s.replace('Footprint.', 'Footprint->')
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','ERR_HQRoadIntercept'):
s=s.replace(name+'.',name+'::')
@@ -31,8 +32,8 @@
#include <vector>
#include <string>
enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};enum class ERR_HQMemoryKind{SIGHTING,DISTRESS,BODY};
-struct SCR_AIGroup{};
-struct Math{static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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 SCR_AIGroup{};struct ERR_InterceptVehicleFootprint{bool valid=true;float radius=4;bool Valid(){return valid;}float Radius(){return radius;}};
+struct Math{static float Max(float a,float b){return std::max(a,b);}static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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;};
@@ -52,7 +53,7 @@
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
struct ERR_SettlementSafety{static inline bool clear=true,rightClear=true;static inline int calls=0;bool OutsideSettlements(vector p){calls++;return clear&&(p.x<=1000||rightClear);}};
-struct ERR_ConvoyPlacement{static inline bool clear=true,rightClear=true,water=false;static inline int calls=0;static inline float heading=0;bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear&&!water&&(p.x<=1000||rightClear);}};
+struct ERR_ConvoyPlacement{static inline bool clear=true,rightClear=true,water=false;static inline int calls=0;static inline float heading=0;bool ClearFootprint(vector p,float h,ERR_InterceptVehicleFootprint*){return Clear(p,h);}bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear&&!water&&(p.x<=1000||rightClear);}};
''' + s + r'''
int main(){
using Rules=ERR_HQMountedInterceptRules;
--- /dev/null
+++ b/tests/test_intercept_vehicle_footprint.py
@@ -0,0 +1,45 @@
+"""Execute full captured footprint class with explicit native bounds/hierarchy doubles."""
+from pathlib import Path
+import os,re
+from policy_harness import ROOT,run
+p=Path(os.environ.get('ERR_INTERCEPT_FOOTPRINT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InterceptVehicleFootprint.c'))
+s=p.read_text();assert 'private float m_Radius;' in s and 'private IEntity m_Root;' in s
+s=re.sub(r'//[^\n]*','',s).replace('class ERR_InterceptVehicleFootprint','struct ERR_InterceptVehicleFootprint').replace('private ','').replace('protected ','').replace('ref ','')
+s=s.replace('ChimeraCharacter.Cast(entity) != null','entity->character').replace('CompartmentAccessComponent.Cast(entity.FindComponent(CompartmentAccessComponent))','entity->access').replace('InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent))','entity->item')
+s=s.replace('array<IEntity>','Array<IEntity*>').replace('Array<IEntity*> nodes,','Array<IEntity*>& nodes,').replace('foreach (','for (').replace('out float','float&').replace('vector minimum, maximum;','vector minimum{}, maximum{};').replace('float radius, low, high;','float radius=0,low=0,high=0;')
+for cls in ['IEntity','ERR_InterceptVehicleFootprint','CompartmentAccessComponent','InventoryItemComponent','InventoryStorageSlot','BaseInventoryStorageComponent']:
+ s=re.sub(r'\b'+cls+r' (\w+)',cls+r'* \1',s)
+for n in ['root','entity','child','item','slot','storage','access','footprint']:s=s.replace(n+'.',n+'->')
+s=s.replace('m_Nodes[0]','m_Nodes[0]').replace('GetCompartment().','GetCompartment()->')
+for n in ['ERR_ForceRules','Math','EStoragePurpose']:s=s.replace(n+'.',n+'::')
+s=s.replace('null','nullptr').replace('\n}\n','\n};\n');s=re.sub(r'\b(float|int|bool) (\w+);',r'\1 \2{};',s)
+run(r'''
+#include <cassert>
+#include <cmath>
+#include <algorithm>
+#include <vector>
+struct vector{float x=0,y=0,z=0;float&operator[](int n){return n==0?x:n==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 Vector(float x,float y,float z){return{x,y,z};}
+namespace Math{float Sqrt(float n){return std::sqrt(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);}}
+template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}explicit operator bool(){return true;}};
+struct IEntity;struct Seat{IEntity*occupant=nullptr;IEntity*GetOccupant(){return occupant;}};
+struct CompartmentAccessComponent{bool inside=true;IEntity*owner=nullptr;Seat*seat=nullptr;bool IsInCompartment(){return inside;}IEntity*GetVehicleCompartmentManagerOwner(){return owner;}Seat*GetCompartment(){return seat;}};
+struct InventoryStorageSlot;
+struct BaseInventoryStorageComponent{InventoryStorageSlot*current=nullptr;InventoryStorageSlot*GetSlot(int id){return id==0?current:nullptr;}IEntity*owner=nullptr;int purpose=1;IEntity*GetOwner(){return owner;}int GetPurpose(){return purpose;}};
+struct InventoryStorageSlot{int GetID(){return 0;}BaseInventoryStorageComponent*storage=nullptr;void*container=nullptr;IEntity*attached=nullptr;BaseInventoryStorageComponent*GetStorage(){return storage;}void*GetParentContainer(){return container;}IEntity*GetAttachedEntity(){return attached;}};
+struct InventoryItemComponent{InventoryStorageSlot*slot=nullptr;InventoryStorageSlot*GetParentSlot(){return slot;}};
+namespace EStoragePurpose{constexpr int PURPOSE_UNSET=0,PURPOSE_DEPOSIT=1,PURPOSE_ANY=127;}
+struct IEntity{IEntity*child=nullptr,*sibling=nullptr,*parent=nullptr;InventoryItemComponent*item=nullptr;CompartmentAccessComponent*access=nullptr;bool character=false,geometry=true;vector lo={-1,-.5,-2},hi={1,2,2},origin={100,10,100};float yaw=0;int reads=0;bool GetVObject(){return geometry;}IEntity*GetChildren(){return child;}IEntity*GetSibling(){return sibling;}IEntity*GetParent(){return parent;}void GetBounds(vector&a,vector&b){reads++;a=lo;b=hi;}vector CoordToParent(vector v){return origin+Vector(v.x*std::cos(yaw)+v.z*std::sin(yaw),v.y,-v.x*std::sin(yaw)+v.z*std::cos(yaw));}vector CoordToLocal(vector v){v=v-origin;return{v.x*std::cos(yaw)-v.z*std::sin(yaw),v.y,v.x*std::sin(yaw)+v.z*std::cos(yaw)};}};
+''' + s + r'''
+int main(){using F=ERR_InterceptVehicleFootprint;IEntity root;auto f=F::Capture(&root);assert(f&&f->Valid()&&f->Matches(&root));assert(std::abs(f->Radius()-(std::sqrt(5.f)+.5f))<.001);assert(std::abs(f->Height()-3.5)<.001);IEntity foreign;assert(!f->Matches(&foreign));float radius=f->Radius();root.origin={-700,33,900};root.yaw=.7;assert(f->Matches(&root)&&f->Radius()==radius);
+ for(int bad=0;bad<6;bad++){IEntity e;if(bad==0)e.geometry=false;if(bad==1)e.lo.x=NAN;if(bad==2)e.hi.x=e.lo.x;if(bad==3)e.hi.z=41;if(bad==4)e.origin.x=NAN;if(bad==5)e.hi.y=16;assert(!F::Capture(&e));}assert(!F::Capture(nullptr));
+ IEntity hull,attachment;hull.child=&attachment;attachment.parent=&hull;attachment.lo={-.5,0,-.5};attachment.hi={.5,.5,.5};f=F::Capture(&hull);assert(f&&f->Matches(&hull));attachment.yaw=.8;assert(f->Matches(&hull));attachment.hi.z=9;assert(!f->Matches(&hull));attachment.hi.z=.5;attachment.origin.y=20;assert(!f->Matches(&hull));attachment.origin.y=10;IEntity replacement;hull.child=&replacement;assert(!f->Matches(&hull));hull.child=&attachment;attachment.sibling=&replacement;assert(!f->Matches(&hull));attachment.sibling=nullptr;
+ // Seated body is measured, but a huge carried inventory child is not traversed.
+ IEntity body,carried;Seat seat;CompartmentAccessComponent access;hull.child=&body;body.parent=&hull;body.character=true;body.lo={-.4,0,-.4};body.hi={.4,1.5,.4};body.access=&access;access.owner=&hull;access.seat=&seat;seat.occupant=&body;body.child=&carried;carried.hi.z=999;f=F::Capture(&hull);assert(f&&f->Matches(&hull)&&carried.reads==0);access.inside=false;assert(!f->Matches(&hull));access.inside=true;seat.occupant=&foreign;assert(!F::Capture(&hull));seat.occupant=&body;access.owner=&foreign;assert(!F::Capture(&hull));
+ // Only exact reciprocal in-root deposit cargo is skipped; unknown/foreign is not.
+ IEntity cargo;InventoryItemComponent item;InventoryStorageSlot slot;BaseInventoryStorageComponent storage;hull.child=&cargo;cargo.parent=&hull;cargo.hi.z=999;cargo.item=&item;item.slot=&slot;slot.storage=&storage;slot.container=&storage;slot.attached=&cargo;storage.owner=&hull;storage.current=&slot;f=F::Capture(&hull);assert(f&&f->Matches(&hull)&&cargo.reads==0);storage.current=nullptr;assert(!F::Capture(&hull)&&!f->Matches(&hull));InventoryStorageSlot otherSlot;storage.current=&otherSlot;assert(!F::Capture(&hull)&&!f->Matches(&hull));storage.current=&slot;assert(f->Matches(&hull));slot.attached=&foreign;assert(!F::Capture(&hull));slot.attached=&cargo;storage.purpose=127;assert(!F::Capture(&hull));storage.purpose=1;storage.owner=&foreign;assert(!F::Capture(&hull));
+ IEntity nodes[129];for(int i=0;i<128;i++){nodes[i].child=&nodes[i+1];nodes[i+1].parent=&nodes[i];}assert(!F::Capture(&nodes[0]));nodes[127].child=nullptr;f=F::Capture(&nodes[0]);assert(f&&f->Matches(&nodes[0]));nodes[127].child=&nodes[0];assert(!F::Capture(&nodes[0]));
+}
+''')
+print('PASS: actual immutable measured footprint/root/attachment matching, bounds/transforms, moving geometry, seated-body and cargo reciprocity, finite hierarchy caps; native geometry doubled')
@@ -0,0 +1 @@
PASS: actual immutable measured footprint/root/attachment matching, bounds/transforms, moving geometry, seated-body and cargo reciprocity, finite hierarchy caps; native geometry doubled
@@ -0,0 +1 @@
PASS: actual immutable measured footprint/root/attachment matching, bounds/transforms, moving geometry, seated-body and cargo reciprocity, finite hierarchy caps; native geometry doubled
@@ -0,0 +1,22 @@
/tmp/err-policy-q6zk6ams/check.cpp: In static member function ‘static bool ERR_HQMountedInterceptRules::Current(ERR_ConvoyInterceptOrder*, ERR_HQBelief*, float)’:
/tmp/err-policy-q6zk6ams/check.cpp:168:105: error: request for member ‘Valid’ in ‘order->ERR_ConvoyInterceptOrder::Footprint’, which is of pointer type ‘ERR_InterceptVehicleFootprint*’ (maybe you meant to use ‘->’ ?)
168 | if (order->VehicleIndex != 4 || order->ConfirmedLosses < 6 || !order->Footprint || !order->Footprint.Valid()) return false;
| ^~~~~
Traceback (most recent call last):
File "/tmp/erri-bmp-geometry-1406/tests/test_bmp_intercept_geometry.py", line 26, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<53 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 13, in run
subprocess.run(['g++','-std=c++17','-Wall','-Werror',str(p/'check.cpp'),'-o',str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['g++', '-std=c++17', '-Wall', '-Werror', '/tmp/err-policy-q6zk6ams/check.cpp', '-o', '/tmp/err-policy-q6zk6ams/check']' returned non-zero exit status 1.
@@ -0,0 +1 @@
PASS: actual BMP singleton geometry and typed current order; one survey, full straight segment, width, known history, reservations, settlement/clearance and work caps; placement/footprint tested separately
@@ -0,0 +1,19 @@
check: /tmp/err-policy-o8_rlhlz/check.cpp:137: int main(): Assertion `!f->Matches(&hull)' failed.
Traceback (most recent call last):
File "/tmp/erri-bmp-geometry-1406/tests/test_intercept_vehicle_footprint.py", line 16, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<24 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-o8_rlhlz/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,19 @@
check: /tmp/err-policy-z86_ut5q/check.cpp:469: int main(): Assertion `!Rules::Current(&order,&belief,10000)' failed.
Traceback (most recent call last):
File "/tmp/erri-bmp-geometry-1406/tests/test_bmp_intercept_geometry.py", line 26, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<54 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-z86_ut5q/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,19 @@
check: /tmp/err-policy-1vxz57sa/check.cpp:170: int main(): Assertion `near(game.world.low.y,10.05)&&near(game.world.high.y,15)' failed.
Traceback (most recent call last):
File "/tmp/erri-bmp-geometry-1406/tests/test_convoy_placement.py", line 14, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<59 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-1vxz57sa/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,19 @@
check: /tmp/err-policy-knu6bm2v/check.cpp:142: int main(): Assertion `!F::Capture(&hull)&&!f->Matches(&hull)' failed.
Traceback (most recent call last):
File "/tmp/erri-bmp-geometry-1406/tests/test_intercept_vehicle_footprint.py", line 16, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<25 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-knu6bm2v/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,19 @@
check: /tmp/err-policy-887mq0mq/check.cpp:458: int main(): Assertion `!select()' failed.
Traceback (most recent call last):
File "/tmp/erri-bmp-geometry-1406/tests/test_bmp_intercept_geometry.py", line 26, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<54 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-887mq0mq/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,19 @@
check: /tmp/err-policy-btz3att3/check.cpp:454: int main(): Assertion `!select()' failed.
Traceback (most recent call last):
File "/tmp/erri-bmp-geometry-1406/tests/test_bmp_intercept_geometry.py", line 26, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<54 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-btz3att3/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1 @@
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
@@ -0,0 +1,144 @@
// Conservative native volume check; no scenery removal or alternate placement.
class ERR_ConvoyPlacement
{
protected bool m_Obstructed;
protected int m_Visited;
protected string m_LastReason;
protected float m_LastLowest;
protected float m_LastHighest;
protected IEntity m_FirstObstruction;
protected float m_LastTrace = -1;
float LastTraceFraction() { return m_LastTrace; }
string Diagnostic()
{
string prefab = "none";
vector position;
if (m_FirstObstruction)
{
prefab = "no prefab";
if (m_FirstObstruction.GetPrefabData()) prefab = m_FirstObstruction.GetPrefabData().GetPrefabName();
position = m_FirstObstruction.GetOrigin();
}
return string.Format("%1; terrainY=%2..%3 span=%4; visited=%5; first=%6 at %7; trace=%8",
m_LastReason, m_LastLowest, m_LastHighest, m_LastHighest - m_LastLowest, m_Visited, prefab, position, m_LastTrace);
}
protected bool Occupancy(IEntity entity)
{
m_Visited++;
if (m_Visited > 128) { m_Obstructed = true; m_LastReason = "native query visit cap"; }
else if (entity && entity.GetVObject())
{
m_Obstructed = true; m_LastReason = "native VObject volume overlap"; m_FirstObstruction = entity;
}
return !m_Obstructed;
}
static bool Terrain(float origin, float lowest, float highest)
{
return ERR_ForceRules.Finite(origin) && ERR_ForceRules.Finite(lowest) && ERR_ForceRules.Finite(highest)
&& lowest > 0.3 && highest >= lowest && highest - lowest <= 0.75 && origin >= lowest - 0.3 && origin <= highest + 0.3;
}
bool Clear(vector position, float heading)
{
m_LastReason = "invalid origin or heading"; m_LastLowest = 1e20; m_LastHighest = -1e20;
m_FirstObstruction = null; m_Visited = 0; m_LastTrace = -1;
if (!ERR_ForceRules.FiniteVector(position) || !ERR_ForceRules.Finite(heading)) return false;
float radians = heading * Math.DEG2RAD;
vector forward = Vector(Math.Sin(radians), 0, Math.Cos(radians));
vector right = Vector(forward[2], 0, -forward[0]);
float lowest = 1e20, highest = -1e20;
vector low = position, high = position;
for (int x = -1; x <= 1; x++)
{
for (int z = -1; z <= 1; z++)
{
vector sample = ERR_Actors.Ground(position + right * (x * 2.0) + forward * (z * 4.5));
if (!ERR_ForceRules.FiniteVector(sample)) { m_LastReason = "nonfinite terrain sample"; return false; }
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), sample))
{ m_LastReason = "water at footprint sample"; return false; }
lowest = Math.Min(lowest, sample[1]); highest = Math.Max(highest, sample[1]);
low[0] = Math.Min(low[0], sample[0]); low[2] = Math.Min(low[2], sample[2]);
high[0] = Math.Max(high[0], sample[0]); high[2] = Math.Max(high[2], sample[2]);
}
}
m_LastLowest = lowest; m_LastHighest = highest;
if (!Terrain(position[1], lowest, highest))
{ m_LastReason = "terrain origin/height/span check"; return false; }
low[1] = lowest + 0.05; high[1] = highest + 3.5;
m_Obstructed = false; m_Visited = 0;
GetGame().GetWorld().QueryEntitiesByAABB(low, high, Occupancy);
// Entity bounding boxes can include distant branches or overhead wires.
// Their overlap is diagnostic only; the filled native volume decides collision.
vector center = (low + high) * 0.5;
TraceBox volume = new TraceBox();
volume.Start = center; volume.End = center + "0 0.01 0";
volume.Mins = low - center; volume.Maxs = high - center;
// Nine terrain/water samples already qualify the supporting ground below us.
volume.Flags = TraceFlags.ENTS | TraceFlags.OCEAN;
m_LastTrace = GetGame().GetWorld().TraceMove(volume, null);
m_Obstructed = !ERR_ForceRules.Finite(m_LastTrace) || m_LastTrace < 1;
m_FirstObstruction = null;
if (m_Obstructed)
{
m_FirstObstruction = volume.TraceEnt;
m_LastReason = "native filled volume collision";
if (!ERR_ForceRules.Finite(m_LastTrace)) m_LastReason = "invalid native trace fraction";
}
else m_LastReason = "clear";
return !m_Obstructed;
}
// Measured padded square enclosing the immutable, yaw-independent circle.
bool ClearFootprint(vector position, float heading, ERR_InterceptVehicleFootprint footprint)
{
m_LastReason = "invalid origin or heading"; m_LastLowest = 1e20; m_LastHighest = -1e20;
m_FirstObstruction = null; m_Visited = 0; m_LastTrace = -1;
if (!footprint || !footprint.Valid() || !GetGame() || !GetGame().GetWorld()) return false;
if (!ERR_ForceRules.FiniteVector(position) || !ERR_ForceRules.Finite(heading)) return false;
float radians = heading * Math.DEG2RAD;
vector forward = Vector(Math.Sin(radians), 0, Math.Cos(radians));
vector right = Vector(forward[2], 0, -forward[0]);
float lowest = 1e20, highest = -1e20;
vector low = position, high = position;
float radius = footprint.Radius();
int steps = Math.Ceil(radius);
if (steps % 2 != 0) steps++;
if (steps < 1 || steps > 20) return false;
for (int x = 0; x <= steps; x++)
{
for (int z = 0; z <= steps; z++)
{
vector sample = ERR_Actors.Ground(position + right * (-radius + 2 * radius * x / steps) + forward * (-radius + 2 * radius * z / steps));
if (!ERR_ForceRules.FiniteVector(sample)) { m_LastReason = "nonfinite terrain sample"; return false; }
if (ChimeraWorldUtils.TryGetWaterSurfaceSimple(GetGame().GetWorld(), sample))
{ m_LastReason = "water at footprint sample"; return false; }
lowest = Math.Min(lowest, sample[1]); highest = Math.Max(highest, sample[1]);
low[0] = Math.Min(low[0], sample[0]); low[2] = Math.Min(low[2], sample[2]);
high[0] = Math.Max(high[0], sample[0]); high[2] = Math.Max(high[2], sample[2]);
}
}
m_LastLowest = lowest; m_LastHighest = highest;
if (!Terrain(position[1], lowest, highest))
{ m_LastReason = "terrain origin/height/span check"; return false; }
low[1] = lowest + 0.05; high[1] = highest + footprint.Height();
m_Obstructed = false; m_Visited = 0;
GetGame().GetWorld().QueryEntitiesByAABB(low, high, Occupancy);
// Entity bounding boxes can include distant branches or overhead wires.
// Their overlap is diagnostic only; the filled native volume decides collision.
vector center = (low + high) * 0.5;
TraceBox volume = new TraceBox();
volume.Start = center; volume.End = center + "0 0.01 0";
volume.Mins = low - center; volume.Maxs = high - center;
// At most441 terrain/water samples with spacing <=2m; destination only.
volume.Flags = TraceFlags.ENTS | TraceFlags.OCEAN;
m_LastTrace = GetGame().GetWorld().TraceMove(volume, null);
m_Obstructed = !ERR_ForceRules.Finite(m_LastTrace) || m_LastTrace < 1;
m_FirstObstruction = null;
if (m_Obstructed)
{
m_FirstObstruction = volume.TraceEnt;
m_LastReason = "native filled volume collision";
if (!ERR_ForceRules.Finite(m_LastTrace)) m_LastReason = "invalid native trace fraction";
}
else m_LastReason = "clear";
return !m_Obstructed;
}
}
@@ -0,0 +1,314 @@
// Detached radio order. Only its issuer chooses Position and the fixed deadline.
class ERR_ConvoyInterceptOrder
{
int Token;
SCR_AIGroup Group;
string SourceId;
int VehicleIndex;
bool Armored;
ref ERR_InterceptVehicleFootprint Footprint;
int ConfirmedLosses;
vector Position;
float IssuedAt;
float ExpiresAt;
float ObservedAt;
int BeliefId;
int BeliefRevision;
int ReportId;
}
// Delivered ground history only; no target entity reads, force creation or orders.
class ERR_HQMountedInterceptRules
{
static bool Eligible(ERR_HQBelief belief, float now)
{
if (!ERR_HQRoadIntercept.Eligible(belief, now)) return false;
if (belief.BeliefId <= 0 || belief.BeliefRevision <= 0 || belief.ObservedAt < 0) return false;
return ERR_HQPursuitRules.PositionValid(belief.Position) && ERR_HQPursuitRules.PositionValid(belief.PredictedPosition);
}
static bool Current(ERR_ConvoyInterceptOrder order, ERR_HQBelief belief, float now)
{
if (!order || !Eligible(belief, now) || !order.Group || order.SourceId.IsEmpty()) return false;
if (order.Token < 400001 || order.Token > 400256) return false;
if (order.Armored)
{
if (order.VehicleIndex != 4 || order.ConfirmedLosses < 6 || !order.Footprint || !order.Footprint.Valid()) return false;
}
else if (order.VehicleIndex != 0 && order.VehicleIndex != 3 && order.VehicleIndex != 5) return false;
if (order.BeliefId != belief.BeliefId || order.BeliefRevision != belief.BeliefRevision
|| order.ReportId != belief.MessageId || order.ObservedAt != belief.ObservedAt) return false;
if (!ERR_ForceRules.Finite(order.IssuedAt) || !ERR_ForceRules.Finite(order.ExpiresAt)) return false;
if (order.IssuedAt < belief.LastDeliveredAt || now < order.IssuedAt || now - order.IssuedAt > 30000) return false;
if (order.ExpiresAt <= order.IssuedAt || order.ExpiresAt > order.ObservedAt + 120000 || now >= order.ExpiresAt) return false;
return ERR_HQPursuitRules.PositionValid(order.Position);
}
static bool Ahead(vector origin, vector velocity, vector point)
{
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_ForceRules.FiniteVector(point) || !ERR_HQPursuitRules.Heading(velocity, true)) return false;
vector delta = point - origin; delta[1] = 0; velocity[1] = 0;
float distance = delta.Length();
return distance >= 300 && distance <= 900 && vector.Dot(delta.Normalized(), velocity.Normalized()) >= 0.8;
}
static bool FromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector result, out float score, out float heading)
{
result = vector.Zero; score = 1e20; heading = 0;
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
bool found;
for (int i = 1; i < points.Count(); i++)
{
vector delta = points[i] - points[i - 1]; delta[1] = 0;
float length = delta.Length();
if (length < 0.01) continue;
vector offset = seed - points[i - 1]; offset[1] = 0;
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
float distance = vector.DistanceXZ(seed, candidate);
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result)) { result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true; }
}
return found;
}
static bool Select(ERR_HQBelief belief, float now, array<vector> reserved, out vector result)
{
result = vector.Zero;
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
if (!GetGame() || !GetGame().GetWorld()) return false;
vector velocity = belief.Velocity; velocity[1] = 0;
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
if (!world || !world.GetRoadNetworkManager()) return false;
array<BaseRoad> roads = {};
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
if (roads.Count() > 128) return false;
int segments;
bool found;
vector best;
float bestScore = 1e20;
float bestHeading;
foreach (BaseRoad road : roads)
{
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
array<vector> points = {}; road.GetPoints(points);
if (points.Count() < 2 || points.Count() > 1024) continue;
segments += points.Count() - 1;
if (segments > 4096) return false;
vector candidate;
float score, heading;
if (!FromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, candidate, score, heading)) continue;
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
{ best = candidate; bestScore = score; bestHeading = heading; found = true; }
}
if (!found) return false;
vector grounded = ERR_Actors.Ground(best);
// Never convert an elevated bridge point into terrain beneath the road.
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
if (!settlements.OutsideSettlements(grounded)) return false;
// Existing conservative vehicle footprint: nine terrain/water samples,
// filled collision volume and segment-aligned heading; no scenery changes.
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
if (!placement.Clear(grounded, bestHeading)) return false;
result = grounded;
return true;
}
// Keep both escorts on one real polyline. Horizontal arc length determines
// placement; interpolation retains each road segment's height and heading.
static bool PairFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, out vector first, out vector second, out float score, out float firstHeading, out float secondHeading)
{
first = vector.Zero; second = vector.Zero; score = 1e20; firstHeading = 0; secondHeading = 0;
if (!points || points.Count() < 2 || points.Count() > 1024 || !ERR_ForceRules.FiniteVector(seed)) return false;
if (!ERR_ForceRules.FiniteVector(origin) || !ERR_HQPursuitRules.Heading(velocity, true) || !reserved || reserved.Count() > 128) return false;
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
velocity[1] = 0;
float total; float anchor; float nearest = 1e20;
float along;
vector projected;
bool found;
for (int i = 1; i < points.Count(); i++)
{
vector delta = points[i] - points[i - 1]; delta[1] = 0;
float length = delta.Length();
if (!ERR_ForceRules.Finite(length)) return false;
if (length < 0.01) continue;
vector offset = seed - points[i - 1]; offset[1] = 0;
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), 0, 1);
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
float distance = vector.DistanceXZ(seed, candidate);
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, nearest, projected))
{
projected = candidate; nearest = distance; anchor = total + length * fraction;
along = vector.Dot(delta.Normalized(), velocity.Normalized()); found = true;
}
total += length;
if (!ERR_ForceRules.Finite(total)) return false;
}
if (!found || nearest > 100 || total < 40) return false;
float span = Math.Clamp(total, 40, 60);
float start = anchor - span * 0.5;
// Longitudinal roads extend the pair ahead of the projection, so the first
// point at the 300m planning boundary is not shifted behind that boundary.
if (along >= 0.25) start = anchor;
else if (along <= -0.25) start = anchor - span;
start = Math.Clamp(start, 0, total - span);
float finish = start + span;
float travelled;
vector a, b;
float aHeading; float bHeading;
bool haveFirst; bool haveSecond;
for (int j = 1; j < points.Count(); j++)
{
vector segment = points[j] - points[j - 1]; segment[1] = 0;
float segmentLength = segment.Length();
if (segmentLength < 0.01) continue;
if (!haveFirst && start <= travelled + segmentLength)
{
a = vector.Lerp(points[j - 1], points[j], Math.Clamp((start - travelled) / segmentLength, 0, 1));
aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
}
if (!haveSecond && finish <= travelled + segmentLength)
{
b = vector.Lerp(points[j - 1], points[j], Math.Clamp((finish - travelled) / segmentLength, 0, 1));
bHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveSecond = true;
}
if (haveFirst && haveSecond) break;
travelled += segmentLength;
}
if (!haveFirst || !haveSecond || vector.DistanceXZ(a, b) < 40) return false;
if (vector.DistanceXZ(seed, a) > 100 || vector.DistanceXZ(seed, b) > 100) return false;
if (!Ahead(origin, velocity, a) || !Ahead(origin, velocity, b)) return false;
if (!ERR_HQRoadIntercept.Unreserved(a, reserved) || !ERR_HQRoadIntercept.Unreserved(b, reserved)) return false;
// Canonical order does not depend on native road enumeration direction.
if (ERR_HQRoadIntercept.Better(0, b, 0, a))
{
vector swap = a; a = b; b = swap;
float swapHeading = aHeading; aHeading = bHeading; bHeading = swapHeading;
}
first = a; second = b; score = nearest; firstHeading = aHeading; secondHeading = bHeading;
return true;
}
static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)
{
first = vector.Zero; second = vector.Zero;
if (!Replication.IsServer() || !Eligible(belief, now) || !reserved || reserved.Count() > 128) return false;
if (!GetGame() || !GetGame().GetWorld()) return false;
vector velocity = belief.Velocity; velocity[1] = 0;
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
if (!world || !world.GetRoadNetworkManager()) return false;
array<BaseRoad> roads = {};
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
if (roads.Count() > 128) return false;
int segments;
bool found;
vector bestFirst, bestSecond;
float bestScore = 1e20;
float bestFirstHeading; float bestSecondHeading;
foreach (BaseRoad road : roads)
{
if (!road || !ERR_ForceRules.Finite(road.GetWidth()) || road.GetWidth() < 6) continue;
array<vector> points = {}; road.GetPoints(points);
if (points.Count() < 2 || points.Count() > 1024) continue;
segments += points.Count() - 1;
if (segments > 4096) return false;
vector a, b;
float score; float aHeading; float bHeading;
if (!PairFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, a, b, score, aHeading, bHeading)) continue;
if (!found || ERR_HQRoadIntercept.Better(score, a, bestScore, bestFirst))
{ bestFirst = a; bestSecond = b; bestScore = score; bestFirstHeading = aHeading; bestSecondHeading = bHeading; found = true; }
}
if (!found) return false;
vector groundedFirst = ERR_Actors.Ground(bestFirst);
vector groundedSecond = ERR_Actors.Ground(bestSecond);
if (!ERR_HQPursuitRules.PositionValid(groundedFirst) || !ERR_HQPursuitRules.PositionValid(groundedSecond)) return false;
if (Math.AbsFloat(groundedFirst[1] - bestFirst[1]) > 2 || Math.AbsFloat(groundedSecond[1] - bestSecond[1]) > 2) return false;
if (vector.DistanceXZ(groundedFirst, groundedSecond) < 40) return false;
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
if (!settlements.OutsideSettlements(groundedFirst) || !settlements.OutsideSettlements(groundedSecond)) return false;
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
if (!placement.Clear(groundedFirst, bestFirstHeading) || !placement.Clear(groundedSecond, bestSecondHeading)) return false;
first = groundedFirst; second = groundedSecond;
return true;
}
// One actual straight segment must support the full retained square. No arc
// interpolation over bends or short adjoining segments; conservative refusal.
static bool ArmoredFromPolyline(array<vector> points, vector origin, vector velocity, vector seed, array<vector> reserved, ERR_InterceptVehicleFootprint footprint, out vector result, out float score, out float heading, out vector supportA, out vector supportB)
{
result = vector.Zero; supportA = vector.Zero; supportB = vector.Zero; score = 1e20; heading = 0;
if (!footprint || !footprint.Valid() || !points || points.Count() < 2 || points.Count() > 1024) return false;
if (!ERR_ForceRules.FiniteVector(seed) || !reserved || reserved.Count() > 128) return false;
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
float radius = footprint.Radius();
bool found;
for (int i = 1; i < points.Count(); i++)
{
vector delta = points[i] - points[i - 1]; delta[1] = 0;
float length = delta.Length();
if (!ERR_ForceRules.Finite(length) || length < 2 * radius) continue;
vector offset = seed - points[i - 1]; offset[1] = 0;
float fraction = Math.Clamp(vector.Dot(offset, delta) / (length * length), radius / length, 1 - radius / length);
vector candidate = vector.Lerp(points[i - 1], points[i], fraction);
float distance = vector.DistanceXZ(seed, candidate);
if (distance > 100 || !Ahead(origin, velocity, candidate) || !ERR_HQRoadIntercept.Unreserved(candidate, reserved)) continue;
if (!found || ERR_HQRoadIntercept.Better(distance, candidate, score, result))
{
result = candidate; score = distance; heading = Math.Atan2(delta[0], delta[2]) * Math.RAD2DEG; found = true;
supportA = vector.Lerp(points[i - 1], points[i], fraction - radius / length);
supportB = vector.Lerp(points[i - 1], points[i], fraction + radius / length);
}
}
return found;
}
static bool SelectArmored(ERR_HQBelief belief, float now, array<vector> reserved, ERR_InterceptVehicleFootprint footprint, out vector result)
{
result = vector.Zero;
if (!Replication.IsServer() || !Eligible(belief, now) || !footprint || !footprint.Valid() || !reserved || reserved.Count() > 128) return false;
if (!GetGame() || !GetGame().GetWorld()) return false;
vector velocity = belief.Velocity; velocity[1] = 0;
vector seed = belief.PredictedPosition + velocity.Normalized() * Math.Clamp(velocity.Length() * 60, 300, 900);
if (!ERR_HQPursuitRules.PositionValid(seed)) return false;
SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());
if (!world || !world.GetRoadNetworkManager()) return false;
array<BaseRoad> roads = {};
world.GetRoadNetworkManager().GetRoadsInAABB(seed + "-100 -1000 -100", seed + "100 1000 100", roads);
if (roads.Count() > 128) return false;
int segments;
bool found;
vector best, bestSupportA, bestSupportB;
float bestScore = 1e20;
float bestHeading;
foreach (BaseRoad road : roads)
{
if (!road) return false;
float width = road.GetWidth();
if (!ERR_ForceRules.Finite(width)) return false;
array<vector> points = {}; road.GetPoints(points);
if (points.Count() < 2 || points.Count() > 1024) return false;
segments += points.Count() - 1;
if (segments > 4096) return false;
foreach (vector point : points) if (!ERR_ForceRules.FiniteVector(point)) return false;
if (width < Math.Max(6, 2 * footprint.Radius())) continue;
vector candidate, supportA, supportB;
float score, heading;
if (!ArmoredFromPolyline(points, belief.PredictedPosition, velocity, seed, reserved, footprint, candidate, score, heading, supportA, supportB)) continue;
if (!found || ERR_HQRoadIntercept.Better(score, candidate, bestScore, best))
{ best = candidate; bestScore = score; bestHeading = heading; bestSupportA = supportA; bestSupportB = supportB; found = true; }
}
if (!found) return false;
vector grounded = ERR_Actors.Ground(best);
if (!ERR_HQPursuitRules.PositionValid(grounded) || Math.AbsFloat(grounded[1] - best[1]) > 2) return false;
vector groundedA = ERR_Actors.Ground(bestSupportA);
vector groundedB = ERR_Actors.Ground(bestSupportB);
if (!ERR_HQPursuitRules.PositionValid(groundedA) || !ERR_HQPursuitRules.PositionValid(groundedB)) return false;
if (Math.AbsFloat(groundedA[1] - bestSupportA[1]) > 2 || Math.AbsFloat(groundedB[1] - bestSupportB[1]) > 2) return false;
ERR_SettlementSafety settlements = new ERR_SettlementSafety();
if (!settlements.OutsideSettlements(grounded)) return false;
ERR_ConvoyPlacement placement = new ERR_ConvoyPlacement();
if (!placement.ClearFootprint(grounded, bestHeading, footprint)) return false;
result = grounded;
return true;
}
}
@@ -0,0 +1,116 @@
// Exact original vehicle geometry, measured once; no prefab dimension guesses.
// A horizontal circle is conservative for any arrival yaw. The enclosing square
// used by road/terrain checks deliberately refuses narrower roads.
class ERR_InterceptVehicleFootprint
{
private IEntity m_Root;
private ref array<IEntity> m_Nodes = {};
private float m_Radius;
private float m_Low;
private float m_High;
float Radius() { return m_Radius; }
float Height() { return m_High - Math.Min(m_Low, 0); }
bool Valid()
{
return m_Root && m_Nodes && m_Nodes.Count() > 0 && m_Nodes.Count() <= 128 && m_Nodes[0] == m_Root
&& ERR_ForceRules.Finite(m_Radius) && m_Radius > 0.5 && m_Radius <= 20
&& ERR_ForceRules.Finite(m_Low) && ERR_ForceRules.Finite(m_High)
&& m_High > m_Low && Height() > 0 && Height() <= 15;
}
protected static bool PartOf(IEntity entity, IEntity root)
{
for (int depth = 0; entity && depth < 32; depth++)
{
if (entity == root) return true;
entity = entity.GetParent();
}
return false;
}
protected static bool StoredCargo(IEntity entity, IEntity root)
{
InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
if (!item || !item.GetParentSlot()) return false;
InventoryStorageSlot slot = item.GetParentSlot();
BaseInventoryStorageComponent storage = slot.GetStorage();
if (!storage || storage.GetSlot(slot.GetID()) != slot || !PartOf(storage.GetOwner(), root) || slot.GetParentContainer() != storage || slot.GetAttachedEntity() != entity) return false;
int purpose = storage.GetPurpose();
return purpose != EStoragePurpose.PURPOSE_UNSET && purpose != EStoragePurpose.PURPOSE_ANY && (purpose & EStoragePurpose.PURPOSE_DEPOSIT) != 0;
}
protected static bool Measure(IEntity root, array<IEntity> nodes, out float radius, out float low, out float high)
{
radius = 0; low = 1e20; high = -1e20;
if (!root || !root.GetVObject() || !nodes || !nodes.IsEmpty()) return false;
nodes.Insert(root);
for (int i = 0; i < nodes.Count(); i++)
{
IEntity entity = nodes[i];
if (!entity) return false;
bool character = ChimeraCharacter.Cast(entity) != null;
if (character)
{
CompartmentAccessComponent access = CompartmentAccessComponent.Cast(entity.FindComponent(CompartmentAccessComponent));
if (!access || !access.IsInCompartment() || !access.GetCompartment() || access.GetCompartment().GetOccupant() != entity
|| !PartOf(access.GetVehicleCompartmentManagerOwner(), root)) return false;
}
else if (entity != root && StoredCargo(entity, root)) continue;
if (entity.GetVObject())
{
vector minimum, maximum; entity.GetBounds(minimum, maximum);
if (!ERR_ForceRules.FiniteVector(minimum) || !ERR_ForceRules.FiniteVector(maximum)) return false;
for (int axis = 0; axis < 3; axis++)
if (maximum[axis] < minimum[axis] || (entity == root && maximum[axis] <= minimum[axis]) || maximum[axis] - minimum[axis] > 40) return false;
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 2; y++)
{
for (int z = 0; z < 2; z++)
{
vector corner = Vector(minimum[0], minimum[1], minimum[2]);
if (x) corner[0] = maximum[0];
if (y) corner[1] = maximum[1];
if (z) corner[2] = maximum[2];
vector worldPoint = entity.CoordToParent(corner);
if (!ERR_ForceRules.FiniteVector(worldPoint)) return false;
vector localPoint = root.CoordToLocal(worldPoint);
if (!ERR_ForceRules.FiniteVector(localPoint)) return false;
float reach = Math.Sqrt(localPoint[0] * localPoint[0] + localPoint[2] * localPoint[2]);
if (!ERR_ForceRules.Finite(reach)) return false;
radius = Math.Max(radius, reach);
low = Math.Min(low, localPoint[1]); high = Math.Max(high, localPoint[1]);
}
}
}
}
// Retain/measure the seated body, but never its carried inventory subtree.
if (character) continue;
IEntity child = entity.GetChildren();
while (child)
{
if (nodes.Count() >= 128 || nodes.Contains(child)) return false;
nodes.Insert(child); child = child.GetSibling();
}
}
return radius > 0 && radius <= 19.5 && high > low && high - Math.Min(low, 0) <= 14;
}
static ERR_InterceptVehicleFootprint Capture(IEntity root)
{
ERR_InterceptVehicleFootprint footprint = new ERR_InterceptVehicleFootprint();
float radius, low, high;
if (!Measure(root, footprint.m_Nodes, radius, low, high)) return null;
footprint.m_Root = root; footprint.m_Radius = radius + 0.5;
footprint.m_Low = low - 0.5; footprint.m_High = high + 0.5;
if (!footprint.Valid()) return null;
return footprint;
}
bool Matches(IEntity root)
{
if (!Valid() || root != m_Root) return false;
array<IEntity> nodes = {};
float radius, low, high;
if (!Measure(root, nodes, radius, low, high) || nodes.Count() != m_Nodes.Count()) return false;
foreach (IEntity entity : nodes) if (!m_Nodes.Contains(entity)) return false;
// Moving turret/attachment bounds may change within this immutable envelope.
// Keep at least 25cm horizontal clearance; never enlarge a pending order.
return radius <= m_Radius - 0.25 && low >= m_Low && high <= m_High;
}
}
@@ -0,0 +1,84 @@
"""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_MOUNTED_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c'))
source=p.read_text()
base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
source='class ERR_HQRoadIntercept\n{\n'+'\n'.join(body(base,x) for x in ('static bool Eligible(', 'static bool Unreserved(', 'static bool Better('))+'\n}\n'+source
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('ref ','').replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
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','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement','ERR_InterceptVehicleFootprint'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
for name in ('belief','world','road','body','order','settlements','placement','footprint'):
s=s.replace(name+'.',name+'->')
s=s.replace('Footprint.', 'Footprint->')
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','ERR_HQRoadIntercept'):
s=s.replace(name+'.',name+'::')
s=s.replace('null','nullptr').replace('string SourceId','std::string SourceId').replace('.IsEmpty()', '.empty()')
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('float score, heading;', 'float score=0, heading=0;').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 SCR_AIGroup{};struct ERR_InterceptVehicleFootprint{bool valid=true;float radius=4;bool Valid(){return valid;}float Radius(){return radius;}};
struct Math{static float Max(float a,float b){return std::max(a,b);}static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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,BeliefId=3,BeliefRevision=2;vector Position={1000,10,1000};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=6;Array<vector>points={{900,10,1300},{1100,10,1300}};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;}};
struct ERR_SettlementSafety{static inline bool clear=true;static inline int calls=0;bool OutsideSettlements(vector){calls++;return clear;}};
struct ERR_ConvoyPlacement{static inline bool clear=true;static inline int calls=0;static inline float heading=0;bool ClearFootprint(vector p,float h,ERR_InterceptVehicleFootprint*){return Clear(p,h);}bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear;}};
''' + s + r'''
int main(){
using Rules=ERR_HQMountedInterceptRules;ERR_HQBelief belief;ERR_InterceptVehicleFootprint footprint;SCR_AIGroup group;
Array<vector> reserved;BaseRoad road;road.width=8;game.ai.manager.roads={&road};vector result={1,2,3};
auto select=[&](){int q=game.ai.manager.queries;bool ok=Rules::SelectArmored(&belief,10000,reserved,&footprint,result);assert(game.ai.manager.queries-q==1);if(!ok)assert(result==vector::Zero);return ok;};
assert(select()&&result==vector(1000,10,1300));
road.width=7.999;assert(!select());road.width=8;
road.points={{997,10,1300},{1000,10,1300},{1000,10,1303}};assert(!select());
road.points={{996,10,1300},{1004,10,1300}};assert(select());
road.points={{996,20,1300},{1004,20,1300}};assert(!select());
road.points={{996,7,1300},{1004,13,1300}};assert(!select()); // Center matches terrain but footprint ends do not.
road.points={{900,10,1300},{1100,10,1300}};
ERR_ConvoyPlacement::clear=false;assert(!select());ERR_ConvoyPlacement::clear=true;
ERR_SettlementSafety::clear=false;assert(!select());ERR_SettlementSafety::clear=true;
reserved={{1000,10,1379.99f}};assert(!select());reserved={{1000,10,1380}};assert(select());reserved.clear();
footprint.valid=false;int q=game.ai.manager.queries;assert(!Rules::SelectArmored(&belief,10000,reserved,&footprint,result)&&q==game.ai.manager.queries);footprint.valid=true;
auto saved=belief;belief.HasVelocity=false;assert(!Rules::SelectArmored(&belief,10000,reserved,&footprint,result)&&q==game.ai.manager.queries);belief=saved;
road.width=NAN;assert(!select());road.width=8;road.points[0].x=NAN;assert(!select());road.points[0].x=900;
game.ai.manager.roads.assign(129,&road);assert(!select());game.ai.manager.roads={&road};
BaseRoad many;many.width=1;many.points.assign(1024,{1000,10,1300});game.ai.manager.roads={&road,&many,&many,&many,&many,&many};assert(!select());
game.ai.manager.roads={&road};ERR_ConvoyInterceptOrder order;order.Token=400001;order.Group=&group;order.SourceId="original-convoy";order.VehicleIndex=4;order.Armored=true;order.Footprint=&footprint;order.ConfirmedLosses=6;order.Position={1000,10,1300};order.IssuedAt=8000;order.ExpiresAt=125000;order.ObservedAt=5000;order.BeliefId=3;order.BeliefRevision=2;order.ReportId=1;
assert(Rules::Current(&order,&belief,10000));order.ConfirmedLosses=5;assert(!Rules::Current(&order,&belief,10000));order.ConfirmedLosses=6;
order.Armored=false;assert(!Rules::Current(&order,&belief,10000));order.Armored=true;
order.VehicleIndex=0;assert(!Rules::Current(&order,&belief,10000));order.VehicleIndex=4;
footprint.valid=false;assert(!Rules::Current(&order,&belief,10000));footprint.valid=true;order.Footprint=nullptr;assert(!Rules::Current(&order,&belief,10000));
}
''')
print('PASS: actual BMP singleton geometry and typed current order; one survey, full straight segment, width, known history, reservations, settlement/clearance and work caps; placement/footprint tested separately')
@@ -0,0 +1,77 @@
"""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::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')
@@ -0,0 +1,103 @@
"""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_MOUNTED_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c'))
source=p.read_text()
base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
source='class ERR_HQRoadIntercept\n{\n'+'\n'.join(body(base,x) for x in ('static bool Eligible(', 'static bool Unreserved(', 'static bool Better('))+'\n}\n'+source
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('ref ','').replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
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','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement','ERR_InterceptVehicleFootprint'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
for name in ('belief','world','road','body','order','settlements','placement','footprint'):
s=s.replace(name+'.',name+'->')
s=s.replace('Footprint.', 'Footprint->')
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','ERR_HQRoadIntercept'):
s=s.replace(name+'.',name+'::')
s=s.replace('null','nullptr').replace('string SourceId','std::string SourceId').replace('.IsEmpty()', '.empty()')
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('float score, heading;', 'float score=0, heading=0;').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 SCR_AIGroup{};struct ERR_InterceptVehicleFootprint{bool valid=true;float radius=4;bool Valid(){return valid;}float Radius(){return radius;}};
struct Math{static float Max(float a,float b){return std::max(a,b);}static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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,BeliefId=3,BeliefRevision=2;vector Position={1000,10,1000};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=6;Array<vector>points={{900,10,1300},{1100,10,1300}};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;}};
struct ERR_SettlementSafety{static inline bool clear=true;static inline int calls=0;bool OutsideSettlements(vector){calls++;return clear;}};
struct ERR_ConvoyPlacement{static inline bool clear=true;static inline int calls=0;static inline float heading=0;bool ClearFootprint(vector p,float h,ERR_InterceptVehicleFootprint*){return Clear(p,h);}bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear;}};
''' + s + r'''
int main(){
using Rules=ERR_HQMountedInterceptRules;ERR_HQBelief belief;SCR_AIGroup group;
assert(Rules::Eligible(&belief,10000));
ERR_ConvoyInterceptOrder order;order.Token=400001;order.Group=&group;order.SourceId="original-convoy";order.VehicleIndex=0;order.Position={1000,10,1300};order.IssuedAt=8000;order.ExpiresAt=125000;order.ObservedAt=5000;order.BeliefId=3;order.BeliefRevision=2;order.ReportId=1;
vector frozen=order.Position;assert(Rules::Current(&order,&belief,10000));belief.PredictedPosition={1050,10,1000};assert(Rules::Current(&order,&belief,11000)&&order.Position==frozen);belief.PredictedPosition={1000,10,1000};
for(int fault=0;fault<21;fault++){auto b=belief;auto o=order;float now=10000;
if(fault==0)o.Token=400000;
if(fault==1)o.Token=400257;
if(fault==2)o.Group=nullptr;
if(fault==3)o.SourceId="";
if(fault==4)o.VehicleIndex=1;
if(fault==5)o.BeliefId++;
if(fault==6)o.BeliefRevision++;
if(fault==7)o.ReportId++;
if(fault==8)o.ObservedAt++;
if(fault==9)o.IssuedAt=6000;
if(fault==10)o.IssuedAt=11000;
if(fault==11)o.ExpiresAt=125001;
if(fault==12)o.ExpiresAt=10000;
if(fault==13)o.IssuedAt=NAN;
if(fault==14)o.Position.x=NAN;
if(fault==15)b.HasVelocity=false;
if(fault==16)b.SampleCount=1;
if(fault==17)b.LastDeliveredAt=10001;
if(fault==18)b.TravelMode="aircraft";
if(fault==19)now=38001;
if(fault==20)b.ObservedAt=-1;
assert(!Rules::Current(&o,&b,now));}
order.VehicleIndex=3;assert(Rules::Current(&order,&belief,38000));order.VehicleIndex=5;assert(Rules::Current(&order,&belief,38000));
assert(!Rules::Eligible(&belief,50001));assert(!Rules::Eligible(nullptr,10000));
Array<vector> reserved;vector result;BaseRoad road;game.ai.manager.roads={&road};
assert(Rules::Select(&belief,10000,reserved,result)&&result==vector(1000,10,1300));assert(ERR_ConvoyPlacement::calls==1&&ERR_SettlementSafety::calls==1);assert(std::abs(ERR_ConvoyPlacement::heading-90)<.001);
road.width=5.99;assert(!Rules::Select(&belief,10000,reserved,result)&&result==vector::Zero);road.width=6;
int placementCalls=ERR_ConvoyPlacement::calls;ERR_SettlementSafety::clear=false;assert(!Rules::Select(&belief,10000,reserved,result)&&result==vector::Zero&&ERR_ConvoyPlacement::calls==placementCalls);ERR_SettlementSafety::clear=true;
ERR_ConvoyPlacement::clear=false;assert(!Rules::Select(&belief,10000,reserved,result)&&result==vector::Zero);ERR_ConvoyPlacement::clear=true;
road.points={{900,20,1300},{1100,20,1300}};placementCalls=ERR_ConvoyPlacement::calls;assert(!Rules::Select(&belief,10000,reserved,result)&&ERR_ConvoyPlacement::calls==placementCalls);road.points={{900,10,1300},{1100,10,1300}};
reserved.Insert({1000,10,1300});assert(!Rules::Select(&belief,10000,reserved,result));reserved.clear();
game.ai.manager.roads.resize(129,&road);assert(!Rules::Select(&belief,10000,reserved,result));game.ai.manager.roads={&road};
auto unknown=belief;unknown.HasVelocity=false;int queries=game.ai.manager.queries;assert(!Rules::Select(&unknown,10000,reserved,result)&&game.ai.manager.queries==queries);
Replication::server=false;assert(!Rules::Select(&belief,10000,reserved,result)&&game.ai.manager.queries==queries);Replication::server=true;
float score,heading;assert(Rules::FromPolyline({{900,10,1300},{1100,10,1300}},belief.Position,belief.Velocity,{1000,10,1350},reserved,result,score,heading)&&result.z==1300);
assert(!Rules::Ahead(belief.Position,belief.Velocity,{1000,10,1299}));assert(!Rules::Ahead(belief.Position,belief.Velocity,{1000,10,1901}));
Array<vector> oversized(1025,{1000,10,1300});assert(!Rules::FromPolyline(oversized,belief.Position,belief.Velocity,{1000,10,1350},reserved,result,score,heading));
}
''')
print('PASS: actual mounted intercept eligibility/order chronology, fixed destinations, native road selection bounds, width, segment heading, clearance and settlement delegation; no native driving claim')
@@ -0,0 +1,134 @@
"""Actual paired road geometry and bounded native survey; native boundaries are doubled."""
from pathlib import Path
import os,re
from policy_harness import ROOT,body,run
p=Path(os.environ.get('ERR_MOUNTED_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c'))
source=p.read_text()
base=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
source='class ERR_HQRoadIntercept\n{\n'+'\n'.join(body(base,x) for x in ('static bool Eligible(', 'static bool Unreserved(', 'static bool Better('))+'\n}\n'+source
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('ref ','').replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept').replace('class ERR_HQMountedInterceptRules','struct ERR_HQMountedInterceptRules').replace('class ERR_ConvoyInterceptOrder','struct ERR_ConvoyInterceptOrder')
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','SCR_AIGroup','ERR_ConvoyInterceptOrder','ERR_SettlementSafety','ERR_ConvoyPlacement','ERR_InterceptVehicleFootprint'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
for name in ('belief','world','road','body','order','settlements','placement','footprint'):
s=s.replace(name+'.',name+'->')
s=s.replace('Footprint.', 'Footprint->')
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','ERR_HQRoadIntercept'):
s=s.replace(name+'.',name+'::')
s=s.replace('null','nullptr').replace('string SourceId','std::string SourceId').replace('.IsEmpty()', '.empty()')
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('float score, heading;', 'float score=0, heading=0;').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 SCR_AIGroup{};struct ERR_InterceptVehicleFootprint{bool valid=true;float radius=4;bool Valid(){return valid;}float Radius(){return radius;}};
struct Math{static float Max(float a,float b){return std::max(a,b);}static constexpr float RAD2DEG=57.2957795131f;static float Atan2(float x,float y){return std::atan2(x,y);}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,BeliefId=3,BeliefRevision=2;vector Position={1000,10,1000};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{static inline int reads=0;float width=6;Array<vector>points={{900,10,1300},{1100,10,1300}};float GetWidth(){return width;}void GetPoints(Array<vector>&p){reads++;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 float rightHeight=10;static vector Ground(vector p){p.y=p.x>1000?rightHeight:10;return p;}};
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
struct ERR_SettlementSafety{static inline bool clear=true,rightClear=true;static inline int calls=0;bool OutsideSettlements(vector p){calls++;return clear&&(p.x<=1000||rightClear);}};
struct ERR_ConvoyPlacement{static inline bool clear=true,rightClear=true,water=false;static inline int calls=0;static inline float heading=0;bool ClearFootprint(vector p,float h,ERR_InterceptVehicleFootprint*){return Clear(p,h);}bool Clear(vector p,float h){calls++;heading=h;assert(p.y==10);return clear&&!water&&(p.x<=1000||rightClear);}};
''' + s + r'''
int main(){
using Rules=ERR_HQMountedInterceptRules;
ERR_HQBelief belief;Array<vector> reserved;vector a={1,2,3},b={4,5,6};float score=0,ha=0,hb=0;
vector origin={1000,10,1000},velocity={0,0,4},seed={1000,10,1300};
auto pair=[&](Array<vector> p){return Rules::PairFromPolyline(p,origin,velocity,seed,reserved,a,b,score,ha,hb);};
auto empty=[&](){assert(a==vector::Zero&&b==vector::Zero);};
// Real arc interpolation over a90-degree bend, not straight interpolation
// between the road's endpoints (which would put both escorts off the road).
assert(pair({{900,10,1300},{1000,10,1300},{1000,10,1400}}));
assert(a==vector(970,10,1300)&&b==vector(1000,10,1330));
assert(std::abs(ha-90)<.001&&std::abs(hb)<.001);
assert(vector::DistanceXZ(a,b)>42&&vector::DistanceXZ(a,b)<43);
// Arc span alone is insufficient on a bend-back/hairpin.
assert(!pair({{970,10,1300},{1000,10,1300},{1000,10,1301},{970,10,1301}}));empty();
assert(!pair({{980,10,1300},{1019.9f,10,1300}}));empty();
assert(pair({{980,10,1300},{1020,10,1300}})&&vector::DistanceXZ(a,b)==40);
// A road aligned with travel must not push a partner behind the300m floor.
assert(pair({{1000,10,1250},{1000,10,1450}}));
assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
assert(pair({{1000,10,1450},{1000,10,1250}}));
assert(a==vector(1000,10,1300)&&b==vector(1000,10,1360));
// Actual sloped segment retains height for subsequent bridge validation.
assert(pair({{900,5,1300},{1100,15,1300}}));
assert(a==vector(970,8.5,1300)&&b==vector(1030,11.5,1300));
// Degenerate segments are harmless but a wholly degenerate road has no span.
assert(pair({{900,10,1300},{900,10,1300},{1100,10,1300}}));
assert(!pair({{1000,10,1300},{1000,10,1300}}));empty();
assert(!pair({{900,10,1300},{NAN,10,1300},{1100,10,1300}}));empty();
assert(!pair(Array<vector>(1025,{1000,10,1300})));empty();
assert(!pair({}));empty();assert(!pair({{1000,10,1300}}));empty();
auto good=Array<vector>{{900,10,1300},{1100,10,1300}};
velocity={0,0,0};assert(!pair(good));empty();velocity={0,0,4};
seed.x=NAN;assert(!pair(good));empty();seed={1000,10,1300};
origin.x=NAN;assert(!pair(good));empty();origin={1000,10,1000};
reserved={{970,10,1380}};assert(pair(good));
reserved={{970,10,1379.99f}};assert(!pair(good));empty();
reserved={{NAN,10,1380}};assert(!pair(good));empty();
reserved=Array<vector>(129,{10000,10,10000});assert(!pair(good));empty();reserved.clear();
for(float z:{1290.f,1901.f,700.f}){seed.z=z;assert(!pair({{900,10,z},{1100,10,z}}));empty();}seed.z=1300;
BaseRoad road;road.points=good;game.ai.manager.roads={&road};
int q=game.ai.manager.queries;
assert(Rules::SelectPair(&belief,10000,reserved,a,b));assert(game.ai.manager.queries==q+1);
assert(a==vector(970,10,1300)&&b==vector(1030,10,1300));
assert(ERR_SettlementSafety::calls==2&&ERR_ConvoyPlacement::calls==2);
auto select=[&](){a={1,2,3};b={4,5,6};bool ok=Rules::SelectPair(&belief,10000,reserved,a,b);if(!ok)empty();return ok;};
// Second position cannot escape settlement, water/collision or bridge checks.
int pc=ERR_ConvoyPlacement::calls;ERR_SettlementSafety::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_SettlementSafety::rightClear=true;
pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::rightClear=false;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+2);ERR_ConvoyPlacement::rightClear=true;
pc=ERR_ConvoyPlacement::calls;ERR_ConvoyPlacement::water=true;assert(!select());assert(ERR_ConvoyPlacement::calls==pc+1);ERR_ConvoyPlacement::water=false;
pc=ERR_ConvoyPlacement::calls;ERR_Actors::rightHeight=7;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);ERR_Actors::rightHeight=10;
road.points={{900,20,1300},{1100,20,1300}};assert(!select());road.points=good;
// Reject a pair assembled from two short unrelated roads, even when their
// endpoints could have formed a geometrically spaced chord.
BaseRoad left,right;left.points={{960,10,1300},{980,10,1300}};right.points={{1020,10,1300},{1040,10,1300}};game.ai.manager.roads={&left,&right};assert(!select());
game.ai.manager.roads={&road};road.width=5.99;assert(!select());road.width=NAN;assert(!select());road.width=6;
// All pre-query gates remain cheap; one permitted pair call is one query.
q=game.ai.manager.queries;belief.HasVelocity=false;assert(!select());belief.HasVelocity=true;
Replication::server=false;assert(!select());Replication::server=true;
reserved.resize(129);assert(!select());reserved.clear();
assert(game.ai.manager.queries==q);
game.ai.available=false;assert(!select());game.ai.available=true;assert(game.ai.manager.queries==q);
// Road count bound refuses before any point arrays are read.
game.ai.manager.roads=Array<BaseRoad*>(129,&road);int reads=BaseRoad::reads;assert(!select());assert(BaseRoad::reads==reads);
game.ai.manager.roads=Array<BaseRoad*>(128,&road);assert(select());assert(BaseRoad::reads==reads+128);
// Total4096 segment allowance includes irrelevant/degenerate vertices; an
// earlier valid pair cannot leak through once later roads exhaust the cap.
BaseRoad big,tail;big.points=Array<vector>(1024,{900,10,1300});big.points.back()={1100,10,1300};tail.points=Array<vector>(5,{900,10,1300});tail.points.back()={1100,10,1300};
game.ai.manager.roads={&big,&big,&big,&big,&tail};assert(select());
tail.points.insert(tail.points.begin(),{900,10,1300});pc=ERR_ConvoyPlacement::calls;assert(!select());assert(ERR_ConvoyPlacement::calls==pc);
// Pair failure still permits unchanged singleton selection in a separate
// call; this explicitly consumes the caller's second survey budget.
road.points={{995,10,1300},{1005,10,1300}};game.ai.manager.roads={&road};q=game.ai.manager.queries;
assert(!select());vector singleton;assert(Rules::Select(&belief,10000,reserved,singleton));assert(singleton==vector(1000,10,1300)&&game.ai.manager.queries==q+2);
}
''')
print('PASS: actual paired arc geometry,40m/80m spacing, separate original polylines, both-position validation and128road/4096segment budget; no native driving proof')
@@ -0,0 +1,45 @@
"""Execute full captured footprint class with explicit native bounds/hierarchy doubles."""
from pathlib import Path
import os,re
from policy_harness import ROOT,run
p=Path(os.environ.get('ERR_INTERCEPT_FOOTPRINT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InterceptVehicleFootprint.c'))
s=p.read_text();assert 'private float m_Radius;' in s and 'private IEntity m_Root;' in s
s=re.sub(r'//[^\n]*','',s).replace('class ERR_InterceptVehicleFootprint','struct ERR_InterceptVehicleFootprint').replace('private ','').replace('protected ','').replace('ref ','')
s=s.replace('ChimeraCharacter.Cast(entity) != null','entity->character').replace('CompartmentAccessComponent.Cast(entity.FindComponent(CompartmentAccessComponent))','entity->access').replace('InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent))','entity->item')
s=s.replace('array<IEntity>','Array<IEntity*>').replace('Array<IEntity*> nodes,','Array<IEntity*>& nodes,').replace('foreach (','for (').replace('out float','float&').replace('vector minimum, maximum;','vector minimum{}, maximum{};').replace('float radius, low, high;','float radius=0,low=0,high=0;')
for cls in ['IEntity','ERR_InterceptVehicleFootprint','CompartmentAccessComponent','InventoryItemComponent','InventoryStorageSlot','BaseInventoryStorageComponent']:
s=re.sub(r'\b'+cls+r' (\w+)',cls+r'* \1',s)
for n in ['root','entity','child','item','slot','storage','access','footprint']:s=s.replace(n+'.',n+'->')
s=s.replace('m_Nodes[0]','m_Nodes[0]').replace('GetCompartment().','GetCompartment()->')
for n in ['ERR_ForceRules','Math','EStoragePurpose']:s=s.replace(n+'.',n+'::')
s=s.replace('null','nullptr').replace('\n}\n','\n};\n');s=re.sub(r'\b(float|int|bool) (\w+);',r'\1 \2{};',s)
run(r'''
#include <cassert>
#include <cmath>
#include <algorithm>
#include <vector>
struct vector{float x=0,y=0,z=0;float&operator[](int n){return n==0?x:n==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 Vector(float x,float y,float z){return{x,y,z};}
namespace Math{float Sqrt(float n){return std::sqrt(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);}}
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}explicit operator bool(){return true;}};
struct IEntity;struct Seat{IEntity*occupant=nullptr;IEntity*GetOccupant(){return occupant;}};
struct CompartmentAccessComponent{bool inside=true;IEntity*owner=nullptr;Seat*seat=nullptr;bool IsInCompartment(){return inside;}IEntity*GetVehicleCompartmentManagerOwner(){return owner;}Seat*GetCompartment(){return seat;}};
struct InventoryStorageSlot;
struct BaseInventoryStorageComponent{InventoryStorageSlot*current=nullptr;InventoryStorageSlot*GetSlot(int id){return id==0?current:nullptr;}IEntity*owner=nullptr;int purpose=1;IEntity*GetOwner(){return owner;}int GetPurpose(){return purpose;}};
struct InventoryStorageSlot{int GetID(){return 0;}BaseInventoryStorageComponent*storage=nullptr;void*container=nullptr;IEntity*attached=nullptr;BaseInventoryStorageComponent*GetStorage(){return storage;}void*GetParentContainer(){return container;}IEntity*GetAttachedEntity(){return attached;}};
struct InventoryItemComponent{InventoryStorageSlot*slot=nullptr;InventoryStorageSlot*GetParentSlot(){return slot;}};
namespace EStoragePurpose{constexpr int PURPOSE_UNSET=0,PURPOSE_DEPOSIT=1,PURPOSE_ANY=127;}
struct IEntity{IEntity*child=nullptr,*sibling=nullptr,*parent=nullptr;InventoryItemComponent*item=nullptr;CompartmentAccessComponent*access=nullptr;bool character=false,geometry=true;vector lo={-1,-.5,-2},hi={1,2,2},origin={100,10,100};float yaw=0;int reads=0;bool GetVObject(){return geometry;}IEntity*GetChildren(){return child;}IEntity*GetSibling(){return sibling;}IEntity*GetParent(){return parent;}void GetBounds(vector&a,vector&b){reads++;a=lo;b=hi;}vector CoordToParent(vector v){return origin+Vector(v.x*std::cos(yaw)+v.z*std::sin(yaw),v.y,-v.x*std::sin(yaw)+v.z*std::cos(yaw));}vector CoordToLocal(vector v){v=v-origin;return{v.x*std::cos(yaw)-v.z*std::sin(yaw),v.y,v.x*std::sin(yaw)+v.z*std::cos(yaw)};}};
''' + s + r'''
int main(){using F=ERR_InterceptVehicleFootprint;IEntity root;auto f=F::Capture(&root);assert(f&&f->Valid()&&f->Matches(&root));assert(std::abs(f->Radius()-(std::sqrt(5.f)+.5f))<.001);assert(std::abs(f->Height()-3.5)<.001);IEntity foreign;assert(!f->Matches(&foreign));float radius=f->Radius();root.origin={-700,33,900};root.yaw=.7;assert(f->Matches(&root)&&f->Radius()==radius);
for(int bad=0;bad<6;bad++){IEntity e;if(bad==0)e.geometry=false;if(bad==1)e.lo.x=NAN;if(bad==2)e.hi.x=e.lo.x;if(bad==3)e.hi.z=41;if(bad==4)e.origin.x=NAN;if(bad==5)e.hi.y=16;assert(!F::Capture(&e));}assert(!F::Capture(nullptr));
IEntity hull,attachment;hull.child=&attachment;attachment.parent=&hull;attachment.lo={-.5,0,-.5};attachment.hi={.5,.5,.5};f=F::Capture(&hull);assert(f&&f->Matches(&hull));attachment.yaw=.8;assert(f->Matches(&hull));attachment.hi.z=9;assert(!f->Matches(&hull));attachment.hi.z=.5;attachment.origin.y=20;assert(!f->Matches(&hull));attachment.origin.y=10;IEntity replacement;hull.child=&replacement;assert(!f->Matches(&hull));hull.child=&attachment;attachment.sibling=&replacement;assert(!f->Matches(&hull));attachment.sibling=nullptr;
// Seated body is measured, but a huge carried inventory child is not traversed.
IEntity body,carried;Seat seat;CompartmentAccessComponent access;hull.child=&body;body.parent=&hull;body.character=true;body.lo={-.4,0,-.4};body.hi={.4,1.5,.4};body.access=&access;access.owner=&hull;access.seat=&seat;seat.occupant=&body;body.child=&carried;carried.hi.z=999;f=F::Capture(&hull);assert(f&&f->Matches(&hull)&&carried.reads==0);access.inside=false;assert(!f->Matches(&hull));access.inside=true;seat.occupant=&foreign;assert(!F::Capture(&hull));seat.occupant=&body;access.owner=&foreign;assert(!F::Capture(&hull));
// Only exact reciprocal in-root deposit cargo is skipped; unknown/foreign is not.
IEntity cargo;InventoryItemComponent item;InventoryStorageSlot slot;BaseInventoryStorageComponent storage;hull.child=&cargo;cargo.parent=&hull;cargo.hi.z=999;cargo.item=&item;item.slot=&slot;slot.storage=&storage;slot.container=&storage;slot.attached=&cargo;storage.owner=&hull;storage.current=&slot;f=F::Capture(&hull);assert(f&&f->Matches(&hull)&&cargo.reads==0);storage.current=nullptr;assert(!F::Capture(&hull)&&!f->Matches(&hull));InventoryStorageSlot otherSlot;storage.current=&otherSlot;assert(!F::Capture(&hull)&&!f->Matches(&hull));storage.current=&slot;assert(f->Matches(&hull));slot.attached=&foreign;assert(!F::Capture(&hull));slot.attached=&cargo;storage.purpose=127;assert(!F::Capture(&hull));storage.purpose=1;storage.owner=&foreign;assert(!F::Capture(&hull));
IEntity nodes[129];for(int i=0;i<128;i++){nodes[i].child=&nodes[i+1];nodes[i+1].parent=&nodes[i];}assert(!F::Capture(&nodes[0]));nodes[127].child=nullptr;f=F::Capture(&nodes[0]);assert(f&&f->Matches(&nodes[0]));nodes[127].child=&nodes[0];assert(!F::Capture(&nodes[0]));
}
''')
print('PASS: actual immutable measured footprint/root/attachment matching, bounds/transforms, moving geometry, seated-body and cargo reciprocity, finite hierarchy caps; native geometry doubled')
@@ -0,0 +1,60 @@
{
"target": "/home/jakub/devel/escapistsrereintroduced",
"files": {
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyPlacement.c": {
"baseline_sha256": "4d0e8b6e147486fb65db82a1060f0c5641c1319ba9e418bdec99cb5a422a7fbe",
"proposal_sha256": "aac2a2c508aebcd9eed0a0cd310c2f3774357bb9b1fc383f305182b41d29caf3",
"index_sha256": "4d0e8b6e147486fb65db82a1060f0c5641c1319ba9e418bdec99cb5a422a7fbe",
"index_absent": false
},
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": {
"baseline_sha256": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
"proposal_sha256": "5f45545c11de5e183969203cdf07e29089697ddc89752a9c5aea0fa2a1a33a97",
"index_sha256": null,
"index_absent": true
},
"addons/core/Scripts/Game/ERR/10_AI/ERR_InterceptVehicleFootprint.c": {
"baseline_sha256": null,
"proposal_sha256": "5dea67d14ebab67231c485e14bc92f34c005a5f1bf6d9c0943db28e9857e958a",
"index_sha256": null,
"index_absent": true
},
"tests/test_bmp_intercept_geometry.py": {
"baseline_sha256": null,
"proposal_sha256": "5b3322e7f7f29dfe47e2ffc52ab71c08c16db3548b1e880d47d4bc49a5188a90",
"index_sha256": null,
"index_absent": true
},
"tests/test_convoy_placement.py": {
"baseline_sha256": "dcbf572747a85edbfac341e7e358b1987150a2eb721708a3a025639be228a7c3",
"proposal_sha256": "895feb51b971cf99e39c3a7d01806b360ff46dfc90f8de2a36910cc49251ba7b",
"index_sha256": "dcbf572747a85edbfac341e7e358b1987150a2eb721708a3a025639be228a7c3",
"index_absent": false
},
"tests/test_hq_mounted_intercept.py": {
"baseline_sha256": "444411a6a97ebaff69a056302f444f986daa5f6f7f792e622fdbcca731f939c8",
"proposal_sha256": "dc28ee9625d961cdbecdd6061879cd42a882f183f70a3a04670f225e61d52776",
"index_sha256": null,
"index_absent": true
},
"tests/test_hq_mounted_intercept_pair.py": {
"baseline_sha256": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9",
"proposal_sha256": "1136c04270a7d63acbf00b8d2ce81d152e00c2110a280463fe547d6c18103d0d",
"index_sha256": null,
"index_absent": true
},
"tests/test_intercept_vehicle_footprint.py": {
"baseline_sha256": null,
"proposal_sha256": "5acfa2d3c16c924a5c88ae734814242dae96b168997a1bc4617476a7a48afb42",
"index_sha256": null,
"index_absent": true
}
},
"patch_sha256": "cc2fc04c98a0e99447f0b54583835d3d4e6182a50c4f44e7c5a72ec652aef24e",
"test_results_sha256": "23e696ee0497231601ea9b93bc22500924d2e828c2c8b8769330e791260625bf",
"new_test_registration": [
"tests/test_intercept_vehicle_footprint.py",
"tests/test_bmp_intercept_geometry.py"
],
"native_validation": "Pending; no native compiler or game launched. Existing external Steam game must be preserved."
}
@@ -0,0 +1,98 @@
[
{
"name": "test_hq_mounted_intercept-green",
"test": "test_hq_mounted_intercept.py",
"exit": 0,
"expected_runtime_red": false,
"expected_result_observed": true,
"log_sha256": "20410d8a7171f3e140f415186697a442452d6eeea8564d622ed093d2259bbc8d"
},
{
"name": "test_hq_mounted_intercept_pair-green",
"test": "test_hq_mounted_intercept_pair.py",
"exit": 0,
"expected_runtime_red": false,
"expected_result_observed": true,
"log_sha256": "295e0d7fe50d343f78f30abbf2df0b6c660fc5799d8fd0dad1adec170b7feb85"
},
{
"name": "test_intercept_vehicle_footprint-green",
"test": "test_intercept_vehicle_footprint.py",
"exit": 0,
"expected_runtime_red": false,
"expected_result_observed": true,
"log_sha256": "2b0439643681d6343bf75453373257c694446994f04e642f749a61e03336d1eb"
},
{
"name": "test_bmp_intercept_geometry-green",
"test": "test_bmp_intercept_geometry.py",
"exit": 0,
"expected_runtime_red": false,
"expected_result_observed": true,
"log_sha256": "f72e628b272979802ef7815412799115494bb4a024280dd3f9318ae50e80d5f7"
},
{
"name": "test_convoy_placement-green",
"test": "test_convoy_placement.py",
"exit": 0,
"expected_runtime_red": false,
"expected_result_observed": true,
"log_sha256": "bf961e22db9c160ed81e4d1d4c516ab7a46ac3cf29527cdebd864b6cbd2c2ad6"
},
{
"name": "mutation-five-losses",
"test": "test_bmp_intercept_geometry.py",
"exit": 1,
"expected_runtime_red": true,
"expected_result_observed": true,
"log_sha256": "9b59e1ee34d1448384f860417738128ce2e5a5f47c977dbd336a9b6316caf9cd"
},
{
"name": "mutation-road-width",
"test": "test_bmp_intercept_geometry.py",
"exit": 1,
"expected_runtime_red": true,
"expected_result_observed": true,
"log_sha256": "69e0fc432881766324870e9d8d93698c1f6c9241cbdf0e47147f08ba919a8ad7"
},
{
"name": "mutation-attachment-replaced",
"test": "test_intercept_vehicle_footprint.py",
"exit": 1,
"expected_runtime_red": true,
"expected_result_observed": true,
"log_sha256": "641f38264c54479221a19434e8191fef12b3f81f4f35abb20cd36885df0f29d6"
},
{
"name": "mutation-fixed-height",
"test": "test_convoy_placement.py",
"exit": 1,
"expected_runtime_red": true,
"expected_result_observed": true,
"log_sha256": "d54a35ab7331a5158317c3312566c63349aedc5ce27d42f1b39246b863f5b5dd"
},
{
"name": "mutation-road-end-support",
"test": "test_bmp_intercept_geometry.py",
"exit": 1,
"expected_runtime_red": true,
"expected_result_observed": true,
"log_sha256": "811a581544fb268437a27da9cc7787005d5f9e8c704984a30a83d4cedbec38f1"
},
{
"name": "footprint-slot-green",
"test": "test_intercept_vehicle_footprint.py",
"exit": 0,
"expected_runtime_red": false,
"expected_result_observed": true,
"log_sha256": "2b0439643681d6343bf75453373257c694446994f04e642f749a61e03336d1eb"
},
{
"name": "mutation-orphan-slot",
"test": "test_intercept_vehicle_footprint.py",
"exit": 1,
"expected_runtime_red": true,
"expected_result_observed": true,
"log_sha256": "0dc205ba09cda6835281e35c2a9b74dfe22b4bbe0315307740876b64dce0d3a5"
}
]
@@ -0,0 +1 @@
PASS: actual BMP singleton geometry and typed current order; one survey, full straight segment, width, known history, reservations, settlement/clearance and work caps; placement/footprint tested separately
@@ -0,0 +1 @@
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
@@ -0,0 +1 @@
PASS: actual mounted intercept eligibility/order chronology, fixed destinations, native road selection bounds, width, segment heading, clearance and settlement delegation; no native driving claim
@@ -0,0 +1 @@
PASS: actual paired arc geometry,40m/80m spacing, separate original polylines, both-position validation and128road/4096segment budget; no native driving proof
@@ -0,0 +1 @@
PASS: actual immutable measured footprint/root/attachment matching, bounds/transforms, moving geometry, seated-body and cargo reciprocity, finite hierarchy caps; native geometry doubled