Files
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

218 lines
13 KiB
Python

"""Execute production alarm lifecycle bodies with native audio/replication doubles.
This checks ownership and finite playback policy, not native acoustics or network
ordering. Successful breach routing is exercised in test_vehicle_security.py.
"""
import re
from policy_harness import ROOT, body, run
component = ROOT / 'addons/core/Scripts/Game/ERR/00_Core/ERR_VehicleSecurityComponent.c'
def translate(source):
source = source.replace('protected ', '').replace('override ', '')
source = source.replace('ERR_GameMode mode', 'ERR_GameMode* mode')
for name in ('Replication', 'AudioHandle', 'AudioSystem', 'SCR_Global',
'ERR_Actors', 'ERR_VehicleAlarmPerception', 'ERR_GameMode', 'ERR_MissionRules', 'RplSession', 'RplMode'):
source = source.replace(name + '.', name + '::')
for name in ('GetGame()', 'GetWorld()', 'GetCallqueue()', 'GetOwner()', 'm_AlarmMode', 'mode'):
source = source.replace(name + '.', name + '->')
source = source.replace('array<string>', 'std::vector<string>').replace('array<float>', 'std::vector<float>')
source = source.replace('vector horn = "0 1 1.2";', 'vector horn{0, 1, 1.2f};')
for name, callback in (('StopAlarm', 'STOP_CALLBACK'), ('AlarmTick', 'TICK_CALLBACK')):
for operation in ('CallLater', 'Remove', 'Insert'):
source = source.replace(operation + '(' + name, operation + '(' + callback)
return source.replace('super.OnDelete(owner)', 'Base::OnDelete(owner)').replace('null', 'nullptr')
methods = '\n'.join(translate(body(component, signature)) for signature in (
'bool AlarmActive(', 'bool AlarmPlaying(', 'protected void BeginAlarm(',
'protected void ApplyAlarm(', 'protected void AlarmTick(', 'protected void StopAlarm(',
'protected void CloseAlarm(', 'protected override void OnDelete('))
run(r'''
#include <cassert>
#include <cmath>
#include <limits>
#include <map>
#include <set>
#include <string>
#include <vector>
using string=std::string;
constexpr int STOP_CALLBACK=1,TICK_CALLBACK=2,FOREIGN_CALLBACK=99;
struct vector {
float x=0,y=0,z=0;
vector Multiply4(const vector* t){return {t[3].x+x,t[3].y+y,t[3].z+z};}
};
struct Entity {
bool alive=true;vector position;
void GetWorldTransform(vector* t){t[0]={1,0,0};t[1]={0,1,0};t[2]={0,0,1};t[3]=position;}
};
using IEntity=Entity*;
struct World {float now=100;float GetWorldTime(){return now;}};
struct Queue {
struct Timer {int delay;bool repeat;};
std::map<int,Timer> timers{{FOREIGN_CALLBACK,{1,true}}};int calls=0;
void CallLater(int id,int delay,bool repeat){calls++;timers[id]={delay,repeat};}
void Remove(int id){timers.erase(id);}
};
struct Game {World* world=nullptr;Queue queue;World* GetWorld(){return world;}Queue* GetCallqueue(){return &queue;}};
Game* currentGame=nullptr;Game* GetGame(){return currentGame;}
struct Invoker {
std::set<int> subscribers{FOREIGN_CALLBACK};int inserts=0;
void Insert(int id){inserts++;subscribers.insert(id);}void Remove(int id){subscribers.erase(id);}
};
struct ERR_GameMode {
static ERR_GameMode* current;bool active=true;int phase=1;Invoker stopped;
static ERR_GameMode* Get(){return current;}bool Active(){return active;}int Phase(){return phase;}Invoker& OnStop(){return stopped;}
};
ERR_GameMode* ERR_GameMode::current=nullptr;
struct ERR_MissionRules {static bool Terminal(int phase){return phase==2;}};
struct ERR_Actors {static bool Alive(IEntity owner){return owner&&owner->alive;}};
struct Replication {inline static bool server=true;inline static int bumps=0;static bool IsServer(){return server;}static void BumpMe(){bumps++;}};
struct SCR_Global {inline static bool edit=false;static bool IsEditMode(){return edit;}};
struct RplMode {enum {Client,Dedicated};};
struct RplSession {inline static int mode=RplMode::Client;static int Mode(){return mode;}};
struct AudioHandle {static const int Invalid=-1;};
struct AudioSystem {
inline static int plays=0,next=10;inline static bool fail=false;
inline static std::set<int> sounds;inline static std::vector<int> ended,moved;
inline static vector lastPosition;
static int PlayEvent(const string& resource,const string& event,const vector* t,const std::vector<string>& signals,const std::vector<float>& values){
assert(resource=="{7E64D37BE3E1F3FA}Sounds/Vehicles/Wheeled/UAZ469/Vehicles_UAZ469_Horn.acp");
assert(event=="SOUND_VEHICLE_HORN_LP"&&signals==std::vector<string>{"HornState"}&&values==std::vector<float>{1});
plays++;lastPosition=t[3];if(fail)return AudioHandle::Invalid;int handle=next++;sounds.insert(handle);return handle;
}
// Native name means playback completed, not currently audible (vanilla sound managers).
static bool IsSoundPlayed(int handle){return !sounds.count(handle);}
static void SetSoundTransformation(int handle,const vector* t){moved.push_back(handle);lastPosition=t[3];}
static void TerminateSound(int handle){assert(handle!=AudioHandle::Invalid);ended.push_back(handle);sounds.erase(handle);}
static void Reset(){plays=0;next=10;fail=false;sounds={99};ended.clear();moved.clear();lastPosition={};}
};
struct ERR_VehicleAlarmPerception{inline static int emits=0;static void Emit(IEntity){emits++;}};
struct Base {int deleted=0;IEntity deletedOwner=nullptr;void OnDelete(IEntity owner){deleted++;deletedOwner=owner;}};
struct Security:Base {
bool m_Breached=false,m_AlarmStarted=false,m_AlarmFinished=false,m_AlarmAudioAttempted=false;
int m_AlarmState=0,m_AlarmHandle=AudioHandle::Invalid;float m_AlarmBegan=-1;ERR_GameMode* m_AlarmMode=nullptr;
IEntity owner=nullptr;IEntity GetOwner(){return owner;}
''' + methods + r'''
};
struct Fixture {
World world;Game game;ERR_GameMode mode;Entity vehicle;Security alarm;
Fixture(){
game.world=&world;currentGame=&game;ERR_GameMode::current=&mode;alarm.owner=&vehicle;
Replication::server=true;Replication::bumps=0;SCR_Global::edit=false;RplSession::mode=RplMode::Client;AudioSystem::Reset();ERR_VehicleAlarmPerception::emits=0;
}
void start(){alarm.m_Breached=true;alarm.BeginAlarm();}
void clean(){
assert(alarm.m_AlarmFinished&&!alarm.AlarmActive()&&!alarm.AlarmPlaying()&&alarm.m_AlarmHandle==AudioHandle::Invalid&&!alarm.m_AlarmMode);
assert(game.queue.timers.size()==1&&game.queue.timers.count(FOREIGN_CALLBACK));
assert(mode.stopped.subscribers==std::set<int>{FOREIGN_CALLBACK});
assert(AudioSystem::sounds.count(99));
}
};
int main(){
// No direct trigger unless authority has committed the successful breach.
for(int variant=0;variant<4;variant++){
Fixture f;f.alarm.m_Breached=true;
if(variant==0)Replication::server=false;
if(variant==1)f.alarm.m_Breached=false;
if(variant==2)f.alarm.m_AlarmState=2;
if(variant==3)f.alarm.m_AlarmFinished=true;
f.alarm.BeginAlarm();assert(!f.alarm.m_AlarmStarted&&AudioSystem::plays==0&&Replication::bumps==0&&f.game.queue.calls==0);
}
{
Fixture f;f.start();int handle=f.alarm.m_AlarmHandle;
assert(f.alarm.AlarmActive()&&f.alarm.AlarmPlaying()&&AudioSystem::plays==1&&Replication::bumps==1&&ERR_VehicleAlarmPerception::emits==1);
assert(f.game.queue.calls==2&&f.game.queue.timers[STOP_CALLBACK].delay==8000&&!f.game.queue.timers[STOP_CALLBACK].repeat);
assert(f.game.queue.timers[TICK_CALLBACK].delay==100&&f.game.queue.timers[TICK_CALLBACK].repeat);
assert(f.mode.stopped.inserts==1&&f.alarm.m_AlarmBegan==100);
assert(AudioSystem::lastPosition.y==1&&std::abs(AudioSystem::lastPosition.z-1.2f)<0.001f);
f.world.now=1099;f.vehicle.position={20,30,40};f.alarm.BeginAlarm();f.alarm.ApplyAlarm();f.alarm.AlarmTick();
assert(AudioSystem::plays==1&&AudioSystem::moved==std::vector<int>{handle});
assert(AudioSystem::lastPosition.x==20&&AudioSystem::lastPosition.y==31&&std::abs(AudioSystem::lastPosition.z-41.2f)<0.001f);
assert(f.game.queue.calls==2&&f.mode.stopped.inserts==1&&f.alarm.m_AlarmBegan==100&&Replication::bumps==1);
f.world.now=8099;f.alarm.AlarmTick();assert(f.alarm.AlarmPlaying());
f.world.now=8100;f.alarm.AlarmTick();f.clean();assert(f.alarm.m_AlarmState==2&&Replication::bumps==2&&AudioSystem::ended==std::vector<int>{handle});
f.alarm.StopAlarm();f.alarm.ApplyAlarm();f.alarm.BeginAlarm();f.alarm.AlarmTick();
assert(AudioSystem::plays==1&&AudioSystem::ended.size()==1&&Replication::bumps==2&&ERR_VehicleAlarmPerception::emits==1);
}
// Replicas close on authority completion, and a terminal snapshot wins even
// when it arrives before active or is followed by stale active snapshots.
for(bool completionFirst:{false,true}){
Fixture f;Replication::server=false;
if(!completionFirst){f.alarm.m_AlarmState=1;f.alarm.ApplyAlarm();f.alarm.ApplyAlarm();assert(AudioSystem::plays==1&&f.game.queue.calls==2);}
f.alarm.m_AlarmState=2;f.alarm.ApplyAlarm();f.clean();
f.alarm.m_AlarmState=1;f.alarm.ApplyAlarm();f.alarm.AlarmTick();
assert(!f.alarm.AlarmActive()&&AudioSystem::plays==(completionFirst?0:1)&&Replication::bumps==0&&ERR_VehicleAlarmPerception::emits==0);
}
// Replica initialization waits within the first deadline; readiness cannot
// renew the deadline, and missing readiness cannot leave playback pending forever.
for(int variant=0;variant<3;variant++){
Fixture f;Replication::server=false;f.mode.active=false;
if(variant!=0)ERR_GameMode::current=nullptr;
f.alarm.m_AlarmState=1;f.alarm.ApplyAlarm();assert(AudioSystem::plays==0&&!f.alarm.m_AlarmMode);
f.world.now=4100;f.alarm.ApplyAlarm();f.alarm.AlarmTick();assert(AudioSystem::plays==0&&f.game.queue.calls==2);
if(variant<2){ERR_GameMode::current=&f.mode;f.mode.active=true;f.alarm.AlarmTick();assert(AudioSystem::plays==1&&f.alarm.m_AlarmBegan==100);}
f.world.now=8100;f.alarm.AlarmTick();f.clean();assert(Replication::bumps==0);
}
// Every live-to-invalid transition disposes only this alarm's handle. Native
// client end is represented by inactive bound mode without firing OnStop.
for(int variant=0;variant<10;variant++){
Fixture f;f.start();int handle=f.alarm.m_AlarmHandle;ERR_GameMode replacement;
if(variant==0)f.vehicle.alive=false;
if(variant==1)f.alarm.owner=nullptr;
if(variant==2)f.mode.phase=2;
if(variant==3){Replication::server=false;f.mode.active=false;}
if(variant==4)ERR_GameMode::current=&replacement;
if(variant==5)ERR_GameMode::current=nullptr;
if(variant==6)f.world.now=99;
if(variant==7)f.world.now=std::numeric_limits<float>::quiet_NaN();
if(variant==8)f.game.world=nullptr;
if(variant==9){Replication::server=false;f.world.now=8100;}
f.alarm.AlarmTick();f.clean();assert(AudioSystem::ended==std::vector<int>{handle});
}
{
Fixture f;f.start();int handle=f.alarm.m_AlarmHandle;
// Native invoker calls the registered production callback.
assert(f.mode.stopped.subscribers.count(STOP_CALLBACK));f.alarm.StopAlarm();f.clean();
assert(AudioSystem::ended==std::vector<int>{handle});
}
{
Fixture f;f.start();int bumps=Replication::bumps;int handle=f.alarm.m_AlarmHandle;
f.alarm.OnDelete(&f.vehicle);f.clean();assert(f.alarm.deleted==1&&f.alarm.deletedOwner==&f.vehicle&&Replication::bumps==bumps);
assert(AudioSystem::ended==std::vector<int>{handle});f.alarm.CloseAlarm();assert(AudioSystem::ended.size()==1);
}
// Engine audio load/play failure is one attempt, never a polling retry.
{
Fixture f;AudioSystem::fail=true;f.start();assert(AudioSystem::plays==1&&f.alarm.m_AlarmAudioAttempted&&!f.alarm.AlarmPlaying());
AudioSystem::fail=false;for(int n=0;n<5;n++){f.alarm.AlarmTick();f.alarm.ApplyAlarm();}
assert(AudioSystem::plays==1&&AudioSystem::moved.empty());f.alarm.StopAlarm();f.clean();assert(AudioSystem::ended.empty());
}
{
Fixture f;f.start();int handle=f.alarm.m_AlarmHandle;
assert(handle!=AudioHandle::Invalid&&!AudioSystem::IsSoundPlayed(handle)&&f.alarm.AlarmPlaying());
AudioSystem::sounds.erase(handle);
assert(f.alarm.m_AlarmHandle==handle&&AudioSystem::IsSoundPlayed(handle)&&!f.alarm.AlarmPlaying());
f.alarm.AlarmTick();assert(AudioSystem::plays==1);f.alarm.StopAlarm();f.clean();
}
{
Fixture f;RplSession::mode=RplMode::Dedicated;f.start();assert(f.alarm.AlarmActive()&&!f.alarm.AlarmPlaying()&&AudioSystem::plays==0);
f.alarm.AlarmTick();f.alarm.StopAlarm();f.clean();assert(AudioSystem::plays==0&&AudioSystem::ended.empty());
}
for(int variant=0;variant<4;variant++){
Fixture f;
if(variant==0)currentGame=nullptr;
if(variant==1)f.game.world=nullptr;
if(variant==2)SCR_Global::edit=true;
if(variant==3)f.mode.phase=2;
f.start();assert(f.alarm.m_AlarmFinished&&AudioSystem::plays==0&&f.alarm.m_AlarmState==2);
currentGame=&f.game;f.clean();
}
{
Fixture f;f.start();currentGame=nullptr;f.alarm.AlarmTick();
assert(f.alarm.m_AlarmFinished&&AudioSystem::ended.size()==1&&f.mode.stopped.subscribers==std::set<int>{FOREIGN_CALLBACK});
currentGame=&f.game;f.alarm.CloseAlarm();f.clean();
}
}
''')
print('PASS: production vehicle alarm single trigger/replica completion/no replay, fixed deadline/readiness wait, failed audio, dedicated server, moving exact handle, destruction/native end/deletion/clock and mission teardown. Native sound and multiplayer delivery require engine qualification.')