Files
EscapistsReintroduced/tests/test_infantry_placement_settle.py
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

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/core/Scripts/Game/ERR/40_Chat/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')