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
+142
View File
@@ -0,0 +1,142 @@
"""Execute actual finite road mine-site geometry and bounded native selector."""
from pathlib import Path
import os,re
from policy_harness import ROOT,body,run
p=Path(os.environ.get('ERR_ENGINEER_MINE_SITE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMineSite.c'))
source=p.read_text()
for forbidden in ('GetPlayer','GetControlledEntity','GetOrigin','Spawn(', 'AddWaypoint','SetOrigin','CallLater'):
assert forbidden not in source,forbidden
s=re.sub(r'//[^\n]*','',source).replace('class ERR_EngineerMineSite','struct ERR_EngineerMineSite')
s=s.replace('inout int','int&').replace('out vector','vector&').replace('array<vector>','Array<vector>').replace('array<BaseRoad>','Array<BaseRoad*>').replace('foreach (','for (')
s=s.replace('Array<vector> starts,','Array<vector>& starts,').replace('Array<vector> ends,','Array<vector>& ends,')
s=s.replace('Array<vector> candidates)', 'Array<vector>& candidates)').replace('FromPolyline(Array<vector> points,','FromPolyline(Array<vector>& points,')
s=s.replace('SCR_AIWorld.Cast(GetGame().GetAIWorld())','GetGame()->GetAIWorld()')
for name in ('SCR_AIWorld','BaseRoad','ERR_SettlementSafety'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
for name in ('world','road','settlements'):s=s.replace(name+'.',name+'->')
s=s.replace('GetGame().','GetGame()->').replace('GetRoadNetworkManager().','GetRoadNetworkManager()->')
for name in ('ERR_ForceRules','ERR_HQRoadIntercept','vector','Math','ERR_Actors','Replication'):s=s.replace(name+'.',name+'::')
s=s.replace('null','nullptr');s=re.sub(r'\bVector\(', 'vector(', s)
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('\n}\n','\n};\n')
settlement=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_SettlementSafety.c'
settle=settlement.read_text().replace('class ERR_SettlementSafety','struct ERR_SettlementSafety').replace('protected ','').replace('static const float','static constexpr float')
settle=settle.replace('MapDescriptorComponent.Cast(entity.FindComponent(MapDescriptorComponent))','entity->descriptor').replace('entity.FindComponent(MapDescriptorComponent)','entity->descriptor')
settle=settle.replace('IEntity entity','Entity* entity').replace('MapDescriptorComponent descriptor','MapDescriptorComponent* descriptor').replace('descriptor.','descriptor->').replace('entity.','entity->')
settle=settle.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->').replace('Vector(','vector(').replace('vector.','vector::').replace('string.Format','string::Format').replace('EMapDescriptorType.','EMapDescriptorType::').replace('EQueryEntitiesFlags.','EQueryEntitiesFlags::')
settle=settle.replace('CheckSettlement, FilterSettlement','[this](Entity* e){return CheckSettlement(e);}, [this](Entity* e){return FilterSettlement(e);}')
settle=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',settle).replace('\n}\n','\n};\n')
road=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'
better=body(road,'static bool Better(')
stand=body(road,'static bool Standable(').replace('array<vector>','Array<vector>').replace('foreach (','for (').replace('TraceBox body','TraceBox* body')
stand=stand.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->').replace('body.','body->').replace('null','nullptr')
for name in ('ERR_ForceRules','ChimeraWorldUtils','ERR_Actors','Math','TraceFlags'):stand=stand.replace(name+'.',name+'::')
clearance_checks=''
if 'static bool FarFromRoads(' in source:
clearance_checks=r'''
{Array<vector> starts={{0,10,0}},ends={{.009f,10,0}};int comparisons=0;
assert(!Site::FarFromRoads({15.001f,10,0},starts,ends,comparisons)&&comparisons==1);
comparisons=0;assert(Site::FarFromRoads({15.01f,10,0},starts,ends,comparisons));
comparisons=65536;assert(!Site::FarFromRoads({30,10,0},starts,ends,comparisons)&&comparisons==65536);
comparisons=65535;ends.Insert({0,10,1});starts.Insert({0,10,1});assert(!Site::FarFromRoads({30,10,0},starts,ends,comparisons)&&comparisons==65536);
starts={{0,10,0}};ends=starts;comparisons=0;assert(!Site::FarFromRoads({14.99f,10,0},starts,ends,comparisons));
comparisons=0;assert(Site::FarFromRoads({15,10,0},starts,ends,comparisons));
}
'''
run(r'''
#include <cassert>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <string>
struct string:std::string{using std::string::string;template<class...T>static string Format(const char*s,T...){return s;}};
#include <vector>
struct Math{static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}static float AbsFloat(float x){return std::abs(x);}static float Sqrt(float x){return std::sqrt(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;}
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{};
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);}void Clear(){this->clear();}void Remove(int i){this->erase(this->begin()+i);}bool IsEmpty(){return this->empty();}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}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 TraceFlags{static constexpr int WORLD=1,ENTS=2;};struct TraceBox{vector Start,End,Mins,Maxs;int Flags=0;};
struct EMapDescriptorType{static constexpr int MDT_NAME_CITY=1,MDT_NAME_TOWN=2,MDT_NAME_VILLAGE=3,MDT_NAME_SETTLEMENT=4,MDT_BUILDING=5;};
struct EQueryEntitiesFlags{static constexpr int ALL=1;};
struct MapDescriptorComponent{int type=EMapDescriptorType::MDT_NAME_VILLAGE;int GetBaseType(){return type;}};
struct Entity{vector position;MapDescriptorComponent*descriptor=nullptr;vector GetOrigin(){return position;}};
struct NativeWorld{Array<Entity*> settlements;int queries=0;float now=1000;float GetWorldTime(){return now;}template<class A,class B>void QueryEntitiesByAABB(vector low,vector high,A check,B filter,int){queries++;assert(high.x-low.x==400&&high.y-low.y==10000&&high.z-low.z==400);for(auto*e:settlements)if(e&&filter(e)&&!check(e))break;}int traces=0;bool obstructed=false,blockNear=false,onlyTop=false;float TraceMove(TraceBox* t,void*){traces++;bool blocked=obstructed||(blockNear&&std::hypot(t->Start.x,t->Start.z)<50)||(onlyTop&&(std::abs(t->Start.x)>1||t->Start.z<50));delete t;return blocked?.5f:1.f;}};
struct BaseRoad{float width=4;int pointReads=0;Array<vector>points;float GetWidth(){return width;}void GetPoints(Array<vector>&p){pointReads++;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==120||high.x-low.x==160)&&high.z-low.z==high.x-low.x&&high.y-low.y==2000);out.clear();for(auto*r:roads){if(!r||r->points.size()<2){out.Insert(r);continue;}float minX=INFINITY,maxX=-INFINITY,minZ=INFINITY,maxZ=-INFINITY;bool invalid=false;for(auto p:r->points){if(!std::isfinite(p.x)||!std::isfinite(p.z))invalid=true;minX=std::min(minX,p.x);maxX=std::max(maxX,p.x);minZ=std::min(minZ,p.z);maxZ=std::max(maxZ,p.z);}if(invalid||(maxX>=low.x&&minX<=high.x&&maxZ>=low.z&&minZ<=high.z))out.Insert(r);}}};
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.y+=p.x;return p;}};
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
struct ERR_HQRoadIntercept{'''+better+stand+r'''};
'''+settle+s+r'''
float roadDistance(vector p,const Array<vector>&points){
float best=INFINITY;
for(size_t i=1;i<points.size();i++){
vector a=points[i-1],b=points[i],d=b-a;d.y=0;float length2=vector::Dot(d,d);
if(length2<.0001f)continue;
vector off=p-a;off.y=0;float t=std::clamp(vector::Dot(off,d)/length2,0.f,1.f);
best=std::min(best,vector::DistanceXZ(p,a+d*t));
}return best;
}
int main(){
using Site=ERR_EngineerMineSite;vector post{0,10,0},out,reversed;
'''+clearance_checks+r'''
BaseRoad road;road.points={{-100,10,0},{100,10,0}};game.ai.manager.roads={&road};
assert(Site::Select(post,out));
// The previous production implementation compiles and fails exactly here.
assert(Site::InBand(post,out)&&roadDistance(out,road.points)>=15.f);
assert(game.ai.manager.queries==1&&game.world.queries>0);
std::reverse(road.points.begin(),road.points.end());assert(Site::Select(post,reversed));assert(vector::DistanceXZ(out,reversed)<.001f);
// A crossing or nearby parallel narrow road is still a clearance hazard.
BaseRoad cross,parallel;cross.width=2;cross.points={{0,10,-100},{0,10,100}};
game.ai.manager.roads={&road,&cross};assert(Site::Select(post,out));assert(roadDistance(out,road.points)>=15&&roadDistance(out,cross.points)>=15);
game.ai.manager.roads={&cross,&road};assert(Site::Select(post,reversed)&&vector::DistanceXZ(out,reversed)<.001f);
parallel.width=1;parallel.points={{-100,10,-16},{100,10,-16}};
game.ai.manager.roads={&road,&parallel};assert(Site::Select(post,out));assert(roadDistance(out,road.points)>=15&&roadDistance(out,parallel.points)>=15&&out.z>0);
// Dense parallel roads leave no safe flank in the25–60m annulus.
BaseRoad grid[9];game.ai.manager.roads={&road};for(int i=0;i<9;i++){grid[i].width=2;grid[i].points={{-100,10,float(-60+i*15)},{100,10,float(-60+i*15)}};game.ai.manager.roads.Insert(&grid[i]);}
assert(!Site::Select(post,out)&&out==vector::Zero);
// Clearance includes a narrow neighbor beyond the old60m query. Only the
// upper outer flank is physically clear, and its neighboring road is10m away.
BaseRoad edge,outer;edge.points={{-100,10,44.98f},{100,10,44.98f}};outer.width=2;outer.points={{-100,10,70},{100,10,70}};
game.ai.manager.roads={&edge};game.world.onlyTop=true;assert(Site::Select(post,out)&&out.z>59);
game.ai.manager.roads={&edge,&outer};assert(!Site::Select(post,out)&&out==vector::Zero);game.world.onlyTop=false;
// Actual shared settlement logic: named village within200m rejects a candidate;
// an opposite clear flank remains selectable. Ordinary buildings are excluded.
game.ai.manager.roads={&road};MapDescriptorComponent descriptor;Entity settlement;settlement.descriptor=&descriptor;settlement.position={0,10,-200};game.world.settlements={&settlement};
assert(Site::Select(post,out)&&out.z>0&&vector::DistanceXZ(out,settlement.position)>200);
settlement.position=post;assert(!Site::Select(post,out)&&out==vector::Zero);
descriptor.type=EMapDescriptorType::MDT_BUILDING;assert(Site::Select(post,out));
descriptor.type=EMapDescriptorType::MDT_NAME_CITY;assert(!Site::Select(post,out));
descriptor.type=EMapDescriptorType::MDT_NAME_TOWN;assert(!Site::Select(post,out));
descriptor.type=EMapDescriptorType::MDT_NAME_SETTLEMENT;assert(!Site::Select(post,out));game.world.settlements.Clear();
// Real bends retain clearance from every constituent segment, never chords.
BaseRoad bend;bend.points={{-70,10,30},{50,10,30},{50,10,-30},{-70,10,-30}};game.ai.manager.roads={&bend};
assert(Site::Select(post,out)&&Site::InBand(post,out)&&roadDistance(out,bend.points)>=15);
std::reverse(bend.points.begin(),bend.points.end());assert(Site::Select(post,reversed)&&vector::DistanceXZ(out,reversed)<.001f);
// Existing exact annulus bounds and deterministic bounded retention survive.
assert(Site::InBand(post,{25,10,0})&&Site::InBand(post,{60,10,0}));assert(!Site::InBand(post,{24.999f,10,0})&&!Site::InBand(post,{60.001f,10,0}));
Array<vector>forward,reverse;for(int i=0;i<200;i++)Site::Keep(post,{30+float(i)/100,10,0},forward);for(int i=199;i>=0;i--)Site::Keep(post,{30+float(i)/100,10,0},reverse);
assert(forward.Count()==128&&reverse.Count()==128);for(auto p:forward)assert(reverse.Contains(p));
// Elevated centerlines cannot be converted to terrain below a bridge.
game.ai.manager.roads={&road};road.points={{-100,20,0},{100,20,0}};int before=game.world.traces;
assert(!Site::Select(post,out)&&out==vector::Zero&&game.world.traces==before);road.points={{-100,10,0},{100,10,0}};
ChimeraWorldUtils::water=true;assert(!Site::Select(post,out)&&out==vector::Zero);ChimeraWorldUtils::water=false;
ERR_Actors::steep=true;assert(!Site::Select(post,out)&&out==vector::Zero);ERR_Actors::steep=false;
game.world.obstructed=true;before=game.world.traces;assert(!Site::Select(post,out)&&game.world.traces-before<=128);game.world.obstructed=false;
game.world.blockNear=true;assert(Site::Select(post,out)&&vector::DistanceXZ(out,post)>=50);game.world.blockNear=false;
// Hard bounds reject incomplete hazard inventories before physical checks.
game.ai.manager.roads.resize(129,&road);before=game.world.traces;assert(!Site::Select(post,out)&&game.world.traces==before);
BaseRoad dense;for(int i=0;i<1024;i++)dense.points.push_back({float(i),10,30});game.ai.manager.roads={&dense,&dense,&dense,&dense,&dense};before=game.world.traces;
assert(!Site::Select(post,out)&&game.world.traces==before);
BaseRoad bad;bad.width=1;bad.points={{0,10,0},{NAN,10,30}};game.ai.manager.roads={&road,&bad};assert(!Site::Select(post,out)&&out==vector::Zero);
bad.points={{0,10,0}};assert(!Site::Select(post,out));bad.points.resize(1025,{0,10,0});assert(!Site::Select(post,out));
game.ai.manager.roads={&road,nullptr};assert(!Site::Select(post,out));game.ai.manager.roads={&road};
Replication::server=false;before=game.ai.manager.queries;int settlementsBefore=game.world.queries;assert(!Site::Select(post,out)&&game.ai.manager.queries==before&&game.world.queries==settlementsBefore);Replication::server=true;
before=game.ai.manager.queries;assert(!Site::Select({NAN,10,0},out)&&out==vector::Zero&&game.ai.manager.queries==before);
}
''')
print('PASS: actual mine selector respects road and settlement flank safety; native boundaries doubled')