Files
EscapistsReintroduced/tests/test_spawn_preload_timeout.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

91 lines
5.7 KiB
Python

"""Actual scene deadline + finalization; a late remote preload cannot hold an expired ticket."""
import os
from pathlib import Path
from policy_harness import ROOT, body, run
base = ROOT/'addons/core/Scripts/Game/ERR/00_Core'
finalizer = Path(os.environ.get('ERR_SPAWN_FINALIZATION_SOURCE', base/'ERR_SpawnFinalization.c'))
scene = base/'ERR_SpawnClientScene.c'
request = '\n'.join(body(scene, key) for key in (
'bool ERR_SpawnSceneReady(', 'bool ERR_SpawnSceneVerified(',
'bool ERR_SpawnSceneMayFinish(', 'bool ERR_TakeScenePreload('))
request = request.replace('GetGame().GetWorld().GetWorldTime()', 'worldTime')
handler = '\n'.join(body(finalizer, key) for key in (
'override bool CanFinalize_S(', 'override SCR_ESpawnResult FinalizeRequest_S('))
for old, new in [('override ', ''), ('SCR_SpawnRequestComponent requestComponent', 'Request* requestComponent'),
('SCR_SpawnData data', 'Data* data'), ('IEntity entity', 'Actor* entity'),
('ERR_SpawnLogic logic', 'Logic* logic'), ('ERR_SpawnTicket ticket', 'Ticket* ticket'),
('ERR_GameMode.Get()', 'Mode::Get()'), ('SCR_ESpawnResult.', 'SCR_ESpawnResult::'),
('ticket.', 'ticket->'), ('requestComponent.', 'requestComponent->'), ('logic.', 'logic->'), ('super.', 'Native::')]:
handler = handler.replace(old,new)
run(r'''
#include <cassert>
float worldTime=0;
template<class...T>void PrintFormat(const char*,T...){}
enum class SCR_ESpawnResult {OK,SPAWN_NOT_ALLOWED};
struct Request {
bool m_ERRScenePending=true,m_ERRSceneAcknowledged=true,m_ERRSceneTimedOut=false;
bool m_ERRSceneVerified=false,m_ERRScenePreloadRequired=false,nativePreloading=true;
float m_ERRSceneDeadline=60000,m_ERRSceneStartedAt=0;
int m_ERRSceneToken=1,ends=0,retained=0;
bool IsPreloading(){return nativePreloading;}
int GetPlayerId(){return 1;}
void ERR_RefreshSceneInventory(){}
bool ERR_VerifyLoadedScene(){return m_ERRSceneTimedOut||ERR_SpawnSceneVerified();}
void ERR_EndSpawnScene(){ends++;m_ERRScenePending=false;m_ERRSceneAcknowledged=false;m_ERRSceneTimedOut=false;m_ERRSceneVerified=false;}
void ERR_RetainSpawnScene(void*){retained++;}
'''+request+r'''
};
struct Data{};struct Actor{};struct Ticket{int releases=0;void ReleaseProtection(){releases++;}};
struct Mode{static inline bool current=true;static bool Get(){return current;}};
struct Logic{
Ticket ticket;bool hasTicket=true,allowed=true;int authorizationChecks=0;
Ticket* TakeSpawnTicket(Request*,Data*,Actor*){if(!hasTicket)return nullptr;hasTicket=false;return &ticket;}
bool MayFinalizeSpawn(Ticket*){authorizationChecks++;return allowed;}
};
struct Native{
int assignments=0,preloadStarts=0;
void OnFinalizeBegin_S(Request* request,Data*,Actor*){preloadStarts++;request->nativePreloading=true;}
// Exact native SCR_SpawnHandlerComponent contract: no server-side timeout.
bool CanFinalize_S(Request* request,Data*,Actor*){return !request->IsPreloading();}
SCR_ESpawnResult FinalizeRequest_S(Request*,Data*,Actor*){assignments++;return SCR_ESpawnResult::OK;}
};
struct Handler:Native{
Logic storage;Logic* logic=&storage;int rejects=0;
Logic* ERR_AdmissionLogic(){return logic;}
void ERR_RejectOwnedActor(Request*,Actor*,Data*){rejects++;}
'''+handler+r'''
};
int main(){
Data data;Actor actor;
// Scene replication was acknowledged, native asset preload started, but its
// remote completion is delayed. The server's own deadline must still reject.
{Request request;Handler h;
worldTime=59999;assert(!h.CanFinalize_S(&request,&data,&actor));
assert(!request.m_ERRSceneTimedOut&&h.assignments==0&&h.rejects==0);
worldTime=60000;assert(h.CanFinalize_S(&request,&data,&actor));
assert(request.m_ERRSceneTimedOut&&request.nativePreloading&&!request.ERR_SpawnSceneVerified());
assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::SPAWN_NOT_ALLOWED);
assert(h.rejects==1&&h.assignments==0&&h.storage.ticket.releases==1&&h.storage.authorizationChecks==0&&request.ends==1&&request.retained==0);
// A late native completion cannot resurrect the consumed ticket or actor.
request.nativePreloading=false;
assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::SPAWN_NOT_ALLOWED);
assert(h.rejects==1&&h.assignments==0&&request.ends==1);}
// No scene acknowledgement also expires without starting a native preload.
{Request request;Handler h;request.m_ERRSceneAcknowledged=false;request.nativePreloading=false;request.m_ERRScenePreloadRequired=true;
worldTime=59999;assert(!h.CanFinalize_S(&request,&data,&actor));
worldTime=60000;assert(h.CanFinalize_S(&request,&data,&actor)&&h.preloadStarts==0);
assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::SPAWN_NOT_ALLOWED&&h.assignments==0);}
// Before the deadline, ordinary scene and native preload gates still hold.
{Request request;Handler h;worldTime=1000;request.nativePreloading=false;request.m_ERRScenePreloadRequired=true;
assert(!h.CanFinalize_S(&request,&data,&actor)&&h.preloadStarts==1);
request.nativePreloading=false;assert(!h.CanFinalize_S(&request,&data,&actor));
request.m_ERRSceneVerified=true;assert(h.CanFinalize_S(&request,&data,&actor));
assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::OK&&h.assignments==1&&request.retained==1&&h.storage.ticket.releases==1);}
// Invalid server snapshots already marked timed-out also take rejection only.
{Request request;Handler h;worldTime=1;request.m_ERRSceneTimedOut=true;
assert(h.CanFinalize_S(&request,&data,&actor));
assert(h.FinalizeRequest_S(&request,&data,&actor)==SCR_ESpawnResult::SPAWN_NOT_ALLOWED&&h.assignments==0);}
}
''')
print('PASS: actual scene deadline overrides delayed native preload only for rejection; late completion cannot assign, ordinary verified admission preserved')