Files
EscapistsReintroduced/tests/test_infantry_placement_settle.py
T

78 lines
6.9 KiB
Python

"""Execute actual native-fixture Place/Stop: delayed pose observation and cleanup."""
import os,re
from pathlib import Path
from policy_harness import ROOT,body,run
p=Path(os.environ.get('ERR_INFANTRY_PROBE_SOURCE',ROOT/'addons/chat/Scripts/Game/ERR/ERR_NativeInfantryTransferProbe.c'))
s=body(p,'protected int Place(float now)')+'\n'+body(p,' void Stop()')
s=re.sub(r'//[^\n]*','',s).replace('protected ','')
s=s.replace('ChimeraCharacter.Cast(m_Actor)','m_Actor')
s=re.sub(r'"(-?\d+(?:\.\d+)?) (-?\d+(?:\.\d+)?) (-?\d+(?:\.\d+)?)"',r'vector{\1,\2,\3}',s)
s=s.replace('array<','Array<').replace('TraceBox body','TraceBox* body').replace('ChimeraCharacter character','Entity* character')
for name in ('m_Truck','m_Actor','character','body','m_Move','m_HQ','m_Unit'):
s=s.replace(name+'.',name+'->')
s=s.replace('Entity.GetTransform','Entity->GetTransform').replace('Entity.GetOrigin','Entity->GetOrigin')
for name in ('ERR_Actors','ERR_InfantryAmmoAccess','TraceFlags'):
s=s.replace(name+'.',name+'::')
s=s.replace('GetGame().GetWorld().','GetGame()->GetWorld()->').replace('null','nullptr')
run(r'''
#include <cassert>
#include <vector>
#include <cmath>
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}};
struct vector{float x=0,y=0,z=0;float operator[](int i){return i==0?x:i==1?y:z;}vector operator+(vector b){return {x+b.x,y+b.y,z+b.z};}vector operator*(float f){return {x*f,y*f,z*f};}bool operator==(vector b){return x==b.x&&y==b.y&&z==b.z;}};
struct Entity{vector origin,aim;int moves=0;vector GetOrigin(){return origin;}vector AimingPosition(){return aim;}void SetOrigin(vector v){origin=v;moves++;}void GetTransform(vector (&out)[4]){out[0]={1,0,0};out[1]={0,1,0};out[2]={0,0,1};out[3]=origin;}void EnginePoseUpdate(){aim=origin+vector{0,1.4f,0};}};
using IEntity=Entity*;
struct TraceFlags{static constexpr int ENTS=2;};
struct TraceBox{vector Start,End,Mins,Maxs;int Flags=0;Array<IEntity>ExcludeArray;};
struct World{int traces=0;float fraction=1;float TraceMove(TraceBox*b,void*){traces++;assert(b->Flags==2&&b->ExcludeArray.Count()==1);return fraction;}};
struct Game{World world;World*GetWorld(){return &world;}} game;Game*GetGame(){return &game;}
struct ERR_Actors{static vector Ground(vector p){p.y=0;return p;}};
struct ERR_InfantryAmmoAccess{static inline bool points=true,clear=true;static inline int clearCalls=0;
static bool Points(IEntity,Array<vector>& out){out={{1,1,1}};return points;}
static bool Clear(IEntity,IEntity actor){clearCalls++;return clear&&actor->aim==actor->origin+vector{0,1.4f,0};}};
template<class... Args>void PrintFormat(const char*,Args...){}
struct Truck{IEntity Entity=nullptr;int SupplyToken=200001;bool SupplyHeld=true;};
struct Unit{int Group=1;};
struct HQ{int token=200001,releases=0;int LogisticsToken(int){return token;}void ReleaseLogistics(int,int expected){if(token!=expected)return;token=0;releases++;}};
struct Move{bool unresolved=false;int stops=0;void Stop(){stops++;}bool Unresolved(){return unresolved;}};
struct Probe{static constexpr int TOKEN=200001;Truck*m_Truck=nullptr;IEntity m_Actor=nullptr;HQ*m_HQ=nullptr;Unit*m_Unit=nullptr;Move*m_Move=nullptr;
bool m_Positioned=false,m_PlacementStarted=false,m_PlacementPending=false,m_Stopped=false,owned=true;
int m_PlacementIndex=0,diagnostics=0;float m_PlacementReadyAt=0;vector m_OriginalPosition;
bool RecipientOwned(){return owned;}void AccessDiagnostic(){diagnostics++;}
''' + s + r'''
};
struct Fixture{Probe p;Truck truck;Entity vehicle,actor;HQ hq;Unit unit;Move move;
Fixture(){game.world=World{};ERR_InfantryAmmoAccess::points=true;ERR_InfantryAmmoAccess::clear=true;ERR_InfantryAmmoAccess::clearCalls=0;
vehicle.origin={100,0,100};actor.origin={900,0,900};actor.EnginePoseUpdate();truck.Entity=&vehicle;p.m_Truck=&truck;p.m_Actor=&actor;p.m_HQ=&hq;p.m_Unit=&unit;}
};
int main(){
// SetOrigin deliberately leaves the native aim pose stale. Same-tick calls must not inspect it.
{Fixture f;auto original=f.actor.origin;assert(f.p.Place(0)==0);assert(f.actor.moves==1&&f.p.m_PlacementPending&&f.p.m_Positioned);
assert(f.actor.aim==original+vector({0,1.4f,0}));assert(ERR_InfantryAmmoAccess::clearCalls==0);
assert(f.p.Place(249)==0&&f.actor.moves==1&&ERR_InfantryAmmoAccess::clearCalls==0);
f.actor.EnginePoseUpdate();assert(f.p.Place(250)==1&&ERR_InfantryAmmoAccess::clearCalls==1);assert(f.p.m_OriginalPosition==original);
f.p.Stop();assert(f.actor.origin==original&&f.actor.moves==2&&f.hq.releases==1&&!f.truck.SupplyHeld&&f.truck.SupplyToken==0);
f.p.Stop();assert(f.actor.moves==2&&f.hq.releases==1);}
// Every controlled candidate is bounded; failed checks never overwrite the true original position.
{Fixture f;auto original=f.actor.origin;ERR_InfantryAmmoAccess::clear=false;
for(int i=0;i<4;i++){assert(f.p.Place(i*250)==0);assert(f.p.m_PlacementIndex==i+1&&f.p.m_OriginalPosition==original);f.actor.EnginePoseUpdate();}
assert(f.p.Place(1000)==-1&&f.p.m_PlacementIndex==4&&ERR_InfantryAmmoAccess::clearCalls==4&&game.world.traces==4);
assert(f.actor.origin==original);int before=f.actor.moves;assert(f.p.Place(1250)==-1&&f.actor.moves==before&&game.world.traces==4);f.p.Stop();assert(f.actor.origin==original);}
// Interrupted async positioning restores its original pose even before a successful access observation.
{Fixture f;auto original=f.actor.origin;assert(f.p.Place(0)==0);f.p.Stop();assert(f.actor.origin==original&&f.hq.releases==1&&!f.truck.SupplyHeld);}
// Geometry refusal tries at most four candidates, never moves the actor and never invokes access rays.
{Fixture f;auto original=f.actor.origin;game.world.fraction=.5;assert(f.p.Place(0)==-1&&game.world.traces==4&&f.actor.moves==0&&ERR_InfantryAmmoAccess::clearCalls==0);
f.p.Stop();assert(f.actor.origin==original&&f.actor.moves==0);}
// Unknown authored anchor cannot silently fall back to fixture coordinates.
{Fixture f;ERR_InfantryAmmoAccess::points=false;assert(f.p.Place(0)==-1&&f.p.diagnostics==1&&f.actor.moves==0&&game.world.traces==0);}
// Do not race an accepted uncertain inventory move with a cleanup teleport.
{Fixture f;assert(f.p.Place(0)==0);auto current=f.actor.origin;f.p.m_Move=&f.move;f.move.unresolved=true;f.p.Stop();assert(f.actor.origin==current&&f.move.stops==1&&f.hq.releases==1);}
// Reassigned/captured actor ownership prevents placement and cleanup mutation.
{Fixture f;f.p.owned=false;assert(f.p.Place(0)==-1&&f.actor.moves==0);}
{Fixture f;assert(f.p.Place(0)==0);auto current=f.actor.origin;f.p.owned=false;assert(f.p.Place(250)==-1);f.p.Stop();assert(f.actor.origin==current&&f.actor.moves==1);}
// Lost HQ reservation similarly cannot authorize cleanup teleport.
{Fixture f;assert(f.p.Place(0)==0);auto current=f.actor.origin;f.hq.token=91;f.p.Stop();assert(f.actor.origin==current&&f.hq.releases==0);}
}
''')
print('PASS: actual Place/Stop waits native pose update, bounds four attempts, preserves original position, restores pending/settled placement and respects ownership/accepted-move quarantine')