Reduce redundant AI equipment, radio and observation checks
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:
2026-11-09 17:05:25 +01:00
parent 47cd4fa2bc
commit 4b737c825f
12 changed files with 335 additions and 18 deletions
+42 -5
View File
@@ -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]));}