Files
EscapistsReintroduced/tests/test_native_alarm_hearing.py
T
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

194 lines
14 KiB
Python

"""Actual WB fixture lifecycle; native danger/radio delivery is deliberately doubled."""
import re
from policy_harness import ROOT,body,run
P=ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeAlarmHearingProbe.c'
s=P.read_text()
assert s.startswith('#ifdef WORKBENCH') and s.rstrip().endswith('#endif')
for forbidden in ('RequestBroadcastDangerEvent(', '.HearAlarm(', '.Hear(', '.Ingest(', '.Advance(', '.ERR_BindAwareness(', '.ActivateAI(', '.SetLOD(', '.SetPermanentLOD(', 'ReserveGroup(', '.ClearDangerEvents(', '.Remember('):
assert forbidden not in s,forbidden
assert s.count('ERR_VehicleAlarmPerception.Emit(')==1
queue=body(P,'protected void ObserveQueue(')
assert 'Math.Min(agent.GetDangerEventsCount(), 32)' in queue
assert 'agent.GetControlledEntity() != m_Witness' in queue
assert 'if (!m_Emitted' in queue
assert 'super.Update(utility, timeSlice)' in s
assert 'Diagnostic("before-emission")' in s and 'Diagnostic("cleanup")' in s
assert 'm_Deadline = now + 120000;' in s and s.count('m_Deadline =')==1
assert 'GetControlAIAgent().IsAIActivated()' in s
assert 'm_Unit.Detection == m_Unit.Utility.m_Perception' in s
assert 'm_HQ.ReserveLogistics(unit.Group, TOKEN)' in s
assert 'm_Record = m_Mode.Forces().FindGroup(unit.Group)' in s
assert 'volume.ExcludeArray = excluded' in s
def cpp(v):
v=v.replace('protected ','')
for name in ('ERR_GameMode','ERR_ForceRules','ERR_HQLocalAwareness','ERR_VehicleAlarmPerception','ERR_Actors','ERR_HQRadio','ChimeraCharacter'):
v=v.replace(name+'.',name+'::')
for name in ('hq','m_Mode','m_HQ','m_Unit','m_Witness','m_Observer','m_Vehicle','report','m_Route','m_Queued','delivered','local','pending','character','observerCharacter','volume'):
v=re.sub(r'\b'+name+r'\.',name+'->',v)
for name in ('GetGame()','GetWorld()','LocalAwareness','Players()'):
v=v.replace(name+'.',name+'->')
v=v.replace('m_Unit->Group','m_Unit->group').replace('m_Route->Nodes.Count()', 'm_Route->nodes')
v=v.replace('ChimeraCharacter.Cast(m_Observer).', 'ChimeraCharacter.Cast(m_Observer)->').replace('ChimeraCharacter.Cast(m_Witness).', 'ChimeraCharacter.Cast(m_Witness)->')
v=v.replace('ChimeraCharacter::Cast(m_Observer).', 'ChimeraCharacter::Cast(m_Observer)->').replace('ChimeraCharacter::Cast(m_Witness).', 'ChimeraCharacter::Cast(m_Witness)->')
v=v.replace('TraceBox volume', 'TraceBox* volume').replace('TraceFlags.ENTS', '1').replace('array<IEntity>', 'std::vector<Actor*>').replace('ChimeraCharacter observerCharacter', 'Actor* observerCharacter').replace('CompartmentAccessComponent.', 'CompartmentAccessComponent::')
for literal,value in [('20 0 0','20,0,0'),('0 0 300','0,0,300'),('0 1 0','0,1,0'),('0 0.01 0','0,0.01f,0'),('-0.3 -0.9 -0.3','-0.3f,-0.9f,-0.3f'),('0.3 0.9 0.3','0.3f,0.9f,0.3f')]:
v=v.replace('"'+literal+'"', 'vector{'+value+'}')
v=v.replace('ERR_HQ hq','HQ* hq').replace('ERR_HQReport report','Report* report').replace('ERR_HQReport local','Report* local').replace('ERR_HQReport pending','Report* pending').replace('ERR_HQBelief delivered','BeliefValue* delivered').replace('ChimeraCharacter character','Actor* character')
v=v.replace('vector.Distance(', 'vector::Distance(').replace('LogLevel.ERROR','0')
return v.replace('null','nullptr')
methods='\n'.join(cpp(body(P,sig)) for sig in ('protected bool Check(', 'protected bool Same(', 'protected bool ObserverOriginal(', 'protected bool ClearPoint(', 'protected bool Arrange(', 'int Tick(', 'void Stop('))
run(r'''
#include <cassert>
#include <cmath>
#include <string>
#include <vector>
using string=std::string;
struct vector{float x=0,y=0,z=0;vector operator+(vector other){return {x+other.x,y+other.y,z+other.z};}bool operator==(vector other){return x==other.x&&y==other.y&&z==other.z;}
static float Distance(vector a,vector b){return std::sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));}};
struct Actor{vector position;bool alive=true,aboard=false;int teleports=0;vector restored[4];vector GetOrigin(){return position;}
void GetWorldTransform(vector* out){out[0]={0,0,1};out[1]={0,1,0};out[2]={-1,0,0};out[3]=position;}
void Teleport(vector* transform){teleports++;for(int i=0;i<4;i++)restored[i]=transform[i];position=transform[3];}};
struct ChimeraCharacter{static Actor* Cast(Actor* actor){return actor;}};
struct CompartmentAccessComponent{static Actor* GetVehicleIn(Actor* actor){return actor&&actor->aboard?actor:nullptr;}};
struct ERR_Actors{static vector Ground(vector position){return position;}static bool Alive(Actor* a){return a&&a->alive;}static bool CanFight(Actor* a){return Alive(a);}};
struct ERR_HQRadio{inline static int capability=2;static int Capability(Actor*){return capability;}};
struct ERR_ForceRules{static bool Finite(float value){return std::isfinite(value);}};
struct ERR_HQLocalAwareness{static vector SoundArea(vector p){return {std::floor(p.x/150)*150+75,p.y,std::floor(p.z/150)*150+75};}};
struct Report{void* Source=nullptr;void* Target=nullptr;string TravelMode="heard vehicle alarm";float ObservedAt=-1;vector Position;
bool HasVelocity=false,Distress=false;int SeenEscapists=-1,MessageId=0;Report* Snapshot(){return new Report(*this);}};
struct Cache{Report* local=nullptr;Report* ERR_AlarmFixtureLocal(){return local?local->Snapshot():nullptr;}};
struct Route{bool Failed=false,Sending=false;int Cursor=0,nodes=1,MessageId=0;};
struct Unit{int Id=1;void* group=this;Cache cache;Cache* LocalAwareness=&cache;Report* PendingReport=nullptr;void* PendingStatus=nullptr;Route* Uplink=nullptr;};
struct HQ{bool registered=true;int token=220002,releases=0;
bool ERR_AlarmFixtureRegistered(Unit*){return registered;}int LogisticsToken(void*){return token;}
void ReleaseLogistics(void*,int expected){if(token==expected){token=0;releases++;}}};
struct Roster{Actor* current=nullptr;bool CurrentActor(Actor* actor){return actor&&actor==current;}};
struct ERR_GameMode{Roster roster;Roster* Players(){return &roster;}inline static ERR_GameMode* current=nullptr;bool active=true;static ERR_GameMode* Get(){return current;}bool Active(){return active;}};
struct TraceBox{vector Start,End,Mins,Maxs;int Flags=0;std::vector<Actor*> ExcludeArray;};
struct World{float time=100;int traces=0,blocked=0;float GetWorldTime(){return time;}float TraceMove(TraceBox* volume,void*){traces++;assert(volume->ExcludeArray.size()==2);return traces==blocked?0.f:1.f;}};
struct Game{World world;bool worldAvailable=true;World* GetWorld(){return worldAvailable?&world:nullptr;}};
Game* game=nullptr;Game* GetGame(){return game;}
struct ERR_VehicleAlarmPerception{inline static int emitted=0;static void Emit(Actor*){emitted++;}};
int failures=0;void Print(string,int=1){}
template<class... T>void PrintFormat(string,T...){}
struct BeliefValue{struct Report* Report=nullptr;int MessageId=0;float ObservedAt=0;void* Target=nullptr;};
struct Probe{
static const int TOKEN=220002;ERR_GameMode* m_Mode=nullptr;HQ* m_HQ=nullptr;Unit* m_Unit=nullptr;
Actor* m_Observer=nullptr;bool m_ObserverMoved=false;vector m_ObserverOriginal[4];vector m_Destination;Actor* m_Witness=nullptr;Actor* m_Vehicle=nullptr;vector m_Original[4];vector m_AlarmPosition;
Route* m_Route=nullptr;Report* m_Queued=nullptr;float m_ObservedAt=-1,m_Deadline=120000,m_Wait=0;
int m_State=0;bool m_Moved=false,m_Emitted=false,m_Stopped=false;
inline static Probe* s_Current=nullptr;int diagnostics=0;bool receiverReady=true;void Diagnostic(string){diagnostics++;}bool ReceiverReady(){return receiverReady;}
bool enabled=true,original=true,bound=true,selected=true,arranged=true;BeliefValue* belief=nullptr;int arrangements=0;
bool Enabled(){return enabled;}bool Original(Unit*,Actor*){return original;}bool Bound(){return bound;}
bool Select(){return selected;}BeliefValue* Belief(){return belief;}
'''+methods+r'''
};
struct Fixture{
Game context;ERR_GameMode mode;HQ hq;Unit unit;Actor witness,vehicle,observer;Probe probe;Route route;Report report;BeliefValue belief;
Fixture(){
game=&context;ERR_GameMode::current=&mode;ERR_HQRadio::capability=2;ERR_VehicleAlarmPerception::emitted=0;
observer.position={1000,10,1000};mode.roster.current=&observer;probe.m_Observer=&observer;probe.m_Mode=&mode;probe.m_Unit=&unit;probe.m_Witness=&witness;probe.m_Vehicle=&vehicle;
probe.m_Original[0]={1,0,0};probe.m_Original[1]={0,1,0};probe.m_Original[2]={0,0,1};probe.m_Original[3]={9,8,7};
witness.position={20,0,0};vehicle.position={0,0,0};
}
void emit(){assert(probe.Tick(&hq,100)==0&&probe.m_State==1&&witness.teleports==1&&observer.teleports==1);
context.world.time=1600;assert(probe.Tick(&hq,1599)==0&&ERR_VehicleAlarmPerception::emitted==0);
assert(probe.Tick(&hq,1600)==0&&probe.m_State==2&&ERR_VehicleAlarmPerception::emitted==1);}
void pending(){report.Source=unit.group;report.Position=ERR_HQLocalAwareness::SoundArea({0,0,0});report.ObservedAt=1600;report.MessageId=7;
unit.PendingReport=&report;unit.Uplink=&route;route.MessageId=7;}
};
int main(){
// Both clear volumes are validated before either original body is moved.
for(int blocked:{1,2}){
Fixture f;f.context.world.blocked=blocked;
assert(f.probe.Tick(&f.hq,100)==-1&&f.witness.teleports==0&&f.observer.teleports==0);
}
{
Fixture f;assert(f.probe.Tick(&f.hq,100)==0);
assert(f.observer.position==vector({0,0,300})&&f.witness.position==vector({20,0,0}));
f.probe.receiverReady=false;assert(f.probe.Tick(&f.hq,1600)==0&&ERR_VehicleAlarmPerception::emitted==0);
f.context.world.time=9000;f.probe.receiverReady=true;assert(f.probe.Tick(&f.hq,9000)==0&&ERR_VehicleAlarmPerception::emitted==1&&f.probe.m_ObservedAt==9000);
f.probe.Stop();assert(f.observer.position==vector({1000,10,1000})&&f.observer.teleports==2);
assert(f.observer.restored[0]==vector({0,0,1})&&f.observer.restored[2]==vector({-1,0,0}));
}
// Canonical observer loss never moves its replacement and never prevents
// restoration of the still-owned witness; inverse loss restores observer.
for(int fault=0;fault<3;fault++){
Fixture f;f.emit();Actor replacement;
if(fault==0)f.mode.roster.current=&replacement;
if(fault==1)f.observer.alive=false;
if(fault==2)f.observer.aboard=true;
assert(f.probe.Tick(&f.hq,1800)==-1&&f.observer.teleports==1&&replacement.teleports==0&&f.witness.teleports==2);
}
{
Fixture f;f.emit();f.probe.original=false;f.probe.Stop();
assert(f.witness.teleports==1&&f.observer.teleports==2&&f.observer.position==vector({1000,10,1000}));
}
{
Fixture f;assert(f.probe.Tick(&f.hq,100)==0);f.probe.receiverReady=false;
assert(f.probe.Tick(&f.hq,1600)==0&&!f.probe.m_Stopped&&ERR_VehicleAlarmPerception::emitted==0);
assert(f.probe.Tick(&f.hq,119999)==0&&f.probe.m_Deadline==120000);
assert(f.probe.Tick(&f.hq,120000)==-1&&f.witness.teleports==2&&f.observer.teleports==2);
}
{
Fixture f;f.emit();f.vehicle.position={900,0,0};
assert(f.probe.Tick(&f.hq,1700)==0&&f.probe.m_State==2&&ERR_VehicleAlarmPerception::emitted==1);
// Explicit native/radio stand-ins establish only fixture verification, never
// actual danger routing. The fixture itself cannot manufacture these states.
f.pending();assert(f.probe.Tick(&f.hq,1800)==0&&f.probe.m_State==3);
f.route.Cursor=1;f.belief.Report=&f.report;f.belief.MessageId=7;f.belief.ObservedAt=1600;f.probe.belief=&f.belief;
assert(f.probe.Tick(&f.hq,8000)==1&&f.probe.m_Stopped&&f.hq.releases==1&&f.witness.teleports==2);
assert(f.witness.position==f.probe.m_Original[3]&&f.witness.restored[0]==f.probe.m_Original[0]);
f.probe.Stop();assert(f.witness.teleports==2&&f.hq.releases==1&&ERR_VehicleAlarmPerception::emitted==1);
}
{
Fixture f;f.probe.selected=false;assert(f.probe.Tick(&f.hq,119999)==0);
assert(f.probe.Tick(&f.hq,120000)==-1&&f.probe.m_Stopped&&ERR_VehicleAlarmPerception::emitted==0); // fixed deadline never renewed
}
{
Fixture f;assert(f.probe.Tick(&f.hq,100)==0);f.unit.PendingStatus=&f.unit;
assert(f.probe.Tick(&f.hq,1600)==0&&f.probe.m_State==1&&ERR_VehicleAlarmPerception::emitted==0);
assert(f.probe.Tick(&f.hq,120000)==-1&&f.witness.teleports==2);
}
for(int fault=0;fault<7;fault++){
Fixture f;f.emit();f.pending();
if(fault==0)f.report.ObservedAt=1800;
if(fault==1)f.report.Target=&f.vehicle;
if(fault==2)f.report.Position={0,0,0};
if(fault==3)f.report.Source=&f.vehicle;
if(fault==4)f.report.HasVelocity=true;
if(fault==5)f.report.SeenEscapists=1;
if(fault==6)f.report.Distress=true;
assert(f.probe.Tick(&f.hq,1800)==-1&&f.probe.m_Stopped&&f.witness.teleports==2);
}
for(int fault=0;fault<4;fault++){
Fixture f;f.emit();f.pending();assert(f.probe.Tick(&f.hq,1800)==0);
f.route.Cursor=1;f.belief.Report=&f.report;f.belief.MessageId=7;f.belief.ObservedAt=1600;f.probe.belief=&f.belief;
if(fault==0)f.route.Cursor=0;
if(fault==1)f.route.Sending=true;
if(fault==2)f.belief.MessageId=8;
if(fault==3)f.belief.ObservedAt=1800;
assert(f.probe.Tick(&f.hq,8000)==-1&&f.probe.m_Stopped);
}
{
Fixture f;f.emit();f.pending();assert(f.probe.Tick(&f.hq,1800)==0);f.route.Failed=true;
assert(f.probe.Tick(&f.hq,1801)==-1&&f.witness.teleports==2);
}
// Loss of original actor/registry/reservation/mode must never move a foreign
// replacement body or steal its reservation during cleanup.
for(int fault=0;fault<4;fault++){
Fixture f;f.emit();ERR_GameMode replacement;
if(fault==0)f.probe.original=false;
if(fault==1)f.hq.registered=false;
if(fault==2)f.hq.token=99;
if(fault==3)ERR_GameMode::current=&replacement;
f.probe.Stop();assert(f.witness.teleports==1);if(fault==2)assert(f.hq.token==99);
}
{
Fixture f;f.emit();f.probe.bound=false;assert(f.probe.Tick(&f.hq,1800)==-1&&f.probe.m_Stopped);
}
}
''')
print('PASS: actual arranged native alarm fixture deadline, single emit, natural-route observation, original timestamp/area/detail assertions, natural observer proximity/activation wait, failure cleanup and independent canonical-body restoration; native delivery itself remains unqualified.')