Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
177 lines
11 KiB
Python
177 lines
11 KiB
Python
"""Run actual alarm emitter/native receiver methods against explicit native doubles.
|
|
|
|
Does not establish engine danger broadcast routing, attenuation, or event subclass
|
|
preservation; those remain native qualification requirements.
|
|
"""
|
|
import re
|
|
from policy_harness import ROOT, body, run
|
|
base = ROOT / 'addons/core/Scripts/Game/ERR'
|
|
source = base / '10_AI/ERR_VehicleAlarmPerception.c'
|
|
awareness = base / '10_AI/ERR_HQLocalAwareness.c'
|
|
|
|
|
|
def cpp(s):
|
|
s = s.replace('override ', '').replace('static void ', 'void ')
|
|
s = s.replace('SCR_AIGroupUtilityComponent.Cast(group.FindComponent(SCR_AIGroupUtilityComponent))', 'group.utility')
|
|
for old, new in [('Replication.', 'Replication::'), ('ERR_Actors.', 'ERR_Actors::'),
|
|
('ERR_GameMode.', 'Mode::'), ('ERR_HQLocalAwareness.', 'ERR_HQLocalAwareness::'),
|
|
('ERR_HQMemoryRules.', 'ERR_HQMemoryRules::'), ('ERR_Phase.', 'ERR_Phase::'),
|
|
('AIAgent.Cast', 'Agent::Cast'), ('SCR_AIGroup.Cast', 'Group::Cast'),
|
|
('SCR_FactionManager.', 'SCR_FactionManager::'), ('Math.', 'Math::'),
|
|
('EAIDangerEventType.', 'EAIDangerEventType::'), ('vector.', 'vector::')]:
|
|
s = s.replace(old, new)
|
|
s = s.replace('ERR_VehicleAlarmDangerEvent.Cast', 'Event::Cast')
|
|
s = s.replace('ERR_VehicleAlarmDangerEvent danger', 'Event* danger')
|
|
s = s.replace('new ERR_VehicleAlarmDangerEvent()', 'new Event()')
|
|
for name in ('vehicle', 'mode', 'utility', 'agent', 'witness', 'group', 'groupUtility', 'danger', 'm_ERRAwareness', 'originMode'):
|
|
s = re.sub(r'\b' + name + r'\.', name + '->', s)
|
|
for name in ('GetGame()', 'GetWorld()', 'GetAIWorld()', 'm_ConfigComponent', 'm_Perception'):
|
|
s = s.replace(name + '.', name + '->')
|
|
s = s.replace('ERR_VehicleAlarmPerception.Receive(utility);', 'perception.Receive(utility);')
|
|
return s.replace('super.Update', 'Base::Update')
|
|
|
|
methods = '\n'.join(cpp(body(source, signature)) for signature in ('static void Emit(', 'static void Receive('))
|
|
callback = cpp(body(awareness, 'void ERR_HearAlarm('))
|
|
hook = cpp(body(source, 'override void Update('))
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <vector>
|
|
struct vector {float x=0,y=0,z=0;
|
|
static float DistanceSq(vector a,vector b){float x=a.x-b.x,y=a.y-b.y,z=a.z-b.z;return x*x+y*y+z*z;}};
|
|
struct Actor;struct Mode;struct Group;struct Agent;struct Utility;struct GroupUtility;
|
|
using IEntity=Actor*;using ERR_GameMode=Mode*;using SCR_AIGroup=Group*;using AIAgent=Agent*;
|
|
using SCR_AIUtilityComponent=Utility*;using SCR_AIGroupUtilityComponent=GroupUtility*;
|
|
struct Faction{};
|
|
struct Actor {vector position;bool alive=true,player=false,awake=true;Group* group=nullptr;Faction* faction=nullptr;vector GetOrigin(){return position;}};
|
|
struct ERR_Phase{static const int PRISON=1;};
|
|
struct Mode {inline static Mode* current=nullptr;bool active=true;int phase=2;Faction* enemy=nullptr;
|
|
static Mode* Get(){return current;}bool Active(){return active;}int Phase(){return phase;}Faction* EnemyFaction(){return enemy;}};
|
|
struct EAIDangerEventType {static const int Danger_VehicleHorn=14,Other=0;};
|
|
struct Danger {virtual ~Danger()=default;int type=0;Actor* victim=nullptr;vector position;
|
|
int GetDangerType(){return type;}void SetDangerType(int value){type=value;}void SetVictim(Actor* value){victim=value;}
|
|
vector GetPosition(){return position;}void SetPosition(vector value){position=value;}};
|
|
struct Event:Danger {ERR_GameMode Mode=nullptr;float ObservedAt=-1;static Event* Cast(Danger* value){return dynamic_cast<Event*>(value);}};
|
|
struct World{float now=100;float GetWorldTime(){return now;}};
|
|
struct AIWorld{std::vector<Danger*> broadcast;void RequestBroadcastDangerEvent(Danger* event){broadcast.push_back(event);}};
|
|
struct Game{World* world=nullptr;AIWorld* ai=nullptr;World* GetWorld(){return world;}AIWorld* GetAIWorld(){return ai;}};
|
|
Game* game=nullptr;Game* GetGame(){return game;}
|
|
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
|
|
struct ERR_Actors{static bool Alive(Actor* actor){return actor&&actor->alive;}static bool CanFight(Actor* actor){return Alive(actor)&&actor->awake;}
|
|
static bool Player(Actor* actor){return actor&&actor->player;}static struct Group* Group(Actor* actor){return actor?actor->group:nullptr;}};
|
|
struct SCR_FactionManager{static Faction* SGetFaction(Actor* actor){return actor?actor->faction:nullptr;}};
|
|
struct ERR_HQLocalAwareness{
|
|
int heard=0;SCR_AIGroup source=nullptr;vector position;float observed=-1,received=-1;
|
|
static bool Fresh(float at,float now){return std::isfinite(at)&&std::isfinite(now)&&at>=0&&now>=at&&now-at<=30000;}
|
|
void HearAlarm(SCR_AIGroup who,vector where,float at,float now){heard++;source=who;position=where;observed=at;received=now;}
|
|
};
|
|
struct ERR_HQMemoryRules{static bool PositionValid(vector p){return std::isfinite(p.x)&&std::isfinite(p.y)&&std::isfinite(p.z);}};
|
|
struct Perception{ERR_HQLocalAwareness* m_ERRAwareness=nullptr;SCR_AIGroup m_Group=nullptr;bool enabled=true;
|
|
bool ERR_AwarenessActive(){return enabled&&m_ERRAwareness;}
|
|
''' + callback + r'''
|
|
};
|
|
struct GroupUtility{Perception* m_Perception=nullptr;};
|
|
struct Group{bool slave=false;int players=0;Faction* faction=nullptr;GroupUtility* utility=nullptr;
|
|
static Group* Cast(Group* g){return g;}bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}Faction* GetFaction(){return faction;}};
|
|
struct Agent{Actor* actor=nullptr;Group* group=nullptr;std::vector<Danger*> events;int reads=0;
|
|
static Agent* Cast(Agent* a){return a;}Actor* GetControlledEntity(){return actor;}Group* GetParentGroup(){return group;}
|
|
int GetDangerEventsCount(){return events.size();}Danger* GetDangerEvent(int i,int& count){reads++;count=1;return events.at(i);}};
|
|
struct Config{bool m_EnableDangerEvents=true;};
|
|
struct Utility{Config* m_ConfigComponent=nullptr;Agent* owner=nullptr;Agent* GetOwner(){return owner;}};
|
|
struct Math{static int Min(int a,int b){return std::min(a,b);}};
|
|
struct PerceptionDriver{
|
|
''' + methods + r'''
|
|
};
|
|
struct Base{int calls=0;void Update(Utility* utility,float){calls++;if(utility&&utility->owner)utility->owner->events.clear();}};
|
|
struct Threat:Base{PerceptionDriver perception;
|
|
''' + hook + r'''
|
|
};
|
|
struct Fixture{
|
|
World world;AIWorld ai;Game context;Mode mode;Faction enemy,other;Actor vehicle,witness;
|
|
Group group;GroupUtility groupUtility;Perception perception;ERR_HQLocalAwareness awareness;Agent agent;Utility utility;Config config;PerceptionDriver driver;
|
|
Fixture(){
|
|
game=&context;context.world=&world;context.ai=&ai;Mode::current=&mode;mode.enemy=&enemy;Replication::server=true;
|
|
group.faction=&enemy;group.utility=&groupUtility;groupUtility.m_Perception=&perception;
|
|
perception.m_ERRAwareness=&awareness;perception.m_Group=&group;
|
|
witness.group=&group;witness.faction=&enemy;agent.actor=&witness;agent.group=&group;utility.owner=&agent;utility.m_ConfigComponent=&config;
|
|
vehicle.position={20,0,0};
|
|
}
|
|
Event* emit(){driver.Emit(&vehicle);assert(ai.broadcast.size()==1);return Event::Cast(ai.broadcast[0]);}
|
|
};
|
|
int main(){
|
|
{
|
|
Fixture f;Event* event=f.emit();assert(event&&event->type==EAIDangerEventType::Danger_VehicleHorn&&event->victim==&f.vehicle&&event->Mode==&f.mode);
|
|
assert(event->ObservedAt==100&&event->position.x==20);
|
|
// A broadcast alone creates no observer or HQ evidence. Native queue delivery
|
|
// is supplied explicitly by this double; no engine-routing claim is made.
|
|
f.driver.Receive(&f.utility);assert(f.awareness.heard==0);
|
|
f.vehicle.position={900,0,0};f.world.now=200;f.agent.events.push_back(event);Threat threat;threat.Update(&f.utility,100);
|
|
assert(f.awareness.heard==1&&f.awareness.source==&f.group&&f.awareness.position.x==20&&f.awareness.observed==100&&f.awareness.received==200);
|
|
assert(threat.calls==1&&f.agent.events.empty()); // read before native superclass clears
|
|
threat.Update(&f.utility,100);assert(f.awareness.heard==1&&threat.calls==2);
|
|
}
|
|
for(int variant=0;variant<10;variant++){
|
|
Fixture f;
|
|
if(variant==0)Replication::server=false;
|
|
if(variant==1)game=nullptr;
|
|
if(variant==2)f.context.world=nullptr;
|
|
if(variant==3)f.context.ai=nullptr;
|
|
if(variant==4)f.vehicle.alive=false;
|
|
if(variant==5)Mode::current=nullptr;
|
|
if(variant==6)f.mode.active=false;
|
|
if(variant==7)f.world.now=-1;
|
|
if(variant==8)f.world.now=std::numeric_limits<float>::quiet_NaN();
|
|
if(variant==9)f.vehicle.position.x=std::numeric_limits<float>::quiet_NaN();
|
|
f.driver.Emit(&f.vehicle);assert(f.ai.broadcast.empty());
|
|
}
|
|
for(int variant=0;variant<23;variant++){
|
|
Fixture f;Event* event=f.emit();f.agent.events.push_back(event);Mode otherMode;Group otherGroup;
|
|
if(variant==0)Replication::server=false;
|
|
if(variant==1)f.utility.m_ConfigComponent=nullptr;
|
|
if(variant==2)f.config.m_EnableDangerEvents=false;
|
|
if(variant==3)f.context.world=nullptr;
|
|
if(variant==4)Mode::current=nullptr;
|
|
if(variant==5)f.mode.active=false;
|
|
if(variant==6)f.mode.phase=ERR_Phase::PRISON;
|
|
if(variant==7)f.utility.owner=nullptr;
|
|
if(variant==8)f.witness.alive=false;
|
|
if(variant==9)f.witness.awake=false;
|
|
if(variant==10)f.witness.player=true;
|
|
if(variant==11)f.witness.group=&otherGroup;
|
|
if(variant==12)f.group.slave=true;
|
|
if(variant==13)f.group.players=1;
|
|
if(variant==14)f.group.faction=&f.other;
|
|
if(variant==15)f.witness.faction=&f.other;
|
|
if(variant==16)f.group.utility=nullptr;
|
|
if(variant==17)f.groupUtility.m_Perception=nullptr;
|
|
if(variant==18)event->Mode=&otherMode;
|
|
if(variant==19)event->type=EAIDangerEventType::Other;
|
|
if(variant==20)f.world.now=30101;
|
|
if(variant==21)f.world.now=99;
|
|
if(variant==22)f.witness.position={70.01f,0,0};
|
|
f.driver.Receive(&f.utility);assert(f.awareness.heard==0);
|
|
}
|
|
{
|
|
Fixture f;Event* event=f.emit();Danger ordinary;ordinary.type=EAIDangerEventType::Danger_VehicleHorn;
|
|
f.agent.events={nullptr,&ordinary,event};f.perception.enabled=false;f.driver.Receive(&f.utility);assert(f.awareness.heard==0);
|
|
f.perception.enabled=true;f.witness.position={70,0,0};f.world.now=30100;f.driver.Receive(&f.utility);
|
|
assert(f.awareness.heard==1&&f.awareness.observed==100); // inclusive local range/freshness boundary
|
|
}
|
|
{
|
|
Fixture f;Event* event=f.emit();Danger ordinary;
|
|
f.agent.events.assign(32,&ordinary);f.agent.events.push_back(event);f.driver.Receive(&f.utility);
|
|
assert(f.agent.reads==32&&f.awareness.heard==0&&f.agent.events.size()==33);
|
|
f.agent.events[31]=event;f.driver.Receive(&f.utility);assert(f.agent.reads==64&&f.awareness.heard==1);
|
|
}
|
|
{
|
|
Fixture f;Event* event=f.emit();f.agent.events={event};
|
|
event->position.x=std::numeric_limits<float>::quiet_NaN();f.driver.Receive(&f.utility);assert(f.awareness.heard==0);
|
|
event->position={20,0,0};f.witness.position.x=std::numeric_limits<float>::quiet_NaN();f.driver.Receive(&f.utility);assert(f.awareness.heard==0);
|
|
f.witness.position={0,0,0};f.perception.m_ERRAwareness=nullptr;f.driver.Receive(&f.utility);assert(f.awareness.heard==0);
|
|
}
|
|
}
|
|
''')
|
|
print('PASS: production alarm emitter/receiver original time/location, explicit native queue only, 32-entry cap, 50m local witness eligibility, bound perception, native-super preservation; actual engine broadcast/hearing remains unverified.')
|