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:
@@ -0,0 +1,118 @@
|
||||
"""Actual planner selection/commit body: identical orders with one local count scan."""
|
||||
import os,re
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT,body,run
|
||||
P=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
|
||||
s=body(P,'protected void Assign(')
|
||||
# Execute the complete final selection/commit loop. The preceding cleanup,
|
||||
# Retask and Investigate boundaries are represented by pre-existing commitments.
|
||||
start=s.index(' assigned = HuntingAssigned();')
|
||||
code=s[start:s.rfind('}')]
|
||||
# In the reference variant, restore only the old repeated-count loop guards.
|
||||
legacy=code.replace(' int totalAssigned = ResponseAssigned();','').replace('totalAssigned < globalLimit','Assigned() < globalLimit').replace(' totalAssigned++;','')
|
||||
assigned=body(P,'protected int Assigned(').replace('protected ','').replace('int Assigned()', 'int ActualAssigned()')
|
||||
def adapt(s):
|
||||
s=s.replace('array<vector>','Arr<Vec>').replace('array<int>','Arr<int>').replace('vector destination','Vec destination').replace('vector position','Vec position')
|
||||
s=s.replace('foreach (ERR_HQUnit unit : m_Units)','for (ERR_HQUnit* unit : m_Units)')
|
||||
for cls in ('ERR_HQBelief','ERR_HQHuntWindow','ERR_HQUnit','ERR_HQRoute','ERR_HQTask'):
|
||||
s=re.sub(r'\b'+cls+r' (\w+)',cls+r'* \1',s)
|
||||
s=re.sub(r'(ERR_HQ(?:Belief|Unit|Route)\* \w+);',r'\1=nullptr;',s)
|
||||
s=s.replace('int total;', 'int total=0;')
|
||||
s=s.replace('bool roadIntercept;', 'bool roadIntercept=false;').replace('int bestId;', 'int bestId=0;')
|
||||
for name in ('unit','belief','candidate','hunt','best','bestRoute','task'):
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('unit->Group.GetOrigin()', 'unit->Group->GetOrigin()').replace('belief->Report.Snapshot()', 'belief->Report->Snapshot()')
|
||||
s=s.replace('new ERR_HQTask()', 'NewTask()').replace('null','nullptr').replace('nullptrptr','nullptr')
|
||||
for name in ('ERR_HQPursuitRules','ERR_HQPursuitRole','ERR_HQMemoryKind','ERR_HQRoadIntercept','Math'):
|
||||
s=s.replace(name+'.',name+'::')
|
||||
s=s.replace('vector.DistanceXZ','Distance').replace('string.Format','Format').replace('ERR_HQPursuitRole role','int role')
|
||||
return s
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
using string=std::string;
|
||||
template<class T> struct Arr:std::vector<T>{using std::vector<T>::vector;int Count()const{return this->size();}void Insert(T t){this->push_back(t);}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}};
|
||||
struct Vec{float x=0;bool operator==(Vec b)const{return x==b.x;}};
|
||||
float Distance(Vec a,Vec b){return std::abs(a.x-b.x);}
|
||||
struct Math{static float Max(float a,float b){return std::max(a,b);}};
|
||||
template<class...T>string Format(string s,T...){return s;}
|
||||
namespace ERR_HQMemoryKind{enum{SIGHTING,GUNFIRE,ALARM};}
|
||||
namespace ERR_HQPursuitRole{enum{SEARCH,INTERCEPT,CONTAIN};}
|
||||
struct ERR_HQReport{int id=0;ERR_HQReport* Snapshot(){return this;}};
|
||||
struct ERR_HQTask{bool Maneuver=false;int BeliefId=0,BeliefRevision=0,Role=0,ApproachBearing=0;bool RoadIntercept=false;Vec Destination;float SearchRadiusMetres=0,QueuedAt=0,ExpiresAt=0;string Name(){return "task";}};
|
||||
struct ERR_HQRoute{int MessageId=0;string Payload;};
|
||||
struct Group{Vec pos;Vec GetOrigin(){return pos;}};
|
||||
struct ERR_HQUnit{int Id=0,Radio=0,HomeId=0,ForceSourceId=0;struct Group* Group=nullptr;ERR_HQReport* Assignment=nullptr;ERR_HQReport* PendingOrder=nullptr;ERR_HQTask* PendingTask=nullptr;ERR_HQRoute* Downlink=nullptr;bool PlanFighting=false,PlanEligible=true,HomeGuard=false;Vec PlanPosition;};
|
||||
struct ERR_HQBelief{int BeliefId=0,BeliefRevision=1,MessageId=1,Kind=0;float Confidence=.7,ObservedAt=1000,Radius=100;Vec Position,PredictedPosition,Velocity;bool HasVelocity=true;ERR_HQReport* Report=nullptr;};
|
||||
struct ERR_HQHuntWindow{float NextHunt=0;};
|
||||
struct ERR_HQPursuitRules{
|
||||
MANEUVER
|
||||
static float SearchRadius(float radius){return std::clamp(radius,45.f,110.f);}
|
||||
static bool Actionable(float c,float age,float r){return c>=.15&&age>=0&&r<=2000;}
|
||||
static bool Heading(Vec,bool h){return h;}
|
||||
static int Role(float,float,float,bool h,int slot){return h?slot:0;}
|
||||
static Vec Objective(Vec p,Vec,bool,float,float,float,int role,int slot){return {p.x+role*10+slot};}
|
||||
static bool CanCommit(bool group,bool eligible,bool busy,bool guard,int available,int assigned,int limit){return group&&eligible&&!busy&&(!guard||available>1)&&assigned<limit;}
|
||||
static int Bearing(Vec p,Vec){return ((int)p.x)%2;}
|
||||
static bool BearingAllowed(int b,int a,int c){return a<0||b==a||c<0||b==c;}
|
||||
static float Score(int,Vec,Vec,Vec p,Vec d){return Distance(p,d);}
|
||||
static bool Better(float score,int id,float old,int oldid){return old<0||score<old||(score==old&&id<oldid);}
|
||||
static float TaskLifetimeSeconds(int,float distance){return 20+distance;}
|
||||
};
|
||||
struct ERR_HQRoadIntercept{static bool Select(ERR_HQBelief* b,float,Arr<Vec>& r,Vec& p){p.x=b->PredictedPosition.x+100+r.Count();return b->BeliefId%2==0;}};
|
||||
struct Watch{bool ArmedSearchBlocked(int source,float){return source%11==0;}};
|
||||
struct Coverage{bool allow=true;Arr<Vec> selections;bool Select(Vec p,float,Vec,float,Vec& result,Arr<Vec>& reserved){if(!allow)return false;result.x=p.x+reserved.Count()*25;selections.Insert(result);return true;}};
|
||||
struct Network{ERR_HQRoute routes[129];ERR_HQRoute* Route(int radio,bool){return radio%7?&routes[radio]:nullptr;}};
|
||||
struct Journal{int writes=0,seq=0;int Message(){return ++seq;}void Write(string,string,int){writes++;}};
|
||||
struct Homes{std::map<int,int> v;void Set(int id,int n){v[id]=n;}int Get(int id){return v[id];}};
|
||||
struct Space{bool Free(void*,Vec,float){return true;}void Claim(Group*,Vec,float){}};
|
||||
struct Harness {
|
||||
Space m_AttackSpace;void ReserveAttackSpace(ERR_HQUnit*,float){}bool allowFlank=true;int flanks=0;bool FlankPosition(ERR_HQUnit*,Vec,int slot,float,Vec& p){flanks++;p.x+=100*slot;return allowFlank;}
|
||||
Arr<ERR_HQUnit*> m_Units;Arr<ERR_HQBelief*> m_PlanBeliefs;Watch m_Watch;Coverage m_Coverage;Network m_Network;Journal m_Journal;Homes m_HomeAvailable;
|
||||
std::map<int,ERR_HQHuntWindow> hunts;std::vector<std::unique_ptr<ERR_HQTask>> tasks;
|
||||
int calls=0,visits=0,hunting=0;ERR_HQTask* NewTask(){tasks.push_back(std::make_unique<ERR_HQTask>());return tasks.back().get();}
|
||||
COUNT
|
||||
int Assigned(){calls++;visits+=m_Units.Count();return ActualAssigned();}
|
||||
int ResponseAssigned(){return Assigned();} // This planner fixture has no site-defence commitments.
|
||||
int HuntingAssigned(){return hunting;}
|
||||
int HomeAvailable(ERR_HQUnit* u){return m_HomeAvailable.Get(u->HomeId);}
|
||||
ERR_HQHuntWindow* Hunt(int id){return &hunts[id];}
|
||||
bool AlarmCommitted(){for(auto u:m_Units)if(u->PendingTask&&u->PendingTask->BeliefId==63)return true;return false;}
|
||||
Vec Containment(ERR_HQBelief*,Vec v){return v;}
|
||||
void ReserveUnit(ERR_HQUnit* u,Arr<Vec>& reserved,int& first,int& second){if(!u->Assignment&&!u->PendingOrder)return;reserved.Insert(u->PlanPosition);int b=u->Id%2;if(first<0)first=b;else if(first!=b&&second<0)second=b;}
|
||||
void Current(int globalLimit,int limit,float now){int assigned=0,radius=10000,roadQueries=0; CURRENT }
|
||||
void Legacy(int globalLimit,int limit,float now){int assigned=0,radius=10000,roadQueries=0; LEGACY }
|
||||
};
|
||||
struct Setup{Harness h;ERR_HQUnit units[128];struct Group groups[128];ERR_HQReport reports[64];ERR_HQBelief beliefs[64];
|
||||
Setup(int busy,int variant){
|
||||
for(int i=0;i<128;i++){auto&u=units[i];u.Id=i+1;u.Radio=i+1;u.ForceSourceId=i+1;u.Group=&groups[i];u.PlanPosition={float(i+1)};u.HomeGuard=(i%5==0);u.HomeId=i%3;h.m_HomeAvailable.Set(u.HomeId,20);u.PlanEligible=variant!=1;u.PlanFighting=(i%17==0);if(i<busy){if(i%3!=1)u.Assignment=&reports[0];if(i%3!=0)u.PendingOrder=&reports[0];}h.m_Units.Insert(&u);}
|
||||
for(int i=0;i<64;i++){auto&b=beliefs[i];b.BeliefId=i+1;b.Report=&reports[i];b.PredictedPosition={float(i*10)};b.Position=b.PredictedPosition;b.Confidence=.2f+.01f*i;b.Kind=i==62?ERR_HQMemoryKind::ALARM:(i%5==0?ERR_HQMemoryKind::GUNFIRE:0);h.m_PlanBeliefs.Insert(&b);h.hunts[i+1].NextHunt=(variant==2?100000:0);}
|
||||
h.m_Coverage.allow=variant!=3;
|
||||
}
|
||||
};
|
||||
void Equal(Setup&a,Setup&b){assert(a.h.m_Journal.writes==b.h.m_Journal.writes&&a.h.m_Coverage.selections==b.h.m_Coverage.selections&&a.h.m_HomeAvailable.v==b.h.m_HomeAvailable.v);
|
||||
for(int i=0;i<128;i++){auto x=a.units[i].PendingTask,y=b.units[i].PendingTask;assert(bool(x)==bool(y));if(!x)continue;assert(x->SearchRadiusMetres==y->SearchRadiusMetres&&x->BeliefId==y->BeliefId&&x->Role==y->Role&&x->RoadIntercept==y->RoadIntercept&&x->Destination==y->Destination&&x->ExpiresAt==y->ExpiresAt&&x->ApproachBearing==y->ApproachBearing);}
|
||||
for(auto [id,window]:a.h.hunts)assert(window.NextHunt==b.h.hunts[id].NextHunt);
|
||||
}
|
||||
int main(){
|
||||
for(int variant=0;variant<4;variant++)for(int global:{0,1,2,3,8,16})for(int busy:{0,1,2,5,16})for(int hunting:{0,1,3}){
|
||||
Setup current(busy,variant),legacy(busy,variant);current.h.hunting=legacy.h.hunting=std::min(busy,hunting);current.h.Current(global,std::min(global,3),1000);legacy.h.Legacy(global,std::min(global,3),1000);Equal(current,legacy);assert(current.h.calls<=1);assert(current.h.ActualAssigned()<=std::max(busy,global));}
|
||||
// Fighting mobile squads accept explicit flanks; guard and placement gates remain enforced.
|
||||
for(bool valid:{false,true}){
|
||||
Setup fight(0,0);fight.h.m_PlanBeliefs={&fight.beliefs[0]};
|
||||
auto& belief=fight.beliefs[0];belief.Kind=ERR_HQMemoryKind::SIGHTING;belief.Confidence=.9f;belief.HasVelocity=false;
|
||||
for(auto&unit:fight.units)unit.PlanFighting=true;
|
||||
fight.h.allowFlank=valid;fight.h.Current(3,3,1000);
|
||||
int orders=0;for(auto&unit:fight.units)if(unit.PendingTask){orders++;assert(!unit.HomeGuard&&unit.PendingTask->Maneuver&&unit.PendingTask->ExpiresAt<=241000);}
|
||||
assert(orders==(valid?2:0)&&fight.h.flanks==2);
|
||||
}
|
||||
Setup current(0,1),legacy(0,1);current.h.Current(16,3,1000);legacy.h.Legacy(16,3,1000);Equal(current,legacy);assert(current.h.visits==128);assert(legacy.h.visits>10000);
|
||||
}
|
||||
'''.replace('MANEUVER',body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c','static bool Maneuver(')).replace('COUNT',adapt(assigned)).replace('CURRENT',adapt(code)).replace('LEGACY',adapt(legacy)))
|
||||
assert s.index('int totalAssigned = ResponseAssigned();')>s.index('Investigate(now, globalLimit);')>s.index('Retask(now);')
|
||||
print('PASS: actual full planner selection/commit body, 360 equivalence scenarios, orders/reservations/cooldowns identical; one128-unit scan vs >10000 repeated unit visits; no FPS claim')
|
||||
Reference in New Issue
Block a user