Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
162 lines
13 KiB
Python
162 lines
13 KiB
Python
"""Execute the production physical body-inspection gate; engine traces are boundary doubles."""
|
|
from policy_harness import ROOT, body, run
|
|
import re
|
|
p=ROOT/'addons/core/Scripts/Game/ERR/20_Intel/ERR_IntelEvidence.c'
|
|
s='\n'.join(body(p,sig) for sig in ('bool Inspect(', 'bool RetainedWithin(', 'void Delivered(', 'void Release('))
|
|
for old,new in [('out array<string> missing','Array<string>& missing'),('array<IEntity>','Array<IEntity>'),('array<string>','Array<string>'),('foreach (','for ('),('ERR_BodyEvidence record','ERR_BodyEvidence* record'),('ERR_IssuedIntel issued','ERR_IssuedIntel* issued'),('TraceParam trace','TraceParam* trace'),('InventoryStorageManagerComponent inventory','InventoryStorageManagerComponent* inventory'),('SCR_GarbageSystem garbage','SCR_GarbageSystem* garbage'),('null','nullptr')]:s=s.replace(old,new)
|
|
s=s.replace('InventoryStorageManagerComponent.Cast(body.FindComponent(InventoryStorageManagerComponent))','body->inventory')
|
|
for cls in ('Replication','ERR_GameMode','ERR_Actors','CompartmentAccessComponent','vector','TraceFlags','ERR_DiscoveryService','SCR_GarbageSystem'):
|
|
s=s.replace(cls+'.',cls+'::')
|
|
s=s.replace('parent.GetParent()', 'parent->GetParent()')
|
|
for v in ('body','observer','record','issued','trace','garbage'):
|
|
s=s.replace(v+'.',v+'->')
|
|
s=s.replace('ERR_GameMode::Get().','ERR_GameMode::Get()->').replace('GetGame().GetWorld().','GetGame()->GetWorld()->')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
using string=std::string;
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Clear(){this->clear();}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}void Insert(T t){this->push_back(t);}};
|
|
struct vector {float x=0,y=0,z=0;vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}vector(const char*){}vector operator+(vector v){return {x+v.x,y+v.y,z+v.z};}static float Distance(vector a,vector b){return std::hypot(std::hypot(a.x-b.x,a.y-b.y),a.z-b.z);}};
|
|
struct Entity;using IEntity=Entity*;
|
|
struct InventoryStorageManagerComponent{Array<IEntity> folders;};
|
|
struct Entity{IEntity parent=nullptr;IEntity GetParent(){return parent;}bool alive=true,fit=true,player=false,seated=false;InventoryStorageManagerComponent* inventory=nullptr;vector position;vector GetOrigin(){return position;}};
|
|
struct ERR_Actors{static bool Alive(IEntity e){return e&&e->alive;}static bool CanFight(IEntity e){return e&&e->alive&&e->fit;}static bool Player(IEntity e){return e&&e->player;}};
|
|
struct CompartmentAccessComponent{static bool GetVehicleIn(IEntity e){return e->seated;}};
|
|
struct TraceFlags{enum{ENTS=1,WORLD=2};};struct TraceParam{vector Start,End;IEntity Exclude=nullptr,TraceEnt=nullptr;int Flags=0;};
|
|
struct World{float fraction=1;IEntity hit=nullptr;float TraceMove(TraceParam* t,void*){t->TraceEnt=hit;return fraction;}};
|
|
struct Game{World world;World* GetWorld(){return &world;}} game;Game* GetGame(){return &game;}
|
|
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
|
|
struct ERR_GameMode{bool active=true;static ERR_GameMode* Get(){static ERR_GameMode mode;return &mode;}bool Active(){return active;}};
|
|
struct ERR_DiscoveryService{static void Folders(InventoryStorageManagerComponent* inv,Array<IEntity>& folders){folders=inv->folders;}};
|
|
struct ERR_IssuedIntel{IEntity Folder;string SiteId;};
|
|
struct ERR_BodyEvidence{IEntity Actor;Array<ERR_IssuedIntel*> Issued;bool Searched=false;bool Reported=false;};
|
|
struct SCR_GarbageSystem {
|
|
inline static int releases=0,inserts=0;inline static bool blacklisted=true;inline static float delay=0;
|
|
static SCR_GarbageSystem* GetByEntityWorld(IEntity){static SCR_GarbageSystem g;return &g;}
|
|
void UpdateBlacklist(IEntity,bool value){blacklisted=value;releases++;}
|
|
void Insert(IEntity,float value){assert(!blacklisted);inserts++;delay=value;}
|
|
};
|
|
struct Evidence{Array<ERR_BodyEvidence*> m_Roster;bool m_Stopped=false;ERR_BodyEvidence* record=nullptr;ERR_BodyEvidence* Find(IEntity b){return record&&record->Actor==b?record:nullptr;}
|
|
'''+s+r'''
|
|
};
|
|
int main(){
|
|
for(int invalid=0;invalid<9;invalid++){
|
|
Entity body,observer,folder;body.alive=false;InventoryStorageManagerComponent inventory;body.inventory=&inventory;
|
|
ERR_IssuedIntel issued{&folder,"radio-1"};ERR_BodyEvidence record{&body,{&issued},false};Evidence service;service.record=&record;Array<string> result;
|
|
game.world.fraction=1;Replication::server=true;ERR_GameMode::Get()->active=true;
|
|
if(invalid==0)Replication::server=false;
|
|
if(invalid==1)service.m_Stopped=true;
|
|
if(invalid==2)body.alive=true;
|
|
if(invalid==3)observer.player=true;
|
|
if(invalid==4)observer.fit=false;
|
|
if(invalid==5)observer.seated=true;
|
|
if(invalid==6)observer.position={3.01f,0,0};
|
|
if(invalid==7)game.world.fraction=.5;
|
|
if(invalid==8)body.inventory=nullptr;
|
|
assert(!service.Inspect(&body,&observer,result)&&!record.Searched&&result.empty());
|
|
}
|
|
Replication::server=true;game.world.fraction=1;
|
|
Entity body,observer,present,missing;body.alive=false;InventoryStorageManagerComponent inv;body.inventory=&inv;inv.folders.Insert(&present);
|
|
ERR_IssuedIntel a{&present,"radio-1"},b{&missing,"mortar-3"},c{nullptr,"depot-1"};ERR_BodyEvidence record{&body,{&a,&b,&c},false};Evidence service;service.record=&record;Array<string> result;
|
|
observer.position={3,0,0};assert(service.Inspect(&body,&observer,result));assert(result.size()==2&&result.Contains("mortar-3")&&result.Contains("depot-1")&&!result.Contains("radio-1"));
|
|
assert(!service.Inspect(&body,&observer,result));
|
|
Entity wreck,seat,unrelated;body.parent=&seat;seat.parent=&wreck;service.m_Roster={&record};
|
|
assert(service.RetainedWithin(&body)&&service.RetainedWithin(&seat)&&service.RetainedWithin(&wreck));
|
|
assert(!service.RetainedWithin(&unrelated)&&!service.RetainedWithin(nullptr));
|
|
body.alive=true;assert(!service.RetainedWithin(&wreck));body.alive=false;
|
|
record.Reported=true;assert(!service.RetainedWithin(&wreck));record.Reported=false;
|
|
service.m_Stopped=true;assert(!service.RetainedWithin(&wreck));service.m_Stopped=false;
|
|
// Searching protects the body until HQ receives the report. Premature,
|
|
// client-side, stopped and repeated deliveries never release/renew cleanup.
|
|
record.Searched=false;service.Delivered(&body);assert(!record.Reported&&SCR_GarbageSystem::inserts==0);
|
|
record.Searched=true;Replication::server=false;service.Delivered(&body);assert(!record.Reported);
|
|
Replication::server=true;service.m_Stopped=true;service.Delivered(&body);assert(!record.Reported);
|
|
service.m_Stopped=false;service.Delivered(&unrelated);assert(!record.Reported);
|
|
assert(service.RetainedWithin(&body));service.Delivered(&body);
|
|
assert(record.Reported&&!service.RetainedWithin(&body));
|
|
assert(SCR_GarbageSystem::inserts==1&&SCR_GarbageSystem::releases==1&&SCR_GarbageSystem::delay==300);
|
|
service.Delivered(&body);assert(SCR_GarbageSystem::inserts==1&&SCR_GarbageSystem::releases==1);
|
|
record.Reported=false;
|
|
// Corrupt/cyclic parent links cannot cause unbounded cleanup traversal.
|
|
seat.parent=&seat;assert(!service.RetainedWithin(&wreck));
|
|
|
|
}
|
|
''')
|
|
print('PASS: physical inspection and parent-wreck retention; server-only searched/report delivery unlocks native cleanup once with a 300-second delay')
|
|
|
|
# Exercise sealing as well as corpse inspection: a large camp population should
|
|
# cover unfinished sites, while already-issued and authored clues stay fixed.
|
|
selection='\n'.join(body(p,sig) for sig in ('protected string NextTarget(', 'void Capture('))
|
|
selection=selection.replace('protected ', '').replace('foreach (', 'for (').replace('array<', 'Array<')
|
|
selection=selection.replace('Array<ERR_LocationComponent>', 'Array<ERR_LocationComponent*>')
|
|
selection=selection.replace('InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent))', 'actor->inventory')
|
|
selection=selection.replace('ERR_DocumentComponent.Cast(folder.FindComponent(ERR_DocumentComponent))', 'folder->document')
|
|
for t in ('ERR_BodyEvidence','ERR_IssuedIntel','InventoryStorageManagerComponent','ERR_LocationComponent','ERR_DocumentComponent'):
|
|
selection=re.sub(r'\b'+t+r' (\w+)', t+r'* \1', selection)
|
|
for n in ('location','record','issued','item','document'):
|
|
selection=selection.replace(n+'.',n+'->')
|
|
for n in ('ERR_GameMode','ERR_Actors','ERR_DiscoveryService','ERR_LocationKind'):
|
|
selection=selection.replace(n+'.',n+'::')
|
|
selection=selection.replace('ERR_GameMode::Get().','ERR_GameMode::Get()->').replace('GetRandomElement().Id()', 'GetRandomElement()->Id()')
|
|
selection=selection.replace('bool known;', 'bool known=false;')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
#include <algorithm>
|
|
struct string:std::string{using std::string::string;using std::string::operator=;string(std::string s):std::string(s){}bool IsEmpty(){return empty();}};
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;void Clear(){this->clear();}int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Insert(T t){this->push_back(t);}T GetRandomElement(){return this->front();}};
|
|
template<class K,class V>struct Map:std::map<K,V>{bool Find(K k,V& v){auto i=this->find(k);if(i==this->end())return false;v=i->second;return true;}void Set(K k,V v){(*this)[k]=v;}};
|
|
namespace ERR_LocationKind{enum{PRISON,EXTRACTION,RADIO,OUTPOST};}
|
|
struct ERR_LocationComponent{string id;int knowledge=0,kind=ERR_LocationKind::OUTPOST;string Id(){return id;}int Knowledge(){return knowledge;}int Kind(){return kind;}};
|
|
struct ERR_GameMode{Array<ERR_LocationComponent*> locations;static ERR_GameMode*Get(){static ERR_GameMode m;return &m;}void Locations(Array<ERR_LocationComponent*>&out){out=locations;}};
|
|
struct ERR_DocumentComponent{string target;string TargetId(){return target;}};
|
|
struct Entity;using IEntity=Entity*;
|
|
struct InventoryStorageManagerComponent{Array<IEntity>folders;};
|
|
struct Entity{bool alive=true,player=false;InventoryStorageManagerComponent*inventory=nullptr;ERR_DocumentComponent*document=nullptr;};
|
|
namespace ERR_Actors{bool Alive(IEntity e){return e&&e->alive;}bool Player(IEntity e){return e&&e->player;}}
|
|
struct ERR_IssuedIntel{IEntity Folder=nullptr;string SiteId;};
|
|
struct ERR_BodyEvidence{IEntity Actor=nullptr;bool Searched=false;Array<ERR_IssuedIntel*>Issued;};
|
|
namespace ERR_DiscoveryService{void Folders(InventoryStorageManagerComponent*inv,Array<IEntity>&out){out=inv->folders;}}
|
|
struct Evidence{Map<string,int>m_TargetAssignments;ERR_BodyEvidence*record=nullptr;ERR_BodyEvidence*Find(IEntity actor){return record&&record->Actor==actor?record:nullptr;}
|
|
'''+selection+r'''
|
|
};
|
|
int main(){
|
|
auto mode=ERR_GameMode::Get();Evidence service;ERR_LocationComponent sites[60];
|
|
for(int i=0;i<60;i++){sites[i].id="camp-"+std::to_string(i);mode->locations.Insert(&sites[i]);}
|
|
// Known sites and automatic prison/extraction disclosures are never new targets.
|
|
sites[0].knowledge=2;sites[1].kind=ERR_LocationKind::PRISON;sites[2].kind=ERR_LocationKind::EXTRACTION;
|
|
sites[3].knowledge=1;mode->locations.Insert(nullptr);
|
|
for(int i=0;i<570;i++)assert(!service.NextTarget().IsEmpty());
|
|
assert(service.m_TargetAssignments.size()==57);
|
|
for(int i=3;i<60;i++)assert(service.m_TargetAssignments[sites[i].id]==10);
|
|
// New generated camps catch up instead of leaving all intel on the early sites.
|
|
ERR_LocationComponent added;added.id="late-camp";mode->locations.Insert(&added);
|
|
for(int i=0;i<10;i++)assert(service.NextTarget()=="late-camp");
|
|
sites[3].knowledge=2;
|
|
for(int i=0;i<56;i++)assert(service.NextTarget()!=sites[3].id);
|
|
assert(service.m_TargetAssignments[sites[3].id]==10);
|
|
// The exact folder is sealed once. Repeated scans/discovery changes cannot retarget it.
|
|
Entity actor,folder;InventoryStorageManagerComponent inv;actor.inventory=&inv;inv.folders={&folder};
|
|
ERR_BodyEvidence record;record.Actor=&actor;service.record=&record;service.Capture(&actor);
|
|
assert(record.Issued.Count()==1);string target=record.Issued[0]->SiteId;
|
|
for(auto site:mode->locations)if(site)site->knowledge=2;
|
|
service.Capture(&actor);assert(record.Issued.Count()==1&&record.Issued[0]->SiteId==target);
|
|
assert(service.NextTarget().IsEmpty());
|
|
// Explicit mission clues survive even when no random unfinished target remains.
|
|
Entity objective;ERR_DocumentComponent doc;doc.target="radio";objective.document=&doc;
|
|
inv.folders.Insert(&objective);service.Capture(&actor);
|
|
assert(record.Issued.Count()==2&&record.Issued[1]->SiteId=="radio");
|
|
Entity extra;inv.folders.Insert(&extra);service.Capture(&actor);assert(record.Issued.Count()==2);
|
|
// Later availability can seal an unassigned folder, but not a spent/reported body's contents.
|
|
added.knowledge=0;record.Searched=true;service.Capture(&actor);assert(record.Issued.Count()==2);
|
|
record.Searched=false;actor.alive=false;service.Capture(&actor);assert(record.Issued.Count()==2);
|
|
actor.alive=true;service.Capture(&actor);assert(record.Issued.Count()==3&&record.Issued[2]->SiteId=="late-camp");
|
|
}
|
|
''')
|
|
print('PASS: 570 folders cover 57 unfinished sites evenly; known/automatic targets excluded, late camps included, and exact issued/authored documents preserved')
|