Files
EscapistsReintroduced/tests/test_prison_placement_policy.py
jakub 1b1a8b5173 Add mission customization, HQ support and aggression-driven AI
Integrate faction and random settings menus, generated objectives, scouts, convoys, air support, mortar stations and body-intel recovery. Add identified-body aggression, finite hunter UAZ deployment, dedicated objective defence and maximum AI skill with separate reduced aim accuracy. Include policy tests, native probes, evidence and documentation.
2026-09-23 15:07:14 +02:00

96 lines
4.4 KiB
Python

"""Execute the actual prison search body with a recording clearance predicate.
This verifies bounded search order/termination, not terrain or body clearance.
"""
from policy_harness import ROOT,body,run
source=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnPlacement.c'
search=(body(source,'bool Prison(').replace('map<int, vector> reservations','map<int, vector>* reservations')
.replace('out vector position','vector& position').replace('ERR_Actors.', 'ERR_Actors::').replace('Math.', 'Math::').replace('ERR_CampTerrain.', 'ERR_CampTerrain::'))
run('''#include <cassert>
#include <cmath>
#include <vector>
#include <set>
#include <utility>
struct vector {float x,y,z;};
vector operator+(vector a,vector b){return {a.x+b.x,a.y+b.y,a.z+b.z};}
vector Vector(float x,float y,float z){return {x,y,z};}
template<class K,class V> struct map {};
namespace ERR_Actors {vector Ground(vector value){return value;}}
namespace Math {int AbsInt(int value){return std::abs(value);}}
namespace ERR_CampTerrain {vector Rotate(vector v,float a){float r=a*3.14159265359f/180;return {v.x*std::cos(r)+v.z*std::sin(r),v.y,-v.x*std::sin(r)+v.z*std::cos(r)};}}
struct Search {
std::vector<vector> visited;
int accept=-1;
bool Clear(vector candidate,map<int,vector>*){visited.push_back(candidate);return int(visited.size())-1==accept;}
''' + search + '''
};
int main(){
Search rotated;rotated.accept=0;vector turned;assert(rotated.Prison(Vector(0,0,0),nullptr,turned,90));assert(std::abs(turned.x+1.5)<.001&&std::abs(turned.z-1.5)<.001);
const vector center={6090,100,7160};
vector result={-1,-1,-1};
Search blocked;
assert(!blocked.Prison(center,nullptr,result));
assert(blocked.visited.size()==16);
assert(result.x==-1&&result.y==-1&&result.z==-1); // no outside fallback
std::set<std::pair<float,float>> unique;
for(int i=0;i<16;++i){
vector candidate=blocked.visited[i];
float x=candidate.x-center.x,z=candidate.z-center.z;
assert(std::abs(x)<=4.5f&&std::abs(z)<=4.5f);
assert(unique.insert({x,z}).second);
assert(std::abs(x)==1.5f||std::abs(x)==4.5f);
assert(std::abs(z)==1.5f||std::abs(z)==4.5f);
int outer=(std::abs(x)==4.5f)+(std::abs(z)==4.5f);
assert(outer==(i<4?0:(i<12?1:2)));
if(i<12)assert(std::hypot(x,z)<5); // inner and edge spawns meet prior 5m check
for(int j=0;j<i;++j){
vector previous=blocked.visited[j];
assert(std::hypot(candidate.x-previous.x,candidate.z-previous.z)>=3);
}
}
// Select any first available position; never bypass the native clearance predicate.
for(int accepted=0;accepted<16;++accepted){
Search search;search.accept=accepted;
assert(search.Prison(center,nullptr,result));
assert(search.visited.size()==unsigned(accepted+1));
vector expected=blocked.visited[accepted];
assert(result.x==expected.x&&result.y==expected.y&&result.z==expected.z);
}
}
''')
print('PASS: prison search packs 16 unique 3m-spaced inside-wall centers, prioritizes inner/edge positions and never bypasses clearance')
occupancy = (body(source, 'protected bool Occupied(').replace('protected ', '')
.replace('IEntity entity', 'IEntity* entity').replace('entity.', 'entity->')
.replace('ChimeraCharacter.Cast', 'ChimeraCharacter::Cast')
.replace('vector.', 'vector::').replace('Math.', 'Math::'))
run(r'''
#include <cassert>
#include <cmath>
#include <initializer_list>
struct vector {
float x=0,y=0,z=0;
float operator[](int i) const {return i==0?x:(i==1?y:z);}
static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}
};
struct IEntity {vector position;bool character=true;vector GetOrigin(){return position;}};
namespace ChimeraCharacter {bool Cast(IEntity* e){return e&&e->character;}}
namespace Math {float AbsFloat(float f){return std::abs(f);}}
struct Placement {
IEntity* m_Exclude=nullptr; vector m_Candidate; bool m_Blocked=false;
''' + occupancy + r'''
};
int main(){
IEntity body;
for(float distance : {0.f,1.199f,1.2f,2.12f,3.f}) {
Placement p;body.position={distance,0,0};
assert(p.Occupied(&body)==(distance>=1.2f));
}
Placement vertical;body.position={0,2,0};assert(vertical.Occupied(&body));
Placement excluded;excluded.m_Exclude=&body;body.position={0,0,0};assert(excluded.Occupied(&body));
Placement scenery;body.character=false;assert(scenery.Occupied(&body)); // geometry uses the separate native trace
assert(scenery.Occupied(nullptr));
}
''')
print('PASS: physical body occupancy rejects overlap, permits diagonal neighboring slots, and excludes own body and unrelated broad-phase bounds')