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.
114 lines
9.8 KiB
Python
114 lines
9.8 KiB
Python
"""Execute both production monitor bodies; native hint/camera boundaries are fakes."""
|
|
from policy_harness import ROOT, body, run
|
|
from pathlib import Path
|
|
import os
|
|
GUIDE=Path(os.environ.get('ERR_GUIDE_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerGuide.c'))
|
|
OBSERVER=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c'
|
|
def cpp(text):
|
|
text=text.replace('protected ', '').replace('override ', '').replace('super.OnControlledEntityChanged(from, to);', 'nativeChanges++;')
|
|
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,'override void OnControlledEntityChanged('))+'\n'+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;using IEntity=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 nativeChanges=0,starts=0;void ERR_StartGuide(){starts++;}
|
|
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);
|
|
// Reacquiring a character in the same phase clears delivery once; native
|
|
// lifecycle and the existing monitor remain responsible for actual delivery.
|
|
Actor replacement;int shown=hints.shown;
|
|
g.OnControlledEntityChanged(&actor,&replacement);g.controlled=&replacement;
|
|
assert(g.nativeChanges==1&&g.starts==1&&g.m_ERRGuidePhase==-1);
|
|
hints.accepts=false;g.ERR_UpdateGuide();assert(hints.shown==shown&&g.m_ERRGuidePhase==-1);
|
|
hints.accepts=true;g.ERR_UpdateGuide();assert(hints.shown==shown+1);
|
|
g.ERR_UpdateGuide();assert(hints.shown==shown+1);
|
|
g.OnControlledEntityChanged(&replacement,&replacement);g.ERR_UpdateGuide();assert(hints.shown==shown+1);
|
|
g.OnControlledEntityChanged(&replacement,nullptr);g.controlled=nullptr;
|
|
g.ERR_UpdateGuide();assert(hints.shown==shown+1);
|
|
g.OnControlledEntityChanged(nullptr,&replacement);g.controlled=&replacement;
|
|
g.ERR_UpdateGuide();assert(hints.shown==shown+2&&g.nativeChanges==4&&g.starts==4);
|
|
// 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')
|