Files
EscapistsReintroduced/tests/test_hq_radio_capability.py
T
jakub 4b737c825f
continuous-integration/drone/push Build is passing
Reduce redundant AI equipment, radio and observation checks
Preserve live ownership and detection rules while skipping redundant work. Record published Workshop version 0.0.150 and add operation-count regressions.
2026-11-09 17:05:25 +01:00

79 lines
6.4 KiB
Python

"""Execute actual HQ/vehicle radio queries; prove max-capability short circuit."""
import os,re
from pathlib import Path
from policy_harness import ROOT,body,run
source=Path(os.environ.get('ERR_HQ_REPORT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQReport.c'))
hq=body(source,'static int Capability(')
hq=hq.replace('IEntity character','Entity* character').replace('ERR_Actors.CanFight','CanFight')
hq=hq.replace('InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(character.FindComponent(InventoryStorageManagerComponent));','Storage* inventory = character->storage;')
hq=hq.replace('array<IEntity> items = {};','std::vector<Entity*> items;').replace('inventory.GetItems','inventory->GetItems').replace('ERR_HQ hq = ERR_HQ.Get();','HQ* hq = HQ::current;').replace('hq.Hunters()','hq->Hunters()').replace('Hunters().','Hunters()->')
hq=hq.replace('foreach (IEntity item : items)','for (Entity* item : items)').replace('item.FindComponent(BaseRadioComponent)','HasBaseRadio(item)').replace('SCR_RadioComponent gadget = SCR_RadioComponent.Cast(item.FindComponent(SCR_RadioComponent));','Radio* gadget = GetRadio(item);').replace('gadget.','gadget->').replace('Math.Max','std::max').replace('int result;','int result=0;')
vehicle=body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HunterService.c','int VehicleRadioCapability(')
vehicle=vehicle.replace('IEntity actor','Entity* actor').replace('foreach (ERR_HunterVehicle unit : m_Hunters)','for (Unit* unit : m_Hunters)').replace('InventoryStorageManagerComponent storage = InventoryStorageManagerComponent.Cast(unit.Entity.FindComponent(InventoryStorageManagerComponent));','Storage* storage = unit->Entity->storage;')
vehicle=vehicle.replace('array<IEntity> items = {};','std::vector<Entity*> items;').replace('foreach (IEntity item : items)','for (Entity* item : items)').replace('item.FindComponent(BaseRadioComponent)','HasBaseRadio(item)').replace('SCR_RadioComponent radio = SCR_RadioComponent.Cast(item.FindComponent(SCR_RadioComponent));','Radio* radio = GetRadio(item);')
for obj in ('m_Mode','unit','storage','radio'):vehicle=vehicle.replace(obj+'.',obj+'->')
vehicle=vehicle.replace('ERR_Actors.Alive','Alive').replace('CompartmentAccessComponent.GetVehicleIn','VehicleIn').replace('ERR_ConvoyRules.Captured','Captured')
run(r'''
#include <cassert>
#include <vector>
#include <algorithm>
#include <iostream>
struct Radio{bool pack=false;bool ERR_IsHQManpack(){return pack;}};
struct Storage;
struct Entity{bool alive=true,fit=true,baseRadio=false,captured=false;Entity* vehicle=nullptr;Storage* storage=nullptr;Radio* radio=nullptr;int componentQueries=0;};
struct Storage{int enumerations=0;std::vector<Entity*>items;void GetItems(std::vector<Entity*>&out){enumerations++;out=items;}};
struct Members:std::vector<Entity*>{bool Contains(Entity*p){return std::find(begin(),end(),p)!=end();}};
struct Mode{bool active=true;bool Active(){return active;}};
struct Unit{struct Entity* Entity=nullptr;void* Group=nullptr;bool Released=false,valid=true;struct Members Members;bool CrewValid(Mode*){return valid;}};
bool Alive(Entity*e){return e&&e->alive;}bool CanFight(Entity*e){return Alive(e)&&e->fit;}
Entity* VehicleIn(Entity*e){return e->vehicle;}bool Captured(Entity*e,void*){return e->captured;}
bool HasBaseRadio(Entity*e){e->componentQueries++;return e->baseRadio;}Radio* GetRadio(Entity*e){e->componentQueries++;return e->radio;}
struct Service{Mode* m_Mode=nullptr;bool m_Stopped=false;std::vector<Unit*>m_Hunters;
'''+vehicle+r'''
};
struct HQ{static inline HQ*current=nullptr;Service* service=nullptr;Service*Hunters(){return service;}};
struct Query{
'''+hq+r'''
};
int main(){
HQ h;Service service;Mode mode;HQ::current=&h;h.service=&service;service.m_Mode=&mode;
Entity actor,car,manpack,basic,junk;Storage carried,mounted;Radio packRadio,basicRadio;packRadio.pack=true;
actor.storage=&carried;car.storage=&mounted;manpack.baseRadio=true;manpack.radio=&packRadio;basic.baseRadio=true;basic.radio=&basicRadio;
Unit unit;unit.Entity=&car;unit.Members.push_back(&actor);service.m_Hunters={&unit};actor.vehicle=&car;mounted.items={&manpack};
assert(Query::Capability(nullptr)==0);actor.fit=false;assert(Query::Capability(&actor)==0&&carried.enumerations==0&&mounted.enumerations==0);actor.fit=true;
actor.alive=false;assert(Query::Capability(&actor)==0);actor.alive=true;
actor.storage=nullptr;assert(Query::Capability(&actor)==0&&mounted.enumerations==0);actor.storage=&carried;
// The real vehicle helper still validates ownership and physical inventory.
//128 carried objects must not be enumerated or scanned when it returns max2.
carried.items.assign(128,&junk);
assert(Query::Capability(&actor)==2);
std::cout<<"OPCOUNTS actor_inventory="<<carried.enumerations<<" carried_item_components="<<junk.componentQueries<<" vehicle_inventory="<<mounted.enumerations<<std::endl;
assert(carried.enumerations==0&&junk.componentQueries==0&&mounted.enumerations==1);
// Every following call observes current vehicle state; no added cache.
carried.items={&basic};
for(int reason=0;reason<7;reason++){
if(reason==0)actor.vehicle=nullptr;
if(reason==1)car.captured=true;
if(reason==2)car.alive=false;
if(reason==3)unit.Released=true;
if(reason==4)unit.valid=false;
if(reason==5)mounted.items.clear();
if(reason==6)unit.Members.clear();
int before=carried.enumerations;assert(Query::Capability(&actor)==1&&carried.enumerations==before+1);
actor.vehicle=&car;car.captured=false;car.alive=true;unit.Released=false;unit.valid=true;mounted.items={&manpack};unit.Members.clear();unit.Members.push_back(&actor);
before=carried.enumerations;assert(Query::Capability(&actor)==2&&carried.enumerations==before);
}
// Lack of HQ/service/active mission never invents a mounted link; ordinary
//personal inventory classification keeps its existing0/1/2 behavior.
mode.active=false;assert(Query::Capability(&actor)==1);mode.active=true;
service.m_Stopped=true;assert(Query::Capability(&actor)==1);service.m_Stopped=false;
h.service=nullptr;
for(int cap=0;cap<=2;cap++){
carried.items={nullptr,&junk};if(cap==1)carried.items.push_back(&basic);if(cap==2)carried.items.push_back(&manpack);
int before=carried.enumerations;assert(Query::Capability(&actor)==cap&&carried.enumerations==before+1);
}
HQ::current=nullptr;assert(Query::Capability(&actor)==2);
}
''')
print('PASS: actual HQ/vehicle capability preserves living/inventory/ownership/physical-radio gates; max2 skips128 carried-item checks and one enumeration; changes remain live')