Files
EscapistsReintroduced/tests/test_spectator_guidance.py
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

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 teammate or body is currently available")!=string::npos)==!available);
assert(message.find("teammate or body")!=string::npos);
}
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')