Add garage car variants and yard parking; record Workshop 0.0.142
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-11-06 11:32:33 +01:00
parent 9caed3f12d
commit 55c0ec775a
27 changed files with 6158 additions and 35 deletions
+64 -3
View File
@@ -1,7 +1,8 @@
"""Run production garage filtering and vehicle-fit rules against unsafe inputs."""
from policy_harness import ROOT, body, run
p = ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_GarageVehicles.c'
rules = '\n'.join(body(p, s) for s in ('static bool Garage(', 'static bool Envelope('))
rules = '\n'.join(body(p, s) for s in ('static bool Garage(', 'static bool Envelope(', 'static bool Yard(', 'static vector YardOffset('))
rules += '\n' + body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_ParkedCarCatalog.c', 'static bool Candidate(')
rules = rules.replace('vector ', 'Vec ').replace('ERR_ForceRules.', 'ERR_ForceRules::')
run(r'''
#include <cassert>
@@ -13,9 +14,10 @@ run(r'''
struct string : std::string {
using std::string::string;
void ToLower(){std::transform(begin(),end(),begin(),[](unsigned char c){return std::tolower(c);});}
bool Contains(const std::string& needle){return find(needle)!=npos;}
bool EndsWith(const std::string& suffix){return size()>=suffix.size() && compare(size()-suffix.size(),suffix.size(),suffix)==0;}
};
struct Vec {float v[3];float operator[](int i)const{return v[i];}};
struct Vec {float v[3];float& operator[](int i){return v[i];}float operator[](int i)const{return v[i];}Vec operator+(Vec b)const{return {{v[0]+b[0],v[1]+b[1],v[2]+b[2]}};}Vec operator*(float n)const{return {{v[0]*n,v[1]*n,v[2]*n}};}};
namespace ERR_ForceRules {bool FiniteVector(Vec v){return std::isfinite(v[0])&&std::isfinite(v[1])&&std::isfinite(v[2]);}}
''' + rules + r'''
int main(){
@@ -25,6 +27,25 @@ int main(){
assert(!Garage("Prefabs/Structures/Houses/Garage/Garage_E_01/Furniture/Garage_E_01_furniture_01.et"));
assert(!Garage("Prefabs/Structures/BuildingParts/Doors/Door_Garage_E_01_A_LEFT_EXT_COV_B.et"));
assert(!Garage("Prefabs/Structures/Military/Houses/GarageMilitary_E_01/GarageMilitary_E_01.et"));
assert(Yard("Prefabs/Structures/Houses/Village/House_Village_E_1I01/House_Village_E_1I01_beige_V2.et"));
assert(Yard("Prefabs/Structures/Houses/Village/House_Village_E_1I06/House_Village_E_1I06_white.et"));
assert(!Yard("Prefabs/Structures/Houses/Village/House_Village_E_1I06/House_Village_E_1I06_base.et"));
assert(!Yard("Prefabs/Structures/Houses/Village/House_Village_E_1I06/Dst/House_Village_E_1I06_ruin.et"));
assert(!Yard("Prefabs/Structures/Houses/Village/House_Village_E_1I06/Furniture/House_Village_E_1I06_furniture_01.et"));
Vec low={{-4,-1,-6}},high={{6,8,8}};
assert(YardOffset(low,high,0)[0]==-7 && YardOffset(low,high,0)[2]==1);
assert(YardOffset(low,high,1)[0]==9 && YardOffset(low,high,1)[1]==0);
assert(YardOffset(low,high,2)[2]==-9 && YardOffset(low,high,2)[0]==1);
assert(YardOffset(low,high,3)[2]==11);
assert(Candidate("{GUID}Prefabs/Vehicles/Wheeled/T601/T601_blue_twoTone.et",false));
assert(Candidate("Prefabs/Vehicles/Wheeled/V2101/V2101_lightBlue.et",false));
assert(Candidate("Prefabs/Vehicles/Wheeled/S105/S105_rally_08.et",true));
assert(!Candidate("Prefabs/Vehicles/Wheeled/S105/S105_rally_08.et",false));
assert(!Candidate("Prefabs/Vehicles/Wheeled/S105/S105_green.et",true));
assert(!Candidate("Prefabs/Vehicles/Wheeled/T601/T601_base.et",false));
assert(!Candidate("Prefabs/Vehicles/Wheeled/T601/T601_rally_01_fullkit.et",true));
assert(!Candidate("Prefabs/Vehicles/Wheeled/T601/VehParts/Wheels/T601_wheel_Rally_Blue.et",false));
assert(!Candidate("Prefabs/Vehicles/Wheeled/S105/S105_FIA.et",false));
assert(Envelope({{-1,-0.2,-2.3}},{{1,1.8,2.3}}));
assert(!Envelope({{-1.06f,0,-2}},{{1,1.8,2}}));
assert(!Envelope({{-1,0,-2.5}},{{1,1.8,2}}));
@@ -35,4 +56,44 @@ int main(){
assert(!Envelope({{-1,0,-2}},{{1,INFINITY,2}}));
}
''')
print('PASS: native garage families only; rubble/furniture/military garages rejected; oversized and invalid vehicle bounds rejected')
print('PASS: garage/yard family admission, bounded wall offsets, civilian and optional Rally catalog exclusions, vehicle-fit limits')
# Execute the actual yard ground veto with road/terrain doubles. A flat centre
# alone must not admit a sloping or submerged edge of the parking footprint.
ground = body(p, 'protected bool YardGround(')
ground = ground.replace('protected ', '').replace('vector ', 'Vec ')
ground = ground.replace('SCR_AIWorld world = SCR_AIWorld.Cast(GetGame().GetAIWorld());', 'SCR_AIWorld* world = worldAvailable ? &nativeWorld : nullptr;')
ground = ground.replace('BaseRoad road; float distance;', 'BaseRoad* road = nullptr; float distance = 0;')
ground = ground.replace('world.', 'world->').replace('road.', 'road->').replace('GetRoadNetworkManager().', 'GetRoadNetworkManager()->')
ground = ground.replace('skipNavlinks: true', 'true')
for name in ['ERR_ForceRules', 'ERR_Actors', 'SCR_TerrainHelper', 'Math']:
ground = ground.replace(name + '.', name + '::')
run(r'''
#include <cassert>
#include <cmath>
struct Vec {float v[3];float operator[](int i)const{return v[i];}
Vec operator+(Vec b)const{return {{v[0]+b[0],v[1]+b[1],v[2]+b[2]}};}
Vec operator*(float n)const{return {{v[0]*n,v[1]*n,v[2]*n}};}};
bool worldAvailable=true,managerAvailable=true,hasRoad=true,wetEdge=false;
float roadDistance=20,edgeHeight=0;
struct BaseRoad{float width=10;float GetWidth(){return width;}} roadInstance;
struct Roads{void GetClosestRoad(Vec,BaseRoad*& road,float& distance,bool){road=hasRoad?&roadInstance:nullptr;distance=roadDistance;}} roads;
struct SCR_AIWorld{Roads* GetRoadNetworkManager(){return managerAvailable?&roads:nullptr;}} nativeWorld;
namespace ERR_ForceRules{bool Finite(float n){return std::isfinite(n);}}
namespace SCR_TerrainHelper{float GetTerrainY(Vec p){return p[0]>.5f?edgeHeight:0;}}
namespace ERR_Actors{bool DryGround(Vec p){return !(wetEdge&&p[2]>2)&&std::isfinite(SCR_TerrainHelper::GetTerrainY(p));}}
namespace Math{float AbsFloat(float n){return std::abs(n);}}
''' + ground + r'''
int main(){Vec p={{0,0,0}},matrix[4]={{{1,0,0}},{{0,1,0}},{{0,0,1}},{{0,0,0}}};
assert(YardGround(p,matrix));
roadDistance=8.99f;assert(!YardGround(p,matrix));roadDistance=9;assert(YardGround(p,matrix));
hasRoad=false;assert(YardGround(p,matrix));hasRoad=true;
worldAvailable=false;assert(!YardGround(p,matrix));worldAvailable=true;
managerAvailable=false;assert(!YardGround(p,matrix));managerAvailable=true;
wetEdge=true;assert(!YardGround(p,matrix));wetEdge=false;
edgeHeight=.19f;assert(!YardGround(p,matrix));edgeHeight=.17f;assert(YardGround(p,matrix));
edgeHeight=NAN;assert(!YardGround(p,matrix));edgeHeight=0;
roadDistance=NAN;assert(!YardGround(p,matrix));
}
''')
print('PASS: yard road-width margin, missing road data, footprint water/slope and nonfinite-input vetoes')