Files
EscapistsReintroduced/tests/test_terrain_editor_registration.py
T
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

86 lines
6.3 KiB
Python

"""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')