"""Execute the real empty-server gate and tick wrapper with native boundaries stubbed.""" 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(source): source = source.replace('protected ', '').replace('PlayerManager manager', 'PlayerManager* manager') for name in ('Replication', 'System', 'SCR_GameModeEndData', 'EGameOverTypes'): source = source.replace(name + '.', name + '::') for name in ('GetGame()', 'manager', 'm_Performance'): source = source.replace(name + '.', name + '->') return source methods = '\n'.join(cpp(body(MODE, signature)) for signature in ( 'void ObservePlayerDisconnect(', 'protected bool CheckEmptyServerRestart(', 'protected void ServerTick(')) run(r''' #include #include namespace Replication {bool server=true;bool IsServer(){return server;}} namespace System {int now=0;int GetTickCount(){return now;}} namespace EGameOverTypes {enum {LOOSE=1};} struct SCR_GameModeEndData {static int Create(int reason){return reason;}}; struct PlayerManager {int connected=0;int GetPlayerCount(){return connected;}}; struct Game {PlayerManager* manager=nullptr;PlayerManager* GetPlayerManager(){return manager;}} game; Game* GetGame(){return &game;} template void PrintFormat(const char*,T...){} struct Performance {int starts=0,ends=0;int BeginTick(){return ++starts;}void EndTick(int){ends++;}}; struct Mode { bool m_Stopped=false,m_NativeEndHandled=false,m_HadConnectedPlayers=false,m_EmptyRestartRequested=false; int m_EmptyMissionStartedAt=System::now,phase=1,endCalls=0,measuredTicks=0; std::string m_EndReason; Performance performance;Performance* m_Performance=&performance; bool Active(){return !m_Stopped&&!m_NativeEndHandled&&phase>0&&phase<5;} // Leave native completion pending to prove repeated callbacks cannot end twice. void EndGameMode(int reason){assert(reason==EGameOverTypes::LOOSE);endCalls++;} void ServerTickMeasured(){measuredTicks++;} ''' + methods + r''' }; struct Fixture { PlayerManager manager;Mode mode; Fixture(){Replication::server=true;game.manager=&manager;} void tick(int now,int count){System::now=now;manager.connected=count;mode.ServerTick();} }; int main(){ const int fiveHours=5*60*60*1000; // Each newly loaded, continuously empty mission gets its own five-hour window. System::now=123456; for(int round=0;round<3;round++){ Fixture f;int start=System::now; f.tick(start,0);f.tick(start+60*60*1000,0);f.tick(start+fiveHours-1,0); assert(f.mode.endCalls==0); int before=f.mode.measuredTicks; f.tick(start+fiveHours,0); assert(f.mode.endCalls==1&&f.mode.measuredTicks==before); assert(f.mode.m_EndReason=="The server has been empty for five hours. Restarting the mission."); f.tick(start+fiveHours+1000,0);assert(f.mode.endCalls==1); } // All native connections block a scheduled restart, even without a spawned life. System::now=0; {Fixture f;f.tick(fiveHours-1,0);f.tick(fiveHours,1);f.tick(2*fiveHours,1); assert(f.mode.endCalls==0&&f.mode.measuredTicks==3); f.tick(2*fiveHours+1000,0); assert(f.mode.endCalls==1&&f.mode.m_EndReason=="All players disconnected. Restarting the mission.");} // One of several players leaves; the last disconnect ends on the next tick. System::now=0; {Fixture f;f.tick(0,2);f.mode.ObservePlayerDisconnect();f.tick(1000,1); assert(f.mode.endCalls==0);f.mode.ObservePlayerDisconnect();f.tick(2000,0); assert(f.mode.endCalls==1);} // Join/disconnect entirely between ticks still triggers a restart. {Fixture f;f.mode.ObservePlayerDisconnect();assert(f.mode.endCalls==0); f.tick(3000,0);assert(f.mode.endCalls==1);} // A connection arriving before the deferred check prevents the restart. {Fixture f;f.mode.ObservePlayerDisconnect();f.tick(4000,1);assert(f.mode.endCalls==0); f.tick(5000,0);assert(f.mode.endCalls==1);} // Preparation is covered before Active() allows normal tactical/service ticks. for(bool disconnected:{false,true}){ System::now=0;Fixture f;f.mode.phase=0; if(disconnected)f.mode.ObservePlayerDisconnect(); f.tick(disconnected?1000:fiveHours,0); assert(f.mode.endCalls==1&&f.mode.measuredTicks==0&&f.mode.performance.starts==0); } // Client, stopped, already-ended and unavailable population must never reload. for(int fault=0;fault<4;fault++){ System::now=0;Fixture f; if(fault==0)Replication::server=false; if(fault==1)f.mode.m_Stopped=true; if(fault==2)f.mode.m_NativeEndHandled=true; if(fault==3)game.manager=nullptr; f.mode.ObservePlayerDisconnect();f.tick(fiveHours,0);assert(f.mode.endCalls==0); } } ''') init = body(MODE, 'protected override void EOnInit(') assert init.index('if (!Replication.IsServer()) return;') < init.index('m_EmptyMissionStartedAt = System.GetTickCount();') assert init.index('m_EmptyMissionStartedAt = System.GetTickCount();') < init.index('CallLater(ServerTick, 1000, true)') assert 'mode.ObservePlayerDisconnect();' in body(SPAWN, 'override void OnPlayerDisconnected_S(') assert 'Remove(ServerTick)' in body(MODE, 'protected void StopServices(') print('PASS: last disconnect, pending admission/spectator population, five-hour boundary, repeated empty rounds, preparation, authority and duplicate-end guards')