"""Actual admission-exhaustion/tick rules; engine population and services substituted.""" import re from policy_harness import ROOT, body, run MODE = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c' SPAWN = ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnLogic.c' def cpp(s): # Test mission scheduling inside the existing performance wrapper. s=s.replace('ServerTickMeasured(', 'ServerTick(').replace('System.', 'System::').replace('ERR_PerfMetric.', 'ERR_PerfMetric::').replace('int perfStamp;', 'int perfStamp=0;') s=s.replace('ERR_HQ.Event','ERR_HQ::Event').replace('protected ', '').replace('Replication.IsServer()', 'server').replace('Replication.BumpMe()', '(void)0') s=s.replace('array connected = {};','std::vector connected;').replace('connected.IsEmpty()', 'connected.empty()') s=s.replace('foreach (int id : connected)', 'for (int id : connected)') s=s.replace('ERR_PlayerLife life', 'Life* life').replace('SCR_PlayerController controller', 'Controller* controller').replace('PlayerController controller', 'Controller* controller') s=s.replace('SCR_PlayerController.Cast(', 'Controller::Cast(').replace('ERR_Phase.DEFEAT','5').replace('ERR_Phase.RADIO_SEARCH','2') for v in ('m_Performance','m_Roster','controller','life','m_Command','m_CivilianTraffic','m_Extraction','m_RadioDefence','m_Recovery','GetGame()','GetWorld()','GetPlayerManager()'): s=s.replace(v+'.', v+'->') return s methods='\n'.join(cpp(body(MODE,x)) for x in ('void InitialAdmissionStarted(', 'void InitialAdmissionExhausted(', 'protected bool InitialAdmissionsExhausted(', 'protected void ServerTickMeasured(')) run(r''' #include #include #include #include bool server=true; templatevoid PrintFormat(const char*,T...){} struct Controller {void* actor=nullptr;void* GetControlledEntity(){return actor;}static Controller* Cast(Controller* c){return c;}void ERR_UpdateObserverServer(){}}; struct Life {bool Connected=true,UsedLife=false;void* Actor=nullptr;}; struct Roster {std::map lives;bool survivor=false;Life* Get(int id){auto i=lives.find(id);return i==lives.end()?nullptr:&i->second;}bool HasSurvivor(){return survivor;}}; struct Players {std::map connected;void GetPlayers(std::vector& v){for(auto p:connected)v.push_back(p.first);}int GetPlayerCount(){return connected.size();}Controller* GetPlayerController(int id){auto i=connected.find(id);return i==connected.end()?nullptr:i->second;}}; struct Game {Players players;Players* GetPlayerManager(){return &players;}} game;Game* GetGame(){return &game;} struct World {float now=0;float GetWorldTime(){return now;}} world; struct ERR_HQ {static void Event(std::string,std::string){}}; struct Service {bool defeat=false;bool DefeatDue(float){return defeat;}void Tick(float=0){}void Maintain(float){}void Invoke(float){}}; struct Exhausted {std::map values;void Remove(int id){values.erase(id);}void Set(int id,Controller* c){values[id]=c;}bool Contains(int id){return values.count(id);}Controller* Get(int id){return values.at(id);}}; namespace System {int GetTickCount(){return 0;}} namespace ERR_PerfMetric {enum {MAINTENANCE,CIVILIANS,FORCES,OBSERVERS,HQ_TICK,LISTENERS};} struct Performance {int Mark(int,int stamp){return stamp;}void Stop(int){}}; struct Mode { Performance* m_Performance=nullptr; void TickForces(float){} bool m_StartedPlayers=false;int m_Phase=1;float m_NoSurvivorsSince=-1;std::string m_EndReason; Exhausted m_ExhaustedInitialAdmissions;Roster roster;Roster* m_Roster=&roster; Service service,m_OnTick;Service *m_Extraction=&service,*m_RadioDefence=&service,*m_Recovery=&service,*m_Command=&service,*m_CivilianTraffic=nullptr; World* GetWorld(){return &world;}bool Active(){return m_Phase>0&&m_Phase<5;}void Transition(int p){m_Phase=p;}bool RefreshRadioFeedback(){return false;} '''+methods+r''' }; struct Fixture {Mode m;Controller first,second;Fixture(){server=true;world.now=0;game.players.connected={{1,&first}};m.roster.lives[1]=Life();}void tick(float t){world.now=t;m.ServerTick();}void exhaust(){m.InitialAdmissionExhausted(1,&first);}}; int main(){ {Fixture f;f.m.roster.survivor=true;f.m.service.defeat=true;f.tick(31000); assert(!f.m.Active() && f.m.m_EndReason=="Everybody died. All prisoners are unconscious and nobody can recover or receive help.");} // Sole exhausted first admission reaches existing grace, without a life/body. {Fixture f;f.exhaust();f.tick(0);f.tick(29999);assert(f.m.Active());f.tick(30000); assert(f.m.m_Phase==5&&!f.m.m_StartedPlayers&&!f.m.roster.lives[1].UsedLife); assert(f.m.m_EndReason=="No prisoners could be admitted to this round.");} // Not-yet-audited arrivals block the timer before they have any roster entry. {Fixture f;f.exhaust();f.tick(0);game.players.connected[2]=&f.second;f.tick(29000); assert(f.m.m_NoSurvivorsSince==-1);f.m.roster.lives[2]=Life();f.tick(60000);assert(f.m.Active()); f.m.InitialAdmissionExhausted(2,&f.second);f.tick(61000);f.tick(90999);assert(f.m.Active());f.tick(91000);assert(!f.m.Active());} // A retry/pending new attempt clears exhaustion immediately, not one tick later. {Fixture f;f.exhaust();f.tick(0);f.m.InitialAdmissionStarted(1);assert(f.m.m_NoSurvivorsSince==-1); f.tick(90000);assert(f.m.Active()&&!f.m.InitialAdmissionsExhausted()); f.exhaust();f.tick(91000);f.tick(120999);assert(f.m.Active());f.tick(121000);assert(!f.m.Active());} // Empty population and disconnect cleanup erase the initial failure grace. {Fixture f;f.exhaust();f.tick(0);game.players.connected.clear();f.tick(30000);assert(f.m.m_NoSurvivorsSince==-1&&f.m.Active()); f.m.InitialAdmissionStarted(1);game.players.connected[1]=&f.first;f.tick(60000);assert(f.m.Active());} // Recycled connection/controller, live body and invalid roster never inherit it. for(int fault=0;fault<5;fault++){Fixture f;f.exhaust();f.tick(0); if(fault==0)game.players.connected[1]=&f.second; if(fault==1)f.first.actor=&f.first; if(fault==2)f.m.roster.lives[1].UsedLife=true; if(fault==3)f.m.roster.lives[1].Actor=&f.first; if(fault==4)f.m.roster.lives[1].Connected=false; f.tick(30000);assert(f.m.Active()&&f.m.m_NoSurvivorsSince==-1);} // Invalid phase, authority, prior participation or controller cannot record failure. for(int fault=0;fault<6;fault++){Fixture f; if(fault==0){server=false;}if(fault==1){f.m.m_Phase=0;}if(fault==2){f.m.m_Phase=5;} if(fault==3){f.m.m_StartedPlayers=true;}if(fault==4){game.players.connected[1]=&f.second;}if(fault==5){f.m.roster.lives.clear();} f.exhaust();assert(f.m.m_ExhaustedInitialAdmissions.values.empty());} // The inner survivor check retains its expiry behavior; the outer tick now // handles empty-server restarts first (see test_empty_server_restart.py). {Fixture f;f.m.m_StartedPlayers=true;f.m.roster.lives[1].UsedLife=true; game.players.connected.clear();f.m.roster.survivor=true; f.tick(900000);assert(f.m.Active()&&f.m.m_NoSurvivorsSince==-1); f.m.roster.survivor=false;f.tick(901000);assert(f.m.m_NoSurvivorsSince==901000); f.tick(930999);assert(f.m.Active());f.tick(931000); assert(!f.m.Active()&&f.m.m_EndReason=="No surviving prisoners remain.");} // Empty, unplayed rounds do not trigger the inner no-survivor check. // Their five-hour deadline belongs to the outer tick, tested separately. {Fixture f;game.players.connected.clear();f.tick(0);f.tick(36000000); assert(f.m.Active()&&f.m.m_NoSurvivorsSince==-1);} // A valid reservation/recovered survivor cancels a pending empty-server loss. {Fixture f;f.m.m_StartedPlayers=true;game.players.connected.clear();f.tick(0); f.m.roster.survivor=true;f.tick(29999);f.tick(60000); assert(f.m.Active()&&f.m.m_NoSurvivorsSince==-1);} // Existing actual first spawn changes participation; normal death grace is unchanged. {Fixture f;f.exhaust();f.m.InitialAdmissionStarted(1);f.m.m_StartedPlayers=true;f.m.roster.lives[1].UsedLife=true;f.m.roster.survivor=true; f.tick(30000);assert(f.m.m_NoSurvivorsSince==-1&&f.m.Active());f.m.roster.survivor=false;f.tick(31000);f.tick(61000); assert(f.m.m_Phase==5&&f.m.m_EndReason=="No surviving prisoners remain.");} } ''') # Tie the tested state seam to actual budget exhaustion and new/disconnected admission. do_spawn=body(SPAWN,'protected override void DoSpawn_S(') assert do_spawn.index('Observe(playerId, ERR_ObserverReason.SPAWN_UNAVAILABLE);\n mode.InitialAdmissionExhausted')>do_spawn.index('if (attempts >= 30)') audit=body(SPAWN,'override void OnPlayerAuditSuccess_S(') assert audit.index('mode.InitialAdmissionStarted(playerId);')