"""Run production editor creation/ownership gates and bounded retry scheduling. Native player ownership and RPC delivery still require multiplayer validation. """ from policy_harness import ROOT, body, run p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_TerrainTakeoverManagers.c' signatures=['protected bool ERR_EditorConnectionReady(', 'protected override SCR_EditorManagerEntity CreateEditorManager(', 'protected void ERR_QueueTerrainEditorRetry(', 'protected void ERR_RetryTerrainEditors(', 'protected void ERR_StopTerrainEditorRetry('] code='\n'.join(body(p,s) for s in signatures).replace('protected ','').replace('override ','') code=code.replace('ERR_TerrainTakeoverMode.Cast(GetGame().GetGameMode())','GetGame().mode') code=code.replace('RplIdentity.Invalid()', '-1').replace('RplIdentity.Local()', '0').replace('RplIdentity identity','int identity') code=code.replace('array editors = {};','std::vector editors;') for cls in ['PlayerManager','PlayerController','SCR_EditorManagerEntity','ERR_TerrainTakeoverMode','ERR_GameMode']: for var in ['players','controller','other','editor','existing','mode']: code=code.replace(cls+' '+var,cls+'* '+var) for var in ['players','controller','other','editor']: code=code.replace(var+'.',var+'->') code=code.replace('foreach (','for (').replace('GetGame().','GetGame()->') for cls in ['Replication','RplSession','RplMode','ERR_GameMode','LogLevel']: code=code.replace(cls+'.',cls+'::') code=code.replace('SCR_EditorManagerEntity CreateEditorManager(', 'SCR_EditorManagerEntity* CreateEditorManager(') code=code.replace('super.CreateEditorManager','NativeCreate') code=code.replace('GetGame()->GetCallqueue().CallLater(ERR_RetryTerrainEditors, 250, false);','queue.Schedule(250);') code=code.replace('GetGame()->GetCallqueue().Remove(ERR_RetryTerrainEditors);','queue.Cancel();') run(r''' #include #include #include #define null nullptr namespace Replication {bool server=true;bool IsServer(){return server;}} namespace RplMode {enum {Dedicated,Listen,Client};} namespace RplSession {int current=RplMode::Dedicated;int Mode(){return current;}} namespace LogLevel {int WARNING=1;} templatevoid PrintFormat(const char*,T...){} int warnings=0;void Print(const char*,int){warnings++;} struct ERR_GameMode {static ERR_GameMode* Get();}; using ERR_TerrainTakeoverMode=ERR_GameMode; struct PlayerController {int identity=-1;int GetRplIdentity(){return identity;}}; struct PlayerManager { std::map controllers;std::map connected; PlayerController* GetPlayerController(int id){return controllers[id];} bool IsPlayerConnected(int id){return connected[id];} }; struct Game {ERR_GameMode* mode=nullptr;PlayerManager players;PlayerManager* GetPlayerManager(){return &players;}}game; Game* GetGame(){return &game;}ERR_GameMode* ERR_GameMode::Get(){return game.mode;} struct SCR_EditorManagerEntity {int id;int GetPlayerID(){return id;}}; struct Queue {int scheduled=0,cancelled=0;void Schedule(int delay){assert(delay==250);scheduled++;}void Cancel(){cancelled++;}}queue; struct Core { ERR_GameMode* m_ERREditorRetryMode=nullptr;bool m_ERREditorRetryQueued=false,m_ERREditorRetryNeeded=false;int m_ERREditorRetryAttempts=0; std::map editors;int nativeCalls=0;int pendingID=2; SCR_EditorManagerEntity* GetEditorManager(int id){auto i=editors.find(id);return i==editors.end()?nullptr:&i->second;} void GetEditorEntities(std::vector& out){for(auto& x:editors)out.push_back(&x.second);} SCR_EditorManagerEntity* NativeCreate(int id){nativeCalls++;assert(!GetEditorManager(id));editors.emplace(id,SCR_EditorManagerEntity{id});return GetEditorManager(id);} void ERR_ReconcileTerrainEditors(ERR_GameMode* expected,bool missing){assert(expected==game.mode&&missing);if(!GetEditorManager(pendingID))CreateEditorManager(pendingID);} '''+code+r''' }; int main(){ ERR_GameMode mode;game.mode=&mode;PlayerController first{11},second{-1}; game.players.controllers={{1,&first},{2,&second}};game.players.connected={{1,true},{2,true}}; Core c;assert(c.CreateEditorManager(1));assert(c.nativeCalls==1); // The later native registration must reuse the editor created by reconciliation. assert(c.CreateEditorManager(1)==c.GetEditorManager(1)&&c.nativeCalls==1); assert(!c.CreateEditorManager(2)&&c.nativeCalls==1&&c.m_ERREditorRetryQueued); assert(queue.scheduled==1);assert(!c.CreateEditorManager(2)&&queue.scheduled==1); second.identity=0;assert(!c.ERR_EditorConnectionReady(2)); // Temporary server owner on dedicated. second.identity=11;assert(!c.ERR_EditorConnectionReady(2)); // Same-machine conflict with existing editor. c.ERR_RetryTerrainEditors();assert(c.nativeCalls==1&&queue.scheduled==2); second.identity=22;c.ERR_RetryTerrainEditors();assert(c.nativeCalls==2&&c.GetEditorManager(2)); assert(!c.m_ERREditorRetryQueued&&c.m_ERREditorRetryAttempts==0); // Listen-server host remains valid; invalid/disconnected controllers never are. Core hosted;RplSession::current=RplMode::Listen;first.identity=0;assert(hosted.ERR_EditorConnectionReady(1)); first.identity=-1;assert(!hosted.ERR_EditorConnectionReady(1));first.identity=0; game.players.connected[1]=false;assert(!hosted.ERR_EditorConnectionReady(1));game.players.connected[1]=true; game.players.controllers[1]=nullptr;assert(!hosted.ERR_EditorConnectionReady(1));game.players.controllers[1]=&first; // Retry window has a hard limit and cleans its queue. No editor is fabricated. RplSession::current=RplMode::Dedicated;second.identity=0;Core blocked; blocked.CreateEditorManager(2);for(int i=0;i<80;i++)blocked.ERR_RetryTerrainEditors(); assert(blocked.nativeCalls==0&&!blocked.m_ERREditorRetryQueued&&warnings==1); // A later real registration can start a fresh attempt and eventually succeed. second.identity=22;assert(blocked.CreateEditorManager(2)); Core stale;second.identity=0;stale.CreateEditorManager(2);game.mode=nullptr; stale.ERR_RetryTerrainEditors();assert(stale.nativeCalls==0&&!stale.m_ERREditorRetryQueued); // Other scenarios retain the vanilla creation path. Core ordinary;assert(ordinary.CreateEditorManager(1)&&ordinary.nativeCalls==1); } ''') print('PASS: native creation called once per editor; pending/duplicate ownership waits, host works, retry is bounded and stale-world callbacks cannot create editors')