Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
92 lines
10 KiB
Python
92 lines
10 KiB
Python
"""Execute the complete production Recover body with controlled inventory and services.
|
|
This verifies integration ordering and retained site-clue semantics, not native custody.
|
|
"""
|
|
import os,re
|
|
from pathlib import Path
|
|
from policy_harness import ROOT,body,run
|
|
P=Path(os.environ.get('ERR_DISCOVERY_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryService.c'))
|
|
s=body(P,'protected void Recover(')
|
|
s=s.replace('protected ','').replace('null','nullptr').replace('foreach (','for (').replace('array<','Array<')
|
|
for typ in ('InventoryStorageManagerComponent','ERR_DocumentComponent','ERR_LocationComponent'):
|
|
s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
|
|
s=s.replace('Array<ERR_LocationComponent>','Array<ERR_LocationComponent*>').replace('bool mineRecovered;', 'bool mineRecovered=false;')
|
|
for ns in ('Replication','ERR_Actors','SCR_FactionManager','InventoryStorageManagerComponent','ERR_DocumentComponent','ERR_LocationKind','Math'):
|
|
s=s.replace(ns+'.',ns+'::')
|
|
for obj in ('m_Mode','player','folder','location','document','chosen'):
|
|
s=re.sub(r'\b'+obj+r'\.',obj+'->',s)
|
|
for method in ('Players','Evidence','Settings','MineIntel','GetGame','GetPlayerManager'):
|
|
s=s.replace(method+'().',method+'()->')
|
|
s=re.sub(r'FindComponent\((\w+)\)',r'FindComponent("\1")',s)
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
struct string:std::string{using std::string::string;using std::string::operator=;bool IsEmpty(){return empty();}};
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}void Insert(T t){this->push_back(t);}bool IsEmpty(){return this->empty();}T GetRandomElement(){return this->front();}};
|
|
struct Entity;using IEntity=Entity*;
|
|
struct Faction{};
|
|
struct ERR_DocumentComponent{string target;string TargetId(){return target;}static ERR_DocumentComponent* Cast(void*p){return (ERR_DocumentComponent*)p;}};
|
|
struct InventoryStorageManagerComponent{Array<IEntity> folders;static InventoryStorageManagerComponent* Cast(void*p){return (InventoryStorageManagerComponent*)p;}};
|
|
struct Entity{bool alive=true,canonical=true;Faction*faction=nullptr;InventoryStorageManagerComponent*inventory=nullptr;ERR_DocumentComponent*document=nullptr;void*FindComponent(std::string type){if(type=="InventoryStorageManagerComponent")return inventory;if(type=="ERR_DocumentComponent")return document;return nullptr;}};
|
|
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
|
namespace ERR_Actors{bool Alive(IEntity e){return e&&e->alive;}}
|
|
namespace SCR_FactionManager{Faction*SGetFaction(IEntity e){return e?e->faction:nullptr;}}
|
|
namespace ERR_LocationKind{enum{PRISON,RADIO,EXTRACTION,DEPOT};}
|
|
namespace Math{int RandomInt(int,int){return 99;}}
|
|
struct ERR_LocationComponent{string id;int kind=ERR_LocationKind::RADIO,knowledge=0,calls=0;bool allow=true;int Kind(){return kind;}int Knowledge(){return knowledge;}string Id(){return id;}bool Discover(int,int){calls++;if(!allow)return false;knowledge++;return true;}};
|
|
struct Roster{IEntity CurrentActor(IEntity e){return e&&e->canonical?e:nullptr;}};
|
|
struct Evidence{IEntity sealed=nullptr;string target;string Target(IEntity e){return e==sealed?target:"";}};
|
|
struct Settings{int Runtime(string){return 15;}};
|
|
struct MineIntel{bool newlyRecovered=false;int calls=0;Array<IEntity> recovered;Array<IEntity>*spent=nullptr;IEntity lastPlayer=nullptr;bool Recover(IEntity player,IEntity folder){assert(spent&&!spent->Contains(folder));calls++;lastPlayer=player;recovered.Insert(folder);return newlyRecovered;}};
|
|
struct Mode{bool worldPreparing=false;bool WorldPreparing(){return worldPreparing;}Roster roster;struct Evidence evidence;struct Settings settings;struct MineIntel mine;Faction*faction=nullptr;bool haveMine=true;Array<ERR_LocationComponent*>locations;int exhausted=0;
|
|
Roster*Players(){return &roster;}Faction*PlayerFaction(){return faction;}struct Evidence*Evidence(){return &evidence;}struct Settings*Settings(){return &settings;}struct MineIntel*MineIntel(){return haveMine?&mine:nullptr;}void Locations(Array<ERR_LocationComponent*>&out){out=locations;}void ERR_IntelExhausted(bool){exhausted++;}};
|
|
struct PlayerManager{int GetPlayerIdFromControlledEntity(IEntity){return 1;}};struct Game{PlayerManager manager;PlayerManager*GetPlayerManager(){return &manager;}}game;Game*GetGame(){return &game;}
|
|
template<class...T>void PrintFormat(T...){}
|
|
struct Discovery{Mode*m_Mode=nullptr;Array<IEntity>m_Spent;void Folders(InventoryStorageManagerComponent*i,Array<IEntity>&out){out=i->folders;}bool MarkSpent(IEntity e){if(!m_Spent.Contains(e))m_Spent.Insert(e);return true;}
|
|
'''+s+r'''
|
|
};
|
|
struct Fixture{Faction friendly,foreign;Mode mode;Discovery discovery;Entity player,second,folder;InventoryStorageManagerComponent firstInv,secondInv;ERR_DocumentComponent objective;ERR_LocationComponent radio,other;
|
|
Fixture(){Replication::server=true;mode.faction=&friendly;discovery.m_Mode=&mode;mode.mine.spent=&discovery.m_Spent;player.faction=&friendly;second.faction=&friendly;player.inventory=&firstInv;second.inventory=&secondInv;firstInv.folders.Insert(&folder);radio.id="radio";other.id="other";mode.locations={&other,&radio};}}
|
|
;
|
|
int main(){
|
|
// Authored target wins over incidental locations, and each exact document
|
|
// advances ordinary discovery once across transfer between two valid players.
|
|
Fixture f;f.objective.target="radio";f.folder.document=&f.objective;
|
|
f.discovery.Recover(&f.player);assert(f.radio.knowledge==1&&f.other.knowledge==0&&f.discovery.m_Spent.Contains(&f.folder));
|
|
assert(f.mode.mine.calls==1&&f.mode.mine.lastPlayer==&f.player&&f.mode.mine.recovered[0]==&f.folder);
|
|
f.firstInv.folders.clear();f.secondInv.folders.Insert(&f.folder);f.discovery.Recover(&f.second);assert(f.radio.calls==1&&f.other.calls==0&&f.mode.mine.calls==1);
|
|
// Existing sealed generic-folder target survives the supplemental hook.
|
|
Fixture sealed;sealed.mode.evidence.sealed=&sealed.folder;sealed.mode.evidence.target="radio";sealed.discovery.Recover(&sealed.player);assert(sealed.radio.knowledge==1&&sealed.other.knowledge==0);
|
|
// Unknown authored target stays unspent; a supplement cannot consume it first.
|
|
Fixture missing;missing.objective.target="absent";missing.folder.document=&missing.objective;missing.discovery.Recover(&missing.player);assert(missing.discovery.m_Spent.IsEmpty()&&missing.radio.calls==0&&missing.other.calls==0);
|
|
// Failed site discovery also retains the original retry semantics.
|
|
Fixture denied;denied.objective.target="radio";denied.folder.document=&denied.objective;denied.radio.allow=false;denied.discovery.Recover(&denied.player);assert(denied.discovery.m_Spent.IsEmpty()&&denied.radio.calls==1);
|
|
// A generic folder remains usable until deferred locations are registered.
|
|
Fixture pending;pending.mode.worldPreparing=true;pending.mode.locations.clear();pending.discovery.Recover(&pending.player);
|
|
assert(pending.discovery.m_Spent.IsEmpty()&&pending.mode.exhausted==0);
|
|
pending.mode.locations={&pending.radio};pending.discovery.Recover(&pending.player);
|
|
assert(pending.radio.knowledge==1&&pending.discovery.m_Spent.Contains(&pending.folder));
|
|
// Generic exhausted clue retains the existing spent/exhaustion behavior.
|
|
Fixture exhausted;exhausted.radio.knowledge=2;exhausted.other.knowledge=2;exhausted.discovery.Recover(&exhausted.player);assert(exhausted.discovery.m_Spent.Contains(&exhausted.folder)&&exhausted.mode.exhausted==1);
|
|
// A newly published mine warning is useful even after site clues are exhausted.
|
|
// Spend the original generic folder once without the contradictory exhaustion notice.
|
|
Fixture mineOnly;mineOnly.mode.mine.newlyRecovered=true;mineOnly.radio.knowledge=2;mineOnly.other.knowledge=2;
|
|
mineOnly.discovery.Recover(&mineOnly.player);assert(mineOnly.mode.mine.calls==1&&mineOnly.discovery.m_Spent.Contains(&mineOnly.folder)&&mineOnly.mode.exhausted==0);
|
|
mineOnly.discovery.Recover(&mineOnly.player);assert(mineOnly.mode.mine.calls==1&&mineOnly.mode.exhausted==0);
|
|
// New warning supplements, never replaces, an available site clue.
|
|
Fixture both;both.mode.mine.newlyRecovered=true;both.discovery.Recover(&both.player);assert(both.other.knowledge==1&&both.discovery.m_Spent.Contains(&both.folder)&&both.mode.exhausted==0);
|
|
// No mine service still produces the ordinary exhausted-folder notice.
|
|
Fixture noWarning;noWarning.mode.haveMine=false;noWarning.radio.knowledge=2;noWarning.other.knowledge=2;noWarning.discovery.Recover(&noWarning.player);assert(noWarning.discovery.m_Spent.Contains(&noWarning.folder)&&noWarning.mode.exhausted==1);
|
|
// Missing optional service cannot stop ordinary discovery.
|
|
Fixture absent;absent.mode.haveMine=false;absent.discovery.Recover(&absent.player);assert(absent.other.knowledge==1&&absent.discovery.m_Spent.Contains(&absent.folder));
|
|
// A distinct folder transferred before recovery is recovered by its real holder.
|
|
Fixture transferred;transferred.firstInv.folders.clear();transferred.secondInv.folders.Insert(&transferred.folder);transferred.discovery.Recover(&transferred.player);assert(transferred.mode.mine.calls==0);transferred.discovery.Recover(&transferred.second);assert(transferred.mode.mine.calls==1&&transferred.mode.mine.lastPlayer==&transferred.second);
|
|
// Full ledger and pre-spent documents never call the extra service.
|
|
Fixture full;full.discovery.m_Spent.resize(4096,nullptr);full.discovery.Recover(&full.player);assert(full.mode.mine.calls==0&&full.other.calls==0);
|
|
// Rejected actor never reaches either inventory or supplemental recovery.
|
|
for(int fault=0;fault<4;fault++){Fixture rejected;if(fault==0)rejected.player.canonical=false;if(fault==1)rejected.player.alive=false;if(fault==2)rejected.player.faction=&rejected.foreign;if(fault==3)Replication::server=false;rejected.discovery.Recover(&rejected.player);assert(rejected.mode.mine.calls==0&&rejected.discovery.m_Spent.IsEmpty()&&rejected.radio.calls==0);}
|
|
}
|
|
''')
|
|
print('PASS: actual full document recovery retains authored/sealed targets, one shared spent ledger, retry/exhaustion semantics and canonical multi-player ownership alongside supplemental mine intel')
|