Files
EscapistsReintroduced/tests/test_intel_reroll.py
T

98 lines
13 KiB
Python

"""Execute actual Intel reroll methods against adversarial async inventory adapters."""
import re
from policy_harness import ROOT, body, run
service=ROOT/'addons/intel/Scripts/Game/ERR/ERR_IntelService.c'
batch=ROOT/'addons/intel/Scripts/Game/ERR/ERR_IntelReroll.c'
def adapt(text,cls):
text=text.replace('protected ', '').replace('null','nullptr')
text=re.sub(r'array<([^>]+)> (\w+) = \{\};',r'Array<\1> \2;',text)
text=text.replace('set<IEntity> cohort = new set<IEntity>();','Set<IEntity> cohort;')
text=re.sub(r'foreach \((\w+) (\w+) : ([^)]+)\)',r'for (\1 \2 : \3)',text)
for n in ('Replication','ERR_Actors','Math','ERR_Resources','ERR_DiscoveryService','SCR_InventoryStorageManagerComponent','InventoryItemComponent'):
text=text.replace(n+'.',n+'::')
text=text.replace('SCR_InventoryStorageManagerComponent inventory','Inventory* inventory').replace('InventoryItemComponent item','Item* item')
for n in ('m_Mode','m_Service','m_Reroll','entry','request','inventory','actor','group','member','known','folder','item'):
text=text.replace(n+'.',n+'->')
text=text.replace('Actor.FindComponent(', 'Actor->FindComponent(').replace('Removing.FindComponent(', 'Removing->FindComponent(').replace('Removing.GetParent()', 'Removing->GetParent()')
text=text.replace('Settings().','Settings()->').replace('Discovery().','Discovery()->')
text=text.replace('FindComponent(SCR_InventoryStorageManagerComponent)','FindComponent(INVENTORY)').replace('FindComponent(InventoryItemComponent)','FindComponent(ITEM)').replace('FindComponent(ERR_DocumentComponent)','FindComponent(DOCUMENT)')
text=text.replace('purpose: EStoragePurpose.PURPOSE_ANY','0').replace('cb: ', '')
text=text.replace('Operation.', 'Operation->')
text=text.replace('new ERR_IntelRerollEntry()', 'new Entry()').replace('new ERR_IntelReroll(', 'new Batch(').replace('new ERR_IntelRequest()', 'new Request()').replace('new ERR_IntelInsertCallback()', 'new Callback()')
text=re.sub(r'^.*Print\([^\n]*$', ';',text,flags=re.M)
text=re.sub(r'^(\w+) (\w+)\(',lambda m:m[1]+' '+cls+'::'+m[2]+'(',text)
# Defaults belong only to declarations in C++.
text=text.replace('bool failed = false, bool skipped = false','bool failed, bool skipped')
return text
service_methods=[('void RegisterGroup(', 'void RegisterGroup(SCR_AIGroup group);'),('void Tick(', 'void Tick(float now);'),('bool BeginReroll(', 'bool BeginReroll();'),('bool HasPendingInsertion(', 'bool HasPendingInsertion(IEntity actor);'),('void Unsettled(', 'void Unsettled(IEntity actor);'),('bool Settled(', 'bool Settled(IEntity actor);'),('void Stop(', 'void Stop();')]
batch_methods=[('void Add(', 'void Add(IEntity actor);'),('bool Running(', 'bool Running();'),('void Tick(', 'void Tick();'),('protected void Finish(', 'void Finish(ERR_IntelRerollEntry entry,bool failed=false,bool skipped=false);'),('protected void Fail(', 'void Fail(ERR_IntelRerollEntry entry,string reason);'),('protected void Process(', 'void Process(ERR_IntelRerollEntry entry);'),('void Stop(', 'void Stop();')]
code=r'''
#include <cassert>
#include <algorithm>
#include <vector>
#include <set>
#include <string>
using string=std::string;
template<class T> struct Array:std::vector<T>{int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T a){this->push_back(a);}void Remove(int i){this->erase(this->begin()+i);}void Clear(){this->clear();}bool Contains(T a){return std::find(this->begin(),this->end(),a)!=this->end();}};
template<class T> struct Set:std::set<T>{bool Contains(T a){return this->count(a);}void Insert(T a){this->insert(a);}void RemoveItem(T a){this->erase(a);}void Clear(){this->clear();}};
namespace Replication{bool server=true;bool IsServer(){return server;}}
namespace Math{int rolls=0;int RandomInt(int,int){rolls++;return 0;}int Min(int a,int b){return std::min(a,b);}}
const int INVENTORY=1,ITEM=2,DOCUMENT=3;
struct Actor;using IEntity=Actor*;
struct Item{bool attached=false;bool GetParentSlot(){return attached;}static Item* Cast(void* p){return (Item*)p;}};using InventoryItemComponent=Item;
struct Callback{bool Finished=false,Succeeded=false;};
struct Inventory{Array<IEntity> folders;int spawns=0,removes=0;bool acceptSpawn=true,acceptRemove=true;bool TrySpawnPrefabToStorage(int,int,Callback*){spawns++;return acceptSpawn;}bool TryRemoveItemFromInventory(IEntity){removes++;return acceptRemove;}static Inventory* Cast(void* p){return (Inventory*)p;}};using SCR_InventoryStorageManagerComponent=Inventory;
struct Actor{bool alive=true,player=false,document=false,parent=false,hasInventory=true;Item item;Inventory inv;void* FindComponent(int kind){if(kind==INVENTORY)return hasInventory?&inv:nullptr;if(kind==ITEM)return &item;return document?this:nullptr;}bool GetParent(){return parent;}};
struct Agent{IEntity actor;IEntity GetControlledEntity(){return actor;}};using AIAgent=Agent*;
struct Group{Array<AIAgent> agents;void GetAgents(Array<AIAgent>& out){out=agents;}};using SCR_AIGroup=Group*;
namespace ERR_Actors{bool Alive(IEntity a){return a&&a->alive;}bool Player(IEntity a){return a&&a->player;}}
namespace ERR_Resources{int IntelFolder(){return 1;}}
struct Discovery{Set<IEntity> spent;bool capacity=true;static void Folders(Inventory* i,Array<IEntity>& out){out=i->folders;}bool IsSpent(IEntity a){return spent.Contains(a);}bool CanMarkSpent(IEntity a){return capacity;}bool MarkSpent(IEntity a){if(!capacity)return false;spent.Insert(a);return true;}};using ERR_DiscoveryService=Discovery;
struct Settings{int chance=100;int Get(const char*){return chance;}};
struct Mode{bool active=true;struct Settings settings;struct Discovery discovery;Set<IEntity> owned;bool Active(){return active;}struct Settings* Settings(){return &settings;}struct Discovery* Discovery(){return &discovery;}void Own(IEntity a){owned.Insert(a);}};
struct Entry{string WaitingFor="inventory";Callback* Operation=nullptr;IEntity Actor=nullptr,Removing=nullptr;int Attempts=0;bool GiveIntel=false,Inserting=false,Accepted=false,Done=false;};using ERR_IntelRerollEntry=Entry*;
struct Request{Callback* Operation=nullptr;IEntity Actor=nullptr;int Attempts=0;bool Accepted=false;};using ERR_IntelRequest=Request*;
struct Service;struct Batch;
struct Batch{Mode* m_Mode;Service* m_Service;Array<ERR_IntelRerollEntry> m_Entries;int m_Percent=0,m_Next=0,m_Completed=0,m_Failed=0,m_Skipped=0;bool m_Cancelled=false;Batch(Mode* m,Service* s,int p):m_Mode(m),m_Service(s),m_Percent(p){}
'''+ '\n'.join(x[1] for x in batch_methods)+r'''
};
struct Service{Mode* m_Mode;Array<ERR_IntelRequest> m_Requests;Set<IEntity> m_Rolled,m_Unsettled;Batch* m_Reroll=nullptr;bool m_Stopped=false;Service(Mode* m):m_Mode(m){}
'''+ '\n'.join(x[1] for x in service_methods)+r'''
};
'''+ '\n'.join(adapt(body(service,x[0]),'Service') for x in service_methods)+ '\n'.join(adapt(body(batch,x[0]),'Batch') for x in batch_methods)+r'''
void Add(Service& s,Actor& a){s.m_Rolled.Insert(&a);}
void Drain(Service& s,int n=70){for(int i=0;i<n;i++)s.Tick(i);}
int main(){
// Authority, terminal/stopped rounds, snapshot chance/cohort and duplicate guard.
Mode m;Service s(&m);Actor a,late;Add(s,a);Replication::server=false;assert(!s.BeginReroll());Replication::server=true;m.active=false;assert(!s.BeginReroll());m.active=true;m.settings.chance=0;assert(s.BeginReroll());assert(!s.BeginReroll());m.settings.chance=100;Add(s,late);Drain(s);assert(s.m_Reroll->m_Entries.Count()==1&&s.m_Reroll->m_Percent==0&&a.inv.spawns==0&&late.inv.spawns==0&&s.m_Reroll->m_Completed==1);
// Old unaccepted work cancelled, accepted native spawn retained and observed.
Mode pendingMode;Service pending(&pendingMode);Actor p,folder;Add(pending,p);auto req=new Request();req->Actor=&p;pending.m_Requests.Insert(req);pendingMode.settings.chance=0;assert(pending.BeginReroll());assert(pending.m_Requests.IsEmpty());Drain(pending);assert(p.inv.spawns==0);
auto accepted=new Request();accepted->Actor=&p;accepted->Accepted=true;accepted->Operation=new Callback();pending.m_Requests.Insert(accepted);assert(pending.BeginReroll());pending.Tick(0);assert(pending.m_Requests.Count()==1&&p.inv.removes==0&&p.inv.spawns==0);p.inv.folders.Insert(&folder);folder.item.attached=true;pending.Tick(1);assert(pending.m_Requests.Count()==1&&p.inv.removes==0);accepted->Operation->Finished=true;accepted->Operation->Succeeded=true;pending.Tick(1);assert(pending.m_Requests.IsEmpty()&&p.inv.removes==1&&pendingMode.discovery.IsSpent(&folder)&&pendingMode.owned.Contains(&folder));pending.Tick(2);assert(p.inv.removes==1);p.inv.folders.Clear();folder.item.attached=false;pending.Tick(3);assert(!pending.m_Reroll->Running()&&p.inv.spawns==0);
// Accepted insertion never repeated. A vanished/late native insertion blocks rerolls.
Mode delayedMode;Service delayed(&delayedMode);Actor d;Add(delayed,d);assert(delayed.BeginReroll());Drain(delayed);assert(d.inv.spawns==1&&delayed.m_Reroll->m_Failed==1&&delayed.m_Unsettled.Contains(&d));assert(delayed.BeginReroll());Drain(delayed);assert(d.inv.spawns==1&&delayed.m_Reroll->m_Failed==1);Actor arrived;d.inv.folders.Insert(&arrived);assert(delayed.BeginReroll());delayed.Tick(0);assert(d.inv.removes==0&&delayed.m_Unsettled.Contains(&d));
// Arbitrary transferred folder never acknowledges a pending reroll insertion.
Mode extraMode;Service extra(&extraMode);Actor e,unrelated;Add(extra,e);assert(extra.BeginReroll());extra.Tick(0);assert(e.inv.spawns==1);e.inv.folders.Insert(&unrelated);extra.Tick(1);assert(extra.m_Reroll->Running()&&!extra.BeginReroll()&&e.inv.spawns==1);auto op=extra.m_Reroll->m_Entries[0]->Operation;op->Finished=true;op->Succeeded=true;extra.Tick(2);assert(!extra.m_Reroll->Running()&&extra.m_Reroll->m_Failed==0);
// A surviving collected/spent folder or targeted mission document is untouched.
Mode safeMode;Service safe(&safeMode);Actor collector,spent,mission,doc;Add(safe,collector);Add(safe,mission);collector.inv.folders.Insert(&spent);safeMode.discovery.spent.Insert(&spent);mission.inv.folders.Insert(&doc);doc.document=true;assert(safe.BeginReroll());Drain(safe);assert(safe.m_Reroll->m_Skipped==2&&collector.inv.removes==0&&mission.inv.removes==0&&safeMode.discovery.IsSpent(&spent));
// Newly player-controlled/dead cohort members skipped without touching inventory.
Mode skipMode;Service skip(&skipMode);Actor player,dead;Add(skip,player);Add(skip,dead);assert(skip.BeginReroll());player.player=true;dead.alive=false;Drain(skip);assert(skip.m_Reroll->m_Skipped==2&&player.inv.spawns==0&&dead.inv.spawns==0);
// Removal ownership race and spent capacity refusal never create replacement.
Mode raceMode;Service race(&raceMode);Actor ai,f;Add(race,ai);ai.inv.folders.Insert(&f);assert(race.BeginReroll());race.Tick(0);assert(ai.inv.removes==1);ai.inv.folders.Clear();f.item.attached=true;race.Tick(1);assert(race.m_Reroll->m_Skipped==1&&ai.inv.spawns==0);
Mode capMode;Service cap(&capMode);Actor ca,cf;Add(cap,ca);ca.inv.folders.Insert(&cf);capMode.discovery.capacity=false;assert(cap.BeginReroll());Drain(cap);assert(cap.m_Reroll->m_Failed==1&&ca.inv.removes==0&&ca.inv.spawns==0);
// Rejected native removal preserves usable unspent documents for a retry.
Mode rejectMode;Service reject(&rejectMode);Actor ra,rf;Add(reject,ra);ra.inv.folders.Insert(&rf);ra.inv.acceptRemove=false;rejectMode.settings.chance=0;assert(reject.BeginReroll());reject.Tick(0);assert(reject.m_Reroll->Running()&&reject.m_Reroll->m_Failed==0&&!rejectMode.discovery.IsSpent(&rf)&&!rejectMode.owned.Contains(&rf));ra.inv.acceptRemove=true;reject.Tick(1);assert(ra.inv.removes==2&&rejectMode.discovery.IsSpent(&rf)&&rejectMode.owned.Contains(&rf));ra.inv.folders.Clear();rf.item.attached=false;reject.Tick(2);assert(!reject.m_Reroll->Running()&&reject.m_Reroll->m_Failed==0);
// Persistent refusal exhausts exactly thirty native attempts without spending.
Mode refusalMode;Service refusal(&refusalMode);Actor pa,pf;Add(refusal,pa);pa.inv.folders.Insert(&pf);pa.inv.acceptRemove=false;assert(refusal.BeginReroll());Drain(refusal);assert(pa.inv.removes==30&&refusal.m_Reroll->m_Failed==1&&!refusalMode.discovery.IsSpent(&pf)&&!refusalMode.owned.Contains(&pf)&&refusal.m_Reroll->m_Entries[0]->WaitingFor=="removal-refused");
// Tick budget, bounded missing inventory, Stop prevents further work/restart.
Mode manyMode;Service many(&manyMode);Actor actors[20];for(auto& actor:actors){actor.hasInventory=false;Add(many,actor);}assert(many.BeginReroll());many.Tick(0);int attempts=0;for(auto entry:many.m_Reroll->m_Entries)attempts+=entry->Attempts;assert(attempts==8);Drain(many,100);assert(many.m_Reroll->m_Failed==20);many.Stop();assert(!many.BeginReroll()&&many.m_Rolled.empty()&&many.m_Requests.IsEmpty()&&many.m_Reroll->m_Cancelled);for(auto entry:many.m_Reroll->m_Entries)assert(!entry->Actor&&!entry->Removing);
}
'''
run(code)
source=batch.read_text()
assert 'DeleteEntity' not in source and 'CallLater' not in source
assert source.index('CanMarkSpent(folder)') < source.index('TryRemoveItemFromInventory(folder)') < source.index('Discovery().MarkSpent(folder)')
assert 'm_Mode.Own(folder)' in source
assert '%3/%4 AI processed, %5 failed, %6 skipped.' in source
assert 'ERR_IntelService Intel()' in service.read_text()
print('PASS: actual reroll snapshot, authority, cancellation, ownership, accepted operations, quarantine, bounded work and stop methods; no native runtime claim')