Files

156 lines
8.2 KiB
Python

"""Execute the production drone scheduling policy and validate integration boundaries."""
from policy_harness import ROOT, body, run
import re
path = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_DroneRecon.c'
schedule = body(path, 'class ERR_DroneSchedule').replace('class ERR_DroneSchedule', 'struct ERR_DroneSchedule') + ';'
run('#include <cassert>\n' + schedule + r'''
int main() {
ERR_DroneSchedule s{};
assert(!s.Due(0,0,0));
assert(s.Due(0,0,1));
assert(!s.Due(0,1,1));
assert(!s.Fresh(-1,0)); assert(!s.Fresh(1,0));
assert(s.Fresh(0,60000)); assert(!s.Fresh(0,60001));
s.Attempt(1000,2000,300);
assert(!s.Fresh(1000,3000)); assert(!s.Fresh(999,3000));
assert(s.Fresh(1001,3000));
assert(!s.Due(301999,0,1)); assert(s.Due(302000,0,1));
s.Result(false); s.Result(false); assert(s.Due(302000,0,1));
s.Result(false); assert(!s.Due(302000,0,1));
ERR_DroneSchedule limit{};
for(int i=0;i<64;i++) { limit.Launched++; limit.Result(true); }
assert(!limit.Due(9999999,0,3));
ERR_DroneSchedule stopped{}; stopped.Stopped=true;
assert(!stopped.Due(9999999,0,3));
}
''')
# Foreign types must remain inside the optional dynamically compiled adapter.
bridge = (path.parent / 'ERR_DroneBridge.c').read_text()
without_strings = re.sub(r'"(?:[^"\\]|\\.)*"', '""', bridge)
assert 'SAL_' not in without_strings
assert '65AD60E204191D37' not in (ROOT/'addons/core/addon.gproj').read_text()
assert 'name.EndsWith("/djibase.et")' in bridge
assert 'ERR_DroneState(e)!=1' in bridge
assert 'c.m_iOwner=old' in bridge
assert 'SetOrigin(' not in bridge and 'SetVelocity(' not in bridge
s = path.read_text()
assert 'report.DroneObservation' in s
assert 'm_Schedule.Fresh(report.ObservedAt, now)' in s
assert 'report.Position' in s and 'report.Target.GetOrigin' not in s
assert 'TraceFlags.ENTS | TraceFlags.WORLD' in s
assert 'CameraCone(Drone.GetOrigin(), Search, point)' in s
assert 'QueueDroneObservation(Group, report, now)' in s
assert 'GetCallqueue().Remove(Advance)' in s
hq = (path.parent/'ERR_HQ.c').read_text()
queue = body(path.parent/'ERR_HQ.c',' bool QueueDroneObservation')
assert 'ObservationRoute(unit)' in queue and 'unit.PendingReport = report.Snapshot()' in queue
assert 'Accept(' not in queue and 'm_Memory' not in queue
assert 'm_DroneRecon.Stop()' in hq
print('PASS: drone cooldown, capacity, freshness, replay, bounded retries, round cap, stop, optional API and radio boundaries')
rules = '\n'.join(body(path, signature) for signature in ('static vector Controls(', 'static bool CameraCone('))
rules = rules.replace('Math.', 'Math::').replace('vector.', 'vector::')
run(r'''
#include <cassert>
#include <cmath>
#include <algorithm>
struct vector {
float x=0,y=0,z=0;
float operator[](int i)const{return i==0?x:(i==1?y:z);}
vector operator-(vector v)const{return{x-v.x,y-v.y,z-v.z};}
float Length()const{return std::sqrt(x*x+y*y+z*z);}
vector Normalized()const{float n=Length();if(n==0)return{};return{x/n,y/n,z/n};}
static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}
};
vector Vector(float x,float y,float z){return{x,y,z};}
namespace Math {float Clamp(float x,float a,float b){return std::clamp(x,a,b);}}
''' + rules + r'''
int main() {
assert(CameraCone({0,70,0},{0,0,0},{0,0,0}));
assert(CameraCone({0,70,0},{0,0,0},{30,0,0}));
assert(!CameraCone({0,70,0},{0,0,0},{50,0,0}));
assert(!CameraCone({0,70,0},{0,0,0},{0,80,0}));
assert(!CameraCone({0,400,0},{0,0,0},{0,0,0}));
assert(!CameraCone({0,1,0},{0,0,0},{0,0,0}));
auto hover=Controls({0,0,0},{0,0,0},{0,0,1},{1,0,0});
assert(hover.x==.49f && hover.y==0 && hover.z==0);
assert(Controls({0,5,0},{0,0,0},{0,0,1},{1,0,0}).x<hover.x);
assert(Controls({0,-5,0},{0,0,0},{0,0,1},{1,0,0}).x>hover.x);
assert(Controls({0,0,0},{0,0,8},{0,0,1},{1,0,0}).y<0);
assert(Controls({0,0,0},{8,0,0},{0,0,1},{1,0,0}).z<0);
for(float speed:{-1000.f,1000.f}) {
auto c=Controls({speed,speed,speed},{0,0,0},{0,0,1},{1,0,0});
assert(c.x>=.15f && c.x<=.85f && std::abs(c.y)<=.5f && std::abs(c.z)<=.5f);
}
}
''')
print('PASS: production camera cone/range rejects behind-camera and distant actors; control directions, damping and limits')
# Execute the actual enqueue method; the route boundary is substituted, so these
# checks prove rejection/snapshot semantics rather than native radio reception.
queue = queue.replace('foreach (ERR_HQUnit unit : m_Units)', 'for (ERR_HQUnit* unit : m_Units)')
for name in ('SCR_AIGroup', 'ERR_HQReport', 'ERR_HQRoute'):
queue = re.sub(r'\b'+name+r' (?=\w)', name+'* ', queue)
for name in ('unit','report','route'):
queue = queue.replace(name+'.', name+'->')
for name in ('Replication','ERR_Actors','ERR_Phase'):
queue = queue.replace(name+'.', name+'::')
run(r'''
#include <cassert>
#include <string>
#include <vector>
struct SCR_AIGroup {};
struct Actor { bool conscious=true; };
namespace Replication { bool server=true; bool IsServer(){return server;} }
namespace ERR_Actors {bool CanFight(Actor* a){return a&&a->conscious;}}
namespace ERR_Phase {constexpr int RADIO_SEARCH=2;}
struct ERR_HQReport {
bool DroneObservation=true;int MessageId=0,Position=19;SCR_AIGroup* Source=nullptr;
ERR_HQReport* Snapshot(){return new ERR_HQReport(*this);}
std::string Description(float){return "camera";}
};
struct ERR_HQRoute {int MessageId=0;std::string Payload;};
struct ERR_HQUnit {SCR_AIGroup* Group=nullptr;Actor* Radio=nullptr;ERR_HQRoute* Uplink=nullptr;ERR_HQReport* PendingReport=nullptr;};
struct HQ {
struct Mode {int phase=2;int Phase(){return phase;}} m_Mode;
struct Journal {int messages=0,writes=0;int Message(){return ++messages;}void Write(std::string,std::string,int){writes++;}} m_Journal;
std::vector<ERR_HQUnit*> m_Units;ERR_HQRoute* available=nullptr;int routeCalls=0;
ERR_HQRoute* ObservationRoute(ERR_HQUnit*){routeCalls++;return available;}
''' + queue + r'''
};
int main(){
HQ h;SCR_AIGroup group,other;Actor radio;ERR_HQUnit unit;ERR_HQReport report;ERR_HQRoute route;
unit.Group=&group;unit.Radio=&radio;h.m_Units.push_back(&unit);h.available=&route;
Replication::server=false;assert(!h.QueueDroneObservation(&group,&report,1000));Replication::server=true;
h.m_Mode.phase=1;assert(!h.QueueDroneObservation(&group,&report,1000));h.m_Mode.phase=2;
assert(!h.QueueDroneObservation(nullptr,&report,1000));assert(!h.QueueDroneObservation(&group,nullptr,1000));
report.DroneObservation=false;assert(!h.QueueDroneObservation(&group,&report,1000));report.DroneObservation=true;
assert(!h.QueueDroneObservation(&other,&report,1000));
radio.conscious=false;assert(!h.QueueDroneObservation(&group,&report,1000));radio.conscious=true;
unit.Radio=nullptr;assert(!h.QueueDroneObservation(&group,&report,1000));unit.Radio=&radio;
unit.Uplink=&route;assert(!h.QueueDroneObservation(&group,&report,1000));unit.Uplink=nullptr;
assert(h.routeCalls==0&&h.m_Journal.messages==0);
h.available=nullptr;assert(!h.QueueDroneObservation(&group,&report,1000));assert(!unit.PendingReport&&!unit.Uplink);
h.available=&route;assert(h.QueueDroneObservation(&group,&report,1000));
assert(unit.PendingReport!=&report&&unit.PendingReport->DroneObservation&&unit.PendingReport->Source==&group);
assert(unit.Uplink==&route&&route.MessageId==report.MessageId&&h.m_Journal.writes==1);
report.Position=77;assert(unit.PendingReport->Position==19);
assert(!h.QueueDroneObservation(&group,&report,2000));assert(h.m_Journal.messages==1);
delete unit.PendingReport;
}
''')
print('PASS: production drone radio queue rejects wrong phase/authority/group, busy or unconscious radios and absent routes; snapshots only eligible observations')
# Native traces can hit the operator's helmet instead of the character root.
hit = body(path, 'static bool HitsTarget(').replace('IEntity ', 'Entity* ').replace('null', 'nullptr').replace('hit.GetParent()', 'hit->GetParent()')
run('#include <cassert>\nstruct Entity {Entity* parent=nullptr;Entity* GetParent(){return parent;}};\n' + hit + r'''
int main(){
Entity actor,helmet{&actor},attachment{&helmet},wall,other,foreignHelmet{&other};
assert(HitsTarget(&actor,&actor)&&HitsTarget(&helmet,&actor)&&HitsTarget(&attachment,&actor));
assert(!HitsTarget(&wall,&actor)&&!HitsTarget(&foreignHelmet,&actor));
assert(!HitsTarget(nullptr,&actor)&&!HitsTarget(&wall,nullptr));
Entity cycle;cycle.parent=&cycle;assert(!HitsTarget(&cycle,&actor));
}
''')
print('PASS: target equipment counts as character contact; unrelated blockers and cyclic hierarchies stay blocked')