Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
"""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<SCR_EditorManagerEntity> editors = {};','std::vector<SCR_EditorManagerEntity*> 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 <cassert>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#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;}
|
||||
template<class...T>void 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<int,PlayerController*> controllers;std::map<int,bool> 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<int,SCR_EditorManagerEntity> 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<SCR_EditorManagerEntity*>& 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')
|
||||
Reference in New Issue
Block a user