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,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 @@
PASS: git apply --check
@@ -0,0 +1,109 @@
// 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;
}
}
@@ -0,0 +1 @@
fatal: path 'addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c' exists on disk, but not in the index
@@ -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,25 @@
{
"source": "/tmp/erri-pair-hq-0054/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c",
"reviewed_sha256": "adf4ee9dfde64b159f2335b80118f64878dba7239b41d105c0a25ade85f1e11d",
"verdict": "One concrete pre-admission pair-selection defect reported to author/root; no additional demonstrated safety blocker in reviewed dispatcher.",
"finding": {
"method": "PlanMountedIntercept paired candidate selection",
"problem": "Greedy closestA excludes the only escort capable of reachingB; singleton fallback may occur despite a complete admissible pair.",
"reproducer": "Response radius800; destinations A.x=0,B.x=60; original same-convoy eligible U.x=0,V.x=-800. U is closestA; V cannot reachB(860). V\u2192A,U\u2192B is feasible. Both radios and original status otherwise valid.",
"required_change": "Bounded enumeration of eligible original escort assignments/permutations before pre-admission singleton fallback. At most3 escort slots. Add actual planner regression.",
"status": "Sent to hq_memory_review and root; reviewer makes no edits."
},
"reviewed_good": [
"queries increments before pair and single selection; per-pass<=2 includes failed pair+fallback. Rotating belief cursor advances even when both queries spent on one blocked track.",
"Two independent strongly-owned routes, two latched receipt flags; ApplyInterceptPair only after both positive real Advance results and both original status/contact/claim checks.",
"Assigned and HuntingAssigned count each member once; role eligibility excludes base assignments, pending orders and logistics. Pending/current pair respects global and hunting caps.",
"Separate monotonically allocated400001..400256 tokens spend1or2; refusal preserves spent attempt/revision, no post-claim fallback or partner replacement.",
"Release detaches both HQ job refs before native service release, clears only matching HQ logistics tokens and retains unrelated claims. ReleaseUnit of either original ends both.",
"Pending order chronology enforces same delivered belief/revision/report,30-second delivery freshness and fixed120-second observation expiry. Active job intentionally retains immutable order until expiry; no live player retargeting."
],
"service_boundary_note": "ClearIntercept still performs native waypoint removal before token reset; sent author targeted direct-vehicle reentrancy question. No demonstrated engine callback reproduction, so not asserted as a blocker.",
"tests_run": false,
"target_writes": false,
"source_edits": false,
"native": false
}
@@ -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,263 @@
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c
@@ -106,4 +106,124 @@
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;
+ }
}
--- /dev/null
+++ b/tests/test_hq_mounted_intercept_pair.py
@@ -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,23 @@
/tmp/err-policy-oiypbgum/check.cpp: In static member function ‘static bool ERR_HQMountedInterceptRules::SelectPair(ERR_HQBelief*, float, Array<vector>, vector&, vector&)’:
/tmp/err-policy-oiypbgum/check.cpp:335:35: error: variable ‘bestSecondHeading’ set but not used [-Werror=unused-but-set-variable=]
335 | float bestFirstHeading{}; float bestSecondHeading{};
| ^~~~~~~~~~~~~~~~~
cc1plus: all warnings being treated as errors
Traceback (most recent call last):
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<104 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-oiypbgum/check.cpp', '-o', '/tmp/err-policy-oiypbgum/check']' returned non-zero exit status 1.
@@ -0,0 +1,49 @@
{
"verdict": "PASS: integrated three-source proposal identity and complete294-program suite bindings verified read-only. Native normal-route run still pending and not inspected by this review.",
"integrated_proposal_hashes": {
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9",
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
"tests/test_hq_mounted_dispatch.py": "50e8973b730c34a54775e7ba5ae2a72c9a6a101bc9b14a1521c4b5c6d401552e",
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
"tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec"
},
"proposal_manifest_hashes": {
"/tmp/erri-pair-geometry-0054/source-manifest.json": "b439a06d5826ebae5681b330d8edc078f7156c02a9a16f451eb50e542fd298e1",
"/tmp/erri-pair-hq-0054/source-manifest.json": "2664094d357c679efdd195016d662846797cc90423ae74332ee85e852baf413d",
"/tmp/erri-pair-service-0054/source-manifest.json": "e27b7654dd458683e8689f1621149b589070ea0177588c08c7328d7d2604cf86"
},
"all_installed_paths_match": true,
"installed_path_count": 9,
"authored_source_files": 420,
"suite": {
"exit": 0,
"program_count": 294,
"unique_program_count": 294,
"all_program_exits_zero": true,
"inputs_unchanged_at_run": true,
"current_full_input_inventory_equals_frozen": true,
"input_files": 976,
"difference": {},
"seconds": 207.57,
"new_pair_and_existing_dispatch_column_tests_registered_and_passed": true
},
"evidence_sha256": {
"/tmp/erri-pair-root-0054/suite-inputs.json": "4eddea7702b382301403bc616a04082965eb1410be893e23b1abf76d1bc0d2cb",
"/tmp/erri-pair-root-0054/suite-result.json": "87844f9f6d95ab2e22388e3ef4e8fef5807aaf605fee44745409dfa0f53bafc6",
"/tmp/erri-pair-root-0054/installed.json": "15a2b934f6885611a26e5a0d4b98056e6fb3f2747ff2e9265f42a1bb69cb8a5b",
"/tmp/erri-pair-root-0054/run-suite.py": "b609a9132f93339d09e9b12cc233f6c1d1984a4df1e9819e55008590dd262646"
},
"limits": [
"Offline actual-method tests do not prove native paired driving/arrival/hold/resume or traffic blockage.",
"No claim for running normal-route result until its final archive is reviewed.",
"No process inspection, compiler/test execution or native action performed by reviewer."
],
"actions": {
"source_or_target_edits": false,
"original_edits": false,
"compilers_or_tests": false,
"native_or_process_inspection": false,
"scratch_output": "/tmp/erri-pair-geometry-0054/integrated-review.json"
}
}
@@ -0,0 +1,18 @@
{
"spacing-removed": {
"exit": 1,
"runtime_assertion_failure": true
},
"pair-second-clear-checks-first": {
"exit": 1,
"runtime_assertion_failure": true
},
"pair-budget-removed": {
"exit": 1,
"runtime_assertion_failure": true
},
"pair-chord-instead-of-arc": {
"exit": 1,
"runtime_assertion_failure": true
}
}
@@ -0,0 +1,26 @@
{
"verdict": "PASS narrow semantic-preserving field rename and matching harness adapter. Corrected native compilation/full suite remain pending.",
"fix": {
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": {
"before": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
"after": "cee3ae301ac2f211c290450b644a7942c9786a2a40d06f16e2a47efa96fd15b6"
},
"tests/test_hq_mounted_dispatch.py": {
"before": "50e8973b730c34a54775e7ba5ae2a72c9a6a101bc9b14a1521c4b5c6d401552e",
"after": "3eb793719b21d508ffc294f5a1bf5c5153f5717a1408a8f779f0a4c1ddefe2a6"
}
},
"delta_exact": "Only choice field Vehicle\u2192Escort declaration and its2producer/consumer uses, plus2matching harness adapter/schema changes. No selection, allocation, receipt, cap, cleanup or geometry behavior changed.",
"initial_suite_preserved": {
"programs": 294,
"original_report_and_inputs_hashes_match_pre_native_name_fix_archive": true,
"native_result": "Initial source failed native compile; offline294 pass does not qualify native compilation."
},
"failed_native_report_sha256": "8a72524342ac2a239d394b2a217d0b6905fee0409867775fb906b05cd1aa1b1f",
"new_source_native_or_fullsuite_claim": false,
"actions": {
"read_only_target": true,
"no_compilers_tests_native_process_inspection": true,
"only_output": "/tmp/erri-pair-geometry-0054/name-fix-review.json"
}
}
@@ -0,0 +1,81 @@
{
"verdict": "PASS corrected native compilation and scripted normal-escape-route regression; strict native report still fails on13 retained known engine errors. This is not physical paired-interception qualification.",
"archive": "/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal",
"raw_session": "/tmp/erri-pair-normal-fixed-0054/logs/logs_2026-10-03_01-12-25",
"raw_report_recomputed_exact": true,
"assertions_passed": 63,
"completions": 1,
"terminal": 5,
"script_or_runtime_failures": [],
"engine_error_count": 13,
"known_error_rows_match_previous_active_restart_after_timestamp_removal": true,
"strict_native_clean": false,
"actual_scope": "One local Workbench Chat normal-escape-route: prepared original sites/prison, carried clues, radio eligibility/occupied timer/reset/completion, extraction occupancy and scripted victory. Native script module compiles after Vehicle\u2192Escort correction.",
"source_bindings": {
"files": 420,
"launch_end_current_inventory_and_hashes_equal": true,
"manifest_sha256": "d7b2a8a4c7bdca7cd78a23747c3190b0f1c41a36c16bb9d14490bdc40792e648",
"console_sha256": "6e8f18f79fc5a2835fe76046981374a85a87e6f9d21ab565875209c68b3dc5eb",
"archive_raw_files_equal": {
"external-addons-inputs.json.txt": true,
"console.log.txt": true,
"script.log.txt": true,
"error.log.txt": true,
"rereintroduced-hq.log.txt": true,
"headless-launch.log.txt": true,
"runner.json.txt": true,
"err-prison.png": true,
"err-result.png": true,
"launch-source-sha256.json": true,
"launch-source.json": true
},
"only_source_delta_from_failed_compile": {
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": {
"failed_compile": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893",
"corrected": "cee3ae301ac2f211c290450b644a7942c9786a2a40d06f16e2a47efa96fd15b6"
}
}
},
"cleanup": {
"runner_result": "smoke-completed",
"confirmed": true,
"idle_verified": true,
"errors": [],
"external_unchanged": true,
"external_count": 7,
"seconds": 183.304,
"scope": "Verified sealed runner metadata against raw original; no process inspection or new native actions."
},
"initial_failure_retained": {
"archive": "/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-compile",
"assertions": 0,
"completions": 0,
"failures": [
"01:10:56.406 SCRIPT (E): @\"Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c,6\": Variable name 'Vehicle' is already used as type name",
"01:10:56.853 SCRIPT (E): Can't compile \"Game\" script module!"
],
"report_sha256": "8a72524342ac2a239d394b2a217d0b6905fee0409867775fb906b05cd1aa1b1f",
"source_manifest_sha256": "7248005290380f3f2c7353fbda78240d267ad1d7a57bd2956f48a468dfb0f319"
},
"final_offline_suite": "Fresh corrected294-program suite is running per parent; this review deliberately makes no claim about its completion. Earlier294 result belongs to pre-name-fix source and is archived separately.",
"limitations": [
"No paired or singleton physical interception travel/arrival/hold/resume demonstrated by normal-route fixture.",
"No manual human escape, remote multiplayer/JIP, performance/FPS, package or release qualification.",
"Native engine/resource errors retained rather than suppressed.",
"Screenshots archived but not interpreted as GUI/visual evidence here."
],
"review_actions": {
"target_original_edits": false,
"tests_compilers": false,
"native_launch": false,
"process_inspection": false,
"only_output": "/tmp/erri-pair-geometry-0054/native-review.json"
},
"evidence_sha256": {
"/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal/report.json": "4818f6010191eb86ace85f7c980a5bb1d4832aedd9e90065eecb00b7ba3b42ec",
"/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal/runner.json.txt": "00bbc4fc36b99f7c0c6adba6c873db6f7744d8ac3256e7dd40b3873ffa51f1ab",
"/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal/launch-source.json": "62e6931574e6c0ee66546dc1ab0e55b33c74fcaf73db94b98ad5cc77d5915d32",
"/home/jakub/devel/escapistsrereintroduced/docs/evidence/native-2026-10-03-mounted-pair-normal/source-sha256.json": "d7b2a8a4c7bdca7cd78a23747c3190b0f1c41a36c16bb9d14490bdc40792e648",
"/tmp/erri-pair-root-0054/name-fix.json": "59de64aef61389f09606469862bde7868e9ed7937e7b4af7c826073aa6f38902"
}
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-0g0moutr/check.cpp:432: int main(): Assertion `!select()' failed.
Traceback (most recent call last):
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<104 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-0g0moutr/check']' died with <Signals.SIGABRT: 6>.
@@ -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 > 999999) 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,74 @@
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);
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-vy8aevwr/check.cpp:373: int main(): Assertion `a==vector(970,10,1300)&&b==vector(1000,10,1330)' failed.
Traceback (most recent call last):
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<104 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-vy8aevwr/check']' died with <Signals.SIGABRT: 6>.
@@ -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[0], points[points.Count() - 1], start / total);
aHeading = Math.Atan2(segment[0], segment[2]) * Math.RAD2DEG; haveFirst = true;
}
if (!haveSecond && finish <= travelled + segmentLength)
{
b = vector.Lerp(points[0], points[points.Count() - 1], finish / total);
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 @@
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 @@
check: /tmp/err-policy-ml30k0l2/check.cpp:411: int main(): Assertion `!select()' failed.
Traceback (most recent call last):
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<104 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-ml30k0l2/check']' died with <Signals.SIGABRT: 6>.
@@ -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(groundedFirst, bestSecondHeading)) return false;
first = groundedFirst; second = groundedSecond;
return true;
}
}
@@ -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)) return false;
first = groundedFirst; second = groundedSecond;
return true;
}
}
@@ -0,0 +1,50 @@
{
"scope": "Scratch-only pair geometry; parent registers new test and integrates dispatcher/service.",
"api": "static bool SelectPair(ERR_HQBelief belief, float now, array<vector> reserved, out vector first, out vector second)",
"singleton_bodies_unchanged": true,
"geometry": "One real polyline,40..60m horizontal arc span; retain segment heights/headings; longitudinal placement extends forward, transverse centers. Both ahead300..900m/cosine0.8, within100m seed, at least40m apart and80m from unrelated reservations. Outputs zero on any failure.",
"bounds": "One query;128roads/4096accepted segments;1024points perroad; finite scan plus2geometry passes. Later overbudget rejects earlier candidate.",
"native_checks": "Both road-ground heights differ<=2m; both outside settlements and Clear footprints; no output publication until both pass.",
"tests": {
"existing": "existing-green.log PASS with no adapter changes",
"new": "pair-green.log PASS actual methods with native boundary doubles",
"mutations": {
"spacing-removed": {
"exit": 1,
"runtime_assertion_failure": true
},
"pair-second-clear-checks-first": {
"exit": 1,
"runtime_assertion_failure": true
},
"pair-budget-removed": {
"exit": 1,
"runtime_assertion_failure": true
},
"pair-chord-instead-of-arc": {
"exit": 1,
"runtime_assertion_failure": true
}
},
"apply_check": true,
"initial_mutation_note": "Deleting second Clear caused unused heading compilefailure; retained log. Final mutation checks first point twice and fails at runtime."
},
"baseline": {
"working_sha256": "c239aaad0ac556744f76b94286c6f204be51b51acdf44e788ca794c2ae967eae",
"index_present": false
},
"proposed": {
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9"
},
"patch_sha256": "184363078545edc1fc505dfaf90e51631cb476143f96ade3dc0ef269d91d99b6",
"command": "ERR_MOUNTED_INTERCEPT_SOURCE=/tmp/erri-pair-geometry-0054/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c PYTHONPATH=/home/jakub/devel/escapistsrereintroduced/tests python3 /tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py",
"limits": [
"No native compilation/driving/physical checkpoint proof.",
"Terrain/settlement/clearance doubled; water/collision tests delegation and output atomicity, not native implementation.",
"Conservative selection, not exhaustive placement search; singleton fallback costs second query."
],
"target_writes": false,
"original_writes": false,
"native": false
}
@@ -0,0 +1,56 @@
{
"verdict": "No concrete blocker found in final paired service addition. Read-only source/test review; no independent compiler/native execution in this review.",
"source_manifest": {
"addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyService.c": "b59016d83cb9d40136b98b4e0067ea490bc553574fc53603593efd5522dbed20",
"tests/test_convoy_intercept_pair.py": "52b55727b7c2441d43530efa701045b9439b9175c71fbd2ae7577f851d2c98ec",
"fix.patch": "dd034f9837703ba3cbf157490999152ef1d00fae7878c5fe8073d808e3abe1c4"
},
"source_manifest_sha256": "e27b7654dd458683e8689f1621149b589070ea0177588c08c7328d7d2604cf86",
"baseline_still_matches_target": true,
"addition_only_existing_singleton_and_release_paths_unchanged": true,
"reviewed_invariants": [
"Reserve pair requires distinct original groups/vehicle slots/tokens/entities in exact admitted task and consumed receipt; real ledger records and original crew ownership pass existing InterceptEligible for each. Same delivered belief/report/revision/times and >=40m spacing; global existing interception prevents other-task adoption.",
"First claim failure leaves both untouched; second claim failure clears only first exact owned claim and preserves original route handles/deadline. Tokens remain spent rather than replayable.",
"First or second waypoint allocation failure precedes any ClearOrder. Second failure deletes only first prepared waypoint. No physical actor, inventory, stock, pool receipt or ledger registration changes in added methods.",
"Both allocations followed by exact receipt/record/route/entity/crew/claim/destination revalidation before route mutation. Invalid precommit ownership deletes prepared handles only.",
"ClearOrder/AddWaypoint are native commit boundaries, not atomic transactions. Callback ownership loss prevents later partner installation and releases exact owned claims. No assertion that original native orders can be transactionally restored after removal.",
"Public replacement waypoint handles are not adopted/deleted by exact private cleanup; original actors/drivers retained. Shared fixed expiry and existing per-member route progression/resume retained."
],
"test_evidence_inspected": {
"path": "/tmp/erri-pair-service-0054/tests/test_convoy_intercept_pair.py",
"binding": "Reuses existing full actual vehicle/service C++ translation through ERR_CONVOY_SERVICE, then inserts actual pair methods. Native allocation/removal/addition are callback doubles.",
"cases": [
"19 malformed/foreign/invalid pair refusals",
"first and second allocation failure retain both original handle and Add/Remove counters",
"crew loss during allocation leaves routes intact and deletes both prepared handles",
"second claim rollback and unrelated global claim preservation",
"mutated claimed destinations refused",
"ClearOrder/first Add/second Add cancellation",
"both physical-position hold/expiry and exact per-member foreign-public-handle preservation"
],
"author_logs": {
"apply-check.log": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"mutation-spacing-red.log": "d11417b94e4ddf6357314e03e6e8560e145e685d094dfaf5a4681171c02c39c7",
"mutation-rollback-red.log": "810fb2ce2337fe3f25ee3404b12a49a263e0310318e54cc2e7b3fe21119e9df7",
"baseline-red.log": "3282ef3cdcb9b5f5557227a37a795e310240d6d598dce23e261bc7fbe8dd308b",
"rules.log": "20410d8a7171f3e140f415186697a442452d6eeea8564d622ed093d2259bbc8d",
"lifecycle.log": "6e7f1708a5e9c0af68ffd0ace1edc33e99edff479cee2e6750bcaca1c94317ff",
"green.log": "376c9f1c0e0fa25eee0d283e57561cd2ed6d1b826a8c2fb9486d681e82ef0f8b"
},
"executed_by_reviewer": false
},
"hq_previous_finding": "Author corrected greedy roleA crew selection by bounded enumeration of each original escort as first choice. Independently inspected current loop; new test was reported green by author, not rerun here.",
"retained_limitations": [
"Legacy ClearIntercept calls native RemoveWaypoint before clearing token. No concrete direct recursive native callback demonstrated in this feature slice; behavior unchanged and not claimed newly qualified.",
"Native waypoint Add/Remove cannot guarantee transactional movement of two separate groups.",
"No actual native driving, paired hold/traffic blockage, radio timing, multiplayer or FPS evidence from source-level doubles."
],
"actions": {
"target_writes": false,
"original_writes": false,
"source_edits": false,
"tests_compilers": false,
"native": false
},
"reviewed_dispatch_sha256": "60d0fef83f43eea0501bf5fe46f369556e22acda6d034908b5965d8820b28893"
}
@@ -0,0 +1,4 @@
{
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedIntercept.c": "a37edcf66e43945f331db47fe81cb2e0e767a7aaf8115244dcf9e6295932be98",
"tests/test_hq_mounted_intercept_pair.py": "a8d758b1ff20bb6d1a95a9ff1d38f687e43c819c279f5ea9692e84f766dc4fb9"
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-zgu986xs/check.cpp:377: int main(): Assertion `!pair({{970,10,1300},{1000,10,1300},{1000,10,1301},{970,10,1301}})' failed.
Traceback (most recent call last):
File "/tmp/erri-pair-geometry-0054/tests/test_hq_mounted_intercept_pair.py", line 25, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<104 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-zgu986xs/check']' died with <Signals.SIGABRT: 6>.
@@ -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) 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,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')