"""Run actual personal-result title/debrief and refresh methods with UI adapters. No native replication, end-screen rendering or multiplayer claim. """ import re from policy_harness import ROOT, body, run SOURCE=ROOT/'addons/core/Scripts/Game/ERR/ERR_EscapeGameOverScreenInfo.c' methods='\n'.join(body(SOURCE, signature) for signature in ( 'protected ERR_PersonalEscapeResult PersonalResult(', 'override string GetTitle(', 'override string GetDebriefing(')) refresh=body(SOURCE,'protected void ERR_RefreshEscapeContent(') def adapt(text): text=re.sub(r' #ifdef WORKBENCH\n.*? #endif\n','',text,flags=re.S) text=text.replace('protected ','').replace('override ','') text=text.replace('ERR_PersonalEscapeResult.','Result::').replace('ERR_PersonalEscapeResult','Result::Value') text=text.replace('array otherPlayerIds','std::vector* otherPlayerIds') text=re.sub(r'\bstring\b','std::string',text) text=text.replace('GetGame().GetPlayerController()', 'GetGame()->GetPlayerController()') text=text.replace('SCR_PlayerController.Cast(', 'Controller::Cast(').replace('SCR_PlayerController controller', 'Controller* controller') text=text.replace('TextWidget.Cast(', 'TextWidget::Cast(') text=text.replace('TextWidget title', 'TextWidget* title').replace('TextWidget debrief', 'TextWidget* debrief') # Enforce manages this local object; the C++ adapter uses scoped ownership. text=text.replace('ERR_EscapeGameOverScreenInfo info = new ERR_EscapeGameOverScreenInfo();', 'auto info = std::make_unique();') for variable in ('controller','info','title','debrief','m_wRoot'): text=text.replace(variable+'.',variable+'->') text=re.sub(r'\bnull\b','nullptr',text) return text run(r''' #include #include #include #include namespace Result {enum Value {UNKNOWN,ESCAPED,MISSED};} struct Controller { int id=7;Result::Value result=Result::UNKNOWN; static Controller* Cast(Controller* value){return value;} int GetPlayerId(){return id;}Result::Value ERR_EscapeResult(){return result;} }; struct Game {Controller* local=nullptr;Controller* GetPlayerController(){return local;}} game; Game* GetGame(){return &game;} struct Info { '''+adapt(methods)+r''' }; struct TextWidget {std::string value;int writes=0; static TextWidget* Cast(TextWidget* widget){return widget;} void SetText(std::string text){value=text;writes++;} }; struct Root {TextWidget title,debrief;bool hasTitle=true,hasDebrief=true; TextWidget* FindAnyWidget(std::string name){if(name=="title")return hasTitle?&title:nullptr;return hasDebrief?&debrief:nullptr;} }; struct Content { bool m_ERREscapeContent=true;Root* m_wRoot=nullptr; std::string m_sTileName="title",m_sDebriefingName="debrief"; '''+adapt(refresh)+r''' }; int main(){ Controller controller;game.local=&controller;Info info; std::vector staleWinners={7};std::vector noWinners; const char* titles[]={"Round ended","You escaped Everon","The helicopter escaped"}; const char* descriptions[]={"Your personal escape result is not available.","You crossed the extraction boundary aboard the helicopter.","You did not escape with the helicopter."}; for(int state=0;state<3;state++){ controller.result=static_cast(state); for(auto* winners:{&staleWinners,&noWinners,static_cast*>(nullptr)}){ assert(info.GetTitle(7,winners)==titles[state]); assert(info.GetDebriefing(7,winners)==descriptions[state]); // A different connection ID cannot use this local controller's result. assert(info.GetTitle(8,winners)==titles[0]); assert(info.GetDebriefing(8,winners)==descriptions[0]); } } game.local=nullptr; assert(info.GetTitle(7,&staleWinners)==titles[0]); assert(info.GetDebriefing(7,&staleWinners)==descriptions[0]); // The unchanged actual refresh method pulls only the current personal result; // late arrival, account reset, and missing local controller refresh both texts. game.local=&controller;Root root;Content content;content.m_wRoot=&root; for(int state:{0,1,2,0}){ controller.result=static_cast(state);content.ERR_RefreshEscapeContent(); assert(root.title.value==titles[state]&&root.debrief.value==descriptions[state]); } game.local=nullptr;content.ERR_RefreshEscapeContent(); assert(root.title.value==titles[0]&&root.debrief.value==descriptions[0]); assert(root.title.writes==5&&root.debrief.writes==5); content.m_ERREscapeContent=false;content.ERR_RefreshEscapeContent(); assert(root.title.writes==5&&root.debrief.writes==5); content.m_ERREscapeContent=true;content.m_wRoot=nullptr;content.ERR_RefreshEscapeContent(); assert(root.title.writes==5&&root.debrief.writes==5); content.m_wRoot=&root;root.hasTitle=false;content.ERR_RefreshEscapeContent(); assert(root.title.writes==5&&root.debrief.writes==6); root.hasTitle=true;root.hasDebrief=false;content.ERR_RefreshEscapeContent(); assert(root.title.writes==6&&root.debrief.writes==6); } ''') # Keep native subscription/detach wiring intact; actual text refresh is executed above. init=body(SOURCE,'override void InitContent(') detach=body(SOURCE,'override void HandlerDeattached(') assert 'SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);' in init assert 'SCR_PlayerController.ERR_OnEscapeResultChanged().Insert(ERR_RefreshEscapeContent);' in init assert 'ERR_RefreshEscapeContent();' in init assert 'SCR_PlayerController.ERR_OnEscapeResultChanged().Remove(ERR_RefreshEscapeContent);' in detach assert 'super.HandlerDeattached(w);' in detach print('PASS: actual three-state result wording and UI refresh; stale winner IDs never manufacture escape')