98 lines
8.6 KiB
Python
98 lines
8.6 KiB
Python
"""Execute both production monitor bodies; native hint/camera boundaries are fakes."""
|
|
from policy_harness import ROOT, body, run
|
|
GUIDE=ROOT/'addons/core/Scripts/Game/ERR/ERR_PlayerGuide.c'
|
|
OBSERVER=ROOT/'addons/core/Scripts/Game/ERR/ERR_Observer.c'
|
|
def cpp(text):
|
|
text=text.replace('protected ', '')
|
|
for name in ('ERR_GameMode','ERR_Phase','ERR_ObserverReason','ERR_Actors','ERR_ObjectiveText','SCR_HintManagerComponent','ERR_ObserverGuidance','Resource','ETransformMode','SCR_ManualCamera','SCR_RespawnSystemComponent','RplId'):
|
|
text=text.replace(name+'.',name+'::')
|
|
for typename in ('ERR_GameMode','SCR_HintManagerComponent','Resource','SCR_RespawnSystemComponent','CameraManager'):
|
|
text=text.replace(typename+' ',typename+'* ')
|
|
for variable in ('mode','hints','resource','respawn','manager'):
|
|
text=text.replace(variable+'.',variable+'->')
|
|
text=text.replace('GetGame().','GetGame()->').replace('EntitySpawnParams params = new EntitySpawnParams();','EntitySpawnParams params;')
|
|
return text
|
|
methods=cpp(body(GUIDE,'protected void ERR_UpdateGuide('))+'\n'+cpp(body(OBSERVER,'protected void ERR_UpdateObserverClient('))
|
|
guidance=cpp(body(OBSERVER,'static string Message('))+'\n'+cpp(body(OBSERVER,'static bool Refresh('))
|
|
run(r'''#include <cassert>
|
|
#include <string>
|
|
struct string:std::string {using std::string::string;using std::string::operator=;string(std::string s):std::string(s){}bool IsEmpty()const{return empty();}};
|
|
namespace ERR_Phase {enum {PREPARING,PRISON,RADIO_SEARCH,RADIO_DEFENCE,EXTRACTION};}
|
|
namespace ERR_ObserverReason {enum {NONE,SPENT_LIFE,LATE_EXTRACTION,SPAWN_UNAVAILABLE};}
|
|
struct Actor {bool alive=true;bool unconscious=false;} actor;
|
|
struct ERR_Actors {static bool Alive(Actor* a){return a&&a->alive;}};
|
|
struct ERR_GameMode {int phase=ERR_Phase::PRISON;bool active=true;string message="prison";static ERR_GameMode* current;static ERR_GameMode* Get(){return current;}int Phase(){return phase;}bool Active(){return active;}} mode;
|
|
ERR_GameMode* ERR_GameMode::current=&mode;
|
|
struct ERR_ObjectiveText {static string Description(ERR_GameMode* m,bool){return m->message;}static string Title(int){return "objective";}};
|
|
struct SCR_HintManagerComponent {static SCR_HintManagerComponent* current;bool accepts=true;int attempts=0,shown=0;string last;static SCR_HintManagerComponent* GetInstance(){return current;}bool ShowCustom(string text,string,int){attempts++;if(!accepts)return false;shown++;last=text;return true;}} hints;
|
|
SCR_HintManagerComponent* SCR_HintManagerComponent::current=&hints;
|
|
struct ERR_ObserverGuidance {
|
|
'''+guidance+r'''
|
|
};
|
|
struct Vec {Vec operator+(const char*)const{return {};}};
|
|
struct EntitySpawnParams {int TransformMode=0;Vec Transform[4];};
|
|
namespace ETransformMode {enum {WORLD};}
|
|
struct Resource {bool valid=true;static Resource* current;static Resource* Load(const char*){return current;}bool IsValid(){return valid;}} resource;
|
|
Resource* Resource::current=&resource;
|
|
struct SCR_ManualCamera {static SCR_ManualCamera* Cast(SCR_ManualCamera* c){return c;}} camera;
|
|
struct CameraManager {SCR_ManualCamera* current=&camera;SCR_ManualCamera* CurrentCamera(){return current;}} manager;
|
|
struct SCR_RespawnSystemComponent {int destroys=0;static SCR_RespawnSystemComponent* current;static SCR_RespawnSystemComponent* GetInstance(){return current;}void DestroyLoadingPlaceholder(){destroys++;}} respawn;
|
|
SCR_RespawnSystemComponent* SCR_RespawnSystemComponent::current=&respawn;
|
|
struct RplId {static int Invalid(){return -1;}};
|
|
struct Game {int spawns=0;SCR_ManualCamera* SpawnEntityPrefab(Resource*,int,EntitySpawnParams){spawns++;return &camera;}int GetWorld(){return 1;}CameraManager* GetCameraManager(){return &manager;}} game;
|
|
Game* GetGame(){return &game;}
|
|
struct Controller {
|
|
int m_ERRGuidePhase=-1;string m_ERRGuideMessage;
|
|
Actor* controlled=&actor;int m_ERRObserverReason=ERR_ObserverReason::NONE;
|
|
SCR_ManualCamera* m_ERRObserverCamera=nullptr;Vec m_ERRObserverAnchor;
|
|
int m_ERRObservedId=1;bool m_ERRObserverReady=false;
|
|
int m_ERRShownObserverReason=0;bool m_ERRShownObserverAvailable=false;int closes=0;
|
|
Actor* GetControlledEntity(){return controlled;}int ERR_ObserverState(){return m_ERRObserverReason;}
|
|
void ERR_CloseObserverCamera(){closes++;m_ERRObserverReady=false;m_ERRObserverCamera=nullptr;}
|
|
'''+methods+r'''
|
|
};
|
|
void reset(){mode=ERR_GameMode();ERR_GameMode::current=&mode;hints=SCR_HintManagerComponent();SCR_HintManagerComponent::current=&hints;actor=Actor();Resource::current=&resource;manager.current=&camera;}
|
|
int main(){
|
|
reset();Controller g;
|
|
SCR_HintManagerComponent::current=nullptr;g.ERR_UpdateGuide();assert(g.m_ERRGuidePhase==-1&&hints.attempts==0);
|
|
SCR_HintManagerComponent::current=&hints;hints.accepts=false;
|
|
g.ERR_UpdateGuide();g.ERR_UpdateGuide();assert(hints.attempts==2&&hints.shown==0&&g.m_ERRGuidePhase==-1&&g.m_ERRGuideMessage.empty());
|
|
hints.accepts=true;g.ERR_UpdateGuide();assert(hints.shown==1&&g.m_ERRGuidePhase==mode.phase&&g.m_ERRGuideMessage==mode.message);
|
|
g.ERR_UpdateGuide();assert(hints.attempts==3);
|
|
mode.message="";g.ERR_UpdateGuide();assert(hints.attempts==3&&g.m_ERRGuideMessage=="prison");
|
|
// Same-phase content updates are retryable and do not erase the delivered cache.
|
|
mode.message="transmission interrupted";hints.accepts=false;g.ERR_UpdateGuide();assert(g.m_ERRGuideMessage=="prison");
|
|
hints.accepts=true;g.ERR_UpdateGuide();assert(hints.shown==2&&g.m_ERRGuideMessage==mode.message);
|
|
mode.phase=ERR_Phase::RADIO_SEARCH;g.ERR_UpdateGuide();assert(hints.shown==3);
|
|
// Explicit observation, death and absence suppress ordinary phase instructions.
|
|
for(int excluded=0;excluded<3;excluded++){
|
|
Controller c;if(excluded==0)c.m_ERRObserverReason=ERR_ObserverReason::SPENT_LIFE;
|
|
if(excluded==1){actor.alive=false;}if(excluded==2){c.controlled=nullptr;}
|
|
int attempts=hints.attempts;c.ERR_UpdateGuide();assert(hints.attempts==attempts&&c.m_ERRGuidePhase==-1);
|
|
actor.alive=true;c.controlled=&actor;c.m_ERRObserverReason=ERR_ObserverReason::NONE;c.ERR_UpdateGuide();assert(hints.attempts==attempts+1);
|
|
}
|
|
// Unconscious living prisoners retain recovery guidance. Preparing needs no body.
|
|
Controller unconscious;actor.unconscious=true;unconscious.ERR_UpdateGuide();assert(unconscious.m_ERRGuidePhase==mode.phase);actor.unconscious=false;
|
|
Controller preparing;preparing.controlled=nullptr;mode.phase=ERR_Phase::PREPARING;mode.message="preparing";preparing.ERR_UpdateGuide();assert(preparing.m_ERRGuideMessage=="preparing");
|
|
ERR_GameMode::current=nullptr;int attempts=hints.attempts;preparing.ERR_UpdateGuide();assert(hints.attempts==attempts);
|
|
reset();Controller s;s.controlled=nullptr;s.m_ERRObserverReason=ERR_ObserverReason::SPENT_LIFE;
|
|
// Actual camera setup executes; missing manager cannot consume delivery state.
|
|
SCR_HintManagerComponent::current=nullptr;s.ERR_UpdateObserverClient();assert(s.m_ERRObserverCamera==&camera&&!s.m_ERRObserverReady);
|
|
SCR_HintManagerComponent::current=&hints;hints.accepts=false;s.ERR_UpdateObserverClient();s.ERR_UpdateObserverClient();assert(hints.attempts==2&&!s.m_ERRObserverReady&&s.m_ERRShownObserverReason==0);
|
|
hints.accepts=true;s.ERR_UpdateObserverClient();assert(hints.shown==1&&s.m_ERRObserverReady&&s.m_ERRShownObserverReason==ERR_ObserverReason::SPENT_LIFE&&s.m_ERRShownObserverAvailable);
|
|
s.ERR_UpdateObserverClient();assert(hints.attempts==3);
|
|
// Reason changes and a genuinely empty survivor set retry independently.
|
|
s.m_ERRObserverReason=ERR_ObserverReason::LATE_EXTRACTION;hints.accepts=false;s.ERR_UpdateObserverClient();assert(s.m_ERRShownObserverReason==ERR_ObserverReason::SPENT_LIFE);
|
|
hints.accepts=true;s.ERR_UpdateObserverClient();assert(s.m_ERRShownObserverReason==ERR_ObserverReason::LATE_EXTRACTION&&hints.shown==2);
|
|
s.m_ERRObservedId=RplId::Invalid();hints.accepts=false;s.ERR_UpdateObserverClient();assert(s.m_ERRShownObserverAvailable);
|
|
hints.accepts=true;s.ERR_UpdateObserverClient();assert(!s.m_ERRShownObserverAvailable&&hints.last.find("No survivor")!=string::npos);
|
|
s.ERR_UpdateObserverClient();assert(hints.shown==3);
|
|
// A living body closes observation without attempting a spectator hint.
|
|
s.controlled=&actor;attempts=hints.attempts;s.ERR_UpdateObserverClient();assert(s.closes==1&&!s.m_ERRObserverReady&&hints.attempts==attempts);
|
|
s.controlled=nullptr;s.ERR_UpdateObserverClient();assert(hints.shown==4&&s.m_ERRObserverReady);
|
|
}
|
|
''')
|
|
for path,signature in ((GUIDE,'protected void ERR_UpdateGuide('),(OBSERVER,'protected void ERR_UpdateObserverClient(')):
|
|
assert 'CallLater' not in body(path,signature), 'Delivery retry uses the existing monitor only'
|
|
print('PASS: actual guide/observer monitors retry native refusal, cache successful delivery, deduplicate and preserve living/preparing/spectator boundaries; native UI not exercised')
|