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:
@@ -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')
|
||||
Reference in New Issue
Block a user