"""Run actual roster, late-join selection and spectator server-selection methods. Engine enumeration/geometry/streaming are adapters, not native/MP coverage. """ import re from policy_harness import ROOT,body,run core=ROOT/'addons/core/Scripts/Game/ERR/00_Core' def adapt(s): s=s.replace('protected ','').replace('out vector position','vector& position').replace('null','nullptr') s=re.sub(r'array (\w+) = \{\};',r'Array \1;',s) s=s.replace('foreach (IEntity survivor : survivors)','for (IEntity survivor : survivors)') for n in ('ERR_Phase','ERR_Actors','SCR_FactionManager','Replication','ERR_GameMode','ERR_ObserverReason','RplComponent','CompartmentAccessComponent','ERR_ObserverRules','ERR_ObserverTargets'): s=s.replace(n+'.',n+'::') for n in ('mode','manager','life','component','target','body'): s=s.replace(n+'.',n+'->') s=s.replace('ERR_GameMode mode','ERR_GameMode* mode').replace('PlayerManager manager','PlayerManager* manager').replace('RplComponent component','RplComponent* component') s=s.replace('life->Actor.', 'life->Actor->').replace('GetGame().','GetGame()->').replace('Players().','Players()->') s=s.replace('ERR_PlayerLife life;', 'ERR_PlayerLife life=nullptr;').replace('IEntity target;','IEntity target=nullptr;').replace('FindComponent(RplComponent)','FindComponent()') return s roster=adapt(body(core/'ERR_PlayerRoster.c','ERR_PlayerLife CurrentActor(')) spawn=adapt(body(core/'ERR_SpawnLogic.c','protected bool SpawnPosition(')) observe=adapt(body(core/'ERR_Observer.c','void ERR_UpdateObserverServer(')) bodies=adapt(body(core/'ERR_ObserverTargets.c','static void Bodies(')).replace('array targets','Array& targets').replace('array lives = {};','Array lives;').replace('foreach (ERR_PlayerLife life : lives)', 'for (ERR_PlayerLife life : lives)') run(r''' #include #include #include #include using vector=int;using Faction=int;using RplId=int; templatestruct Array:std::vector{bool Contains(T a){return Find(a)>=0;}void Insert(T a){this->push_back(a);}int Count(){return this->size();}bool IsEmpty(){return this->empty();}void RemoveOrdered(int i){this->erase(this->begin()+i);}int Find(T a){auto p=std::find(this->begin(),this->end(),a);return p==this->end()?-1:p-this->begin();}}; struct RplComponent{int id=1;int Id(){return id;}static RplComponent* Cast(void* p){return static_cast(p);}}; struct Actor{bool alive=true,conscious=true;int faction=1,origin=123;Actor* vehicle=nullptr;RplComponent replication;int GetOrigin(){return origin;}void* FindComponent(){return &replication;}};using IEntity=Actor*; struct Identity{int id=1;bool IsNull(){return id==0;}}; struct Life{Identity AccountId;bool Connected=true,UsedLife=true;int ConnectionId=1;IEntity Actor=nullptr;};using ERR_PlayerLife=Life*; struct PlayerManager{std::map controlled;int GetPlayerIdFromControlledEntity(IEntity a){for(auto& p:controlled)if(p.second==a)return p.first;return 0;}IEntity GetPlayerControlledEntity(int id){return controlled[id];}}; struct Game{PlayerManager* manager;PlayerManager* GetPlayerManager(){return manager;}};Game* game_;Game* GetGame(){return game_;} namespace Replication{bool server=true;int bumps=0;bool IsServer(){return server;}void BumpMe(){bumps++;}} namespace ERR_Phase{const int PRISON=1;} namespace ERR_ObserverReason{const int NONE=0,SPENT_LIFE=1,UNCONSCIOUS=4;} namespace ERR_ObserverRules{ '''+adapt(body(core/'ERR_Observer.c','static bool Casualty('))+r''' } namespace ERR_Actors{bool Alive(IEntity a){return a&&a->alive;}bool CanFight(IEntity a){return Alive(a)&&a->conscious;}void Players(Array& out,bool conscious=false){out.clear();for(auto& p:GetGame()->manager->controlled){IEntity a=p.second;if(Alive(a)&&(!conscious||a->conscious))out.push_back(a);}}} namespace SCR_FactionManager{int SGetFaction(IEntity a){return a?a->faction:0;}} namespace CompartmentAccessComponent{IEntity GetVehicleIn(IEntity a){return a?a->vehicle:nullptr;}} struct Roster{std::map lives;void Participants(Array& out){for(auto p:lives)if(p.second->UsedLife)out.push_back(p.second);}ERR_PlayerLife Get(int id){auto p=lives.find(id);return p==lives.end()?nullptr:p->second;} '''+roster+r''' }; struct ERR_GameMode{static ERR_GameMode* current;Roster* roster;int phase=2,faction=1;bool active=true;static ERR_GameMode* Get(){return current;}bool Active(){return active;}int Phase(){return phase;}int PlayerFaction(){return faction;}int PrisonPosition(){return 0;}float PrisonHeading(){return 90;}Roster* Players(){return roster;}};ERR_GameMode* ERR_GameMode::current=nullptr; namespace ERR_ObserverTargets { '''+bodies+r''' } struct Placement{int nearCalls=0,prisonCalls=0;IEntity last=nullptr;bool clear=true;bool Near(IEntity a,int,vector& p){nearCalls++;last=a;p=a->origin;return clear;}bool Prison(int,int,vector& p,float heading){assert(heading==90);prisonCalls++;p=0;return true;}}; struct Spawn{Placement m_Placement;int m_AuthorizedPositions=0; '''+spawn+r''' }; struct Observer{int playerId=0;int GetPlayerId(){return playerId;}int m_ERRObserverReason=1,m_ERRObserverAnchor=-1,m_ERRObservedId=-1,releases=0,streamRequests=0;IEntity m_ERRServerObserved=nullptr,m_ERRServerObservedVehicle=nullptr,own=nullptr;IEntity GetControlledEntity(){return own;} void ERR_ReleaseObserved(){releases++;m_ERRServerObserved=nullptr;m_ERRServerObservedVehicle=nullptr;m_ERRObservedId=-1;} void ERR_StreamObserved(IEntity,bool){streamRequests++;} '''+observe+r''' }; int main(){ PlayerManager manager;Game game{&manager};game_=&game;Roster roster;ERR_GameMode mode;mode.roster=&roster;ERR_GameMode::current=&mode; Actor original,replacement,other;original.origin=100;replacement.origin=900;other.origin=200; Life life;life.Actor=&original;roster.lives[1]=&life;manager.controlled[1]=&original; auto accepted=[&](){Spawn s;vector p=-1;assert(s.SpawnPosition(&mode,p)&&p==original.origin&&s.m_Placement.last==&original&&s.m_Placement.prisonCalls==0);Observer o;o.ERR_UpdateObserverServer();assert(o.m_ERRServerObserved==&original&&o.m_ERRObserverAnchor==original.origin);}; auto rejected=[&](){Spawn s;vector p=-1;assert(s.SpawnPosition(&mode,p)&&p==0&&s.m_Placement.nearCalls==0&&s.m_Placement.prisonCalls==1);Observer o;o.m_ERRServerObserved=&original;o.ERR_UpdateObserverServer();assert(!o.m_ERRServerObserved&&o.releases==1);}; accepted(); roster.lives.clear();rejected();roster.lives[1]=&life; // unregistered controller life.UsedLife=false;rejected();life.UsedLife=true; // pending initial assignment life.Connected=false;rejected();life.Connected=true; life.AccountId.id=0;rejected();life.AccountId.id=1; life.ConnectionId=2;rejected();life.ConnectionId=1; manager.controlled[1]=&replacement;rejected(); // recycled connection ID/controlled replacement cannot inherit original body record manager.controlled[1]=&original;accepted(); // restored canonical handoff original.alive=false;Observer corpse;corpse.ERR_UpdateObserverServer();assert(corpse.m_ERRServerObserved==&original);Spawn noCorpseSpawn;vector deadPos;noCorpseSpawn.SpawnPosition(&mode,deadPos);assert(noCorpseSpawn.m_Placement.nearCalls==0);original.alive=true; original.faction=2;rejected();original.faction=1; mode.faction=0;rejected();mode.faction=1; mode.roster=nullptr;rejected();mode.roster=&roster; original.conscious=false;Spawn unconscious;vector p=-1;assert(unconscious.SpawnPosition(&mode,p)&&p==0&&unconscious.m_Placement.nearCalls==0); Observer following;following.ERR_UpdateObserverServer();assert(following.m_ERRServerObserved==&original);original.conscious=true; // living unconscious prisoners still followable // An invalid earlier candidate cannot shadow a later canonical survivor. manager.controlled[1]=&replacement;Life second;second.Actor=&other;second.ConnectionId=2;second.AccountId.id=2;manager.controlled[2]=&other;roster.lives[2]=&second; Spawn mixed;assert(mixed.SpawnPosition(&mode,p)&&p==other.origin&&mixed.m_Placement.last==&other); Observer mixedFollow;mixedFollow.m_ERRServerObserved=&replacement;mixedFollow.ERR_UpdateObserverServer();assert(mixedFollow.m_ERRServerObserved==&other&&mixedFollow.releases==1); manager.controlled[1]=&original;Observer cycling;cycling.ERR_UpdateObserverServer();assert(cycling.m_ERRServerObserved==&original);cycling.ERR_UpdateObserverServer(1);assert(cycling.m_ERRServerObserved==&other);cycling.ERR_UpdateObserverServer(1);assert(cycling.m_ERRServerObserved==&original);cycling.ERR_UpdateObserverServer(-1);assert(cycling.m_ERRServerObserved==&other); // Unconscious player keeps possession and follows only conscious canonical teammates. Observer casualty;casualty.own=&original;casualty.m_ERRObserverReason=0;original.conscious=false; casualty.ERR_UpdateObserverServer();assert(casualty.m_ERRObserverReason==4&&casualty.m_ERRServerObserved==&other&&casualty.own==&original); other.conscious=false;casualty.ERR_UpdateObserverServer();assert(!casualty.m_ERRServerObserved&&casualty.m_ERRObserverAnchor==original.origin); other.conscious=true;casualty.ERR_UpdateObserverServer();assert(casualty.m_ERRServerObserved==&other); original.conscious=true;casualty.ERR_UpdateObserverServer();assert(casualty.m_ERRObserverReason==0&&!casualty.m_ERRServerObserved&&casualty.own==&original); original.conscious=false;life.Connected=false;casualty.ERR_UpdateObserverServer();assert(casualty.m_ERRObserverReason==0); life.Connected=true;casualty.ERR_UpdateObserverServer();assert(casualty.m_ERRObserverReason==4); mode.active=false;casualty.ERR_UpdateObserverServer();assert(casualty.m_ERRObserverReason==0&&!casualty.m_ERRServerObserved); mode.active=true;original.conscious=true; // A dead/disconnected teammate remains selectable while another is alive. original.alive=false;life.Connected=false;manager.controlled.erase(1); Observer mixedDead;mixedDead.ERR_UpdateObserverServer();assert(mixedDead.m_ERRServerObserved==&other); mixedDead.ERR_UpdateObserverServer(1);assert(mixedDead.m_ERRServerObserved==&original); mixedDead.ERR_UpdateObserverServer(0);assert(mixedDead.m_ERRServerObserved==&original); // polling retains selected corpse mixedDead.ERR_UpdateObserverServer(-1);assert(mixedDead.m_ERRServerObserved==&other); // Every participant dead, with native controllers already detached: cycle both bodies, including own. original.alive=false;other.alive=false;manager.controlled.clear();life.Connected=false;second.Connected=false; Observer allDead;allDead.own=&original;allDead.m_ERRObserverReason=4; allDead.ERR_UpdateObserverServer();assert(allDead.m_ERRObserverReason==1&&allDead.m_ERRServerObserved==&original); allDead.ERR_UpdateObserverServer(1);assert(allDead.m_ERRServerObserved==&other); allDead.ERR_UpdateObserverServer(1);assert(allDead.m_ERRServerObserved==&original); allDead.ERR_UpdateObserverServer(-1);assert(allDead.m_ERRServerObserved==&other); second.Actor=nullptr;allDead.ERR_UpdateObserverServer();assert(allDead.m_ERRServerObserved==&original);second.Actor=&other; other.faction=2;allDead.ERR_UpdateObserverServer(1);assert(allDead.m_ERRServerObserved==&original); other.faction=0;allDead.ERR_UpdateObserverServer(1);assert(allDead.m_ERRServerObserved==&other);other.faction=1; // detached corpse without affiliation is still a canonical player body original.alive=true;other.alive=true;life.Connected=true;second.Connected=true;manager.controlled[1]=&original;manager.controlled[2]=&other; allDead.ERR_UpdateObserverServer();assert(allDead.m_ERRObserverReason==0); // healed body takes camera back // Solo death without entity-lost callback still selects the controlled corpse. roster.lives.erase(2);manager.controlled.erase(2);original.alive=false; Observer solo;solo.playerId=1;solo.own=&original;solo.m_ERRObserverReason=0; solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==1&&solo.m_ERRServerObserved==&original&&solo.m_ERRObserverAnchor==original.origin); // Native detachment before the callback is equally covered by the roster. solo.own=nullptr;solo.m_ERRObserverReason=0;solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==1&&solo.m_ERRServerObserved==&original); mode.active=false;solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==0);mode.active=true; life.UsedLife=false;solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==0);life.UsedLife=true; // A new living controlled body must not inherit its predecessor's death camera. solo.own=&replacement;solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==0); original.alive=true;roster.lives[2]=&second;manager.controlled[2]=&other; // Existing terrain rejection and prison-phase placement stay delegated. Spawn blocked;blocked.m_Placement.clear=false;assert(blocked.SpawnPosition(&mode,p)&&p==0&&blocked.m_Placement.nearCalls==2); mode.phase=ERR_Phase::PRISON;Spawn prison;assert(prison.SpawnPosition(&mode,p)&&p==0&&prison.m_Placement.nearCalls==0); Replication::server=false;Observer client;client.ERR_UpdateObserverServer();assert(!client.m_ERRServerObserved&&client.streamRequests==0);Replication::server=true; } ''') print('PASS: actual CurrentActor, SpawnPosition and observer-selection methods reject pending/unregistered/replaced/recycled/foreign bodies; canonical live selection and roster-corpse cycling, unconscious following and prison/geometry fallback preserved; native boundaries adapted')