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
+147
View File
@@ -0,0 +1,147 @@
"""Execute actual collection policy; compare outputs and classification calls.
Native sphere queries and FriendlyActor's engine getters are not benchmarked.
"""
from pathlib import Path
import os
from policy_harness import ROOT, body, run
source = Path(os.environ.get('ERR_MINE_SOURCE', str(
ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_KnownMines.c')))
candidate = body(source, 'protected bool Collect(').replace('protected ', '')
new = 'if (ChimeraCharacter.Cast(actor) && !m_Near.Contains(actor) && FriendlyActor(actor))'
old = 'if (FriendlyActor(actor) && !m_Near.Contains(actor))'
# Retain the exact predecessor expression as a differential reference. Nothing
# else in the actual extracted body is replaced except the function name.
baseline = candidate.replace(new, old).replace('bool Collect(', 'bool Baseline(')
friendly = body(source, 'protected bool FriendlyActor(').replace('protected ', '')
friendly = friendly.replace('{', '{ ++calls;', 1)
for name in ('ChimeraCharacter', 'ERR_Actors', 'CompartmentAccessComponent', 'SCR_FactionManager'):
friendly = friendly.replace(name + '.', name + '::')
friendly = friendly.replace('m_Mode.', 'm_Mode->').replace('faction.', 'faction->')
friendly = friendly.replace('EnemyFaction().', 'EnemyFaction()->')
candidate = candidate.replace('ChimeraCharacter.Cast', 'ChimeraCharacter::Cast')
run(r'''
#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
struct Actor {bool friendly=false;bool character=true;};
using IEntity=Actor*;
int casts=0;
struct ChimeraCharacter {static IEntity Cast(IEntity actor) {
++casts;return actor && actor->character ? actor : nullptr;
}};
struct ERR_Actors {
static bool CanFight(IEntity actor) {return actor && actor->friendly;}
static bool Player(IEntity) {return false;}
};
struct CompartmentAccessComponent {static bool GetVehicleIn(IEntity) {return false;}};
struct FactionState {bool IsFactionEnemy(FactionState*) {return false;}};
using Faction=FactionState*;
FactionState faction;
struct SCR_FactionManager {static Faction SGetFaction(IEntity) {return &faction;}};
struct Mode {Faction EnemyFaction() {return &faction;}};
struct Entities:std::vector<IEntity> {
mutable int contains=0;
int Count() const {return size();}
bool Contains(IEntity actor) const {
++contains;
return std::find(begin(),end(),actor)!=end();
}
void Insert(IEntity actor) {push_back(actor);}
};
struct Collector {
Entities m_Near;
int calls=0,castCalls=0;
Mode mode;Mode* m_Mode=&mode;
''' + friendly + '\n' + candidate + '\n' + baseline + r'''
};
bool Step(Collector& before,Collector& after,IEntity actor) {
int stamp=casts;bool b=before.Baseline(actor);before.castCalls+=casts-stamp;
stamp=casts;bool a=after.Collect(actor);after.castCalls+=casts-stamp;
assert(b==a);
assert(before.m_Near==after.m_Near); // identity, order and multiplicity
return a;
}
int main() {
{
Collector b,a;Actor actor{true};
for(int i=0;i<32;i++) assert(Step(b,a,&actor));
assert(a.m_Near.size()==1 && b.calls==32 && a.calls==1);
// Already accepted actors remain in this pass even if later classified false.
actor.friendly=false;assert(Step(b,a,&actor));
assert(b.calls==33 && a.calls==1);
std::cout<<"duplicate_accepted baseline_calls=33 candidate_calls=1 members=1\n";
}
{
Collector b,a;Actor actor{false};
assert(Step(b,a,&actor));assert(Step(b,a,&actor));
assert(a.m_Near.empty() && b.calls==2 && a.calls==2);
actor.friendly=true;assert(Step(b,a,&actor));
assert(Step(b,a,&actor));
assert(a.m_Near.size()==1 && b.calls==4 && a.calls==3);
std::cout<<"rejected_then_friendly baseline_calls=4 candidate_calls=3 members=1\n";
}
{
Collector b,a;
assert(Step(b,a,nullptr));assert(Step(b,a,nullptr));
assert(a.m_Near.empty() && b.calls==2 && a.calls==0);
assert(b.castCalls==2 && a.castCalls==2);
assert(b.m_Near.contains==0 && a.m_Near.contains==0);
std::cout<<"null baseline_calls=2 candidate_calls=0 casts_each=2 contains_each=0 members=0\n";
}
{
Collector b,a;Actor actors[129];
for(int i=0;i<128;i++) {actors[i].friendly=true;assert(Step(b,a,&actors[i]));}
assert(a.m_Near.size()==128 && b.calls==128 && a.calls==128);
actors[128].friendly=true;
assert(!Step(b,a,&actors[128])); // new eligible actor
assert(!Step(b,a,&actors[0])); // accepted duplicate
assert(!Step(b,a,nullptr)); // rejected/null callback
assert(b.calls==128 && a.calls==128);
std::cout<<"capacity baseline_calls=128 candidate_calls=128 blocked_callbacks=3 members=128\n";
}
{
Collector b,a;Actor actors[3]{{true},{false},{true}};
for(IEntity actor:{&actors[2],&actors[0],&actors[2],&actors[1],IEntity(nullptr),&actors[0]})
assert(Step(b,a,actor));
assert(a.m_Near[0]==&actors[2] && a.m_Near[1]==&actors[0]);
assert(b.calls==6 && a.calls==3);
// A fresh tick must reconsider every actor; no cache survives m_Near.Clear().
b.m_Near.clear();a.m_Near.clear();
assert(Step(b,a,&actors[0]));assert(Step(b,a,&actors[2]));
assert(b.calls==8 && a.calls==5);
std::cout<<"mixed_and_fresh_pass baseline_calls=8 candidate_calls=5 members=2\n";
}
{
Collector b,a;Actor accepted{true},prop{true,false};
assert(Step(b,a,&accepted));
int bc=b.castCalls,ac=a.castCalls,bl=b.m_Near.contains,al=a.m_Near.contains;
for(int i=0;i<1000;i++) assert(Step(b,a,&prop));
assert(b.castCalls-bc==1000 && a.castCalls-ac==1000);
assert(b.m_Near.contains==bl && a.m_Near.contains==al);
assert(b.calls==1001 && a.calls==1);
std::cout<<"props callbacks=1000 casts_each=1000 contains_each=0 baseline_classifications=1000 candidate_classifications=0\n";
}
{
Collector b,a;Actor accepted{true};
assert(Step(b,a,&accepted));
assert(b.castCalls==1 && a.castCalls==2); // Explicit first-seen character overhead.
for(int i=0;i<31;i++) assert(Step(b,a,&accepted));
assert(b.castCalls==32 && a.castCalls==33);
assert(b.calls==32 && a.calls==1);
std::cout<<"character_cast_tradeoff callbacks=32 baseline_casts=32 candidate_casts=33 baseline_classifications=32 candidate_classifications=1\n";
}
{
Collector b,a;Actor accepted[127],rejected{false};
for(auto& actor:accepted) {actor.friendly=true;assert(Step(b,a,&actor));}
int bl=b.m_Near.contains,al=a.m_Near.contains;
for(int i=0;i<100;i++) assert(Step(b,a,&rejected));
assert(b.m_Near.contains==bl && a.m_Near.contains-al==100);
assert(a.m_Near.size()==127 && b.calls==227 && a.calls==227);
std::cout<<"rejected_character_tradeoff callbacks=100 accepted_members=127 baseline_contains=0 candidate_contains=100 classifications_each=100\n";
}
}
''')
print('PASS: actual Collect output/order/capacity equivalence and classification operation counts')