Files
EscapistsReintroduced/tests/test_enemy_optics.py
T

114 lines
11 KiB
Python

"""Execute actual startup optics service through native attachment API doubles; no native proof."""
from pathlib import Path
import os,re
from policy_harness import ROOT,run
P=Path(os.environ.get('ERR_OPTICS_SOURCE',ROOT/'addons/ai/Scripts/Game/ERR/ERR_EnemyOptics.c'))
s=P.read_text()
assert 'GetGame(' not in s and 'CallLater' not in s and 'DeleteEntity' not in s
callback=(ROOT/'addons/ai/Scripts/Game/ERR/ERR_EnemyEquipment.c').read_text().split('class ERR_EnemyEquipmentCallback',1)[1].split('class ERR_EnemyEquipmentEntry',1)[0]
code='class ERR_EnemyEquipmentCallback'+callback+s
code=re.sub(r'//[^\n]*','',code).replace('protected ','').replace('override ','').replace('class ','struct ')
for src,dst in {
'BaseWeaponManagerComponent.Cast(m_Actor.FindComponent(BaseWeaponManagerComponent))':'m_Actor->manager',
'InventoryStorageManagerComponent.Cast(m_Actor.FindComponent(InventoryStorageManagerComponent))':'m_Actor->inventory',
'BaseWeaponComponent.Cast(job.Rifle.FindComponent(BaseWeaponComponent))':'job->Rifle->weapon',
'BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent))':'rifle->weapon',
'WeaponAttachmentsStorageComponent.Cast(job.Rifle.FindComponent(WeaponAttachmentsStorageComponent))':'job->Rifle->storage',
'entity.FindComponent(SCR_2DOpticsComponent)':'entity->scope2D',
'entity.FindComponent(SCR_2DPIPSightsComponent)':'entity->scope2DPip',
'entity.FindComponent(SCR_PIPSightsComponent)':'entity->scopePip',
}.items():code=code.replace(src,dst)
code=code.replace('ref array<ref ERR_EnemyOpticsJob>','Array<ERR_EnemyOpticsJob*>').replace('ref array<','Array<').replace('array<','Array<')
code=code.replace('out Array<ResourceName> result','Array<ResourceName>& result')
for name in ('ERR_EnemyEquipmentCallback','ERR_EnemyOpticsJob','ERR_EnemyOpticsInspection'):
code=re.sub(r'\b(?:ref )?'+name+r' (?=\w)',name+'* ',code)
code=code.replace('void ERR_EnemyOptics(','ERR_EnemyOptics(')
code=code.replace('EWeaponType type','int type').replace('foreach (','for (').replace('null','nullptr')
for name in ('ERR_EnemyEquipmentRules','ERR_HQ','Math','EStoragePurpose','EWeaponType'):code=code.replace(name+'.',name+'::')
for name in ('entity','child','weapon','sights','slot','job','manager','inventory','storage','inspection'):
code=re.sub(r'\b'+name+r'\.',name+'->',code)
code=code.replace('job->Operation.','job->Operation->')
code=re.sub(r'\b(bool|int|IEntity|SCR_AIGroup) (\w+);',r'\1 \2{};',code)
code=re.sub(r'\b(ERR_EnemyEquipmentCallback\*) (\w+);',r'\1 \2{};',code)
code=code.replace('\n}\n','\n};\n')
preamble=r'''
#include <cassert>
#include <algorithm>
#include <string>
#include <vector>
using string=std::string;using ResourceName=std::string;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T t){this->push_back(t);}void Remove(int index){this->erase(this->begin()+index);}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}};
struct Entity;struct Group;struct Weapon;struct Manager;struct Inventory;struct Storage;struct Slot;struct Sights;struct ERR_GameMode{};
using IEntity=Entity*;using SCR_AIGroup=Group*;using BaseWeaponComponent=Weapon*;using BaseWeaponManagerComponent=Manager*;
using InventoryStorageManagerComponent=Inventory*;using WeaponAttachmentsStorageComponent=Storage*;using InventoryStorageSlot=Slot*;using AttachmentSlotComponent=Slot*;using BaseSightsComponent=Sights*;
struct ScriptedInventoryOperationCallback{virtual void OnComplete(){}virtual void OnFailed(){}virtual void OnAllComplete(){}virtual void OnAllFailed(){}virtual ~ScriptedInventoryOperationCallback()=default;};
struct EWeaponType{static const int WT_SNIPERRIFLE=1,WT_RIFLE=2,WT_MACHINEGUN=3,WT_HANDGUN=4;};
struct EStoragePurpose{static const int PURPOSE_ANY=0;};struct Math{static int RandomInt(int,int){return 0;}};
struct Group{};
struct Slot{IEntity attached=nullptr;IEntity GetAttachedEntity(){return attached;}};
struct Storage{Array<Slot*>slots;int GetSlotsCount(){return slots.Count();}Slot* GetSlot(int n){return slots[n];}};
struct Sights{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
struct Weapon{IEntity owner=nullptr;int kind=EWeaponType::WT_RIFLE;Sights*sights=nullptr;Array<Slot*>slots;
IEntity GetOwner(){return owner;}int GetWeaponType(){return kind;}Sights* GetAttachedSights(){return sights;}void GetAttachments(Array<Slot*>&out){out=slots;}};
struct Manager{Array<IEntity>weapons;void GetWeaponsList(Array<IEntity>&out){out=weapons;}};
struct Entity{Manager*manager=nullptr;Inventory*inventory=nullptr;Weapon*weapon=nullptr;Storage*storage=nullptr;
Entity*child=nullptr,*sibling=nullptr;Group*group=nullptr;bool eligible=true,scope2D=false,scope2DPip=false,scopePip=false;
IEntity GetChildren(){return child;}IEntity GetSibling(){return sibling;}};
struct Inventory{int submits=0,checks=0;bool accept=true,fit=true;int callback=0;ScriptedInventoryOperationCallback*last=nullptr;Array<int>requested;
bool CanInsertResourceInStorage(ResourceName,Storage*,int){checks++;return fit;}
bool TrySpawnPrefabToStorage(ResourceName,Storage*,int slot,int,ScriptedInventoryOperationCallback*cb){submits++;requested.Insert(slot);last=cb;
if(callback==1){cb->OnComplete();}if(callback==2){cb->OnFailed();}return accept;}};
struct ERR_EnemyEquipmentRules{static bool Eligible(ERR_GameMode* mode,Group*group,IEntity actor){return mode&&group&&actor&&actor->eligible&&actor->group==group;}};
struct ERR_HQ{static void Event(string,string){}};
'''
# Enforce class arguments are reference types.
code=re.sub(r'\bERR_GameMode (?=\w)','ERR_GameMode* ',code)
main=r'''
struct Fixture{ERR_GameMode mode;Group group,other;Entity actor,rifle;Weapon weapon;Manager manager;Inventory inv;Storage storage;Slot slot;
Fixture(){actor.group=&group;actor.manager=&manager;actor.inventory=&inv;manager.weapons={&rifle};rifle.weapon=&weapon;rifle.storage=&storage;weapon.owner=&rifle;storage.slots={&slot};}
};
int main(){
// Existing mounted/nested/sights magnified optics are preserved. Occupied red dots aren't overwritten.
for(int style=0;style<3;style++){Fixture f;Entity optic,adapter;Sights sights;Slot attach;
optic.scope2D=style==0;optic.scope2DPip=style==1;optic.scopePip=style==2;if(style==0){f.rifle.child=&adapter;adapter.child=&optic;}
if(style==1){sights.owner=&optic;f.weapon.sights=&sights;}
if(style==2){attach.attached=&optic;f.weapon.slots={&attach};}
ERR_EnemyOptics s(&f.actor,&f.group);assert(s.Check(&f.mode));assert(f.inv.submits==0&&s.m_Jobs[0]->Confirmed);}
{Fixture f;Entity reddot;f.slot.attached=&reddot;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<6;i++)s.Check(&f.mode);assert(f.inv.submits==0);}
// Positive grant targets one concrete native free compatible slot; accepted unknown is never repeated.
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);assert(!s.Check(&f.mode));assert(f.inv.submits==1&&f.inv.requested[0]==0);
for(int i=0;i<20;i++){s.Check(&f.mode);}assert(f.inv.submits==1&&s.m_Checks==5&&s.m_Jobs[0]->Terminal);
f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
// Both sync/async success terminate even if the optic disappears before physical observation.
for(int sync=0;sync<2;sync++){Fixture f;f.inv.callback=sync;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);
if(!sync){f.inv.last->OnAllComplete();}assert(s.Check(&f.mode));assert(f.inv.submits==1&&!s.m_Jobs[0]->Confirmed);}
// Callback first terminal result wins; never turn a success into another grant.
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);f.inv.last->OnComplete();f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
// Explicit failures permit a different prefab/mount, but only one request per check and never fifth.
for(int kind=0;kind<3;kind++){Fixture f;f.inv.accept=kind!=0;f.inv.callback=kind==1?2:0;ERR_EnemyOptics s(&f.actor,&f.group);
for(int i=0;i<5;i++){s.Check(&f.mode);if(kind==2&&f.inv.last)f.inv.last->OnAllFailed();}
assert(f.inv.submits==4&&s.m_Jobs[0]->Rejected.Count()>=3);assert(s.m_Jobs[0]->Attempt==48);}
{Fixture f;f.inv.accept=false;f.inv.callback=1;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);s.Check(&f.mode);assert(f.inv.submits==1);}
// Delayed inventory is bounded; fifth check can observe a starting rifle but cannot issue it.
{Fixture f;f.actor.inventory=nullptr;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<4;i++)assert(!s.Check(&f.mode));f.actor.inventory=&f.inv;assert(s.Check(&f.mode));assert(f.inv.submits==0);}
{Fixture f;f.inv.fit=false;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<5;i++)s.Check(&f.mode);assert(f.inv.submits==0&&f.inv.checks==28);}
// Exact original actor and original physical weapon identity are terminal; no picked-up replacements.
{Fixture f;f.inv.fit=false;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);Entity replacement;replacement.weapon=&f.weapon;replacement.storage=&f.storage;
f.manager.weapons={&replacement};f.inv.fit=true;assert(s.Check(&f.mode));f.manager.weapons={&f.rifle};s.Check(&f.mode);assert(f.inv.submits==0&&s.m_Jobs.Count()==1);}
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);f.actor.group=&f.other;assert(s.Check(&f.mode));f.actor.group=&f.group;f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
{Fixture f;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);s.Stop();f.inv.last->OnFailed();s.Check(&f.mode);assert(f.inv.submits==1);}
{Fixture f;f.actor.eligible=false;ERR_EnemyOptics s(&f.actor,&f.group);assert(s.Check(&f.mode));assert(f.inv.submits==0);}
// Duplicate native weapon roster entries don't create duplicate requests; nonrifles are ignored.
{Fixture f;f.manager.weapons={&f.rifle,&f.rifle};ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==1&&s.m_Jobs.Count()==1);}
{Fixture f;f.weapon.kind=EWeaponType::WT_HANDGUN;ERR_EnemyOptics s(&f.actor,&f.group);for(int i=0;i<5;i++)s.Check(&f.mode);assert(f.inv.submits==0);}
// Malformed/oversized hierarchies fail closed, including cycles and too-deep adapters.
{Fixture f;f.rifle.child=&f.rifle;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
{Fixture f;Entity child;f.rifle.child=&child;child.sibling=&child;ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
{Fixture f;for(int i=0;i<17;i++)f.weapon.slots.Insert(&f.slot);ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
{Fixture f;for(int i=0;i<17;i++)f.storage.slots.Insert(&f.slot);ERR_EnemyOptics s(&f.actor,&f.group);s.Check(&f.mode);assert(f.inv.submits==0);}
{Fixture f;for(int i=0;i<9;i++)f.manager.weapons.Insert(&f.rifle);ERR_EnemyOptics s(&f.actor,&f.group);assert(s.Check(&f.mode));assert(f.inv.submits==0);}
}
'''
run(preamble+code+main)
print('Enemy optics actual-service ownership, preservation, callback, deadline and bounded-inspection policies passed')