Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
111 lines
8.2 KiB
Python
111 lines
8.2 KiB
Python
"""Execute actual Core tick/stop/default maintenance methods with fake engine APIs.
|
|
|
|
Checks scheduling/authority boundaries, not native aircraft, network or timing.
|
|
"""
|
|
from policy_harness import ROOT,body,run
|
|
MODE=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c'
|
|
ACTORS=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Actors.c'
|
|
|
|
def cpp(s):
|
|
s=s.replace('protected ','').replace('Replication.IsServer()','server').replace('Replication.BumpMe()','Bump()')
|
|
s=s.replace('ERR_Phase.DEFEAT','5').replace('ERR_Phase.PRISON','1').replace('ERR_Phase.RADIO_SEARCH','2').replace('ERR_MissionRules.Terminal(m_Phase)','(m_Phase>=5)')
|
|
s=s.replace('array<int> connected = {};','std::vector<int> connected;').replace('foreach (int id : connected)','for (int id : connected)')
|
|
s=s.replace('SCR_PlayerController controller = SCR_PlayerController.Cast(', 'Controller* controller = Controller::Cast(')
|
|
s=s.replace('foreach (IEntity entity : m_OwnedEntities)','for (Entity* entity : m_OwnedEntities)')
|
|
s=s.replace('SCR_AIGroup.Cast(entity)','(entity && entity->group)').replace('ERR_Actors.DeleteSpawned(entity)','Delete(entity)')
|
|
s=s.replace('ERR_ObserverReason.NONE','0')
|
|
for var in ('m_Extraction','m_RadioDefence','m_Recovery','m_Command','m_Roster','controller','GetWorld()','GetGame()','GetPlayerManager()','GetCallqueue()'):
|
|
s=s.replace(var+'.',var+'->')
|
|
for name in ('StartScenario','ServerTick','BindLocalObserver'):s=s.replace('Remove('+name+')','Remove("'+name+'")')
|
|
return s
|
|
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <functional>
|
|
#include <string>
|
|
#include <vector>
|
|
bool server=true;int bumps=0;void Bump(){bumps++;}
|
|
std::vector<std::string> events;
|
|
struct World {float now=1234;float GetWorldTime(){return now;}}world;
|
|
struct Controller {int updates=0,resets=0;static Controller* Cast(Controller* p){return p;}void ERR_UpdateObserverServer(){updates++;events.push_back("observer");}void ERR_SetObserver(int){resets++;}};
|
|
struct Players {int count=0;Controller controller;int GetPlayerCount(){return count;}void GetPlayers(std::vector<int>& out){if(count)out.push_back(1);}Controller* GetPlayerController(int){return &controller;}}players;
|
|
struct Queue {std::vector<std::string> removed;void Remove(std::string s){removed.push_back(s);}}queue;
|
|
struct Game {Players* GetPlayerManager(){return &players;}Queue* GetCallqueue(){return &queue;}}game;Game* GetGame(){return &game;}
|
|
struct Hook {int calls=0;std::function<void()> callback;std::string name;void Invoke(float=0){calls++;events.push_back(name);if(callback)callback();}};
|
|
struct Work {int calls=0;std::string name;std::function<void()> callback;void Tick(float=0){calls++;events.push_back(name);if(callback)callback();}};
|
|
struct Roster {bool survivor=true;bool HasSurvivor(){return survivor;}}roster;
|
|
struct Entity {bool group=false;};int deleted=0;void Delete(Entity*){deleted++;}
|
|
struct Command {
|
|
virtual '''+body(ACTORS,'void Maintain(')+r'''
|
|
virtual void Tick(float){events.push_back("tactics");}
|
|
virtual void Stop(){events.push_back("stop");}
|
|
virtual ~Command()=default;
|
|
};
|
|
struct Retained : Command {
|
|
int maintenance=0,tactics=0,stops=0;float time=-1;bool pending=true;
|
|
std::function<void()> callback;
|
|
void Maintain(float now) override {maintenance++;time=now;events.push_back("maintain");if(callback)callback();}
|
|
void Tick(float) override {tactics++;events.push_back("tactics");}
|
|
void Stop() override {stops++;pending=false;events.push_back("stop");}
|
|
};
|
|
struct ForceBoundary {int clears=0;void Clear(){clears++;}};
|
|
struct Mode {
|
|
ForceBoundary m_Forces,m_ForceAgents;
|
|
int forceTicks=0;void TickForces(float){forceTicks++;}
|
|
bool m_Stopped=false,m_NativeEndHandled=false,m_StartedPlayers=false;int m_Phase=2;
|
|
float m_NoSurvivorsSince=-1;std::string m_EndReason;
|
|
Work extraction{0,"extraction",{}},radio{0,"radio",{}},recovery{0,"recovery",{}};
|
|
Work* m_Extraction=&extraction;Work* m_RadioDefence=&radio;Work* m_Recovery=&recovery;
|
|
Command* m_Command=nullptr;Roster* m_Roster=&roster;
|
|
Hook m_OnTick{0,{},"event"},m_OnStop{0,{},"onstop"};std::vector<Entity*> m_OwnedEntities;
|
|
bool InitialAdmissionsExhausted(){return false;}
|
|
World* GetWorld(){return &world;}bool RefreshRadioFeedback(){return false;}
|
|
void Transition(int phase){m_Phase=phase;StopServices();}
|
|
'''+cpp(body(MODE,'bool Active('))+cpp(body(MODE,'protected void ServerTick('))+cpp(body(MODE,'protected void StopServices('))+r'''
|
|
};
|
|
void reset(){server=true;events.clear();world.now=1234;players.count=0;players.controller=Controller();queue.removed.clear();roster.survivor=true;deleted=0;}
|
|
int main(){
|
|
// Existing defaults remain inert; the no-player tactical pause is preserved.
|
|
{reset();Mode m;Command c;m.m_Command=&c;m.ServerTick();assert((events==std::vector<std::string>{"extraction","radio","recovery"}));assert(m.m_OnTick.calls==0);}
|
|
// Retained work receives exactly one maintenance call on an empty server,
|
|
// including later ticks, without tactical decisions, observer updates or OnTick.
|
|
{reset();Mode m;Retained c;m.m_Command=&c;m.m_NoSurvivorsSince=100;
|
|
m.ServerTick();world.now=2234;m.ServerTick();assert(c.maintenance==2&&c.time==2234&&c.tactics==0&&c.pending&&m.forceTicks==2);
|
|
assert(players.controller.updates==0&&m.m_OnTick.calls==0&&m.m_NoSurvivorsSince==-1);}
|
|
// Populated ticks have exactly one maintenance call before ordinary decisions.
|
|
{reset();players.count=1;Mode m;Retained c;m.m_Command=&c;m.ServerTick();assert(c.maintenance==1&&c.tactics==1);
|
|
assert((events==std::vector<std::string>{"extraction","radio","recovery","maintain","observer","tactics","event"}));}
|
|
// No command service is valid both with and without connected players.
|
|
{reset();Mode m;m.ServerTick();players.count=1;m.ServerTick();assert(m.m_OnTick.calls==1);}
|
|
// During prison, maintenance/observers still work, but enemies receive no
|
|
// tactical updates or virtual route movement before the lockpick.
|
|
{reset();players.count=1;Mode m;m.m_Phase=1;Retained c;m.m_Command=&c;
|
|
m.ServerTick();assert(c.maintenance==1&&c.tactics==0&&m.forceTicks==0);
|
|
assert(players.controller.updates==1&&m.m_OnTick.calls==1);
|
|
m.m_Phase=2;m.ServerTick();assert(c.tactics==1&&m.forceTicks==1);}
|
|
// Authority, preparation, terminal phase, native end and stopped service all
|
|
// refuse maintenance as well as tactics. No stale clock/actor admission exists.
|
|
for(int fault=0;fault<5;fault++){reset();Mode m;Retained c;m.m_Command=&c;
|
|
if(fault==0){server=false;} if(fault==1){m.m_Phase=0;} if(fault==2){m.m_Phase=5;}
|
|
if(fault==3){m.m_NativeEndHandled=true;} if(fault==4){m.m_Stopped=true;}
|
|
m.ServerTick();assert(c.maintenance==0&&c.tactics==0&&events.empty());}
|
|
// A preceding service can terminalize the round: later maintenance must stop.
|
|
for(int earlier=0;earlier<2;earlier++){reset();Mode m;Retained c;m.m_Command=&c;
|
|
auto end=[&](){m.Transition(5);};if(earlier==0)m.extraction.callback=end;else m.recovery.callback=end;
|
|
m.ServerTick();assert(c.maintenance==0&&c.tactics==0&&c.stops==1&&!m.Active());}
|
|
// Maintenance may itself end the round. Its return cannot resurrect tactics.
|
|
{reset();players.count=1;Mode m;Retained c;m.m_Command=&c;c.callback=[&](){m.Transition(5);};m.ServerTick();
|
|
assert(c.maintenance==1&&c.tactics==0&&c.stops==1&&m.m_OnTick.calls==0&&players.controller.updates==0);}
|
|
// Existing defeat path still runs after maintenance with players present.
|
|
{reset();players.count=1;roster.survivor=false;Mode m;Retained c;m.m_Command=&c;m.m_StartedPlayers=true;m.m_NoSurvivorsSince=0;world.now=30000;
|
|
m.ServerTick();assert(c.maintenance==1&&c.stops==1&&c.tactics==0&&m.m_Phase==5&&!m.Active());}
|
|
// Stop removes the existing callback before the command receives terminal Stop;
|
|
// no maintenance drain loop or new scheduling has been introduced in Core.
|
|
{reset();Mode m;Retained c;m.m_Command=&c;Entity group{true},other{false};m.m_OwnedEntities={&group,&other};
|
|
m.StopServices();m.StopServices();m.ServerTick();assert(c.stops==1&&c.maintenance==0&&!c.pending&&deleted==1&&m.m_Forces.clears==1&&m.m_ForceAgents.clears==1&&m.forceTicks==0);
|
|
assert((queue.removed==std::vector<std::string>{"StartScenario","ServerTick","BindLocalObserver"}));
|
|
assert((events==std::vector<std::string>{"stop","onstop"}));}
|
|
}
|
|
''')
|
|
print('PASS: actual Core maintenance/default/tick/stop methods; empty/populated authority, terminal reentry, no tactical resume or new scheduler; native boundaries substituted')
|