Reduce redundant AI equipment, radio and observation checks
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
Preserve live ownership and detection rules while skipping redundant work. Record published Workshop version 0.0.150 and add operation-count regressions.
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
from policy_harness import ROOT, run
|
||||
from pathlib import Path
|
||||
import re, os
|
||||
expected_empty=int(os.environ.get('ERR_EXPECT_EMPTY_LOOKUPS','0'))
|
||||
expected_far=int(os.environ.get('ERR_EXPECT_FAR_TRANSFORMS','0'))
|
||||
path=Path(os.environ.get('ERR_BODY_SOURCE', str(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQBodyObserver.c')))
|
||||
s=path.read_text()
|
||||
assert 'GroupLookup' not in s and 'Spawn(' not in s and 'Accept(' not in s and 'm_Contacts' not in s
|
||||
@@ -31,19 +33,21 @@ code=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2=0;',code)
|
||||
code=re.sub(r'\b(IEntity|SCR_AIGroup) (\w+);',r'\1 \2=nullptr;',code)
|
||||
code=code.replace('ERR_HQBodyScan* state;','ERR_HQBodyScan* state=nullptr;').replace('ERR_HQBodyScan* selected;','ERR_HQBodyScan* selected=nullptr;')
|
||||
code=code.replace('\n}\n','\n};\n').replace('class ', 'struct ')
|
||||
run(r'''
|
||||
run(f"#define EXPECT_EMPTY {expected_empty}\n#define EXPECT_FAR {expected_far}\n"+r'''
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <iostream>
|
||||
using string=std::string;
|
||||
int normalizations=0;
|
||||
struct vector {float x=0,y=0,z=0;float& operator[](int i){return i==0?x:i==1?y:z;}
|
||||
vector operator-(vector v)const{return {x-v.x,y-v.y,z-v.z};}
|
||||
vector operator+(vector v)const{return {x+v.x,y+v.y,z+v.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};}
|
||||
vector Normalized()const{normalizations++;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;}};
|
||||
template<class T> struct Array:std::vector<T>{using std::vector<T>::vector;
|
||||
int Count(){return this->size();}bool IsEmpty(){return this->empty();}
|
||||
@@ -51,8 +55,8 @@ template<class T> struct Array:std::vector<T>{using std::vector<T>::vector;
|
||||
void Remove(int n){this->erase(this->begin()+n);}
|
||||
bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
|
||||
struct ECharacterLifeState{static constexpr int DEAD=1;};
|
||||
struct Entity {vector position,forward{0,0,1};bool dead=false,awake=true,inVehicle=false,character=true;int reads=0;
|
||||
vector GetOrigin(){reads++;return position;}void GetWorldTransform(vector v[4]){v[2]=forward;}
|
||||
struct Entity {vector position,forward{0,0,1};bool dead=false,awake=true,inVehicle=false,character=true;int reads=0,transformReads=0;
|
||||
vector GetOrigin(){reads++;return position;}void GetWorldTransform(vector v[4]){transformReads++;v[2]=forward;}
|
||||
Entity* GetCharacterController(){return character?this:nullptr;}
|
||||
int GetLifeState(){return dead?1:0;}};
|
||||
using IEntity=Entity*;Entity* CastCharacter(Entity* p){return p&&p->character?p:nullptr;}
|
||||
@@ -61,7 +65,7 @@ struct Group {Array<AIAgent> agents;int faction=1,players=0;
|
||||
int GetFaction(){return faction;}int GetPlayerCount(bool){return players;}
|
||||
void GetAgents(Array<AIAgent>& out){out=agents;}};using SCR_AIGroup=Group*;
|
||||
struct ForceRecord {bool Physical=true,Eliminated=false;};using ERR_ForceRecord=ForceRecord*;
|
||||
struct Ledger {ForceRecord force;ERR_ForceRecord FindGroup(Group*){return &force;}};
|
||||
struct Ledger {ForceRecord force;int finds=0;ERR_ForceRecord FindGroup(Group*){finds++;return &force;}};
|
||||
struct GameMode {Ledger ledger;static GameMode* current;
|
||||
static GameMode* Get(){return current;}int EnemyFaction(){return 1;}Ledger* Forces(){return &ledger;}};
|
||||
GameMode* GameMode::current=nullptr;
|
||||
@@ -156,6 +160,39 @@ int main(){
|
||||
service.m_Groups[0]->LastAsked=0;service.m_Groups[1]->NextScan=1;
|
||||
assert(!service.Observe(&contenders[1],1000,report));
|
||||
service.Observe(&contenders[1],6000,report);assert(service.m_Groups[1]->NextScan==21000);
|
||||
// Independently count the empty-body and impossible-distance paths in actual source.
|
||||
service.Stop();Entity healthy;Agent healthyAgent;healthyAgent.actor=&healthy;Group healthyGroup;healthyGroup.agents={&healthyAgent};
|
||||
auto* healthyRoster=Roster(&healthyGroup);service.RegisterGroup(&healthyGroup,healthyRoster);
|
||||
mode.ledger.finds=0;int beforeEmptyTraces=game.world.traces;
|
||||
for(int i=0;i<100;i++){assert(!service.Observe(&healthyGroup,0,report));assert(!report);}
|
||||
assert(service.m_Now==0&&service.m_NextBudget==5000&&service.m_Traces==12); // budget still refreshes
|
||||
assert(service.m_Groups[0]->LastAsked==-1&&service.m_Groups[0]->NextScan==0);
|
||||
assert(game.world.traces==beforeEmptyTraces&&mode.ledger.finds==EXPECT_EMPTY);
|
||||
std::cout<<"BODY_WORK empty_calls=100 ledger_lookups="<<mode.ledger.finds<<"\n";
|
||||
// A later death becomes a candidate on the normal5s refresh; no stale empty cache.
|
||||
healthy.dead=true;service.TickBudget(5000);assert(service.m_Bodies.Count()==1);
|
||||
int beforeOwnership=mode.ledger.finds;mode.ledger.force.Physical=false;
|
||||
assert(!service.Observe(&healthyGroup,5000,report)&&mode.ledger.finds==beforeOwnership+1);
|
||||
assert(service.m_Groups[0]->LastAsked==-1);mode.ledger.force.Physical=true;
|
||||
healthyGroup.players=1;assert(!service.Observe(&healthyGroup,5000,report));healthyGroup.players=0;
|
||||
healthyGroup.faction=2;assert(!service.Observe(&healthyGroup,5000,report));healthyGroup.faction=1;
|
||||
service.Stop();service.m_Traces=12;observer.position={0,0,0};observer.forward={0,0,1};observer.awake=true;observer.inVehicle=false;
|
||||
Entity remote;remote.dead=true;remote.position={0,0,1000};observer.transformReads=0;normalizations=0;
|
||||
int beforeFarTraces=game.world.traces;
|
||||
for(int i=0;i<128;i++)assert(!service.Visible(&observer,&remote,point));
|
||||
assert(observer.transformReads==EXPECT_FAR&&normalizations==EXPECT_FAR*2);
|
||||
assert(game.world.traces==beforeFarTraces&&service.m_Traces==12);
|
||||
std::cout<<"BODY_WORK far_pairs=128 transforms="<<observer.transformReads<<" normalizations="<<normalizations<<" traces=0\n";
|
||||
// Exact edge60m is still visible and12m rear remains allowed; outside60 cannot trace.
|
||||
game.world.hit=nullptr;game.world.fraction=1;remote.position={0,0,60};
|
||||
assert(service.Visible(&observer,&remote,point));remote.position={0,0,60.01f};
|
||||
assert(!service.Visible(&observer,&remote,point));remote.position={0,0,-12};assert(service.Visible(&observer,&remote,point));
|
||||
remote.position={0,0,-12.01f};assert(!service.Visible(&observer,&remote,point));
|
||||
remote.position={0,0,NAN};assert(!service.Visible(&observer,&remote,point));
|
||||
remote.position={0,0,INFINITY};assert(!service.Visible(&observer,&remote,point));
|
||||
// Backward request cannot refresh candidates or observe released/dead identities anew.
|
||||
service.m_Now=10000;int beforeStale=mode.ledger.finds;
|
||||
assert(!service.Observe(&healthyGroup,9999,report)&&mode.ledger.finds==beforeStale);
|
||||
// Registry cap is finite, logs once, and deleted identities free future slots.
|
||||
service.Stop();logs=0;Entity actors[4128];Agent agents[4128];Group groups[129];
|
||||
for(int g=0;g<129;g++){for(int i=0;i<32;i++){int n=g*32+i;agents[n].actor=&actors[n];groups[g].agents.Insert(&agents[n]);}service.RegisterGroup(&groups[g],Roster(&groups[g]));}
|
||||
|
||||
Reference in New Issue
Block a user