Add typed field-flight prototype and preserve native qualification failures

This commit is contained in:
2026-09-19 21:01:20 +02:00
parent 83ea247e62
commit 61e165ef5b
91 changed files with 40570 additions and 85 deletions
+146
View File
@@ -0,0 +1,146 @@
"""Execute bounded whole-field survey with fake native terrain/traces, never fly."""
from policy_harness import ROOT, body, run
import re
P=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFieldEnvelope.c'
RULES=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFieldRules.c'
def cpp(s):
for name in ('Math','vector','ERR_AircraftFieldRules','SCR_TerrainHelper','TraceFlags'):
s=s.replace(name+'.',name+'::')
s=s.replace('array<IEntity> excluded = null','void* excluded = nullptr')
s=s.replace('TraceBox corridor','TraceBox* corridor').replace('TraceBox low','TraceBox* low')
for name in ('corridor','low'):s=s.replace(name+'.',name+'->')
s=s.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->').replace(', null)', ', nullptr)')
return re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])', r'\1f', s)
run(r'''
#include <cassert>
#include <cmath>
#include <algorithm>
#include <vector>
#include <cstdio>
struct Math {static float Clamp(float v,float lo,float hi){return std::clamp(v,lo,hi);}static float Min(float a,float b){return std::min(a,b);}static float Max(float a,float b){return std::max(a,b);}static int Ceil(float x){return std::ceil(x);}static float AbsFloat(float x){return std::abs(x);}};
struct vector {float v[3]={};vector(){}vector(float x,float y,float z){v[0]=x;v[1]=y;v[2]=z;}vector(const char*s){sscanf(s,"%f %f %f",v,v+1,v+2);}float&operator[](int i){return v[i];}float operator[](int i)const{return v[i];}vector operator+(vector b)const{return {v[0]+b[0],v[1]+b[1],v[2]+b[2]};}vector operator-(vector b)const{return {v[0]-b[0],v[1]-b[1],v[2]-b[2]};}vector operator*(float n)const{return {v[0]*n,v[1]*n,v[2]*n};}static float Dot(vector a,vector b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];}static float DistanceXZ(vector a,vector b){return std::hypot(a[0]-b[0],a[2]-b[2]);}};
vector Vector(float x,float y,float z){return {x,y,z};}
struct ERR_AircraftFieldRules {
'''+cpp(body(RULES,'static bool Length('))+r'''
};
namespace TraceFlags {enum {ENTS=1,WORLD=2};}
struct TraceBox {vector Start,End,Mins,Maxs;int Flags=0;void* ExcludeArray=nullptr;};
struct World {int calls=0,refuseAt=0;std::vector<TraceBox> seen;float TraceMove(TraceBox* t,void*){calls++;seen.push_back(*t);delete t;return calls==refuseAt?.5:1;}} world;
struct Game {bool hasWorld=true;World* GetWorld(){return hasWorld?&world:nullptr;}} game;
bool hasGame=true;Game* GetGame(){return hasGame?&game:nullptr;}
struct SCR_TerrainHelper {
static int samples,kind;static std::vector<vector> points;
static float GetTerrainY(vector p){samples++;points.push_back(p);if(kind==1)return 10.31f;if(kind==2&&samples==2000)return 9.69f;if(kind==3)return NAN;return 10;}
};
int SCR_TerrainHelper::samples=0;int SCR_TerrainHelper::kind=0;std::vector<vector> SCR_TerrainHelper::points;
static const int MAX_TERRAIN_NODES=4096;
'''+ '\n'.join(cpp(body(P,key)) for key in ('static bool FinitePoint(', 'static int TerrainColumns(', 'static bool TerrainBudget(', 'static bool Survey('))+r'''
void reset(){world=World();SCR_TerrainHelper::samples=0;SCR_TerrainHelper::kind=0;SCR_TerrainHelper::points.clear();hasGame=true;game.hasWorld=true;}
int main(){
assert(TerrainColumns(100)==90);assert(TerrainColumns(100.05f)==91);
assert(TerrainColumns(99.94f)==0&&TerrainColumns(100.06f)==0&&TerrainColumns(NAN)==0);
assert(TerrainBudget(90,40)&&TerrainBudget(91,40)&&TerrainBudget(63,63));
assert(!TerrainBudget(64,63)&&!TerrainBudget(0,40)&&!TerrainBudget(4096,1)&&!TerrainBudget(-1,40));
assert(!FinitePoint({NAN,10,0})&&!FinitePoint({0,INFINITY,0})&&!FinitePoint({0,10,1000001}));
for(float angle: {0.f,.4f,1.2f,2.8f}){
reset();vector a={100,10,200},u={std::cos(angle),0,std::sin(angle)},v={-u[2],0,u[0]},b=a+u*100;
int excluded=7;assert(Survey(a,b,&excluded));
int columns=TerrainColumns(vector::DistanceXZ(a,b));
assert(SCR_TerrainHelper::samples==(columns+1)*41&&SCR_TerrainHelper::samples<=4096&&world.calls==2);
auto high=world.seen[0],low=world.seen[1];assert(high.Flags==(TraceFlags::ENTS|TraceFlags::WORLD)&&low.Flags==TraceFlags::ENTS);
assert(high.ExcludeArray==&excluded&&low.ExcludeArray==&excluded);
assert(high.Start[1]+high.Mins[1]==12&&high.End[1]+high.Maxs[1]==71);
assert(std::abs(low.Start[1]+low.Mins[1]-10.3f)<.0001);
// Assert the real terrain-query coordinates, including both endpoint caps.
float minimumAlong=1e6,maximumAlong=-1e6,minimumAcross=1e6,maximumAcross=-1e6;
for(auto p:SCR_TerrainHelper::points){float along=vector::Dot(p-a,u),across=vector::Dot(p-a,v);minimumAlong=std::min(minimumAlong,along);maximumAlong=std::max(maximumAlong,along);minimumAcross=std::min(minimumAcross,across);maximumAcross=std::max(maximumAcross,across);}
assert(std::abs(minimumAlong+40)<.001&&std::abs(maximumAlong-140)<.001&&std::abs(minimumAcross+40)<.001&&std::abs(maximumAcross-40)<.001);
for(size_t i=0;i<SCR_TerrainHelper::points.size();i++){
if(i%41)assert(vector::DistanceXZ(SCR_TerrainHelper::points[i],SCR_TerrainHelper::points[i-1])<=2.0001);
if(i>=41)assert(vector::DistanceXZ(SCR_TerrainHelper::points[i],SCR_TerrainHelper::points[i-41])<=2.0001);
}
// Every sampled bearing of the entire40m segment tube fits both traces.
for(int step=0;step<=10;step++)for(int bearing=0;bearing<16;bearing++){
float theta=bearing*6.2831853f/16;vector p=a+u*(step*10)+Vector(std::cos(theta)*40,0,std::sin(theta)*40);
for(int axis:{0,2}){assert(p[axis]>=high.Start[axis]+high.Mins[axis]-.001&&p[axis]<=high.Start[axis]+high.Maxs[axis]+.001);assert(low.Mins[axis]==high.Mins[axis]&&low.Maxs[axis]==high.Maxs[axis]);}
}
}
vector a={0,10,0},b={100,10,0};
for(int fault=1;fault<=3;fault++){reset();SCR_TerrainHelper::kind=fault;assert(!Survey(a,b));assert(world.calls==0&&SCR_TerrainHelper::samples<=4096);if(fault==2)assert(SCR_TerrainHelper::samples==2000);}
for(int trace=1;trace<=2;trace++){reset();world.refuseAt=trace;assert(!Survey(a,b));assert(world.calls==trace&&SCR_TerrainHelper::samples==3731);}
for(vector end:{Vector(99.94f,10,0),Vector(100.06f,10,0),Vector(100,10.31f,0),Vector(NAN,10,0)}){reset();assert(!Survey(a,end));assert(world.calls==0&&SCR_TerrainHelper::samples==0);}
reset();assert(!Survey({0,1,0},{100,1,0}));assert(SCR_TerrainHelper::samples==0);
reset();hasGame=false;assert(!Survey(a,b));assert(SCR_TerrainHelper::samples==0);
reset();game.hasWorld=false;assert(!Survey(a,b));assert(SCR_TerrainHelper::samples==0);
reset();assert(Survey(a,{100.05f,10,0}));assert(SCR_TerrainHelper::samples==3772);
}
''')
source=P.read_text();qualify=body(P,'static ERR_AircraftFieldEnvelope QualifyField(')
assert 'bool Claim(' not in source and 'bool ClaimedBy(' not in source
assert 'ERR_AircraftFlightEnvelope.Qualify(mode, aircraft, pilot)' in qualify
assert qualify.index('ERR_AircraftFlightEnvelope.Qualify(')<qualify.index('Hierarchy(')<qualify.index('ClearColumn(')<qualify.index('Survey(')<qualify.index('new ERR_AircraftFieldEnvelope')
assert 'm_Home = point.Home()' in qualify and 'm_Radius = point.Radius()' in qualify
assert source.count('m_Destination = end')==1
assert 'record.m_QualifiedAt = began;' in qualify and 'now - began <= 2000' in qualify
assert 'vector.Distance(aircraft.GetOrigin(), point.Home()) > 0.1' in qualify
assert 'GetGame().GetWorld() != world' in qualify
assert 'ERR_AircraftFieldRules.Contains(m_Home, m_Destination, position, upright)' in body(P,'bool Contains(')
print('PASS: actual100m whole-field survey sampling/4096cap, rotated coverage, all-node terrain and native trace refusal; original certificate lineage retained; no native qualification')
# Execute the actual certificate issuance body with fake native boundaries.
# Geometry/terrain/trace internals are exercised above, not replaced in that test.
q=cpp(qualify)
q=q.replace('static ERR_AircraftFieldEnvelope QualifyField(', 'static Field* QualifyField(')
q=q.replace('ERR_GameMode mode','Mode* mode').replace('Vehicle aircraft','Entity* aircraft').replace('IEntity pilot','Entity* pilot')
q=q.replace('BaseWorld world','World* world').replace('ERR_AircraftFlightEnvelope point','Base* point')
q=q.replace('ERR_AircraftFlightEnvelope.Qualify','Base::Qualify').replace('ERR_Actors.Ground','Ground')
q=q.replace('array<IEntity> excluded = {}','int excluded = 1')
q=q.replace('ERR_AircraftFieldEnvelope record = new ERR_AircraftFieldEnvelope()','Field* record = new Field()')
q=q.replace('ERR_GameMode.Get()','Mode::Get()')
for name in ('world','point','record','mode','aircraft','pilot'):q=q.replace(name+'.',name+'->')
q=q.replace('return null;', 'return nullptr;')
run(r'''
#include <cassert>
#include <cmath>
#include <algorithm>
#include <vector>
struct vector{float v[3]={};vector(){}vector(float x,float y,float z){v[0]=x;v[1]=y;v[2]=z;}float&operator[](int i){return v[i];}float operator[](int i)const{return v[i];}static float DistanceXZ(vector a,vector b){return std::hypot(a[0]-b[0],a[2]-b[2]);}static float Distance(vector a,vector b){return std::sqrt((a[0]-b[0])*(a[0]-b[0])+(a[1]-b[1])*(a[1]-b[1])+(a[2]-b[2])*(a[2]-b[2]));}};
struct Math{static float AbsFloat(float x){return std::abs(x);}};
struct ERR_AircraftFieldRules{
'''+cpp(body(RULES,'static bool Length('))+r'''
};
struct World{float now=1000;float GetWorldTime(){return now;}} world,foreignWorld;
struct Game{World* current=&world;World* GetWorld(){return current;}} game;
Game* liveGame=&game;Game* GetGame(){return liveGame;}
struct Mode{bool active=true;bool Active(){return active;}static Mode* current;static Mode* Get(){return current;}} mode,foreignMode;Mode* Mode::current=&mode;
struct Entity{World* worldPtr=&world;vector origin={0,10.1f,0};World* GetWorld(){return worldPtr;}vector GetOrigin(){return origin;}} aircraft,pilot;
int fault=0,allocations=0;std::vector<int> order;
vector Ground(vector point){point[1]=10;return point;}
struct Base {
Mode* m_Mode=nullptr;Entity* m_Aircraft=nullptr;Entity* m_Pilot=nullptr;vector m_Home={0,10.1f,0};float m_Radius=28,m_QualifiedAt=-1;
vector Home(){return m_Home;}float Radius(){return m_Radius;}
static Base* Qualify(Mode*,Entity*,Entity*){order.push_back(1);static Base point;return fault==1?nullptr:&point;}
};
struct Field:Base{
vector m_Destination;Field(){allocations++;}
'''+cpp(body(P,'static bool FinitePoint('))+r'''
static bool Hierarchy(Entity* entity,int){order.push_back(entity==&aircraft?2:3);return fault!=(entity==&aircraft?2:3);}
static bool ClearColumn(Entity*,Entity*,vector){order.push_back(4);return fault!=4;}
static bool Survey(vector,vector,int){order.push_back(5);world.now+=500;if(fault==6)world.now=3001;if(fault==7)aircraft.origin[0]=.101f;if(fault==8)game.current=&foreignWorld;if(fault==9)Mode::current=&foreignMode;if(fault==10)mode.active=false;if(fault==11)pilot.worldPtr=&foreignWorld;return fault!=5;}
'''+q+r'''
};
void reset(){fault=0;world.now=1000;game.current=&world;Mode::current=&mode;mode.active=true;aircraft.origin={0,10.1f,0};aircraft.worldPtr=&world;pilot.worldPtr=&world;allocations=0;order.clear();}
int main(){
reset();Field* qualified=Field::QualifyField(&mode,&aircraft,&pilot,{100,999,0});
assert(qualified&&allocations==1&&order==std::vector<int>({1,2,3,4,5}));
assert(qualified->m_Mode==&mode&&qualified->m_Aircraft==&aircraft&&qualified->m_Pilot==&pilot);
assert(qualified->m_QualifiedAt==1000&&world.now==1500); // survey never renews age
assert(qualified->m_Radius==28&&qualified->m_Home[1]==10.1f);
assert(qualified->m_Destination[0]==100&&qualified->m_Destination[1]==10.1f);
delete qualified;
for(int problem=1;problem<=11;problem++){reset();fault=problem;assert(!Field::QualifyField(&mode,&aircraft,&pilot,{100,10,0}));assert(allocations==0);}
reset();assert(!Field::QualifyField(&mode,&aircraft,&pilot,{99,10,0}));assert(order==std::vector<int>({1})&&allocations==0);
reset();assert(!Field::QualifyField(&mode,&aircraft,&pilot,{NAN,10,0}));assert(order.empty()&&allocations==0);
}
''')
print('PASS: actual QualifyField preserves original identity/footprint/time, orders complete surveys before issuance, and refuses failed stages/moved body/expired or changed world; native boundaries faked')
+181
View File
@@ -0,0 +1,181 @@
"""Execute prototype flight methods with real rules; native boundaries are fakes."""
from policy_harness import ROOT, body, run
import re
P=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFieldFlight.c'
B=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFlight.c'
R=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFieldRules.c'
source=P.read_text()
def cpp(s):
s=s.replace('protected ','').replace('override ','').replace('ref ','').replace('super.','Base::')
for old,new in [('ERR_AircraftFlightState.','State::'),('ERR_AircraftFlightRules.','FlightRules::'),('ERR_AircraftFieldRules.','Rules::'),('ERR_AircraftTranslationRules.','TranslationRules::'),('Math.','Math::'),('vector.','vector::')]:s=s.replace(old,new)
s=s.replace('ERR_AircraftFieldEnvelope m_Field;', 'ERR_AircraftFieldEnvelope* m_Field=nullptr;')
s=s.replace('FuelManagerComponent fuel = FuelManagerComponent.Cast(aircraft.FindComponent(FuelManagerComponent));','FuelManagerComponent* fuel = FixtureFuel(aircraft);')
s=s.replace('fuel.', 'fuel->').replace('m_Fuel.', 'm_Fuel->')
s=s.replace('ERR_AircraftFieldEnvelope field','ERR_AircraftFieldEnvelope* field').replace('ERR_AircraftFlightEnvelope qualification','int qualification')
s=s.replace('SignalsManagerComponent signals','SignalsManagerComponent* signals').replace('ActionManager actions','ActionManager* actions')
for name in ('field','m_Field','signals','actions'):s=s.replace(name+'.',name+'->')
s=s.replace('"0 0 0"','vector{}').replace('out vector nextIntegral','vector& nextIntegral')
return s
rules=cpp(R.read_text().replace('class ERR_AircraftFieldRules','struct Rules')).rstrip()+';'
fields=cpp(source[source.index(' protected ref'):source.index(' int FieldPhase()')])
# Explicit defaults emulate Enforce's zero-initialized script fields.
fields=re.sub(r'(bool m_\w+);',r'\1=false;',fields)
fields=re.sub(r'(int m_\w+);',r'\1=0;',fields)
methods='\n'.join(cpp(body(P,k)) for k in (
'protected bool FuelAdmission(', 'bool StartField(', 'override bool Start(', 'override bool StartTranslation(',
'protected bool FieldContains(', 'protected override bool Grounded(',
'protected bool BeginFieldBrake(', 'protected override bool Landing(',
'protected override bool BeginTranslation(', 'protected override bool ControlLease(',
'protected vector FieldDemand(', 'override void PrepareActions(',
'protected override void CheckTranslation(', 'override void Tick('))
run(r'''#include <cassert>
#include <cmath>
#include <algorithm>
#include <string>
#include <initializer_list>
#define PrintFormat(...) ((void)0)
using string=std::string;using ERR_GameMode=int;using Vehicle=int;using SCR_AIGroup=int;using IEntity=int;using PilotCompartmentSlot=int;using CharacterControllerComponent=int;
namespace State {enum {IDLE,HOOKS,STARTING,POSITIVE,NEGATIVE,CLIMB,LANDING,SHUTDOWN,COMPLETE,CANCELLED,FAILED,RELINQUISHED,TRANSLATING};}
struct vector {
float v[3]={0,0,0};vector()=default;vector(float x,float y,float z):v{x,y,z}{}
float& operator[](int i){return v[i];}float operator[](int i)const{return v[i];}
vector operator+(vector b)const{return {v[0]+b[0],v[1]+b[1],v[2]+b[2]};}vector operator-(vector b)const{return {v[0]-b[0],v[1]-b[1],v[2]-b[2]};}vector operator*(float s)const{return {v[0]*s,v[1]*s,v[2]*s};}
float Length()const{return std::sqrt(Dot(*this,*this));}static float Dot(vector a,vector b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];}static float DistanceXZ(vector a,vector b){return std::hypot(a[0]-b[0],a[2]-b[2]);}
};
struct Math {static float AbsFloat(float x){return std::abs(x);}static float Clamp(float x,float lo,float hi){return std::clamp(x,lo,hi);}static float Min(float a,float b){return std::min(a,b);}static float Max(float a,float b){return std::max(a,b);}static float Sqrt(float a){return std::sqrt(a);}};
'''+rules+r'''
struct FlightRules {
'''+''.join(cpp(body(B,k)) for k in ('static bool Hover(', 'static float Vertical(', 'static int CancelAction('))+r'''
};
struct TranslationRules {
'''+cpp(body(B,'static vector Demand('))+r'''
};
struct ERR_AircraftFieldEnvelope {vector home={0,0,0},destination={100,0,0};bool available=true;vector Home(){return home;}vector Destination(){return destination;}bool Contains(vector p,float u){return available&&Rules::Contains(home,destination,p,u);}};
struct SignalsManagerComponent {bool available=true;int FindSignal(const char*){return available?1:-1;}};
struct Controller {bool hover=true,engine=true,enableAllowed=true;int hoverWrites=0;SignalsManagerComponent signals;explicit operator bool()const{return true;}SignalsManagerComponent* GetSignalsManager(){return &signals;}bool GetAutohoverEnabled(){return hover;}void SetAutohoverEnabled(bool enabled){hoverWrites++;if(enableAllowed)hover=enabled;}bool IsEngineOn(){return engine;}};
struct Simulation {vector velocity,angular;bool contact=false;vector GetVelocity(){return velocity;}vector GetAngularVelocityLocal(){return angular;}bool HasAnyGroundContact(){return contact;}};
struct Aircraft {vector origin={0,8,0};float upright=1;vector GetOrigin(){return origin;}vector GetYawPitchRoll(){return {};}void GetWorldTransform(vector (&t)[4]){t[0]={1,0,0};t[1]={0,upright,0};t[2]={0,0,1};t[3]=origin;}};
struct ActionManager {bool active=true;int activated=0;bool IsContextActive(const char*){return active;}void ActivateContext(const char*,int){activated++;}};
struct FuelManagerComponent {float total=100;bool present=true;bool HasFuel(){return present&&total>0;}float GetTotalFuel(){return total;}};
FuelManagerComponent fuelBoundary;FuelManagerComponent* FixtureFuel(int aircraft){return aircraft==2?&fuelBoundary:nullptr;}
struct Base {
bool m_StartAttempted=false,m_TranslationRequested=false,m_TranslationStarted=false,m_TranslationProven=false,m_Readback=true,m_Cancelled=false;
vector m_TranslationTarget,m_TranslationOrigin,m_Home;float m_TranslationDistance=0,m_Stable=-1,m_Deadline=240000,m_StageDeadline=240000,m_LastControl=0,m_LastTick=-1,m_Collective=-.8,m_Heading=0,clock=0;
int m_State=State::IDLE,m_Token=0,m_TranslationUpdates=0,m_ActionUpdates=0,m_HelicopterUpdates=0,m_Character=11,m_Pilot=12;
FuelManagerComponent* m_Fuel=&fuelBoundary;Controller m_Controller;Simulation m_Simulation;Aircraft m_Aircraft;ActionManager manager;ActionManager* m_Actions=&manager;string m_Reason;
bool owner=true,healthy=true,leased=true,grounded=false,pairsOkay=true,startOkay=true;
int starts=0,claims=0,landings=0,failures=0,relinquished=0,releases=0,shutdowns=0,pairs=0,baseInputs=0,baseTicks=0;float collectiveWrite=0,forwardWrite=0,asideWrite=0;
bool Start(ERR_GameMode,Vehicle,SCR_AIGroup,IEntity,PilotCompartmentSlot,ERR_AircraftFieldEnvelope* q,int token,float deadline){m_StartAttempted=true;starts++;if(!startOkay)return false;claims++;m_Home=q->Home();m_Token=token;m_Deadline=deadline;m_State=State::HOOKS;return true;}
virtual bool Grounded(){return grounded;}virtual bool Landing(float now){landings++;m_State=State::LANDING;m_Stable=-1;m_StageDeadline=Math::Min(m_Deadline,now+65000);return true;}
virtual bool BeginTranslation(float,vector){assert(false);return false;}
bool Owns(){return owner;}bool Healthy(){return healthy;}bool Lease(){return leased&&!Finished();}float Now(){return clock;}bool Finished(){return m_State>=State::COMPLETE&&m_State<=State::RELINQUISHED;}
void Fail(string r){m_Reason=r;failures++;m_State=State::FAILED;}void Relinquish(string r){m_Reason=r;relinquished++;m_State=State::RELINQUISHED;}void Release(bool){releases++;}void RecordState(){}void Shutdown(float now){shutdowns++;m_State=State::SHUTDOWN;m_StageDeadline=Math::Min(m_Deadline,now+15000);m_Stable=-1;m_Controller.engine=false;}
bool Pair(const char* p,const char*,float value){pairs++;string name=p;if(name=="HelicopterCollectiveIncrease")collectiveWrite=value;if(name=="HelicopterCyclicForward")forwardWrite=value;if(name=="HelicopterCyclicRight")asideWrite=value;return pairsOkay;}
void PrepareActions(int,int,ActionManager*,float){baseInputs++;}void Tick(){baseTicks++;}
'''+cpp(body(B,'void Cancel('))+r'''
};
struct Flight:Base {
'''+fields+methods+r'''
};
void started(Flight& f,ERR_AircraftFieldEnvelope& q){assert(f.StartField(1,2,3,4,5,&q,77,240000));f.m_State=State::CLIMB;f.clock=60000;f.m_LastControl=f.clock;assert(f.BeginTranslation(f.clock,{0,8,0}));}
bool near(float a,float b){return std::abs(a-b)<.0001f;}
int main(){
ERR_AircraftFieldEnvelope q;
Flight fuelCheck;assert(!fuelCheck.FuelAdmission(0)&&!fuelCheck.FuelAdmission(999));fuelBoundary.total=49.99f;assert(!fuelCheck.StartField(1,2,3,4,5,&q,77,240000)&&fuelCheck.starts==0&&fuelCheck.claims==0&&!fuelCheck.m_Field);
fuelBoundary.total=50;assert(fuelCheck.FuelAdmission(2));fuelBoundary.present=false;assert(!fuelCheck.FuelAdmission(2));fuelBoundary.present=true;fuelBoundary.total=100;
Flight refuse;assert(!refuse.StartField(1,2,3,4,5,nullptr,77,240000)&&refuse.starts==0);
q.destination={101,0,0};assert(!refuse.StartField(1,2,3,4,5,&q,77,240000)&&refuse.starts==0);q.destination={100,0,0};
assert(!refuse.StartField(1,2,3,4,5,&q,77,238999)&&refuse.starts==0);
assert(!refuse.Start(1,2,3,4,5,1,77,240000));assert(!refuse.StartTranslation(1,2,3,4,5,1,77,240000,{100,0,0}));
Flight f;started(f,q);assert(f.starts==1&&f.claims==1&&f.m_Field==&q&&f.m_TranslationTarget[0]==100&&f.m_TranslationTarget[1]==8&&f.m_Token==77&&f.m_Deadline==240000);
assert(!f.StartField(1,2,3,4,5,&q,78,240000)&&f.starts==1);
assert(f.m_State==State::TRANSLATING&&f.m_FieldPhase==1&&f.m_StageDeadline==220000);
f.CheckTranslation(61000,{-0.23f,8,0},8,0,1);assert(f.m_FieldPhase==1); // actual post-hover distance can exceed100m
vector demand=f.FieldDemand({0,8,0},{},.1f);assert(f.m_FieldReference[0]>0&&f.m_FieldReference.Length()<=.020001f&&demand.Length()<=.080001f);
f.m_FieldReference={2,0,0};f.m_FieldIntegral={1,0,0};f.m_Aircraft.origin={80,8,0};f.m_Simulation.velocity={2,0,0};
f.CheckTranslation(90000,f.m_Aircraft.origin,8,2,1);assert(f.m_FieldPhase==2&&f.m_FieldStopFrozen&&f.m_FieldLandingPoint[0]==100&&f.m_State==State::TRANSLATING&&f.landings==0&&f.m_StageDeadline==135000);
float brakeDeadline=f.m_StageDeadline;float brakeCap=f.m_FieldBrakeSpeed;assert(near(brakeCap,1.8f));
auto firstBrake=f.FieldDemand(f.m_Aircraft.origin,f.m_Simulation.velocity,.1f);assert(f.m_FieldReference.Length()<2&&f.m_FieldReference.Length()>=1.9799f&&firstBrake.Length()<=.080001f);
float previous=f.m_FieldReference.Length();for(int i=0;i<20;i++){f.FieldDemand({85,8,0},{1.9f,0,0},.1f);assert(f.m_FieldReference.Length()<=previous+.000001f);previous=f.m_FieldReference.Length();}
f.clock=91000;f.Cancel("stop during brake");assert(f.m_Cancelled&&f.m_FieldLandingPoint[0]==100&&f.m_StageDeadline==brakeDeadline&&f.landings==0);
f.clock=92000;f.Cancel("duplicate");assert(f.m_Reason=="stop during brake"&&f.m_StageDeadline==brakeDeadline&&f.m_Token==77&&f.m_FieldBrakeSpeed==brakeCap);
f.m_Simulation.velocity={.1f,0,0};f.CheckTranslation(92000,{99.6f,8,0},8,.1f,1);f.CheckTranslation(93999,{99.6f,8,0},8,.1f,1);assert(f.m_FieldPhase==2);
f.m_Simulation.velocity={.3f,0,0};f.CheckTranslation(94000,{99.6f,8,0},8,.3f,1);assert(f.m_FieldLowSince<0);
f.m_Simulation.velocity={.1f,0,0};f.CheckTranslation(95000,{99.6f,8,0},8,.1f,1);f.CheckTranslation(97000,{99.6f,8,0},8,.1f,1);
assert(f.m_FieldPhase==3&&f.m_FieldIntegral.Length()==0&&f.m_FieldReference.Length()==0&&f.m_StageDeadline==brakeDeadline&&f.landings==0);
f.CheckTranslation(101999,{99.6f,8,0},8,.1f,1);assert(f.landings==0);
f.CheckTranslation(102000,{99.6f,8,0},8,.1f,1);assert(f.landings==1&&f.m_State==State::LANDING&&!f.m_FieldArrived&&!f.m_TranslationProven&&f.m_StageDeadline==127000&&f.m_Deadline==240000);
// Grounded completion requires exact frozen centre, stable native contact and shutdown.
f.grounded=true;f.m_Aircraft.origin={98,0,0};assert(!f.Grounded());f.m_Aircraft.origin={99.6f,0,0};assert(f.Grounded());
f.clock=103000;f.m_LastControl=f.clock;f.Tick();f.clock=104000;f.m_LastControl=f.clock;f.Tick();f.clock=104999;f.m_LastControl=f.clock;f.Tick();assert(f.shutdowns==0);
f.clock=105000;f.m_LastControl=f.clock;f.Tick();assert(f.shutdowns==1&&f.m_State==State::SHUTDOWN);
f.clock=105100;f.m_LastControl=f.clock;f.Tick();assert(f.m_State==State::CANCELLED&&f.releases==1&&f.m_Token==77&&f.m_Deadline==240000);
// Uncancelled normal hold can produce COMPLETE only after native ground observations.
Flight normal;started(normal,q);normal.m_Aircraft.origin={99.7f,8,0};assert(normal.BeginFieldBrake(80000));
normal.CheckTranslation(80000,normal.m_Aircraft.origin,8,0,1);normal.CheckTranslation(82000,normal.m_Aircraft.origin,8,0,1);
normal.CheckTranslation(85000,{98,8,0},8,0,1);assert(normal.m_Stable<0&&!normal.m_FieldArrived);
normal.CheckTranslation(86000,normal.m_Aircraft.origin,8,0,1);normal.CheckTranslation(90999,normal.m_Aircraft.origin,8,0,1);assert(!normal.m_FieldArrived);
normal.CheckTranslation(91000,normal.m_Aircraft.origin,8,0,1);assert(normal.m_FieldArrived&&normal.m_TranslationProven&&normal.landings==1);
normal.m_Aircraft.origin={99.7f,0,0};normal.grounded=true;
for(int t=92000;t<=94100;t+=700){normal.clock=t;normal.m_LastControl=t;normal.Tick();}
assert(normal.m_State==State::SHUTDOWN);normal.clock=94200;normal.m_LastControl=normal.clock;normal.Tick();assert(normal.m_State==State::COMPLETE&&normal.releases==1);
// Cancelling during climb retains altitude/braking instead of invoking descent.
Flight climb;assert(climb.StartField(1,2,3,4,5,&q,77,240000));climb.m_State=State::CLIMB;climb.clock=30000;climb.m_Aircraft.origin={1,3,0};climb.Cancel("climb stop");
assert(climb.m_State==State::TRANSLATING&&climb.m_FieldPhase==2&&climb.landings==0&&!climb.m_TranslationStarted);
// Actual inherited Cancel may run airborne before startup enabled autohover.
for(int stage:{State::POSITIVE,State::NEGATIVE}){
Flight early;assert(early.StartField(1,2,3,4,5,&q,77,240000));early.m_State=stage;early.m_Controller.hover=false;early.clock=30000;early.m_Aircraft.origin={1,.2f,0};early.m_Simulation.velocity={.1f,0,0};
early.Cancel("pre-climb airborne stop");assert(early.m_Cancelled&&early.m_FieldPhase==2&&early.m_State==State::TRANSLATING&&early.m_Controller.hover&&early.m_Controller.hoverWrites==1&&early.failures==0&&early.landings==0);
auto frozen=early.m_FieldLandingPoint;auto stageDeadline=early.m_StageDeadline;early.PrepareActions(early.m_Character,early.m_Pilot,early.m_Actions,.1f);assert(early.pairs==4&&early.failures==0);
early.Cancel("duplicate");assert(early.m_Controller.hoverWrites==1&&early.m_FieldLandingPoint[0]==frozen[0]&&early.m_StageDeadline==stageDeadline&&early.m_Deadline==240000);
}
Flight refusedHover;started(refusedHover,q);refusedHover.m_Controller.hover=false;refusedHover.m_Controller.enableAllowed=false;refusedHover.Cancel("native hover refuses");assert(refusedHover.failures==1&&!refusedHover.m_FieldStopFrozen&&refusedHover.m_Controller.hoverWrites==1&&refusedHover.m_State==State::FAILED);
Flight capturedBrake;started(capturedBrake,q);capturedBrake.owner=false;capturedBrake.Cancel("captured before brake");assert(capturedBrake.relinquished==1&&capturedBrake.m_Controller.hoverWrites==0&&!capturedBrake.m_FieldStopFrozen);
// First cancellation freezes a velocity-projected qualified point exactly once.
Flight cancel;started(cancel,q);cancel.m_Aircraft.origin={30,8,0};cancel.m_Simulation.velocity={2,0,0};cancel.m_FieldReference={2,0,0};cancel.clock=70000;cancel.Cancel("first");
assert(cancel.m_State==State::TRANSLATING&&cancel.m_FieldPhase==2&&cancel.landings==0&&near(cancel.m_FieldLandingPoint[0],30+Rules::BrakingReserve(2)));
auto centre=cancel.m_FieldLandingPoint;float deadline=cancel.m_StageDeadline;cancel.clock=71000;cancel.m_Aircraft.origin={40,8,0};cancel.Cancel("again");assert(cancel.m_FieldLandingPoint[0]==centre[0]&&cancel.m_StageDeadline==deadline);
Flight settling;started(settling,q);settling.m_Aircraft.origin={99.7f,8,0};assert(settling.BeginFieldBrake(80000));settling.m_FieldPhase=3;settling.m_Stable=81000;settling.clock=82000;auto old=settling.m_StageDeadline;settling.Cancel("mid hold");assert(settling.failures==0&&settling.landings==0&&settling.m_State==State::TRANSLATING&&settling.m_StageDeadline==old);
// Cancellation cannot invent a stopping point beyond qualified space.
Flight outside;started(outside,q);outside.m_Aircraft.origin={100,8,0};outside.m_Simulation.velocity={2,0,0};outside.Cancel("too late");assert(outside.failures==1&&!outside.m_FieldStopFrozen&&outside.landings==0);
// Early abort is owner-tick based and cannot transition inherited input mid-call.
Flight abort;started(abort,q);abort.clock=155000;abort.m_LastControl=abort.clock;abort.m_Aircraft.origin={20,8,0};assert(abort.ControlLease()&&!abort.m_Cancelled);abort.Tick();assert(abort.m_Cancelled&&abort.m_FieldPhase==2&&abort.m_Deadline==240000&&abort.m_StageDeadline==200000);
Flight edge;started(edge,q);edge.clock=153499;edge.m_LastControl=edge.clock;edge.m_Aircraft.origin={20,8,0};edge.Tick();assert(!edge.m_Cancelled);edge.clock=153500;edge.m_LastControl=edge.clock;edge.Tick();assert(edge.m_Cancelled&&edge.m_Deadline==240000);
Flight low;started(low,q);low.m_Aircraft.origin={20,8,0};low.clock=70000;low.m_LastControl=low.clock;fuelBoundary.total=20;low.Tick();assert(low.m_Cancelled&&low.m_FieldPhase==2&&low.m_Reason=="field candidate low-fuel reserve reached");fuelBoundary.total=100;
Flight capture;started(capture,q);capture.owner=false;assert(!capture.ControlLease()&&capture.relinquished==1&&capture.pairs==0);
Flight expiry;started(expiry,q);expiry.clock=240000;assert(!expiry.ControlLease()&&expiry.failures==1);
Flight fast;started(fast,q);fast.m_Simulation.velocity={2.501f,0,0};assert(!fast.ControlLease()&&fast.failures==1);
Flight corridor;started(corridor,q);corridor.m_Aircraft.origin={50,8,8};assert(!corridor.ControlLease()&&corridor.failures==1);
// Exact callbacks only; no native input on invalid timestep or failed lease.
Flight input;started(input,q);input.m_Aircraft.origin={10,8,0};ActionManager other;
input.PrepareActions(999,input.m_Pilot,input.m_Actions,.1f);input.PrepareActions(input.m_Character,999,input.m_Actions,.1f);input.PrepareActions(input.m_Character,input.m_Pilot,&other,.1f);assert(input.pairs==0);
for(float dt:{0.0f,-1.0f,.201f,NAN}) { input.PrepareActions(input.m_Character,input.m_Pilot,input.m_Actions,dt); }
assert(input.pairs==0);
input.PrepareActions(input.m_Character,input.m_Pilot,input.m_Actions,.1f);assert(input.pairs==4&&input.m_ActionUpdates==1&&input.m_TranslationUpdates==1&&input.m_FieldReference.Length()<=.020001f);
input.m_State=State::LANDING;input.m_FieldPhase=3;input.m_FieldLandingPoint={10,0,0};input.m_Aircraft.origin={10,0,0};input.grounded=true;input.PrepareActions(input.m_Character,input.m_Pilot,input.m_Actions,.1f);assert(input.collectiveWrite==-1&&input.forwardWrite==0&&input.asideWrite==0);
input.owner=false;int writes=input.pairs;input.PrepareActions(input.m_Character,input.m_Pilot,input.m_Actions,.1f);assert(input.pairs==writes&&input.relinquished==1);
Flight inactive;started(inactive,q);inactive.manager.active=false;inactive.PrepareActions(inactive.m_Character,inactive.m_Pilot,inactive.m_Actions,.1f);assert(inactive.pairs==0&&!inactive.m_Readback&&inactive.manager.activated==1);
Flight gap;started(gap,q);gap.m_Aircraft.origin={99.7f,8,0};gap.BeginFieldBrake(80000);gap.m_FieldLowSince=81000;gap.m_Stable=81000;gap.m_LastTick=81000;gap.clock=83000;gap.m_LastControl=gap.clock;gap.Tick();assert(gap.m_FieldPhase==2&&gap.m_FieldLowSince==83000&&gap.m_Stable<0);
// Slow positioning/landing never consumes or retains the cruise PI output.
Flight slow;started(slow,q);slow.m_FieldPhase=3;slow.m_FieldStopFrozen=true;slow.m_FieldLandingPoint={100,0,0};slow.m_FieldIntegral={4,0,0};
auto actual=slow.FieldDemand({90,8,0},{1,2,0},.1f);auto shared=TranslationRules::Demand({100,0,0},{90,8,0},{1,2,0});assert((actual-shared).Length()==0&&actual.Length()<=.030001f);
Flight startup;assert(startup.StartField(1,2,3,4,5,&q,77,240000));startup.PrepareActions(11,12,startup.m_Actions,.1f);startup.Tick();assert(startup.baseInputs==1&&startup.baseTicks==1);
}
''')
# Preserve shared ownership, startup and bridge; no new actuation domain.
assert 'class ERR_AircraftFieldFlight : ERR_AircraftFlight' in source
for forbidden in ('s_Owners', 'modded class', 'SetWorldTransform(', 'SetOrigin(', 'SetVelocity(', 'SetActive(', 'Spawn(', '.Claim(', '.SetActionValue(', '.StartEngine(', '.StopEngine('):assert forbidden not in source
for unchanged in ('protected bool Owns(', 'protected bool Healthy(', 'protected bool Lease(', 'protected void Release(', 'void Cancel('):assert unchanged not in source
assert 'return super.Start(' in body(P,'bool StartField(')
assert 'AbortDue' not in body(P,'protected override bool ControlLease(')
assert 'ERR_AircraftFieldRules.AbortDue(now + 1500, m_Deadline)' in body(P,'override void Tick(')
assert 'm_Deadline =' not in source and 'm_Token =' not in source
prepare=body(P,'override void PrepareActions(')
assert prepare.index('!ControlLease()')<prepare.index('FieldDemand(')<prepare.index('Pair(')
assert 'm_StageDeadline' not in body(P,'protected override void CheckTranslation(')
assert 'm_FieldIntegral = "0 0 0"' in body(P,'protected override void CheckTranslation(')
print('PASS: actual field startup delegation, immutable lease/target, braking/settling/cancel transitions, early abort, callback scope, physical terminal policy and shared landing demand; native boundaries faked')
brake=body(P,'protected bool BeginFieldBrake(')
assert brake.index('if (m_FieldStopFrozen) return true;')<brake.index('!Owns()')<brake.index('m_Field.Contains(point, 1)')<brake.index('m_Controller.SetAutohoverEnabled(true)')<brake.index('if (!m_Controller.GetAutohoverEnabled())')<brake.index('m_FieldStopFrozen = true;')
print('PASS: actual inherited airborne pre-CLIMB cancellation enables verified native autohover once, refuses failed enable and never writes after capture')
+103
View File
@@ -0,0 +1,103 @@
"""Actual field evidence/observation bodies; physical engine boundaries remain untested."""
from policy_harness import ROOT, body, run
import re
P=ROOT/'addons/ai/Scripts/Game/ERR/ERR_NativeAircraftFieldProbe.c'
s=P.read_text()
def cpp(x):
x=x.replace('ERR_AircraftFlightState.','State::').replace('ERR_NativeAircraftFieldRules.','Rules::').replace('ERR_AircraftFieldRules.','FieldRules::').replace('vector.','vector::').replace('ERR_AircraftFieldFlight flight','Flight* flight').replace('flight.','flight->')
return re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])',r'\1f',x)
rules=s[s.index('class ERR_NativeAircraftFieldRules'):s.index('class ERR_NativeAircraftFieldProbe')].replace('class ERR_NativeAircraftFieldRules','struct Rules').rstrip()+';'
observed='\n'.join(cpp(body(P,k)) for k in ('void BrakeBegan(', 'void BrakeStopped('))
run(r'''#include <cassert>
#include <cmath>
#include <string>
using string=std::string;
struct vector {float x=0,y=0,z=0;static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}};
namespace State {enum {COMPLETE=8,CANCELLED,FAILED,RELINQUISHED};}
'''+cpp(rules)+r'''
struct FieldRules {static float HorizontalSpeed(vector v){return std::hypot(v.x,v.z);}static float BrakingReserve(float s){return s*s/.30f+s+3;}};
struct Flight {float ERR_NativeFieldStageDeadline(){return 70000;}vector LandingPoint(){return {100,0,0};}};
template<typename...T>void PrintFormat(T...args){}
struct Probe {
bool m_Stopped=false,m_BrakeObserved=false,m_BrakeStopped=false,m_BrakePassed=false;
string m_ObservationFailure;
Flight* m_Flight=nullptr;float m_BrakeAt=0,m_BrakeReserve=0,m_BrakeStageDeadline=0,m_ControlDeadline=240000,m_BrakeSampledTravel=0,m_BrakeLowSince=-1;
vector m_BrakeOrigin,m_BrakePrevious,m_BrakeLandingPoint;
'''+observed+r'''
};
int main(){
assert(Rules::Cruise(1,1,false,1)&&!Rules::Cruise(.99f,1,false,1)&&!Rules::Cruise(1,2.501f,false,1));
assert(!Rules::Cruise(1,1,true,1)&&!Rules::Cruise(1,1,false,2));
assert(Rules::Terminal(false,State::COMPLETE,0,true,true,true,true,100,.5f));
assert(!Rules::Terminal(false,State::COMPLETE,0,true,false,true,true,100,.5f));
assert(!Rules::Terminal(false,State::COMPLETE,0,true,true,false,true,100,.5f));
assert(!Rules::Terminal(false,State::COMPLETE,0,true,true,true,false,100,.5f));
assert(!Rules::Terminal(false,State::COMPLETE,0,true,true,true,true,98,.5f));
assert(!Rules::Terminal(false,State::COMPLETE,0,true,true,true,true,100,.76f));
assert(Rules::Terminal(true,State::CANCELLED,1,false,false,false,true,30,.5f));
assert(!Rules::Terminal(true,State::CANCELLED,1,true,false,false,true,30,.5f));
assert(!Rules::Terminal(true,State::COMPLETE,1,false,false,false,true,30,.5f));
assert(!Rules::Terminal(true,State::CANCELLED,0,false,false,false,true,30,.5f));
assert(!Rules::Terminal(true,State::CANCELLED,2,false,false,false,true,30,.5f));
assert(Rules::PhysicalFinish(true,true,true,1,1,1,1,101,101));
assert(!Rules::PhysicalFinish(true,false,true,1,1,1,1,101,101));
assert(!Rules::PhysicalFinish(true,true,false,1,1,1,1,101,101));
assert(!Rules::PhysicalFinish(true,true,true,0,1,1,1,101,101));
assert(!Rules::PhysicalFinish(true,true,true,1,2,1,1,101,101));
for(int i=0;i<5;i++)assert(!Rules::CanDispose(i!=0,i!=1,i!=2,i!=3,i!=4));
Flight flight,foreign;Probe p;p.m_Flight=&flight;
p.BrakeBegan(&foreign,1000,{0,0,0},{2,0,0});assert(!p.m_BrakeObserved);
p.BrakeBegan(&flight,1000,{0,0,0},{2,0,0});assert(p.m_BrakeObserved&&p.m_BrakeReserve>18&&p.m_BrakeReserve<19);
p.m_BrakeSampledTravel=15;p.m_BrakePrevious={15,0,0};p.m_BrakeLowSince=2000;
p.BrakeStopped(&flight,4000,{16,0,0},{.1f,0,0});assert(p.m_BrakePassed&&p.m_BrakeStopped&&p.m_ObservationFailure.empty());
p.BrakeBegan(&flight,5000,{50,0,0},{2,0,0});assert(!p.m_ObservationFailure.empty()&&p.m_BrakeAt==1000); // no renewal
for(int fault=0;fault<4;fault++){
Probe bad;bad.m_Flight=&flight;bad.BrakeBegan(&flight,1000,{0,0,0},{2,0,0});bad.m_BrakeLowSince=2000;
float stopped=4000;vector at={16,0,0},vel={.1f,0,0};
if(fault==0){at={19,0,0};} if(fault==1){stopped=3999;} if(fault==2){vel={.25f,0,0};} if(fault==3){bad.m_BrakeSampledTravel=20;}
bad.BrakeStopped(&flight,stopped,at,vel);assert(!bad.m_BrakePassed&&!bad.m_ObservationFailure.empty()&&!bad.m_Stopped); // failure deferred outside callback
}
assert(!Rules::Braking(NAN,1,1,0,3000,0)&&!Rules::Braking(10,NAN,1,0,3000,0));
}
''')
assert s.startswith('#ifdef WORKBENCH') and s.rstrip().endswith('#endif')
assert 'outcome == "aircraft-field"' in body(P,'static bool Enabled(')
find=body(P,'protected bool FindPad(')
assert '"4850.953 0 11787.412"' in find and '"100 0 0"' in find
assert find.count('.Survey(')==1 and 'for (' not in find and 'AirportOffset' not in s
assert s.count('ERR_Actors.Spawn(AIRCRAFT,')==1 and s.count('SpawnCharacterInCompartment(')==1
assert s.count('physics.SetActive(ActiveState.ACTIVE)')==1
for prohibited in ('SetWorldTransform(', 'SetOrigin(', 'SetVelocity(', '.StartEngine(', '.StopEngine(', 'm_Mode.Own(', 'm_Flight.Tick(', 'm_Flight.Cancel(', 'm_Flight.StartField(', 'FieldDemand('):assert prohibited not in s
for sig in ('void PrepareActions(', 'void PrepareHelicopter('):
assert '++' in body(P,sig) and '.Prepare' not in body(P,sig)
for sig in ('void BrakeBegan(', 'void BrakeStopped('):assert 'Check(' not in body(P,sig) and 'Stop(' not in body(P,sig)
assert 'super.BeginFieldBrake(now)' in body(P,'protected override bool BeginFieldBrake(')
assert 'super.CheckTranslation(now, position, height, speed, upright)' in body(P,'protected override void CheckTranslation(')
start=body(P,'protected bool StartRetainedField(');assert '.StartField(m_Aircraft, m_Envelope, ORDER_TOKEN, m_ControlDeadline)' in start
assert 'ERR_AircraftFieldFlight.Cast(entry.Flight)' in body(P,' ERR_AircraftFieldFlight ERR_NativeOriginalFieldFlight(')
tick=body(P,'int Tick(')
assert tick.index('FindPad(m_Pad)')<tick.index('ERR_Actors.Spawn(AIRCRAFT,')
assert tick.index('QualifyField(')<tick.index('StartRetainedField()')
assert tick.index('m_Service.Tick();')<tick.index('m_ObservationFailure.IsEmpty()')<tick.index('if (!m_Flight.Finished())')
assert 'now - m_CruiseSince >= 10000' in tick and 'now - m_CruiseSince >= 1000' in tick
assert 'now - m_TranslationStable >= 5000' in tick and 'now - m_LandingStable >= 2000' in tick
stop=body(P,'void Stop(')
assert stop.index('s_Current = null')<stop.index('m_Service.Stop()')<stop.index('ERR_Actors.DeleteSpawned(')<stop.index('m_Service.Tick()')<stop.index('m_Flight = null')
assert 'CanDispose(' in stop
# Execute root-owned Chat dispatch branch: failure cannot become completion/reset.
chat=ROOT/'addons/chat/Scripts/Game/ERR/ERR_NativeSmoke.c';cs=chat.read_text()
a=cs.index('if (m_ERRSmokeStep == 240)');opening=cs.index('{',a);i=opening+1;depth=1
while depth:depth+=(cs[i]=='{')-(cs[i]=='}');i+=1
branch=cs[opening+1:i-1].replace('m_ERRAircraftField.Tick','m_ERRAircraftField.Tick').replace('ERR_Phase.DEFEAT','2')
run(r'''#include <cassert>
#include <initializer_list>
struct Fixture {int result=0;int Tick(float){return result;}};
struct Chat {Fixture m_ERRAircraftField;int m_ERRSmokeStep=240,failures=0,transitions=0;void ERR_Check(bool,const char*){failures++;}void Transition(int){transitions++;}void Tick(float now){
'''+branch+r'''} };
int main(){for(int result:{-1,0,1}){Chat c;c.m_ERRAircraftField.result=result;c.Tick(1000);assert(c.failures==(result<0));assert(c.transitions==(result==1));assert(c.m_ERRSmokeStep==(result==1?241:240));}}
''')
assert 'm_ERRTestAircraftField = outcome == "aircraft-field"' in cs
assert 'm_ERRTestAircraftControl || m_ERRTestAircraftRoute || m_ERRTestAircraftField' in cs and '+ 330000' in cs
assert 'm_ERRAircraftField.Stop()' in body(chat,'protected void ERR_StopHQPatrol(')
assert 'errSmokeFieldServiceCancel' in cs and 'current == ERR_Phase.DEFEAT && m_ERRSmokeStep == 241' in cs
print('PASS: actual field result/braking observer and Chat dispatch rules; one finite fixed candidate, deferred callback failure and guarded cleanup; no native measurement')
+99
View File
@@ -0,0 +1,99 @@
"""Execute the actual field policy equations; no claim about native stopping/flight."""
from policy_harness import ROOT, run
import re
source = (ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftFieldRules.c').read_text()
cpp = source.replace('class ERR_AircraftFieldRules', 'struct Rules').replace('out vector nextIntegral', 'vector& nextIntegral')
cpp = cpp.replace('Math.', 'Math::').replace('vector.', 'vector::').replace('"0 0 0"', 'vector{}')
cpp = re.sub(r'(?<![\w.])(\d+\.\d+)(?![\w.])', r'\1f', cpp)
cpp = cpp.rstrip() + ';'
run(r'''#include <cassert>
#include <cmath>
#include <algorithm>
struct vector {
float v[3]={0,0,0};
vector()=default; vector(float x,float y,float z):v{x,y,z}{}
float& operator[](int i){return v[i];} float operator[](int i)const{return v[i];}
vector operator+(vector b)const{return {v[0]+b[0],v[1]+b[1],v[2]+b[2]};}
vector operator-(vector b)const{return {v[0]-b[0],v[1]-b[1],v[2]-b[2]};}
vector operator*(float s)const{return {v[0]*s,v[1]*s,v[2]*s};}
float Length()const{return std::sqrt(Dot(*this,*this));}
static float Dot(vector a,vector b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];}
static float DistanceXZ(vector a,vector b){return std::hypot(a[0]-b[0],a[2]-b[2]);}
};
struct Math {static float AbsFloat(float x){return std::abs(x);} static float Clamp(float x,float lo,float hi){return std::clamp(x,lo,hi);} static float Max(float a,float b){return std::max(a,b);} static float Min(float a,float b){return std::min(a,b);} static float Sqrt(float a){return std::sqrt(a);}};
'''+cpp+r'''
bool close(float a,float b){return std::abs(a-b)<.00001f;}
int main(){
assert(Rules::Length(99.95f)&&Rules::Length(100.05f));
assert(!Rules::Length(99.949f)&&!Rules::Length(100.051f)&&!Rules::Length(NAN));
assert(Rules::SpeedAllowed({1.5f,9,2})&&!Rules::SpeedAllowed({1.501f,0,2}));
assert(!Rules::SpeedAllowed({0,NAN,0})&&!Rules::SpeedAllowed({INFINITY,0,0}));
assert(Rules::ValidStep(.5f)&&!Rules::ValidStep(0)&&!Rules::ValidStep(.501f)&&!Rules::ValidStep(NAN));
float speed=0;
for(int i=0;i<100;i++){float next=Rules::RampSpeed(speed,2,.1f);assert(next-speed<=.020001f);speed=next;}
assert(close(speed,2));
for(int i=0;i<100;i++){float next=Rules::RampSpeed(speed,0,.1f);assert(next<=speed&&speed-next<=.020001f);speed=next;}
assert(close(speed,0));
assert(close(Rules::RampSpeed(1,2,1),1)&&close(Rules::RampSpeed(1,NAN,.1f),1));
vector previous={2,0,0};
for(int i=0;i<400;i++){
vector ramped=Rules::RampReference(previous,{-2,8,0},.1f);
assert((ramped-previous).Length()<=.020001f&&ramped.Length()<=2.00001f&&ramped[1]==0);
previous=ramped;
}
assert(previous[0]<-1.99f);
vector sideways=Rules::RampReference({2,0,0},{0,0,2},.1f);
assert((sideways-vector{2,0,0}).Length()<=.020001f);
assert(close(Rules::RampReference({1,4,0},{-2,0,0},0)[0],1));
assert(close(Rules::ApproachSpeed(100,2),2)&&close(Rules::ApproachSpeed(.75f,2),0));
assert(close(Rules::ApproachSpeed(3,2),0));
assert(close(Rules::ApproachSpeed(Rules::BrakingReserve(1.5f),2),1.5f));
assert(Rules::ApproachSpeed(18,2)<2); // before the2m/s reserve, unlike old sqrt(distance-.75)
for(int i=3;i<=120;i++){
float admitted=Rules::ApproachSpeed(float(i),2);
assert(admitted<=2&&Rules::BrakingReserve(admitted)<=i+.00002f);
}
assert(Rules::ApproachSpeed(15,1.2f)<=1.2f);
assert(Rules::ApproachSpeed(10,.1f)<=.1f&&Rules::ApproachSpeed(NAN,1)==0);
assert(close(Rules::ApproachSpeed(108,2),2)&&Rules::ApproachSpeed(120.01f,2)==0);
assert(!Rules::ShouldBrake(100.23f,1)&&!Rules::ShouldBrake(108,2)&&!Rules::ShouldBrake(120,2));
assert(Rules::ShouldBrake(120.01f,1)&&!Rules::Length(108));
vector integral, next;
for(int i=0;i<2000;i++){
vector demand=Rules::VelocityDemand({2,100,0},{0,-2,0},integral,.1f,next);
assert(close(demand[0],.08f)&&demand[1]==0&&next.Length()==0);integral=next;
}
// Low error accumulates useful demand rather than permanently freezing I.
integral={};vector demand=Rules::VelocityDemand({.1f,5,0},{0,7,0},integral,.5f,next);
assert(close(next[0],.05f)&&close(demand[0],.011f)&&demand[1]==0);
// Opposing error unwinds a positive stored integral.
integral={4,0,0};demand=Rules::VelocityDemand({0,0,0},{.1f,0,0},integral,.5f,next);
assert(next[0]<4&&demand[0]<.08f);
// Diagonal demand is capped as a vector, not independently on both axes.
demand=Rules::VelocityDemand({2,3,2},{-1,4,-1},{4,5,4},.1f,next);
assert(demand.Length()<=.080001f&&demand[1]==0&&next.Length()<=4.00001f&&next[1]==0);
demand=Rules::VelocityDemand({0,0,0},{2,0,0},{},.1f,next);
assert(demand[0]<0&&close(demand.Length(),.08f));
demand=Rules::VelocityDemand({NAN,0,0},{},{1,0,0},.1f,next);assert(demand.Length()==0&&next.Length()==0);
demand=Rules::VelocityDemand({1,0,0},{},{1,0,0},0,next);assert(demand.Length()==0&&next.Length()==0);
assert(close(Rules::BrakingReserve(0),3)&&close(Rules::BrakingReserve(2.5f),26.333334f));
assert(Rules::ShouldBrake(25,0)&&!Rules::ShouldBrake(25.01f,0));
assert(Rules::ShouldBrake(26,2.5f)&&!Rules::ShouldBrake(27,2.5f));
assert(Rules::ShouldBrake(50,NAN)&&Rules::ShouldBrake(NAN,1));
assert(Rules::LeaseAdmitted(1000,241000)&&Rules::LeaseAdmitted(1000,240000));
assert(!Rules::LeaseAdmitted(1000,239999)&&!Rules::LeaseAdmitted(1000,241001)&&!Rules::LeaseAdmitted(-1,239999));
assert(!Rules::AbortDue(154999,240000)&&Rules::AbortDue(155000,240000));
assert(Rules::AbortDue(240000,240000)&&Rules::AbortDue(NAN,240000));
assert(Rules::LowSpeedHeld(.249f,1000,3000)&&!Rules::LowSpeedHeld(.25f,1000,3000));
assert(!Rules::LowSpeedHeld(0,1000,2999)&&!Rules::LowSpeedHeld(0,-1,3000)&&!Rules::LowSpeedHeld(NAN,0,3000));
vector start={0,10,0},end={100,10,0};
assert(Rules::Contains(start,end,{50,18,7.999f},1));
assert(!Rules::Contains(start,end,{50,18,8},1));
assert(Rules::Contains(start,end,{-7.99f,18,0},1)&&!Rules::Contains(start,end,{-8,18,0},1));
assert(!Rules::Contains(start,end,{108,18,0},1)&&!Rules::Contains(start,end,{50,28.01f,0},1));
assert(!Rules::Contains(start,end,{50,6.99f,0},1)&&!Rules::Contains(start,end,{50,18,0},.84f));
assert(!Rules::Contains(start,end,{NAN,18,0},1)&&!Rules::Contains(start,end,{50,18,0},NAN));
}
''')
print('PASS: actual field PI/ramp/vector caps, antiwindup, braking hypothesis, immutable admission limits and segment bounds; no native physics tested')
+50
View File
@@ -0,0 +1,50 @@
"""Actual early CLI refusals and AST-executed argv construction; never launches native tools."""
import ast
from pathlib import Path
import subprocess
import sys
import tempfile
import unittest
from types import SimpleNamespace
from policy_harness import ROOT
RUNNER=ROOT/'tools/headless_workbench.py'
class FieldRunnerTests(unittest.TestCase):
def test_early_refusal_before_any_profile_or_lock(self):
cases=[
(['--project','core','--rounds','1','--timeout','420','--smoke','aircraft-field'],'requires Chat, one round'),
(['--project','chat','--rounds','2','--timeout','420','--smoke','aircraft-field'],'requires Chat, one round'),
(['--project','chat','--rounds','1','--timeout','419','--smoke','aircraft-field'],'requires Chat, one round'),
(['--project','chat','--rounds','1','--timeout','420','--smoke','aircraft-route','--field-service-cancel'],'--field-service-cancel requires'),
(['--project','chat','--rounds','1','--timeout','420','--smoke','aircraft-field','--route-service-cancel'],'--route-service-cancel requires'),
(['--project','chat','--rounds','1','--timeout','420','--smoke','aircraft-field'],'must be a disposable cloned prefix'),
(['--project','chat','--rounds','1','--timeout','420','--smoke','aircraft-field','--field-service-cancel'],'must be a disposable cloned prefix'),
]
for arguments,error in cases:
with self.subTest(arguments=arguments),tempfile.TemporaryDirectory(prefix='erri-field-args-') as folder:
root=Path(folder)
result=subprocess.run([sys.executable,str(RUNNER),'--compat',str(root/'missing'),'--profile',str(root/'profile'),*arguments],capture_output=True,text=True,timeout=10)
self.assertEqual(result.returncode,2);self.assertIn(error,result.stderr);self.assertEqual(list(root.iterdir()),[])
def command(self,smoke,cancel=False):
tree=ast.parse(RUNNER.read_text());nodes=[]
for node in tree.body:
if isinstance(node,ast.Assign) and any(isinstance(t,ast.Name) and t.id=='command' for t in node.targets):nodes.append(node)
if isinstance(node,ast.If) and isinstance(node.test,ast.Attribute) and node.test.attr in ('field_service_cancel','route_service_cancel'):nodes.append(node)
self.assertEqual(len(nodes),3)
namespace={'a':SimpleNamespace(project='chat',smoke=smoke,field_service_cancel=cancel,route_service_cancel=False),'ROOT':Path('/private/source'),'proton':Path('/private/proton'),'wb':Path('/private/workbench'),'profile_arg':'Z:/private/profile'}
exec(compile(ast.Module(body=nodes,type_ignores=[]),str(RUNNER),'exec'),namespace)
return namespace['command']
def test_exact_field_command_and_cancel_optin(self):
normal=self.command('aircraft-field');cancelled=self.command('aircraft-field',True)
self.assertEqual(cancelled,normal+['-errSmokeFieldServiceCancel','1'])
self.assertEqual(normal[normal.index('-errSmokeOutcome')+1],'aircraft-field')
self.assertEqual(normal[normal.index('-gproj')+1],'Z:/private/source/addons/chat/addon.gproj')
self.assertEqual(normal[normal.index('--backend')+1],'headless')
self.assertIn('-disableCrashReporter',normal)
self.assertNotIn('-errSmokeFieldServiceCancel',self.command('aircraft-route'))
def test_cleanup_provenance_not_bypassed(self):
source=RUNNER.read_text()
self.assertIn('assert_prefix_idle(compat)',source)
self.assertIn('cleanup_owned_run(',source)
self.assertIn('seal_run(',source)
self.assertIn("env.update(PULSE_SINK=silent_sink",source)
if __name__=='__main__':unittest.main()
+4 -4
View File
@@ -64,7 +64,7 @@ for project,rounds,timeout,extra,message in cases:
chat=ROOT/'addons/chat/Scripts/Game/ERR/ERR_NativeSmoke.c'
text=chat.read_text()
assert 'm_ERRTestAircraftRoute = outcome == "aircraft-route";' in text
assert 'if (m_ERRTestAircraftControl || m_ERRTestAircraftRoute) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;' in text
assert 'if (m_ERRTestAircraftControl || m_ERRTestAircraftRoute || m_ERRTestAircraftField) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 330000;' in text
assert 'OnStop().Insert(ERR_StopHQPatrol);' in text
assert 'if (m_ERRAircraftRoute) m_ERRAircraftRoute.Stop();' in body(chat,'protected void ERR_StopHQPatrol(')
assert 'ERR_StopHQPatrol();' in body(chat,'protected bool ERR_Check(')
@@ -153,11 +153,11 @@ int main(){
# The sole fixture getter observes the service's original retained controller.
getter=body(P,'ERR_AircraftRouteFlight ERR_NativeOriginalFlight(')
run(r'''#include <cassert>
struct Flight{};struct Entry {Flight* FlightPtr=nullptr;};
struct BaseFlight {virtual ~BaseFlight()=default;};struct Flight:BaseFlight{};struct FieldFlight:BaseFlight{};struct Entry {BaseFlight* FlightPtr=nullptr;};
struct Observe {Entry* entry=nullptr;Entry* Find(int){return entry;}
''' + getter.replace('ERR_AircraftRouteFlight ', 'Flight* ').replace('Vehicle aircraft','int aircraft').replace('ERR_AircraftServiceEntry entry','Entry* entry').replace('entry.Flight','entry->FlightPtr').replace('return null;','return nullptr;') + r'''
''' + getter.replace('ERR_AircraftRouteFlight.Cast(entry.Flight)', 'dynamic_cast<Flight*>(entry.Flight)').replace('ERR_AircraftRouteFlight ', 'Flight* ').replace('Vehicle aircraft','int aircraft').replace('ERR_AircraftServiceEntry entry','Entry* entry').replace('entry.Flight','entry->FlightPtr').replace('null','nullptr') + r'''
};
int main(){Observe p;assert(!p.ERR_NativeOriginalFlight(1));Flight flight;Entry e;e.FlightPtr=&flight;p.entry=&e;assert(p.ERR_NativeOriginalFlight(1)==&flight);}
int main(){Observe p;assert(!p.ERR_NativeOriginalFlight(1));Flight flight;Entry e;e.FlightPtr=&flight;p.entry=&e;assert(p.ERR_NativeOriginalFlight(1)==&flight);FieldFlight field;e.FlightPtr=&field;assert(!p.ERR_NativeOriginalFlight(1));}
''')
assert 'm_Service.Drained() && !m_Service.Pending(m_Aircraft) && !m_Service.Retired(m_Aircraft)' in tick
assert 'if (!RetainedIdentity())' in tick
+52 -8
View File
@@ -5,14 +5,14 @@ P=ROOT/'addons/ai/Scripts/Game/ERR/ERR_AircraftService.c'
def cpp(s):
s=s.replace('protected ','').replace('return null;', 'return nullptr;')
for a,b in [('ERR_AircraftServiceEntry entry','Entry* entry'),('ERR_AircraftServiceEntry known','Entry* known'),('new ERR_AircraftServiceEntry()','new Entry()'),('ERR_AircraftRouteEnvelope qualification','Qualification* qualification'),('new ERR_AircraftRouteFlight()','new Flight()'),('ERR_GameMode mode','Mode* mode'),('ERR_GameMode.Get()','Mode::Get()'),('ERR_AircraftFlightRules.','Rules::'),('ERR_AircraftFlightState.','State::'),('Replication.IsServer()','server'),('string reason','std::string reason'),('vector destination = "0 0 0"','Vec destination')]:s=s.replace(a,b)
for a,b in [('ERR_AircraftServiceEntry entry','Entry* entry'),('ERR_AircraftServiceEntry known','Entry* known'),('new ERR_AircraftServiceEntry()','new Entry()'),('ERR_AircraftRouteEnvelope qualification','Qualification* qualification'),('ERR_AircraftRouteFlight flight','RouteFlight* flight'),('ERR_AircraftFieldFlight flight','FieldFlight* flight'),('ERR_AircraftFieldEnvelope qualification','FieldQualification* qualification'),('new ERR_AircraftRouteFlight()','new RouteFlight()'),('new ERR_AircraftFieldFlight()','new FieldFlight()'),('ERR_GameMode mode','Mode* mode'),('ERR_GameMode.Get()','Mode::Get()'),('ERR_AircraftFlightRules.','Rules::'),('ERR_AircraftFlightState.','State::'),('Replication.IsServer()','server'),('string reason','std::string reason'),('vector destination = "0 0 0"','Vec destination')]:s=s.replace(a,b)
s=re.sub(r'foreach \(Entry\* (entry|known) : m_Entries\)',r'for (Entry* \1 : m_Entries.values)',s)
for name in ('entry','known','qualification','GetGame()','m_Mode','m_World','s_Retainers','entry.Flight'):
for name in ('entry','known','qualification','GetGame()','m_Mode','m_World','s_Retainers','entry.Flight','flight'):
s=s.replace(name+'.',name+'->')
s=s.replace('entry->Flight.','entry->Flight->').replace('entry->Reason.IsEmpty()','entry->Reason.empty()')
return s.replace('ERR_AircraftServiceEntry Find(', 'Entry* Find(').replace('ERR_AircraftServiceEntry entry)', 'Entry* entry)').replace('vector Destination(', 'Vec Destination(').replace('string Reason(', 'std::string Reason(')
return s.replace('ERR_AircraftServiceEntry BeginAttempt(', 'Entry* BeginAttempt(').replace('ERR_AircraftServiceEntry Find(', 'Entry* Find(').replace('ERR_AircraftServiceEntry entry)', 'Entry* entry)').replace('vector Destination(', 'Vec Destination(').replace('string Reason(', 'std::string Reason(')
keys=['protected bool Context(', 'protected ERR_AircraftServiceEntry Find(', 'bool Register(', 'bool Ready(', 'bool StartRoute(', 'protected void Finish(', 'bool Cancel(', 'void Tick(', 'void Stop(', 'protected void ReleaseRetainers(', 'bool Drained(', 'bool Stopping(', 'int RegisteredCount(', 'int AttemptedOrders(', 'bool Pending(', 'bool Retired(', 'int OrderToken(', 'float OrderDeadline(', 'int FlightState(', 'string Reason(', 'vector Destination(']
keys=['protected bool Context(', 'protected ERR_AircraftServiceEntry Find(', 'bool Register(', 'bool Ready(', 'protected ERR_AircraftServiceEntry BeginAttempt(', 'bool StartRoute(', 'bool StartField(', 'protected bool RecordStart(', 'protected void Finish(', 'bool Cancel(', 'void Tick(', 'void Stop(', 'protected void ReleaseRetainers(', 'bool Drained(', 'bool Stopping(', 'int RegisteredCount(', 'int AttemptedOrders(', 'bool Pending(', 'bool Retired(', 'int OrderToken(', 'float OrderDeadline(', 'int FlightState(', 'string Reason(', 'vector Destination(']
run(r'''
#include <cassert>
#include <map>
@@ -30,8 +30,10 @@ struct Rules {static bool Deadline(float now,float deadline){return now>=0&&dead
struct Entity {bool original=true,ready=true;int startMode=0,next=State::HOOKS;};
using Vehicle=Entity*;using SCR_AIGroup=Entity*;using IEntity=Entity*;using PilotCompartmentSlot=Entity*;
struct Qualification {bool claimed=false;Vec target={20,0,0};Vec Destination(){return target;}};
int starts=0,claims=0,ticks=0,cancels=0;
struct FieldQualification:Qualification {FieldQualification(){target={100,0,0};}};
int starts=0,claims=0,ticks=0,cancels=0,routeConstructions=0,fieldConstructions=0;
struct Flight {
virtual ~Flight()=default;int kind=0;
Entity* aircraft=nullptr;int token=0,state=State::IDLE;float deadline=0;bool cancelled=false;std::string reason;
bool Finished(){return state>=State::COMPLETE&&state<=State::RELINQUISHED;}
bool StartRoute(Mode*,Vehicle a,SCR_AIGroup,IEntity,PilotCompartmentSlot,Qualification* q,int t,float d){
@@ -46,6 +48,8 @@ struct Flight {
void Tick(){ticks++;if(!aircraft->original||!Mode::Get()||!Mode::Get()->active){state=State::RELINQUISHED;return;}if(world.now>=deadline){state=State::FAILED;return;}state=aircraft->next;}
int State(){return state;}int OrderToken(){return token;}std::string Reason(){return reason;}
};
struct RouteFlight:Flight {RouteFlight(){kind=1;routeConstructions++;}};
struct FieldFlight:Flight {FieldFlight(){kind=2;fieldConstructions++;}bool StartField(Mode* m,Vehicle a,SCR_AIGroup g,IEntity p,PilotCompartmentSlot s,FieldQualification* q,int t,float d){return StartRoute(m,a,g,p,s,q,t,d);}};
struct Entry {Vehicle Aircraft=nullptr;SCR_AIGroup Group=nullptr;IEntity Pilot=nullptr;PilotCompartmentSlot Seat=nullptr;struct Flight* Flight=nullptr;int Token=0;float Deadline=0;Vec Destination;bool Pending=false,CancelRequested=false,Retired=false;std::string Reason;};
struct Entries {std::vector<Entry*> values;int Count(){return values.size();}void Insert(Entry* e){values.push_back(e);}};
struct Tokens {std::set<int> values;int Count(){return values.size();}bool Contains(int n){return values.count(n);}void Insert(int n){values.insert(n);}};
@@ -61,7 +65,7 @@ struct Service {
};Registry* Service::s_Retainers=&registry;
struct Setup {
Mode mode;Entity a[5],g[5],p[5],seat[5];Service service;Qualification q;
Setup():service(&mode){registry.values.clear();server=true;gamePointer=&game;game.current=&world;world.now=1000;Mode::current=&mode;starts=claims=ticks=cancels=0;}
Setup():service(&mode){registry.values.clear();server=true;gamePointer=&game;game.current=&world;world.now=1000;Mode::current=&mode;starts=claims=ticks=cancels=routeConstructions=fieldConstructions=0;}
bool Register(int i=0){return service.Register(&a[i],&g[i],&p[i],&seat[i]);}
bool Start(int token=1){return service.StartRoute(&a[0],&q,token,200000);}
};
@@ -88,6 +92,26 @@ int main(){
for(int fail:{1,2,3}){Setup x;assert(x.Register());x.a[0].startMode=fail;assert(!x.Start());assert(starts==1&&x.service.AttemptedOrders()==1&&x.service.Retired(&x.a[0]));assert(!x.Start(2)&&starts==1);if(fail==3){assert(x.service.Pending(&x.a[0])&&cancels==1);assert(!x.service.Cancel(&x.a[0],1,"duplicate"));x.a[0].next=State::CANCELLED;x.service.Tick();assert(x.service.Drained());}else assert(x.service.Drained());}
{Setup x;assert(x.Register());x.q.claimed=true;assert(!x.Start()&&starts==1&&claims==0&&x.service.AttemptedOrders()==1);}
{Setup x;assert(x.Register());for(int i=1;i<=Service::MAX_ORDERS;i++){Qualification q;assert(x.service.StartRoute(&x.a[0],&q,i,200000));x.a[0].next=State::COMPLETE;x.service.Tick();}assert(x.service.AttemptedOrders()==128);Qualification q;assert(!x.service.StartRoute(&x.a[0],&q,129,200000)&&starts==128);}
// Explicit field kind constructs one known controller, records actual target,
// and shares tokens/reservations with the normal route kind.
{Setup x;assert(x.Register()&&x.Register(1));FieldQualification field;assert(x.service.StartField(&x.a[0],&field,10,241000));Entry* e=x.service.Find(&x.a[0]);Flight* original=e->Flight;assert(original->kind==2&&fieldConstructions==1&&routeConstructions==0&&starts==1&&claims==1);assert(x.service.Destination(&x.a[0]).x==100);field.target.x=999;assert(x.service.Destination(&x.a[0]).x==100);
Qualification normal;FieldQualification another;assert(!x.service.StartRoute(&x.a[0],&normal,11,200000));assert(!x.service.StartRoute(&x.a[1],&normal,10,200000));assert(!x.service.StartField(&x.a[1],&another,10,241000));assert(starts==1&&claims==1);
assert(x.service.Cancel(&x.a[0],10,"field radio loss")&&!x.service.Cancel(&x.a[0],10,"duplicate"));assert(e->Flight==original&&x.service.Pending(&x.a[0])&&cancels==1&&x.service.OrderDeadline(&x.a[0])==241000);
x.a[0].next=State::LANDING;x.service.Tick();assert(e->Flight==original&&x.service.Pending(&x.a[0]));x.a[0].next=State::CANCELLED;x.service.Tick();assert(x.service.Drained()&&!x.service.Retired(&x.a[0]));
assert(!x.service.StartRoute(&x.a[0],&normal,10,200000));assert(x.service.StartRoute(&x.a[0],&normal,11,200000));assert(x.service.Find(&x.a[0])->Flight->kind==1&&routeConstructions==1&&claims==2);
}
// Tokens attempted by normal routes cannot replay as field routes even on
// another ready asset after physical completion.
{Setup x;assert(x.Register()&&x.Register(1)&&x.Start(21));x.a[0].next=State::COMPLETE;x.service.Tick();FieldQualification q;assert(!x.service.StartField(&x.a[1],&q,21,241000)&&fieldConstructions==0);assert(x.service.StartField(&x.a[1],&q,22,241000)&&claims==2);}
{Setup x;assert(x.Register());FieldQualification q;assert(!x.service.StartField(&x.a[0],nullptr,1,241000));assert(!x.service.StartField(&x.a[0],&q,0,241000));assert(!x.service.StartField(&x.a[0],&q,1,NAN));assert(starts==0&&claims==0&&x.service.AttemptedOrders()==0&&fieldConstructions==0);}
// Failed field admission/claim uses exactly the existing retirement and
// retained partial-start protocol. Changing kind cannot create a retry.
for(int fail:{1,2,3}){Setup x;assert(x.Register()&&x.Register(1));x.a[0].startMode=fail;FieldQualification q;assert(!x.service.StartField(&x.a[0],&q,31,241000));assert(x.service.Retired(&x.a[0])&&x.service.AttemptedOrders()==1&&fieldConstructions==1);Qualification route;assert(!x.service.StartRoute(&x.a[0],&route,32,200000)&&!x.service.StartRoute(&x.a[1],&route,31,200000));if(fail==3){Flight* retained=x.service.Find(&x.a[0])->Flight;assert(x.service.Pending(&x.a[0])&&cancels==1);x.a[0].next=State::LANDING;x.service.Tick();assert(x.service.Find(&x.a[0])->Flight==retained&&x.service.Pending(&x.a[0]));x.a[0].next=State::CANCELLED;x.service.Tick();}assert(x.service.Drained()&&x.service.Retired(&x.a[0]));}
{Setup x;assert(x.Register());FieldQualification q;q.claimed=true;assert(!x.service.StartField(&x.a[0],&q,41,241000)&&starts==1&&claims==0&&x.service.Retired(&x.a[0]));}
// Stop drains both kinds together; no early release or additional starts.
{Setup x;assert(x.Register()&&x.Register(1)&&x.Start(51));FieldQualification q;assert(x.service.StartField(&x.a[1],&q,52,241000));Flight* field=x.service.Find(&x.a[1])->Flight;x.service.Stop();x.service.Stop();assert(cancels==2&&x.service.Stopping()&&!x.service.Drained());x.a[0].next=State::CANCELLED;x.a[1].next=State::LANDING;x.service.Tick();assert(!x.service.Drained()&&registry.Get(&x.a[0])==&x.service&&x.service.Find(&x.a[1])->Flight==field);x.a[1].next=State::CANCELLED;x.service.Tick();assert(x.service.Drained()&&registry.values.empty()&&!x.service.Ready(&x.a[1]));}
// One bounded attempt budget covers both kinds without independent counters.
{Setup x;assert(x.Register());for(int i=1;i<=Service::MAX_ORDERS;i++){if(i%2){FieldQualification q;assert(x.service.StartField(&x.a[0],&q,i,241000));}else{Qualification q;assert(x.service.StartRoute(&x.a[0],&q,i,200000));}x.a[0].next=State::COMPLETE;x.service.Tick();}FieldQualification q;assert(!x.service.StartField(&x.a[0],&q,129,241000)&&starts==128&&claims==128&&routeConstructions==64&&fieldConstructions==64);}
}
''')
source=P.read_text()
@@ -98,8 +122,28 @@ for required in ('entry.Seat.GetOccupant() != entry.Pilot','controller.GetPilotC
assert required in original
for required in ('!Original(entry)','!ERR_Actors.CanFight(entry.Pilot)','!fuel.HasFuel()','!damage.GetEngineFunctional()','simulation.RotorGetState(rotor) == RotorState.DESTROYED','!simulation.HasAnyGroundContact()','!physics.IsActive()','controller.IsEngineOn()'):
assert required in ready
assert source.count('new ERR_AircraftRouteFlight()')==1
assert source.count('new ERR_AircraftRouteFlight()')==1 and source.count('new ERR_AircraftFieldFlight()')==1
assert 'ref ERR_AircraftFlight Flight;' in source
assert source.count('m_UsedTokens.Insert(token)')==1
for signature in ('bool StartRoute(', 'bool StartField('):
method=body(P,signature)
assert method.index('if (!qualification) return false;')<method.index('BeginAttempt(')<method.index('entry.Flight = flight; entry.Pending = true;')<method.index('bool started = flight.')<method.index('RecordStart(entry, started)')
assert '.Claim(' not in source and 'QualifyRoute(' not in source
for prohibited in ('Spawn(', 'Delete(', 'DeleteSpawned(', 'SetOrigin(', 'SetWorldTransform(', 'SetVelocity(', 'SetActive(', 'SetFuel(', 'SetHealth(', 'PrepareActions(', 'PrepareHelicopter(', 'CallLater(', 'GetPlayerCount() == 0'):
assert prohibited not in source
print('PASS: actual finite registration, unique claims/tokens, retention/cancel/stop, readiness gating, capture/expiry retirement and bounded order history; native boundaries faked')
print('PASS: actual finite registration, unique claims/tokens, retention/cancel/stop, readiness gating, capture/expiry retirement and bounded cross-kind order history and typed field/route construction; native boundaries faked')
# The existing route probe sees only its exact subtype; a field controller can
# never be relabeled as route evidence through the shared retained base pointer.
route_probe=ROOT/'addons/ai/Scripts/Game/ERR/ERR_NativeAircraftRouteProbe.c'
getter=body(route_probe,'ERR_AircraftRouteFlight ERR_NativeOriginalFlight(')
getter=getter.replace('ERR_AircraftRouteFlight.Cast(entry.Flight)','dynamic_cast<Route*>(entry->Flight)').replace('ERR_AircraftRouteFlight ','Route* ').replace('ERR_AircraftServiceEntry entry','Entry* entry').replace('Vehicle aircraft','int aircraft').replace('null','nullptr')
run(r'''#include <cassert>
struct Base {virtual ~Base()=default;};struct Route:Base{};struct Field:Base{};
struct Entry {Base* Flight=nullptr;};
struct Observer {Entry* entry=nullptr;Entry* Find(int){return entry;}
'''+getter+r'''
};
int main(){Observer o;assert(!o.ERR_NativeOriginalFlight(1));Entry e;o.entry=&e;assert(!o.ERR_NativeOriginalFlight(1));Route route;e.Flight=&route;assert(o.ERR_NativeOriginalFlight(1)==&route);Field field;e.Flight=&field;assert(!o.ERR_NativeOriginalFlight(1));}
''')
print('PASS: actual WORKBENCH route observer returns exact retained route subtype and refuses absent/field controllers')
+45 -3
View File
@@ -631,13 +631,14 @@ class ApproachTests(unittest.TestCase):
'baseline_samples':[20,21],'response_sample':22,'return_sample':23,
'motions':[{'dx':24,'dy':0,'monotonic':10.1,'after_sample':21,'after_real_ms':3310},
{'dx':-24,'dy':0,'monotonic':10.3,'after_sample':22,'after_real_ms':3420}]}
report={'look_proof':look,'events':[]};keyboard=FakeKeyboard(observer);notifications=[];motions=[]
report={'look_proof':look,'events':[],'coarse_approach_requested':True};keyboard=FakeKeyboard(observer);notifications=[];motions=[]
mouse=SimpleNamespace(approach_move=lambda *a,**k:motions.append((a,k)))
def module(guard,k,m,observe,geometry,calibration,r,**kwargs):
self.assertEqual(calibration['ready_monotonic'],10)
self.assertEqual(calibration['ready_real_ms'],3091)
self.assertEqual(calibration['observer_start_real_ms'],1000)
self.assertEqual(kwargs['key_state_busy'],proof.KeyStateBusy)
self.assertEqual(kwargs['profile'],'coarse')
self.assertEqual(observe()[-1]['geometry'],geometry)
self.assertEqual(geometry['nonce'],NONCE)
with self.assertRaises(proof.Refused):k.send('F',True)
@@ -999,7 +1000,7 @@ class SelectionIntegration(unittest.TestCase):
def test_actual_main_wires_selection_only_to_observer_and_sequence(self):
with tempfile.TemporaryDirectory() as folder:
root=Path(folder);args=SimpleNamespace(nonce='N',prefix=root/'pfx',profile=root/'profile',game_executable=root/'game',host_display=':0',report=root/'report.json',console=root/'console.log',command=[],enable_private_look_proof=True,enable_private_approach_proof=True,enable_private_alignment_proof=True,enable_private_selection_proof=True)
root=Path(folder);args=SimpleNamespace(nonce='N',prefix=root/'pfx',profile=root/'profile',game_executable=root/'game',host_display=':0',report=root/'report.json',console=root/'console.log',command=[],enable_private_look_proof=True,enable_private_approach_proof=True,enable_private_alignment_proof=True,enable_private_selection_proof=True,enable_private_coarse_approach_proof=True)
owner={'window':1,'pid':2,'server_pid':2,'process':{'pid':2}}
query=SimpleNamespace(windows=lambda:[{}],focus=lambda:(1,0),close=lambda:None)
observer=SimpleNamespace(identity=(1,2),start_offset=0,offset=0,raw=[],events=[],look_records=[],gate_records=[],close=lambda:None)
@@ -1021,7 +1022,8 @@ class SelectionIntegration(unittest.TestCase):
with patch.object(proof,'arguments',return_value=args),patch.object(proof,'wrapper_command',return_value=[]),patch.object(proof,'console_path'),patch.object(proof.display,'validate_paths'),patch.object(proof.display,'binding',return_value={'display':':2'}),patch.object(proof.display,'X11',return_value=query),patch.object(proof.display,'owned_window',return_value=owner),patch.object(proof.display,'input_target',return_value={'verified':True}),patch.object(proof,'Observer',autospec=True,return_value=observer) as ob,patch.object(proof,'Guard',autospec=True),patch.object(proof,'Keyboard',autospec=True),patch.object(proof,'RelativeMouse',autospec=True) as mouse,patch.object(proof,'sequence',side_effect=sequence),patch.object(proof.signal,'signal'):
self.assertEqual(proof.main([]),0)
self.assertTrue(ob.call_args.kwargs['enable_selection']);self.assertNotIn('selection',mouse.call_args.kwargs)
archived=json.loads(args.report.read_text());self.assertTrue(archived['selection_requested']);self.assertEqual(archived['wheel_events'],[])
self.assertTrue(mouse.call_args.kwargs['enable_coarse_approach']);self.assertNotIn('coarse_approach',ob.call_args.kwargs)
archived=json.loads(args.report.read_text());self.assertTrue(archived['coarse_approach_requested']);self.assertTrue(archived['selection_requested']);self.assertEqual(archived['wheel_events'],[])
def test_one_native_wheel_pair_and_mask_roundtrip_no_retries(self):
r=WheelRig();w=r.wheel();notices=[]
@@ -1173,4 +1175,44 @@ class SelectionIntegration(unittest.TestCase):
self.assertEqual(proof.supervise([]),1);self.assertEqual(child.terminated,1)
self.assertEqual(failed.call_args.kwargs['button'],5);self.assertIsNone(failed.call_args.args[1]);stop.assert_called_once()
class CoarseApproachTests(unittest.TestCase):
def test_opt_in_requires_approach_and_default_is_unchanged(self):
argv=['--enable-private-keyboard-proof','--prefix','/tmp/pfx','--game-executable','/tmp/game','--profile','/tmp/profile','--report','/tmp/report','--host-display',':0','--nonce','N']
self.assertFalse(proof.arguments(argv).enable_private_coarse_approach_proof)
with self.assertRaises(SystemExit),patch('sys.stderr'):proof.arguments(argv+['--enable-private-coarse-approach-proof'])
args=proof.arguments(argv+['--enable-private-coarse-approach-proof','--enable-private-approach-proof','--enable-private-look-proof'])
self.assertTrue(args.enable_private_coarse_approach_proof)
with self.assertRaisesRegex(proof.Refused,'Coarse approach'):
proof.sequence(FakeGuard(),FakeKeyboard(FakeObserver()),FakeObserver(),{},coarse_approach=True)
with self.assertRaisesRegex(proof.Refused,'Coarse approach'):
proof.sequence(FakeGuard(),FakeKeyboard(FakeObserver()),FakeObserver(),{'coarse_approach_requested':True},approach=True)
def test_coarse_mouse_preserves_actual_private_pointer_focus_and_deadline_checks(self):
class Fn:
def __init__(self,fn):self.fn=fn
def __call__(self,*a):return self.fn(*a)
events=[];xt=SimpleNamespace(XTestFakeRelativeMotionEvent=Fn(lambda *a:events.append(a) or 1))
query=SimpleNamespace(d=1,x=SimpleNamespace(XQueryPointer=Fn(lambda *a:1),XFlush=lambda d:None),server_pid=lambda child:9)
keyboard=SimpleNamespace(xt=xt,require_idle=lambda:None);clock=Clock()
target={'geometry':{'root':1},'focused_clipped_bounds':(0,0,1000,720)}
checks=[];guard=SimpleNamespace(check=lambda:checks.append(True) or target)
mouse=proof.RelativeMouse(query,keyboard,{'window':100,'pid':9},enable_approach=True,enable_coarse_approach=True)
mouse.pointer=lambda:{'root':1,'child':0,'root_x':500,'root_y':300,'mask':0}
for dx in (384,-384):mouse.approach_move(dx,0,guard,deadline=35,clock=clock)
self.assertEqual(len(events),2);self.assertEqual(len(checks),4)
for dx,dy in ((385,0),(300,1),(True,0)):
with self.assertRaises(proof.Refused):mouse.approach_move(dx,dy,guard,deadline=35,clock=clock)
mouse.enable_coarse_approach=False
with self.assertRaises(proof.Refused):mouse.approach_move(300,0,guard,deadline=35,clock=clock)
mouse.enable_coarse_approach=True
mouse.pointer=lambda:{'root':1,'child':0,'root_x':900,'root_y':300,'mask':0}
with self.assertRaisesRegex(proof.Refused,'clipped'):mouse.approach_move(300,0,guard,deadline=35,clock=clock)
mouse.pointer=lambda:{'root':1,'child':77,'root_x':500,'root_y':300,'mask':0};query.server_pid=lambda child:10
with self.assertRaisesRegex(proof.Refused,'foreign'):mouse.approach_move(300,0,guard,deadline=35,clock=clock)
mouse.pointer=lambda:{'root':1,'child':0,'root_x':500,'root_y':300,'mask':0}
def late():clock.now=35;return target
with self.assertRaisesRegex(proof.Refused,'deadline'):mouse.approach_move(300,0,SimpleNamespace(check=late),deadline=35,clock=clock)
self.assertEqual(len(events),2)
with self.assertRaisesRegex(proof.Refused,'capability'):proof.RelativeMouse(query,keyboard,{'window':100,'pid':9},enable_coarse_approach=True)
if __name__ == '__main__': unittest.main()
+19 -4
View File
@@ -3,7 +3,7 @@ import re
from policy_harness import ROOT, body, run
P=ROOT/'addons/core/Scripts/Game/ERR/ERR_OrdinaryGateObserver.c'
def cpp(s):
s=s.replace('protected ', '').replace('out float ', 'float& ').replace('out bool ', 'bool& ')
s=s.replace('protected ', '').replace('out bool held', 'bool& held').replace('out float ', 'float& ').replace('out bool ', 'bool& ')
types=('ERR_GameMode','BaseWorld','PlayerController','IEntity','ERR_PlayerLife','PlayerManager','BaseUserAction','ERR_UnlockPrisonAction','UserActionContext')
for typ in types:s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
s=s.replace('array<BaseUserAction>', 'Array<BaseUserAction*>').replace('array<bool>', 'Array<bool>')
@@ -19,8 +19,10 @@ def cpp(s):
s=s.replace(scope+'.',scope+'::')
for getter in ('GetGame','Players','PrisonEscape','GetInputManager','GetMenuManager'):
s=s.replace(getter+'().',getter+'()->')
s=s.replace('nonce.Length()', 'int(nonce.size())').replace('allowed.Contains(nonce[i])', '(allowed.find(nonce[i]) != string::npos)')
return s.replace('null','nullptr').replace('string.Format(', 'Format(')
accessor=cpp(body(P,'bool ERR_GateReadProgress('))
shared='\n'.join(cpp(body(ROOT/'addons/core/Scripts/Game/ERR/ERR_OrdinaryInputObserver.c', signature)) for signature in ('static bool Nonce(', 'static bool HeldScope(', 'static bool PhaseAllowed('))
methods='\n'.join(cpp(body(P,s)) for s in ('protected static bool Enabled(', 'static void Sample(', 'protected bool BindGate(', 'protected bool Context(', 'protected void Cancel(', 'protected bool CachedPermission(', 'protected static bool FiniteVector(', 'protected static string ChoiceIdentity(', 'protected void ObserveChoices(', 'protected void Observe('))
run(r'''
#include <cassert>
@@ -51,7 +53,7 @@ struct PlayerManager {PlayerController* controller=nullptr;IEntity* actor=nullpt
struct ERR_PlayerLife {UUID AccountId="original-account";};
struct Roster {ERR_PlayerLife* life=nullptr;IEntity* actor=nullptr;ERR_PlayerLife* CurrentActor(IEntity* value){return value==actor?life:nullptr;}};
struct Prison {IEntity* gate=nullptr;bool registered=true,lost=false;bool Registered(){return registered;}bool GateLost(){return lost;}IEntity* Gate(){return gate;}};
namespace ERR_Phase {enum {PRISON,RADIO_SEARCH,EXTRACTION};}
namespace ERR_Phase {enum {PRISON=1,RADIO_SEARCH=2,EXTRACTION=4};}
struct ERR_GameMode {static ERR_GameMode* current;bool active=true;int phase=ERR_Phase::PRISON;BaseWorld* world=nullptr;Roster* roster=nullptr;Prison* prison=nullptr;static ERR_GameMode* Get(){return current;}bool Active(){return active;}int Phase(){return phase;}BaseWorld* GetWorld(){return world;}Roster* Players(){return roster;}Prison* PrisonEscape(){return prison;}vector prisonPosition=Vector(6090,210,7160);vector PrisonPosition(){return prisonPosition;}};ERR_GameMode* ERR_GameMode::current=nullptr;
struct InputManager {float pressed=0;ERR_GameMode* changeDuringRead=nullptr;float GetActionValue(string name){assert(name=="PerformAction");if(changeDuringRead)changeDuringRead->active=false;return pressed;}};
struct MenuManager {bool open=false;void* GetTopMenu(){return open?this:nullptr;}};
@@ -59,7 +61,9 @@ struct Game {BaseWorld* world=nullptr;PlayerController* controller=nullptr;Playe
namespace System {bool console=false;std::map<string,string> cli;bool IsConsoleApp(){return console;}bool GetCLIParam(string key,string& value){if(!cli.count(key))return false;value=cli[key];return true;}}
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace RplMode {enum {None,Listen,Dedicated};}namespace RplSession {int mode=RplMode::None;int Mode(){return mode;}}
namespace ERR_OrdinaryInputObserverRules {bool Nonce(string n){return !n.empty()&&n.size()<=32&&n.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")==string::npos;}}
struct ERR_OrdinaryInputObserverRules {
'''+shared+r'''
};
namespace ERR_Actors {bool CanFight(IEntity* actor){return actor&&actor->living;}}
namespace CompartmentAccessComponent {IEntity* GetVehicleIn(IEntity* actor){return actor->vehicle?actor:nullptr;}}
int headers=0,samples=0,geometries=0,cancels=0,choices=0;string lastChoice;string reason;bool lastSelected=false,lastContext=false,lastPermitted=false,lastBound=false,lastPerforming=false;float lastProgress=0;int lastPhase=0;
@@ -74,7 +78,7 @@ void PrintFormat(string format,string,int,int,string value){if(format.find("ERR
class ERR_OrdinaryGateObserver {public:
static ERR_OrdinaryGateObserver* s_Request;static bool s_Attempted;
ERR_GameMode* m_Mode=nullptr;BaseWorld* m_World=nullptr;PlayerController* m_Controller=nullptr;IEntity* m_Actor=nullptr;ERR_PlayerLife* m_Life=nullptr;UUID m_Account;IEntity* m_Gate=nullptr;BaseActionsManagerComponent* m_Manager=nullptr;ERR_UnlockPrisonAction* m_Lock=nullptr;UserActionContext* m_Context=nullptr;BaseDoorComponent* m_Door=nullptr;SCR_InteractionHandlerComponent* m_Handler=nullptr;
string m_Nonce;int m_Start=0,m_Previous=0,m_Index=0;bool m_Stopped=false;
string m_Nonce;int m_Start=0,m_Previous=0,m_Index=0;bool m_Stopped=false,m_HeldAction=false;int m_ObservedPhase=-1;
'''+methods+r'''
};
ERR_OrdinaryGateObserver* ERR_OrdinaryGateObserver::s_Request=nullptr;bool ERR_OrdinaryGateObserver::s_Attempted=false;
@@ -84,6 +88,17 @@ struct Fixture {BaseWorld world,otherWorld;ERR_GameMode mode;IEntity actor,other
};
int main(){
// Held mode changes only admissible phase, never the original pins or clock.
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();auto p=ERR_OrdinaryGateObserver::s_Request;assert(p&&p->m_HeldAction&&p->m_ObservedPhase==ERR_Phase::PRISON);f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(2,1200);f.sample(3,1300);assert(samples==3&&!cancels&&p->m_Start==1000&&p->m_ObservedPhase==ERR_Phase::RADIO_SEARCH);f.mode.phase=ERR_Phase::PRISON;f.sample(4,1400);assert(samples==3&&cancels==1);f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(5,1500);assert(samples==3&&cancels==1);}
for(int fault=0;fault<5;fault++){Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; if(fault==0)System::cli["errOrdinaryHeldAction"]="foreign";if(fault==1)System::cli.erase("errOrdinaryLookObserve");if(fault==2)System::cli["errOrdinaryLookObserve"]="foreign";if(fault==3)f.mode.phase=ERR_Phase::RADIO_SEARCH;if(fault==4)System::cli["errOrdinaryHeldAction"]="";f.sample();assert(!samples);f.mode.phase=ERR_Phase::PRISON;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample(2,1200);assert(!samples);}
for(int fault=0;fault<8;fault++){Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(2,1200);assert(samples==2&&!cancels);if(fault==0)System::cli.erase("errOrdinaryHeldAction");if(fault==1)System::cli["errOrdinaryLookObserve"]="foreign";if(fault==2)f.actor.vehicle=true;if(fault==3)f.menus.open=true;if(fault==4)f.life.AccountId="replacement";if(fault==5)f.mode.phase=ERR_Phase::EXTRACTION;if(fault==6)f.players.actor=nullptr;if(fault==7)game.world=nullptr;f.sample(3,1300);assert(samples==2&&cancels==1);}
{Fixture f;f.sample();System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample(2,1200);assert(samples==1&&cancels==1);}
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(2,45999);assert(samples==2);f.sample(3,46000);assert(samples==2&&cancels==1);}
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(451,1200);assert(samples==1&&cancels==1);}
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.door.control=1;f.door.state=.4;f.context.origin=Vector(6091,212,7155);f.gate.forward=Vector(1,0,0);f.handler.selected=nullptr;f.handler.current=nullptr;f.sample(2,1200);assert(samples==2&&geometries==2&&!cancels&&!lastSelected&&!lastBound&&lastPhase==ERR_Phase::RADIO_SEARCH);f.manager.context=&f.otherContext;f.sample(3,1300);assert(samples==2&&cancels==1);}
// Legacy gate-only observation retains its prior permitted phase reversal.
{Fixture f;f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(2,1200);f.mode.phase=ERR_Phase::PRISON;f.sample(3,1300);assert(samples==3&&!cancels);}
// Diagnostics are an independent opt-in beneath the same validated observer.
{Fixture f;f.sample();assert(choices==0);System::cli["errOrdinaryGateChoices"]="foreign";f.sample(2,1200);assert(choices==0&&samples==2);}
// A disabled stock door can be selected while the exact lock is cached available.
+42 -7
View File
@@ -2,12 +2,12 @@
from policy_harness import ROOT, body, run
P=ROOT/'addons/core/Scripts/Game/ERR/ERR_OrdinaryInputObserver.c'
def cpp(s):
s=s.replace('protected ', '').replace('out string nonce','string& nonce')
s=s.replace('protected ', '').replace('out bool held', 'bool& held').replace('out string nonce','string& nonce')
for t in ('ERR_GameMode','PlayerController','IEntity','ERR_PlayerLife','ERR_OrdinaryInputObserver','PlayerManager','SCR_InventoryMenuUI','Widget'):
s=s.replace(t+' mode',t+'* mode').replace(t+' controller',t+'* controller').replace(t+' actor',t+'* actor').replace(t+' life',t+'* life').replace(t+' request',t+'* request').replace(t+' current',t+'* current').replace(t+' manager',t+'* manager').replace(t+' menu',t+'* menu').replace(t+' root',t+'* root').replace(t+' expectedMenu',t+'* expectedMenu').replace(t+' expectedRoot',t+'* expectedRoot').replace(t+' overlay',t+'* overlay')
for v in ('mode','controller','actor','request','life','current','manager','menu','root','m_Mode','m_World','m_Controller','m_Actor','m_Character','m_Menu','s_Request','expectedMenu','expectedRoot','m_CaptureMenu','m_CaptureRoot','overlay','m_aHitZonePoints'):
s=s.replace(v+'.',v+'->')
for scope in ('System','Replication','ERR_Phase','ERR_GameMode','ERR_Actors','SCR_InventoryMenuUI','ERR_OrdinaryInputObserverRules','ERR_OrdinaryLookObserver','ERR_OrdinaryGateObserver','vector','Math'):
for scope in ('System','Replication','RplSession','RplMode','CompartmentAccessComponent','ERR_Phase','ERR_GameMode','ERR_Actors','SCR_InventoryMenuUI','ERR_OrdinaryInputObserverRules','ERR_OrdinaryLookObserver','ERR_OrdinaryGateObserver','vector','Math'):
s=s.replace(scope+'.',scope+'::')
s=s.replace('GetGame().','GetGame()->').replace('GetPlayerManager().','GetPlayerManager()->').replace('GetInputManager().','GetInputManager()->').replace('GetMenuManager().','GetMenuManager()->').replace('GetCallqueue().','GetCallqueue()->').replace('Players().','Players()->')
s=s.replace('CharacterControllerComponent.Cast(actor->FindComponent(CharacterControllerComponent))','actor->FindComponent(0)').replace('FindComponent(CharacterControllerComponent)','FindComponent(0)')
@@ -17,9 +17,10 @@ def cpp(s):
s=s.replace('string.Format', 'Format').replace('Widget* overlay;', 'Widget* overlay=null;')
s=s.replace('null','nullptr').replace('bool open;', 'bool open = false;')
s=s.replace('nonce.Length()', 'int(nonce.size())').replace('allowed.Contains(nonce[i])', '(allowed.find(nonce[i]) != string::npos)')
s=s.replace('nonce.Length()', 'int(nonce.size())').replace('allowed.Contains(nonce[i])', '(allowed.find(nonce[i]) != string::npos)')
return s
state=cpp(body(P,'string ERR_InputObserverInventoryState('))
rules='\n'.join(cpp(body(P,x)) for x in ('static bool Nonce(', 'static bool Within(', 'static bool Stationary('))
rules='\n'.join(cpp(body(P,x)) for x in ('static bool Nonce(', 'static bool HeldScope(', 'static bool PhaseAllowed(', 'static bool Within(', 'static bool Stationary('))
methods='\n'.join(cpp(body(P,x)) for x in ('protected static bool Enabled(', 'static void Queue(', 'protected bool Valid(', 'protected void Record(', 'protected void CancelCapture(', 'protected void Capture(', 'protected void CaptureLater(', 'protected void Observe(', 'protected void Sample(', 'protected void End(', 'void Cancel(', 'static void ClearWorld('))
run(r'''
#include <cassert>
@@ -33,10 +34,12 @@ struct vector {float x=0,y=0,z=0;float Length(){return std::sqrt(x*x+y*y+z*z);}s
namespace Math {float AbsFloat(float x){return std::abs(x);}}
namespace System {bool console=false;int now=0;std::map<string,string> cli;std::vector<string> shots;bool IsConsoleApp(){return console;}int GetTickCount(){return now;}bool GetCLIParam(string key,string& value){if(!cli.count(key))return false;value=cli[key];return true;}void MakeScreenshot(string path){shots.push_back(path);}}
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace ERR_Phase {enum {PRISON,PREPARING,DEFEAT};}
namespace ERR_Phase {enum {PREPARING=0,PRISON=1,RADIO_SEARCH=2,DEFEAT=6};}
namespace RplMode {enum {None,Listen,Dedicated};}namespace RplSession {int mode=RplMode::None;int Mode(){return mode;}}
struct BaseWorld {float time=0;float GetWorldTime(){return time;}};
struct CharacterControllerComponent {bool unconscious=false,disabled=false;vector input,velocity;bool IsUnconscious(){return unconscious;}bool GetDisableMovementControls(){return disabled;}vector GetMovementInput(){return input;}vector GetVelocity(){return velocity;}};
struct IEntity {BaseWorld* world=nullptr;CharacterControllerComponent* component=nullptr;bool conscious=true;vector origin;BaseWorld* GetWorld(){return world;}CharacterControllerComponent* FindComponent(int){return component;}vector GetOrigin(){return origin;}};
struct IEntity {BaseWorld* world=nullptr;CharacterControllerComponent* component=nullptr;bool conscious=true,vehicle=false;vector origin;BaseWorld* GetWorld(){return world;}CharacterControllerComponent* FindComponent(int){return component;}vector GetOrigin(){return origin;}};
namespace CompartmentAccessComponent {IEntity* GetVehicleIn(IEntity* actor){return actor->vehicle?actor:nullptr;}}
namespace ERR_Actors {bool CanFight(IEntity* actor){return actor&&actor->conscious;}}
struct PlayerController {int id=1;IEntity* actor=nullptr;int GetPlayerId(){return id;}IEntity* GetControlledEntity(){return actor;}};
struct ERR_PlayerLife {UUID AccountId="private-account";};
@@ -75,14 +78,14 @@ struct ERR_OrdinaryGateObserver {
struct ERR_OrdinaryInputObserver {
static ERR_OrdinaryInputObserver* s_Request;static bool s_Attempted;
ERR_GameMode* m_Mode=nullptr;BaseWorld* m_World=nullptr;PlayerController* m_Controller=nullptr;IEntity* m_Actor=nullptr;CharacterControllerComponent* m_Character=nullptr;ERR_PlayerLife* m_Life=nullptr;
UUID m_Account;int m_PlayerId=0;string m_Nonce;bool m_Screenshots=false,m_Finished=false,m_Ready=false,m_Opened=false,m_Closed=false;SCR_InventoryMenuUI* m_Menu=nullptr;
UUID m_Account;int m_PlayerId=0;string m_Nonce;bool m_HeldAction=false;int m_ObservedPhase=-1;bool m_Screenshots=false,m_Finished=false,m_Ready=false,m_Opened=false,m_Closed=false;SCR_InventoryMenuUI* m_Menu=nullptr;
string m_CaptureSuffix;SCR_InventoryMenuUI* m_CaptureMenu=nullptr;Widget* m_CaptureRoot=nullptr;int m_CaptureDue=0,m_CaptureExpires=0,m_CaptureLoadedAt=-1,m_InventoryStateLines=0;bool m_BaselineCaptureQueued=false,m_OpenCaptureQueued=false,m_ClosedCaptureQueued=false;
int m_Start=0,m_Previous=0,m_Samples=0,m_BaselineSince=-1;vector m_BaselineOrigin;float m_LastWorldTime=0;
'''+methods+r'''
};
ERR_OrdinaryInputObserver* ERR_OrdinaryInputObserver::s_Request=nullptr;bool ERR_OrdinaryInputObserver::s_Attempted=false;
struct Fixture {ERR_GameMode mode;BaseWorld world;PlayerController controller;IEntity actor;CharacterControllerComponent character;ERR_PlayerLife life;Widget widget;SCR_InventoryMenuUI menu;
Fixture(){game={};System::cli={{"errOrdinaryScenario","core"},{"errOrdinaryInputObserve","Run42"},{"errOrdinaryInputScreenshots","1"}};System::now=0;System::console=false;System::shots.clear();Replication::server=true;events.clear();inventoryStates.clear();inventoryReasons.clear();samples=0;ERR_OrdinaryInputObserver::s_Request=nullptr;ERR_OrdinaryInputObserver::s_Attempted=false;SCR_InventoryMenuUI::current=nullptr;ERR_GameMode::current=&mode;mode.world=&world;mode.roster.actor=&actor;mode.roster.life=&life;actor.world=&world;actor.component=&character;controller.actor=&actor;game.world=&world;game.controller=&controller;game.manager.controller=&controller;game.manager.actor=&actor;menu.character=&character;menu.root=&widget;}
Fixture(){game={};System::cli={{"errOrdinaryScenario","core"},{"errOrdinaryInputObserve","Run42"},{"errOrdinaryInputScreenshots","1"}};System::now=0;System::console=false;System::shots.clear();Replication::server=true;RplSession::mode=RplMode::None;events.clear();inventoryStates.clear();inventoryReasons.clear();samples=0;ERR_OrdinaryInputObserver::s_Request=nullptr;ERR_OrdinaryInputObserver::s_Attempted=false;SCR_InventoryMenuUI::current=nullptr;ERR_GameMode::current=&mode;mode.world=&world;mode.roster.actor=&actor;mode.roster.life=&life;actor.world=&world;actor.component=&character;controller.actor=&actor;game.world=&world;game.controller=&controller;game.manager.controller=&controller;game.manager.actor=&actor;menu.character=&character;menu.root=&widget;}
ERR_OrdinaryInputObserver* queue(){ERR_OrdinaryInputObserver::Queue(&mode,&controller,&actor);return ERR_OrdinaryInputObserver::s_Request;}
void sample(int ms){System::now=ms;world.time=ms;ERR_OrdinaryInputObserver::s_Request->Sample();}
void capture(int ms){System::now=ms;world.time=ms;ERR_OrdinaryInputObserver::s_Request->CaptureLater();}
@@ -92,6 +95,38 @@ struct Fixture {ERR_GameMode mode;BaseWorld world;PlayerController controller;IE
};
int count(string value){int n=0;for(auto event:events)if(event==value)n++;return n;}
int main(){
// Execute the actual shared option/phase rules and actual queued lifecycle.
for(int fault=0;fault<9;fault++){Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42";
if(fault==0)System::cli["errOrdinaryHeldAction"]="foreign";
if(fault==1)System::cli.erase("errOrdinaryLookObserve");
if(fault==2)System::cli["errOrdinaryLookObserve"]="foreign";
if(fault==3)System::cli.erase("errOrdinaryGateObserve");
if(fault==4)System::cli["errOrdinaryGateObserve"]="foreign";
if(fault==5)f.mode.phase=ERR_Phase::RADIO_SEARCH;
if(fault==6)RplSession::mode=RplMode::Listen;
if(fault==7)RplSession::mode=RplMode::Dedicated;
if(fault==8)System::cli["errOrdinaryHeldAction"]="";
assert(!f.queue()&&game.queue.queued==0);assert(ERR_OrdinaryInputObserver::s_Attempted==(fault==5));
if(fault==5){f.mode.phase=ERR_Phase::PRISON;assert(!f.queue());}}
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42";
auto p=f.queue();assert(p&&p->m_HeldAction&&p->m_ObservedPhase==ERR_Phase::PRISON);f.sample(100);f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(200);f.sample(300);assert(samples==3&&!p->m_Finished&&p->m_ObservedPhase==ERR_Phase::RADIO_SEARCH&&p->m_Start==0);
f.mode.phase=ERR_Phase::PRISON;f.sample(400);assert(samples==3&&p->m_Finished);f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(500);assert(samples==3);ERR_OrdinaryInputObserver::ClearWorld();assert(!f.queue());}
for(int fault=0;fault<10;fault++){Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42";auto p=f.queue();f.sample(100);f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(200);
if(fault==0)System::cli.erase("errOrdinaryHeldAction");
if(fault==1)System::cli["errOrdinaryGateObserve"]="foreign";
if(fault==2)f.open();
if(fault==3)f.actor.vehicle=true;
if(fault==4)f.life.AccountId="replacement";
if(fault==5)f.mode.phase=ERR_Phase::DEFEAT;
if(fault==6)f.character.unconscious=true;
if(fault==7)game.world=nullptr;
if(fault==8)game.manager.actor=nullptr;
if(fault==9)p->m_Samples=450;
f.sample(300);assert(p->m_Finished&&samples==2);}
{Fixture f;auto p=f.queue();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(100);assert(p->m_Finished&&samples==0);}
{Fixture f;auto p=f.queue();System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42";f.sample(100);assert(p->m_Finished&&samples==0);}
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42";auto p=f.queue();f.sample(100);f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(44999);assert(samples==2&&p->m_Start==0);f.sample(45000);assert(samples==2&&p->m_Finished);}
assert(ERR_OrdinaryInputObserverRules::Nonce("aZ09"));for(string n:std::vector<string>{"","../bad","a-b","a_b","line\n","a b","$logs:x",string(33,'x')})assert(!ERR_OrdinaryInputObserverRules::Nonce(n));
assert(ERR_OrdinaryInputObserverRules::Within(0,44900,44999,449));assert(!ERR_OrdinaryInputObserverRules::Within(0,44900,45000,449));assert(!ERR_OrdinaryInputObserverRules::Within(0,100,99,2));assert(!ERR_OrdinaryInputObserverRules::Within(0,1,2,450));
for(int fault=0;fault<6;fault++){Fixture f;
+21 -6
View File
@@ -3,16 +3,18 @@ import re
from policy_harness import ROOT, body, run
P=ROOT/'addons/core/Scripts/Game/ERR/ERR_OrdinaryLookObserver.c'
def cpp(s):
s=s.replace('protected ', '')
s=s.replace('protected ', '').replace('out bool held', 'bool& held')
types=('ERR_GameMode','BaseWorld','PlayerController','IEntity','PlayerCamera','ERR_PlayerLife','PlayerManager','CameraManager')
for typ in types:s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
for name in ('mode','world','controller','actor','manager','cameras','m_Mode','m_World','m_Controller','m_Actor','m_Camera','m_Life','s_Request'):
s=s.replace(name+'.',name+'->')
for scope in ('System','Replication','RplSession','RplMode','ERR_Phase','ERR_GameMode','ERR_Actors','ERR_OrdinaryInputObserverRules'):
for scope in ('System','Replication','RplSession','RplMode','ERR_Phase','ERR_GameMode','ERR_Actors','ERR_OrdinaryInputObserverRules','CompartmentAccessComponent'):
s=s.replace(scope+'.',scope+'::')
s=s.replace('GetGame().','GetGame()->').replace('Players().','Players()->').replace('GetInputManager().','GetInputManager()->').replace('GetMenuManager().','GetMenuManager()->')
s=s.replace('null','nullptr').replace('new ERR_OrdinaryLookObserver()', 'new ERR_OrdinaryLookObserver()')
s=s.replace('nonce.Length()', 'int(nonce.size())').replace('allowed.Contains(nonce[i])', '(allowed.find(nonce[i]) != string::npos)')
return s
shared='\n'.join(cpp(body(ROOT/'addons/core/Scripts/Game/ERR/ERR_OrdinaryInputObserver.c', signature)) for signature in ('static bool Nonce(', 'static bool HeldScope(', 'static bool PhaseAllowed('))
methods='\n'.join(cpp(body(P,signature)) for signature in (
'protected static bool Enabled(', 'static void Sample(', 'protected void Cancel(', 'protected bool Context(', 'protected static bool FiniteVector(', 'protected void Observe('))
run(r'''
@@ -26,7 +28,7 @@ using string=std::string;using UUID=string;
struct vector {float v[3]={0,0,0}; float& operator[](int i){return v[i];}float Length(){return std::sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]);}vector operator/(float n){return {{v[0]/n,v[1]/n,v[2]/n}};}};
vector Vector(float a,float b,float c){return {{a,b,c}};}
struct BaseWorld {};
struct IEntity {BaseWorld* world=nullptr;bool living=true;vector origin=Vector(100,2,100);vector GetOrigin(){return origin;} vector matrix[4]={Vector(1,0,0),Vector(0,1,0),Vector(0,0,1),Vector(100,2,100)};BaseWorld* GetWorld(){return world;}void GetTransform(vector out[4]){for(int i=0;i<4;i++)out[i]=matrix[i];}};
struct IEntity {BaseWorld* world=nullptr;bool living=true,vehicle=false;vector origin=Vector(100,2,100);vector GetOrigin(){return origin;} vector matrix[4]={Vector(1,0,0),Vector(0,1,0),Vector(0,0,1),Vector(100,2,100)};BaseWorld* GetWorld(){return world;}void GetTransform(vector out[4]){for(int i=0;i<4;i++)out[i]=matrix[i];}};
struct CameraManager;
struct PlayerCamera:IEntity {CameraManager* replaceDuringRead=nullptr;void GetWorldCameraTransform(vector out[4]);};
struct CameraManager {PlayerCamera* current=nullptr;PlayerCamera* CurrentCamera(){return current;}};
@@ -35,7 +37,7 @@ struct PlayerController {int id=1;IEntity* actor=nullptr;PlayerCamera* camera=nu
struct PlayerManager {PlayerController* controller=nullptr;IEntity* actor=nullptr;PlayerController* GetPlayerController(int){return controller;}IEntity* GetPlayerControlledEntity(int){return actor;}};
struct ERR_PlayerLife {UUID AccountId="account";};
struct Roster {ERR_PlayerLife* life=nullptr;IEntity* actor=nullptr;ERR_PlayerLife* CurrentActor(IEntity* value){return value==actor?life:nullptr;}};
namespace ERR_Phase {enum {PRISON,EXTRACTION};}
namespace ERR_Phase {enum {PRISON=1,RADIO_SEARCH=2,EXTRACTION=4};}
struct ERR_GameMode {static ERR_GameMode* current;bool active=true;int phase=ERR_Phase::PRISON;BaseWorld* world=nullptr;Roster* roster=nullptr;static ERR_GameMode* Get(){return current;}bool Active(){return active;}int Phase(){return phase;}BaseWorld* GetWorld(){return world;}Roster* Players(){return roster;}};ERR_GameMode* ERR_GameMode::current=nullptr;
struct InputManager {float right=0,up=0;float GetActionValue(string name){return name=="CharacterTurnRight"?right:up;}};
struct MenuManager {bool open=false;void* GetTopMenu(){return open?this:nullptr;}};
@@ -43,7 +45,10 @@ struct Game {BaseWorld* world=nullptr;PlayerController* controller=nullptr;Playe
namespace System {bool console=false;std::map<string,string> cli;bool IsConsoleApp(){return console;}bool GetCLIParam(string key,string& value){if(!cli.count(key))return false;value=cli[key];return true;}}
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace RplMode {enum {None,Listen,Dedicated};}namespace RplSession {int mode=RplMode::None;int Mode(){return mode;}}
namespace ERR_OrdinaryInputObserverRules {bool Nonce(string n){return !n.empty()&&n.size()<=32&&n.find_first_not_of("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")==string::npos;}}
struct ERR_OrdinaryInputObserverRules {
'''+shared+r'''
};
namespace CompartmentAccessComponent {IEntity* GetVehicleIn(IEntity* actor){return actor->vehicle?actor:nullptr;}}
namespace ERR_Actors {bool CanFight(IEntity* actor){return actor&&actor->living;}}
int headers=0,samples=0,cancels=0;string reason;vector lastDirection,lastCamera,lastBody,lastFacing,lastTurn;
void PrintFormat(string,string,int,int){headers++;}
@@ -52,7 +57,7 @@ void PrintFormat(string,string,int,int,vector direction,vector camera,vector act
class ERR_OrdinaryLookObserver {public:
static ERR_OrdinaryLookObserver* s_Request;static bool s_Attempted;
ERR_GameMode* m_Mode=nullptr;BaseWorld* m_World=nullptr;PlayerController* m_Controller=nullptr;IEntity* m_Actor=nullptr;PlayerCamera* m_Camera=nullptr;ERR_PlayerLife* m_Life=nullptr;
UUID m_Account;string m_Nonce;int m_Start=0,m_Previous=0,m_Index=0;bool m_Stopped=false;
UUID m_Account;string m_Nonce;int m_Start=0,m_Previous=0,m_Index=0;bool m_Stopped=false,m_HeldAction=false;int m_ObservedPhase=-1;
'''+methods+r'''
};
ERR_OrdinaryLookObserver* ERR_OrdinaryLookObserver::s_Request=nullptr;bool ERR_OrdinaryLookObserver::s_Attempted=false;
@@ -61,6 +66,16 @@ struct Fixture {BaseWorld world,foreignWorld;ERR_GameMode mode;IEntity actor,oth
void sample(int index=1,int now=1100){ERR_OrdinaryLookObserver::Sample(&mode,&world,&controller,&actor,"Run42",1000,index,now);}
};
int main(){
// Held mode changes only admissible phase, never the original pins or clock.
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();auto p=ERR_OrdinaryLookObserver::s_Request;assert(p&&p->m_HeldAction&&p->m_ObservedPhase==ERR_Phase::PRISON);f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(2,1200);f.sample(3,1300);assert(samples==3&&!cancels&&p->m_Start==1000&&p->m_ObservedPhase==ERR_Phase::RADIO_SEARCH);f.mode.phase=ERR_Phase::PRISON;f.sample(4,1400);assert(samples==3&&cancels==1);f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(5,1500);assert(samples==3&&cancels==1);}
for(int fault=0;fault<5;fault++){Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; if(fault==0)System::cli["errOrdinaryHeldAction"]="foreign";if(fault==1)System::cli.erase("errOrdinaryGateObserve");if(fault==2)System::cli["errOrdinaryGateObserve"]="foreign";if(fault==3)f.mode.phase=ERR_Phase::RADIO_SEARCH;if(fault==4)System::cli["errOrdinaryHeldAction"]="";f.sample();assert(!samples);f.mode.phase=ERR_Phase::PRISON;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample(2,1200);assert(!samples);}
for(int fault=0;fault<8;fault++){Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(2,1200);assert(samples==2&&!cancels);if(fault==0)System::cli.erase("errOrdinaryHeldAction");if(fault==1)System::cli["errOrdinaryGateObserve"]="foreign";if(fault==2)f.actor.vehicle=true;if(fault==3)f.menus.open=true;if(fault==4)f.life.AccountId="replacement";if(fault==5)f.mode.phase=ERR_Phase::EXTRACTION;if(fault==6)f.players.actor=nullptr;if(fault==7)game.world=nullptr;f.sample(3,1300);assert(samples==2&&cancels==1);}
{Fixture f;f.sample();System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample(2,1200);assert(samples==1&&cancels==1);}
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(2,45999);assert(samples==2);f.sample(3,46000);assert(samples==2&&cancels==1);}
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(451,1200);assert(samples==1&&cancels==1);}
{Fixture f;System::cli["errOrdinaryHeldAction"]="Run42";System::cli["errOrdinaryLookObserve"]="Run42";System::cli["errOrdinaryGateObserve"]="Run42"; f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.controller.camera=&f.otherCamera;f.sample(2,1200);assert(samples==1&&cancels==1);}
{Fixture f;f.sample();f.mode.phase=ERR_Phase::RADIO_SEARCH;f.sample(2,1200);assert(samples==1&&cancels==1);}
{Fixture f;f.sample();assert(headers==1&&samples==1&&!cancels);assert(lastDirection[0]==1&&lastDirection[2]==0);assert(lastFacing[2]==1);assert(lastCamera[1]!=lastBody[1]);assert(lastTurn[0]==.25f&&lastTurn[1]==-.1f);f.sample(2,1200);assert(headers==1&&samples==2);}
{Fixture f;f.actor.origin=Vector(100.04,1.999,99.97);f.actor.matrix[3]=Vector(100,2,100);f.actor.matrix[2]=Vector(0,0,-1);f.sample();assert(samples==1&&!cancels);assert(lastBody[0]==f.actor.origin[0]&&lastBody[1]==f.actor.origin[1]&&lastBody[2]==f.actor.origin[2]);assert(lastBody[0]!=f.actor.matrix[3][0]);assert(lastFacing[2]==-1);assert(lastCamera[1]==f.camera.matrix[3][1]&&lastDirection[0]==1);}
for(int fault=0;fault<7;fault++){Fixture f;if(fault==0)System::cli.erase("errOrdinaryLookObserve");if(fault==1)System::cli["errOrdinaryLookObserve"]="foreign";if(fault==2)System::cli["errOrdinaryInputObserve"]="foreign";if(fault==3)System::console=true;if(fault==4)Replication::server=false;if(fault==5)RplSession::mode=RplMode::Listen;if(fault==6)System::cli["errOrdinaryScenario"]="other";f.sample();assert(!samples&&!headers&&!cancels&&!ERR_OrdinaryLookObserver::s_Attempted);}
+38
View File
@@ -79,4 +79,42 @@ class RouteEvidence(unittest.TestCase):
self.reject(lambda d,l,p:p['motions'].append({'dx':129,'dy':0,'monotonic':10.2,'after_sample':21,'response_sample':23}))
self.reject(lambda d,l,p:p['motions'].append({'dx':24,'dy':0,'monotonic':10.2,'after_sample':21,'response_sample':23}))
def test_coarse_profile_is_known_explicit_and_has_no_default_upgrade(self):
import math
def turn(d,l,p):
p['profile']='coarse'
angle=math.radians(155)
d['snapshots'][21]['look']['direction']=[math.sin(angle),0,math.cos(angle)]
p['motions']=[{'dx':300,'dy':0,'monotonic':10.1,'after_sample':21,'response_sample':23}]
self.assertTrue(self.run_case(turn)['verified'])
def fine(d,l,p):turn(d,l,p);p.pop('profile')
self.assertFalse(self.run_case(fine)['verified'])
for profile in ('unknown',True,None):self.reject(lambda d,l,p:p.update(profile=profile))
def overshoot(d,l,p):
turn(d,l,p);p['motions'][0]['response_sample']=24
angle=math.radians(191)
d['snapshots'][22]['look']['direction']=[math.sin(angle),0,math.cos(angle)]
self.reject(overshoot)
def excessive(d,l,p):turn(d,l,p);p['motions'][0]['dx']=385
self.reject(excessive)
def test_actual_coarse_policy_trace_passes_independent_raw_chain(self):
# Simulated dynamics exercise actual policy and every raw parser; no
# native runtime/input claim and no mocked evidence verdicts.
from test_prison_alignment_evidence import build, raw, ApproachScene
original = ApproachScene.run
with patch.object(ApproachScene, 'run', lambda scene: original(scene, profile='coarse')):
frames, look, route, _ = build()
self.assertEqual(route['profile'], 'coarse')
self.assertTrue(any(abs(m['dx']) > 128 for m in route['motions']))
result = v.validate_approach_evidence(raw(frames), 'test', look, route)
self.assertTrue(result['verified'], result)
route['profile'] = 'standard'
self.assertFalse(v.validate_approach_evidence(raw(frames), 'test', look, route)['verified'])
def test_coarse_does_not_expand_clocks_or_permit_action_keys(self):
for change in (lambda p:p.update(deadline_monotonic=100),lambda p:p['events'][1].update(monotonic=11),lambda p:p['events'][0].update(key='F')):
def mutate(d,l,p):p['profile']='coarse';change(p)
self.reject(mutate)
if __name__=='__main__':unittest.main()
+59 -4
View File
@@ -21,7 +21,7 @@ class Scene:
self.position=[98.5,0.,98.5];self.angle=0.;self.speed=0.;self.held=False
self.key_events=[];self.turns=[];self.notifications=[];self.focus=True
self.gain=.085;self.acceleration=8.;self.braking=8.;self.move_scale=1.
self.guard_cost=.22;self.ignore_turn=False;self.wrong_turn=False
self.coarse=False;self.guard_cost=.22;self.ignore_turn=False;self.wrong_turn=False
self.turn_delay=0.;self.pending_turns=[]
self.stale=False;self.no_movement=False;self.changed_geometry=False
self.context_lost=False;self.fail_down=False;self.coast_events=[]
@@ -64,7 +64,7 @@ class Scene:
self.held=down
if down and self.fail_down:raise OSError('uncertain native press')
def move(self,dx,dy,guard):
if dy != 0 or not 1<=abs(dx)<=128:raise AssertionError('motion cap')
if dy != 0 or not 1<=abs(dx)<=(384 if self.coarse else 128):raise AssertionError('motion cap')
guard.check();guard.check()
self.turns.append((dx,dy,self.now))
if not self.ignore_turn:
@@ -78,14 +78,15 @@ class Scene:
batch=[s for s in self.history if s['sample'] > self.last_read] or [self.sample]
self.last_read=batch[-1]['sample']
return copy.deepcopy(batch)
def run(self,report=None,calibration=None):
def run(self,report=None,calibration=None,profile="standard"):
self.coarse=profile=="coarse"
self.report={} if report is None else report
angle=math.radians(24*.085)
calibration=calibration or {'nonce':'test','completed':True,'pixels':24,
'baseline_direction':[0.,0.,1.], 'response_direction':[math.sin(angle),0.,math.cos(angle)],
'ready_real_ms':2000,'ready_monotonic':0.,'observer_start_real_ms':0}
return policy.perform_approach(self,self,self,self.observe,self.geometry,calibration,self.report,
clock=self.clock,sleep=self.sleep,notify=self.notifications.append,key_state_busy=Busy)
clock=self.clock,sleep=self.sleep,notify=self.notifications.append,key_state_busy=Busy,profile=profile)
class ApproachTests(unittest.TestCase):
@@ -325,5 +326,59 @@ class ApproachTests(unittest.TestCase):
self.assertEqual([event[1] for event in scene.key_events],[True,False])
self.assertFalse(proof['completed']);self.assertFalse(scene.held)
def test_explicit_coarse_profile_reduces_heading_requests_with_original_clocks(self):
fine=Scene();standard=fine.run()
coarse=Scene();result=coarse.run(profile='coarse')
self.assertEqual(standard['profile'],'standard');self.assertEqual(result['profile'],'coarse')
self.assertLess(len(coarse.turns),len(fine.turns));self.assertTrue(any(abs(dx)>128 for dx,_,_ in coarse.turns))
self.assertTrue(all(abs(dx)<=384 and dy==0 for dx,dy,_ in coarse.turns))
self.assertLessEqual(sum(abs(dx) for dx,_,_ in coarse.turns),2048)
self.assertLessEqual(len(coarse.turns),16);self.assertLessEqual(len(coarse.key_events)//2,16)
self.assertEqual(result['deadline_monotonic'],35);self.assertLess(coarse.now,35)
self.assertLessEqual(result['cumulative_horizontal_m'],6);self.assertFalse(coarse.coast_events)
self.assertTrue(all(key=='W' for key,_,_ in coarse.key_events))
def test_coarse_profile_never_hides_intermediate_native_overshoot(self):
scene=Scene()
def turn(dx,dy,guard):
guard.check();guard.check();scene.angle+=36;scene.sleep(.2);scene.angle-=11
scene.move=turn
with self.assertRaisesRegex(policy.Refused,'angle bound'):scene.run(profile='coarse')
self.assertEqual(scene.key_events,[])
def test_coarse_requires_known_profile_and_respects_focus_and_deadline(self):
for profile in ('other',True,None):
scene=Scene()
with self.assertRaisesRegex(policy.Refused,'Unknown approach profile'):scene.run(profile=profile)
self.assertEqual(scene.key_events,[]);self.assertEqual(scene.turns,[])
scene=Scene();scene.focus=False
with self.assertRaisesRegex(policy.Refused,'focus lost'):scene.run(profile='coarse')
self.assertEqual(scene.key_events,[])
scene=Scene();scene.now=35
with self.assertRaisesRegex(policy.Refused,'Insufficient'):scene.run(profile='coarse')
self.assertEqual(scene.key_events,[]);self.assertEqual(scene.turns,[])
def test_coarse_keeps_actual_hold_and_stopping_deadzone(self):
scene=Scene();original=scene.send
def slow(key,down):
original(key,down)
if down:scene.guard_cost=.41
scene.send=slow
with self.assertRaisesRegex(policy.Refused,'400ms'):scene.run(profile='coarse')
self.assertEqual([e[1] for e in scene.key_events],[True,False])
scene=Scene();scene.position=[102.7,0.,97.3];scene.angle=180.;scene.guard_cost=.33
scene.sample=scene.snapshot();scene.history=[copy.deepcopy(scene.sample)]
with self.assertRaisesRegex(policy.Refused,'measured stopping reserve'):scene.run(profile='coarse')
self.assertEqual([e[1] for e in scene.key_events],[True,False])
def test_coarse_has_exact_sixteen_pulse_cap_not_unbounded_retries(self):
scene=Scene();scene.guard_cost=.19
result=scene.run(profile='coarse')
self.assertTrue(result['completed']);self.assertEqual(len(scene.key_events),32)
self.assertLess(scene.now,35);self.assertFalse(scene.held)
scene=Scene();scene.guard_cost=.18
with self.assertRaisesRegex(policy.Refused,'pulse budget'):scene.run(profile='coarse')
self.assertEqual(len(scene.key_events),32);self.assertFalse(scene.held)
if __name__=='__main__':unittest.main()
+41
View File
@@ -295,4 +295,45 @@ class SelectionRunner(unittest.TestCase):
session,context,report,observation=fixtures.ExerciseTests.make_proof(self,Path(folder));report.update(payload);Path(context['report']).write_text(json.dumps(report))
outcome,proof=r.input_exercise_result('game-started',context,session,observation,{'verified':True});self.assertFalse(proof['verified'])
class CoarseApproachRunner(unittest.TestCase):
def test_coarse_explicit_command_flag_and_hash_bound_context(self):
with self.assertRaises(ValueError):r.validate_input_exercise(SimpleNamespace(exercise_prison_coarse_approach=True))
valid=dict(exercise_prison_coarse_approach=True,exercise_prison_approach=True,exercise_relative_look=True,observe_prison_gate=True,exercise_player_input=True,observe_player_input=True,inspect_headless_display=True,project='chat',settle=50)
r.validate_input_exercise(SimpleNamespace(**valid))
with tempfile.TemporaryDirectory() as folder:
root=Path(folder);(root/'tools').mkdir();profile=root/'profile';profile.mkdir()
for name in ('headless_input_proof.py','headless_display_probe.py','prison_approach_proof.py','ordinary_gate_evidence.py','ordinary_look_evidence.py','prison_approach_evidence.py'):(root/'tools'/name).write_text('# fake')
native=['gamescope','--backend','headless','--','/proton','run','/game','-errOrdinaryInputObserve',NONCE,'-errOrdinaryLookObserve',NONCE,'-errOrdinaryGateObserve',NONCE]
wrapped,display=r.display_probe_command(native,root,Path('/compat'),profile,Path('/game'),NONCE,':0');args=(wrapped,root,Path('/compat'),profile,Path('/game'),NONCE,':0',display)
ordinary,normal=r.input_exercise_command(*args,look=True,approach=True)
coarse,context=r.input_exercise_command(*args,look=True,approach=True,coarse_approach=True)
self.assertNotIn('--enable-private-coarse-approach-proof',ordinary);self.assertFalse(normal.get('coarse_approach_requested',False))
self.assertTrue(context['coarse_approach_requested']);self.assertEqual(context['approach_module_sha256'],normal['approach_module_sha256'])
coarse.remove('--enable-private-coarse-approach-proof');self.assertEqual(coarse,ordinary)
for kwargs in ({'look':True,'coarse_approach':True},{'look':True,'approach':True,'coarse_approach':1}):
with self.assertRaises(ValueError):r.input_exercise_command(*args,**kwargs)
def test_final_result_refuses_unrequested_coarse_or_profile_substitution(self):
for requested,reported,profile in ((False,True,'coarse'),(False,False,'coarse'),(True,False,'coarse'),(True,True,'standard'),(False,1,'standard'),(True,True,'unknown')):
with tempfile.TemporaryDirectory() as folder:
session,context,report,observation=fixtures.ExerciseTests.make_proof(self,Path(folder))
context.update(look_requested=True,approach_requested=True,coarse_approach_requested=requested,approach_module_sha256=r.approach_module_hashes(r.ROOT))
report.update(look_requested=True,approach_requested=True,coarse_approach_requested=reported,approach_module_sha256=context['approach_module_sha256'],look_proof={})
report['events']=report['events'][:2];report['observer']['events']=['READY'];report['approach_proof']={'profile':profile,'events':copy.deepcopy(report['events'])}
Path(context['report']).write_text(json.dumps(report))
result,proof=r.input_exercise_result('game-started',context,session,observation,{'verified':True})
self.assertFalse(proof['verified']);self.assertEqual(result,'input-exercise-incomplete')
def test_matching_coarse_proof_still_requires_independent_raw_validation(self):
for okay in (False,True):
with tempfile.TemporaryDirectory() as folder:
session,context,report,observation=fixtures.ExerciseTests.make_proof(self,Path(folder))
context.update(look_requested=True,approach_requested=True,coarse_approach_requested=True,approach_module_sha256=r.approach_module_hashes(r.ROOT))
report.update(look_requested=True,approach_requested=True,coarse_approach_requested=True,approach_module_sha256=context['approach_module_sha256'],look_proof={})
report['events']=[{'key':'W','down':i%2==0,'monotonic':1+i*.1} for i in range(32)];report['observer']['events']=['READY'];report['approach_proof']={'profile':'coarse','events':copy.deepcopy(report['events'])}
(session/'console.log').write_text('raw native');Path(context['report']).write_text(json.dumps(report))
module=ModuleType('prison_approach_evidence');module.validate_approach_evidence=lambda *a:{'verified':okay}
with patch.dict(sys.modules,{'prison_approach_evidence':module}):result,proof=r.input_exercise_result('game-started',context,session,observation,{'verified':True})
self.assertEqual(proof['verified'],okay)
if __name__=='__main__':unittest.main()