Files
EscapistsReintroduced/tests/test_enemy_optics.py
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

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/core/Scripts/Game/ERR/10_AI/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/core/Scripts/Game/ERR/10_AI/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')