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:
@@ -2,7 +2,8 @@
|
||||
import re
|
||||
from policy_harness import ROOT, body, run
|
||||
rules_path=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterOpeningPlan.c'
|
||||
rules='\n'.join(body(rules_path,x) for x in ('static bool Spread(', 'static bool FreshOrder(', 'static bool Select('))
|
||||
rules='\n'.join(body(rules_path,x) for x in ('static bool InBand(', 'static float ScanRadius(', 'static bool Spread(', 'static bool FreshOrder(', 'static bool Select('))
|
||||
rules=rules.replace('ERR_ForceRules.FiniteVector', 'FiniteVector').replace('ERR_ForceRules.Finite', 'std::isfinite').replace('Math.Max', 'std::max<float>')
|
||||
rules=re.sub(r'\bvector (?=\w)', 'Vec ', rules).replace('vector.', 'Geometry::').replace('array<vector> candidates','Array<Vec> candidates').replace('array<int> selected','Array<int>& selected')
|
||||
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c'
|
||||
queue=body(p,'protected void QueueDestination(').replace('protected ','').replace('foreach (','for (')
|
||||
@@ -12,46 +13,65 @@ for typ in ('ERR_HunterVehicle','ERR_HQ','ERR_HQReport','ERR_HQBelief','ERR_Site
|
||||
for name in ('unit','hq','report','belief','best','m_Mode','site'):
|
||||
queue=queue.replace(name+'.',name+'->')
|
||||
queue=queue.replace('best->Confidence.ToString()', 'std::to_string(best->Confidence)')
|
||||
queue=queue.replace('Search.', 'Search->').replace('Waypoint.GetOrigin', 'Waypoint->GetOrigin')
|
||||
queue=queue.replace('Memory().Get','Memory()->Get').replace('Sites().','Sites()->').replace('Definition.','Definition->')
|
||||
queue=queue.replace('Waypoint.IsWithinCompletionRadius','Waypoint->IsWithinCompletionRadius').replace('Entity.GetOrigin','Entity->GetOrigin').replace('Group.GetFaction','Group->GetFaction')
|
||||
queue=queue.replace('ERR_HQBelief* best;', 'ERR_HQBelief* best=nullptr;').replace('bool freshContact;', 'bool freshContact=false;').replace('bool found;', 'bool found=false;')
|
||||
queue=queue.replace('vector destination, angles;', 'Vec destination, angles;')
|
||||
queue=re.sub(r'\bvector (?=\w)', 'Vec ', queue).replace('Downlink.MessageId','Downlink->MessageId').replace('Tactics.Stopped','Tactics->Stopped')
|
||||
for cls in ('ERR_HunterOpeningRules','ERR_SupplyConvoySystem','ERR_ObjectiveDefence','Math','vector'):
|
||||
queue=queue.replace(cls+'.', ('Geometry' if cls=='vector' else cls)+'::')
|
||||
queue=queue.replace('string.Format(', 'Format(').replace('ERR_HQ.Event(', 'Event(')
|
||||
queue=queue.replace('null','nullptr').replace('nullptrptr','nullptr')
|
||||
search=body(p,'class ERR_HunterSearch').replace('class ERR_HunterSearch', 'struct ERR_HunterSearch').replace('int MessageId;', 'int MessageId=0;').replace('vector Center;', 'Vec Center;').replace('vector center', 'Vec center').replace('Math.Clamp(', 'std::clamp<float>(')+';'
|
||||
run(r'''
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
using string=std::string;
|
||||
template<class... T>string Format(string,T...){return "investigate";}void Event(string,string,int){}
|
||||
struct Vec{float x=0,y=0,z=0;float& operator[](int i){return i==0?x:i==1?y:z;}Vec operator-(Vec b){return {x-b.x,y-b.y,z-b.z};}string ToString(){return "position";}};
|
||||
bool FiniteVector(Vec v){return std::isfinite(v.x)&&std::isfinite(v.y)&&std::isfinite(v.z);}
|
||||
namespace Geometry{float DistanceXZ(Vec a,Vec b){return std::hypot(a.x-b.x,a.z-b.z);}float Dot(Vec a,Vec b){return a.x*b.x+a.y*b.y+a.z*b.z;}}
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}void Clear(){this->clear();}};
|
||||
struct ERR_HunterOpeningRules{
|
||||
'''+rules+r'''
|
||||
};
|
||||
'''+search+r'''
|
||||
struct Group{int GetFaction(){return 1;}};
|
||||
struct Entity{Vec GetOrigin(){return {};}};
|
||||
struct Waypoint{bool complete=false;bool IsWithinCompletionRadius(Group*){return complete;}};
|
||||
struct Route{};
|
||||
struct ERR_HunterVehicle{float NextOrder=99999,LastContact=-1,NextContactOrder=0;Route* Downlink=nullptr;int SourceMessage=0;Vec Destination,ContactPosition,DeliveredContactPosition;bool DeliveredContact=false;string Source="hunter";struct Group* Group=nullptr;struct Entity* Entity=nullptr;struct Waypoint* Waypoint=nullptr;};
|
||||
struct Waypoint{Vec GetOrigin(){return {1000,0,2000};}bool complete=false;bool IsWithinCompletionRadius(Group*){return complete;}};
|
||||
struct Route{int MessageId=1;};
|
||||
struct TacticsType{bool Stopped=false;};
|
||||
struct ERR_HunterVehicle{TacticsType*Tactics=nullptr;ERR_HunterSearch search;ERR_HunterSearch* Search=&search;bool PendingSearch=false,DeliveredSearch=false;float PendingObservedAt=-1;bool OpeningPatrol=false,TroopTransport=false;Vec PatrolHome;float NextOrder=99999,LastContact=-1,NextContactOrder=0;Route* Downlink=nullptr;void CancelHQDownlink(){Downlink=nullptr;}int SourceMessage=0;Vec Destination,ContactPosition,DeliveredContactPosition;bool DeliveredContact=false;string Source="hunter";struct Group* Group=nullptr;struct Entity* Entity=nullptr;struct Waypoint* Waypoint=nullptr;};
|
||||
struct ERR_HQReport{int Target=1;};
|
||||
struct ERR_HQBelief{bool actionable=true;float ObservedAt=10,Confidence=1;int MessageId=7;Vec PredictedPosition={1000,0,2000};bool Actionable(){return actionable;}};
|
||||
struct ERR_HQBelief{bool actionable=true;float ObservedAt=10,Confidence=1,Radius=50;int MessageId=7;Vec PredictedPosition={1000,0,2000};bool Actionable(){return actionable;}};
|
||||
struct Memory{ERR_HQBelief belief;ERR_HQBelief* Get(int){return &belief;}};
|
||||
struct ERR_HQ{bool contact=false,connected=true;int requests=0;struct Memory memory;Route route;void SupportContacts(Array<ERR_HQReport*>& out,float){static ERR_HQReport report;if(contact)out.Insert(&report);}struct Memory* Memory(){return &memory;}Route* Downlink(Group*,string,string,int){requests++;return connected?&route:nullptr;}};
|
||||
struct ERR_HQ{bool CoordinatedDoctrine(){return false;}bool CoordinatedHunterSector(string,ERR_HQBelief*,float,Vec&){return false;}bool CoordinatedHunterQueued(string,ERR_HQBelief*,Vec,int,float){return false;}bool contact=false,connected=true;int requests=0;struct Memory memory;Route route;void HunterContacts(Array<ERR_HQReport*>& out,float){static ERR_HQReport report;if(contact)out.Insert(&report);}struct Memory* Memory(){return &memory;}Route* Downlink(Group*,string,string,int){requests++;return connected?&route:nullptr;}};
|
||||
struct Definition{int Kind=1;Vec Position;};struct ERR_SiteRuntime{struct Definition* Definition=nullptr;struct Group* Group=nullptr;};
|
||||
struct Sites{int Count(){return 0;}ERR_SiteRuntime* At(int){return nullptr;}};
|
||||
struct Mode{struct Sites sites;struct Sites* Sites(){return &sites;}int EnemyFaction(){return 1;}};
|
||||
namespace Math{int RandomInt(int,int){return 0;}}
|
||||
namespace ERR_ObjectiveDefence{bool Objective(int){return true;}}
|
||||
namespace ERR_SupplyConvoySystem{bool available=true;int calls=0;bool ApproachDirected(Vec p,Vec& dst,Vec& angles,float radius=500){calls++;dst=p;return available;}}
|
||||
struct Service{Mode mode;Mode* m_Mode=&mode;
|
||||
struct Service{int searchCalls=0;bool searchRoad=true;bool SearchPoint(ERR_HunterVehicle*,float,Vec& point,Vec&){searchCalls++;point={1400,0,2000};return searchRoad;}int localCalls=0;bool localRoad=true;bool OpeningPatrolPoint(ERR_HunterVehicle*,Vec& point,Vec&){localCalls++;point={800,0,0};return localRoad;}bool coordinated=false;bool Coordinated(Vec){return coordinated;}Mode mode;Mode* m_Mode=&mode;
|
||||
'''+queue+r'''
|
||||
};
|
||||
Vec polar(float degrees,float radius=1000){return {std::sin(degrees*3.14159265f/180)*radius,0,std::cos(degrees*3.14159265f/180)*radius};}
|
||||
int main(){
|
||||
Array<int> chosen;Vec center;
|
||||
assert(ERR_HunterOpeningRules::InBand(center,polar(0,750),1000));
|
||||
assert(ERR_HunterOpeningRules::InBand(center,polar(0,1250),1000));
|
||||
assert(!ERR_HunterOpeningRules::InBand(center,polar(0,749),1000));
|
||||
assert(!ERR_HunterOpeningRules::InBand(center,polar(0,1251),1000));
|
||||
assert(!ERR_HunterOpeningRules::InBand(center,polar(0,599),800));
|
||||
assert(ERR_HunterOpeningRules::InBand(center,polar(0,600),800));
|
||||
assert(!ERR_HunterOpeningRules::InBand(center,polar(0,1000),0));
|
||||
assert(ERR_HunterOpeningRules::ScanRadius(1000,0)==1000);
|
||||
assert(ERR_HunterOpeningRules::ScanRadius(1000,1)==850);
|
||||
assert(ERR_HunterOpeningRules::ScanRadius(1000,2)==1150);
|
||||
assert(ERR_HunterOpeningRules::ScanRadius(1000,3)==1000);
|
||||
assert(ERR_HunterOpeningRules::Select(center,{polar(0),polar(120),polar(240)},chosen)&&chosen.Count()==3);
|
||||
assert(!ERR_HunterOpeningRules::Select(center,{polar(0),polar(5),polar(10)},chosen)&&chosen.empty());
|
||||
assert(!ERR_HunterOpeningRules::Spread(center,polar(359),polar(1)));
|
||||
@@ -61,6 +81,15 @@ int main(){
|
||||
assert(!ERR_HunterOpeningRules::FreshOrder(10,10,100,0,false));
|
||||
assert(!ERR_HunterOpeningRules::FreshOrder(11,10,100,101,false));
|
||||
assert(!ERR_HunterOpeningRules::FreshOrder(11,10,100,0,true));
|
||||
// Both opening vehicle types use a local patrol until a delivered contact.
|
||||
for(bool transport:{false,true}){
|
||||
Service opening;ERR_HQ command;ERR_HunterVehicle crew;crew.OpeningPatrol=true;crew.TroopTransport=transport;crew.NextOrder=0;
|
||||
opening.QueueDestination(&crew,&command,100);assert(opening.localCalls==1&&command.requests==1&&crew.SourceMessage==0&&crew.Destination.x==800);
|
||||
crew.Downlink=nullptr;crew.NextOrder=0;opening.localRoad=false;
|
||||
opening.QueueDestination(&crew,&command,200);assert(command.requests==1); // unsafe/absent local road holds, no distant fallback
|
||||
crew.NextOrder=0;command.contact=true;
|
||||
opening.QueueDestination(&crew,&command,300);assert(command.requests==2&&crew.SourceMessage==7&&crew.Destination.x==1000);
|
||||
}
|
||||
Service service;ERR_HQ hq;ERR_HunterVehicle unit;Route patrol;unit.Downlink=&patrol;
|
||||
service.QueueDestination(&unit,&hq,100);assert(unit.Downlink==&patrol&&hq.requests==0); // no magical position
|
||||
hq.contact=true;service.QueueDestination(&unit,&hq,100);
|
||||
@@ -75,14 +104,45 @@ int main(){
|
||||
// Repeated/new reports in an already delivered area must not churn routing.
|
||||
Waypoint route;unit.Waypoint=&route;unit.DeliveredContact=true;unit.DeliveredContactPosition={1000,0,2000};
|
||||
unit.Downlink=nullptr;unit.NextOrder=0;unit.NextContactOrder=0;
|
||||
int kept=hq.requests;int projections=ERR_SupplyConvoySystem::calls;
|
||||
hq.connected=true;int kept=hq.requests;int projections=ERR_SupplyConvoySystem::calls;
|
||||
hq.memory.belief.ObservedAt=12;service.QueueDestination(&unit,&hq,12000);
|
||||
assert(hq.requests==kept&&ERR_SupplyConvoySystem::calls==projections&&unit.LastContact==12);
|
||||
assert(hq.requests==kept+1&&ERR_SupplyConvoySystem::calls==projections&&unit.LastContact==12);
|
||||
assert(unit.Destination.x==1000);unit.Downlink=nullptr; // renewal traverses radio but preserves existing road endpoint
|
||||
hq.memory.belief.PredictedPosition.x=1100;hq.memory.belief.ObservedAt=13;
|
||||
service.QueueDestination(&unit,&hq,18000);assert(hq.requests==kept+1); // Material displacement redirects.
|
||||
hq.memory.belief.ObservedAt=11;
|
||||
service.QueueDestination(&unit,&hq,18000);assert(hq.requests==kept+2); // Material displacement redirects.
|
||||
hq.connected=false;hq.memory.belief.ObservedAt=11;
|
||||
int requests=hq.requests;service.QueueDestination(&disconnected,&hq,11000);assert(hq.requests==requests);
|
||||
hq.connected=true;service.QueueDestination(&disconnected,&hq,15000);assert(disconnected.Downlink&&disconnected.LastContact==11);
|
||||
// A two-crew encounter must not suppress a spare hunter's delivered response.
|
||||
ERR_HunterVehicle reserve;service.coordinated=true;hq.memory.belief.ObservedAt=20;
|
||||
requests=hq.requests;service.QueueDestination(&reserve,&hq,20000);
|
||||
assert(hq.requests==requests+1&&reserve.Downlink&&reserve.LastContact==20);
|
||||
|
||||
// Delivered sector persists after HQ contact expiry; travel and one-minute arrival wait gate sweeps.
|
||||
ERR_HunterVehicle searching;Waypoint approach;searching.Waypoint=&approach;searching.Search->Remember({1000,0,2000},0,7);
|
||||
searching.LastContact=0;hq.contact=false;hq.connected=true;searching.NextOrder=0;
|
||||
int before=hq.requests;service.QueueDestination(&searching,&hq,310000);assert(hq.requests==before);
|
||||
approach.complete=true;service.QueueDestination(&searching,&hq,340000);assert(hq.requests==before);
|
||||
service.QueueDestination(&searching,&hq,399999);assert(hq.requests==before);
|
||||
searching.NextOrder=0;service.QueueDestination(&searching,&hq,400000);
|
||||
assert(hq.requests==before+1&&searching.PendingSearch&&searching.SourceMessage==7&&searching.Destination.x==1400);
|
||||
// Fresh delivered HQ sighting preempts a sweep still in radio transit.
|
||||
hq.contact=true;hq.memory.belief.ObservedAt=400001;hq.memory.belief.PredictedPosition={2000,0,3000};
|
||||
service.QueueDestination(&searching,&hq,400001);
|
||||
assert(hq.requests==before+2&&!searching.PendingSearch&&searching.PendingObservedAt==400001&&searching.Destination.x==2000);
|
||||
assert(searching.Search->ObservedAt==0); // queueing cannot refresh crew memory
|
||||
// Lost route and unreachable roads do not cause distant patrol fallback while search is valid.
|
||||
hq.contact=false;searching.Downlink=nullptr;searching.NextOrder=0;hq.connected=false;
|
||||
service.QueueDestination(&searching,&hq,450000);assert(!searching.Downlink&&searching.Search->ObservedAt==0);
|
||||
service.searchRoad=false;searching.NextOrder=0;before=hq.requests;
|
||||
service.QueueDestination(&searching,&hq,480000);assert(hq.requests==before);
|
||||
service.searchRoad=true;searching.NextOrder=0;
|
||||
int searchCalls=service.searchCalls;service.QueueDestination(&searching,&hq,600001);assert(service.searchCalls==searchCalls);
|
||||
// Repeated unchanged belief also becomes a sweep after arrival, without an approach loop.
|
||||
searching.Search->Remember({2000,0,3000},600002,9);searching.NextOrder=0;hq.contact=true;hq.connected=true;
|
||||
hq.memory.belief.ObservedAt=600002;searching.LastContact=600002;
|
||||
service.QueueDestination(&searching,&hq,600002);assert(!searching.Downlink);
|
||||
service.QueueDestination(&searching,&hq,660002);assert(searching.Downlink&&searching.PendingSearch);
|
||||
}
|
||||
''')
|
||||
print('PASS: real opening road geometry enforces wide azimuths; fresh delivered contact preempts patrol cooldown, respects radio failures and does not starve in-flight interception')
|
||||
|
||||
Reference in New Issue
Block a user