65 lines
4.1 KiB
Python
65 lines
4.1 KiB
Python
"""Execute the actual spectator message/refresh policy; no native UI claim."""
|
|
from policy_harness import ROOT,body,run
|
|
p=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Observer.c'
|
|
source=body(p,'static string Message(')+'\n'+body(p,'static bool Refresh(')
|
|
source=source.replace('ERR_ObserverReason.','ERR_ObserverReason::')
|
|
run('''#include <cassert>
|
|
#include <string>
|
|
using string=std::string;
|
|
namespace ERR_ObserverReason {enum {NONE,SPENT_LIFE,LATE_EXTRACTION,SPAWN_UNAVAILABLE,UNCONSCIOUS};}
|
|
'''+source+'''
|
|
int main(){
|
|
for(int reason=0;reason<4;reason++)for(bool available:{false,true}){
|
|
auto message=Message(reason,available);
|
|
if(reason==0){assert(message.empty());continue;}
|
|
assert(message.find("next round")!=string::npos);
|
|
assert((message.find("/esc spectate next")!=string::npos)==available);
|
|
assert((message.find("/esc spectate previous")!=string::npos)==available);
|
|
assert((message.find("No survivor is currently available")!=string::npos)==!available);
|
|
assert((message.find("when one becomes available")!=string::npos)==!available);
|
|
}
|
|
assert(Message(1,true).find("life for this round has been used")!=string::npos);
|
|
assert(Message(2,true).find("Extraction has started")!=string::npos);
|
|
assert(Message(3,true).find("could not be spawned")!=string::npos);
|
|
for(bool shown:{false,true})for(int previous=0;previous<5;previous++)for(int reason=0;reason<5;reason++)for(bool oldAvailable:{false,true})for(bool available:{false,true}){
|
|
bool expected=reason!=0&&(!shown||previous!=reason||oldAvailable!=available);
|
|
assert(Refresh(shown,previous,reason,oldAvailable,available)==expected);
|
|
}
|
|
auto casualty=Message(ERR_ObserverReason::UNCONSCIOUS,true);
|
|
assert(casualty.find("Your body remains")!=string::npos&&casualty.find("next round")==string::npos);
|
|
assert(casualty.find("/esc spectate next")!=string::npos);
|
|
assert(Message(ERR_ObserverReason::UNCONSCIOUS,false).find("No conscious teammate")!=string::npos);
|
|
// Stable monitoring must not repeat. Availability and reason changes do refresh.
|
|
assert(!Refresh(true,1,1,true,true));assert(Refresh(true,1,1,true,false));
|
|
assert(Refresh(true,1,1,false,true));assert(Refresh(true,1,2,true,true));
|
|
// Camera close/reopen resets shown; missing hint manager must not consume it.
|
|
assert(Refresh(false,1,1,true,true));assert(Refresh(false,1,1,false,false));
|
|
}
|
|
''')
|
|
monitor=body(p,'protected void ERR_UpdateObserverClient(')
|
|
assert 'bool survivorAvailable = m_ERRObservedId != RplId.Invalid();' in monitor
|
|
assert 'if (!hints) return;' in monitor
|
|
assert monitor.index('if (!hints) return;')<monitor.index('hints.ShowCustom(')<monitor.index('m_ERRObserverReady = true;')
|
|
assert 'm_ERRShownObserverReason = m_ERRObserverReason;' in monitor and 'm_ERRShownObserverAvailable = survivorAvailable;' in monitor
|
|
assert 'm_ERRObserverReady = false;' in body(p,'protected void ERR_CloseObserverCamera(')
|
|
assert 'CallLater' not in monitor, 'No additional timer belongs to guidance'
|
|
commands=body(ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PlayerAdminCommands.c','void ERR_AdminSubmit(')
|
|
assert commands.index('ERR_CycleObserver(direction);')<commands.index('Rpc(ERR_AdminRequest,')
|
|
admin=body(ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c','void Execute(')
|
|
assert 'if (!authorized) { replies.Insert("Administrator access required."); return; }' in admin
|
|
print('PASS: actual reason/availability guidance and refresh matrix; no-target copy omits cycling, hint retry retained, existing command/admin paths unchanged; native UI pending')
|
|
|
|
view=body(p,'static bool View(').replace('ERR_ObserverReason.','ERR_ObserverReason::')
|
|
run('''#include <cassert>
|
|
#include <initializer_list>
|
|
namespace ERR_ObserverReason{enum{NONE,SPENT_LIFE,LATE_EXTRACTION,SPAWN_UNAVAILABLE,UNCONSCIOUS};}
|
|
'''+view+'''
|
|
int main(){
|
|
for(int reason=0;reason<5;reason++)for(bool active:{false,true})for(bool alive:{false,true})for(bool conscious:{false,true}){
|
|
bool wanted=active&&reason!=0&&(reason==4?(alive&&!conscious):!alive);
|
|
assert(View(reason,active,alive,conscious)==wanted);
|
|
}
|
|
}
|
|
''')
|
|
print('PASS: actual client camera policy closes immediately on healing or mission end; late unconscious metadata cannot keep the view active')
|