"""Actual settings read/default/bounds for next-round automatic convoy startup.""" import os,re from pathlib import Path from policy_harness import ROOT,body,run BASE=Path(os.environ.get('ERR_CONVOY_SETTINGS_ROOT',ROOT)) P=BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c' keys=body(P,'static array Keys(').replace('array','std::vector') valid='\n'.join(body(P,sig) for sig in ('static bool Valid(', 'static bool Randomizable(', 'static bool ValidSelection(')) default=body(P,'static int AddedDefault(') read=(body(P,'protected bool Read(')+'\n'+body(P,'static bool Parse(')).replace('protected ','').replace('out map values','Map*& values').replace('new map()','new Map()') read=read.replace('JsonLoadContext json','JsonLoadContext* json').replace('json.','json->').replace('values.','values->').replace('foreach (string key : Keys())','for (string key : Keys())').replace('Math.Round','std::round').replace('ERR_ProfileConfig.Load(path, json)', 'json->LoadFromFile(path)') run(r''' #include #include #include #include #include using string=std::string; struct Map{std::map values;void Set(string k,int v){values[k]=v;}int Get(string k){return values[k];}}; std::map> files; struct JsonLoadContext{std::map* data=nullptr; bool LoadFromFile(string path){if(!files.count(path))return false;data=&files[path];return true;} bool DoesKeyExist(string key){return data->count(key);} bool ReadValue(string key,float& value){if(!DoesKeyExist(key))return false;value=(*data)[key];return true;}}; struct Settings{ '''+keys+valid+default+read+r''' }; int main(){ assert(Settings::AddedDefault("automaticConvoyEnabled")==1); assert(Settings::Valid("automaticConvoyEnabled",0)&&Settings::Valid("automaticConvoyEnabled",1)); assert(!Settings::Valid("automaticConvoyEnabled",-1)&&!Settings::Valid("automaticConvoyEnabled",2)); assert(Settings::AddedDefault("reportIntervalSeconds")==-1); files["old"]={{"reportIntervalSeconds",10},{"supportSquadLimit",4},{"responseRadiusMetres",2000}, {"aiIntelChancePercent",60},{"intelRevealChancePercent",15},{"radioDefenceSeconds",180}}; Settings s;Map* loaded=nullptr;assert(s.Read("old",loaded)&&loaded->Get("automaticConvoyEnabled")==1); assert(loaded->Get("reportIntervalSeconds")==10&&loaded->Get("antiAirChancePercent")==7); files["disabled"]=files["old"];files["disabled"]["automaticConvoyEnabled"]=0; assert(s.Read("disabled",loaded)&&loaded->Get("automaticConvoyEnabled")==0); files["enabled"]=files["old"];files["enabled"]["automaticConvoyEnabled"]=1; assert(s.Read("enabled",loaded)&&loaded->Get("automaticConvoyEnabled")==1); for(float bad:{-1.0f,2.0f,0.5f}){files["invalid"]=files["old"];files["invalid"]["automaticConvoyEnabled"]=bad;assert(!s.Read("invalid",loaded));} files["incomplete"]=files["old"];files["incomplete"].erase("reportIntervalSeconds");assert(!s.Read("incomplete",loaded)); for(auto pair:std::map{{0,0},{1,100},{15,7},{37,3},{10000,0}}){ files["legacy"]=files["old"];files["legacy"]["stingerChanceOneIn"]=pair.first; assert(s.Read("legacy",loaded)&&loaded->Get("antiAirChancePercent")==pair.second); } for(float bad:{-1.0f,10001.0f,0.5f}){files["legacy"]=files["old"];files["legacy"]["stingerChanceOneIn"]=bad;assert(!s.Read("legacy",loaded));} files["new"]=files["old"];files["new"]["stingerChanceOneIn"]=15;files["new"]["antiAirChancePercent"]=100; assert(s.Read("new",loaded)&&loaded->Get("antiAirChancePercent")==100&&loaded->Get("antiPersonnelMineChancePercent")==60); assert(Settings::AddedDefault("sarkaAngerPerCar")==3); for(int old=0;old<=5;++old){ files["sarka"]=files["old"];files["sarka"]["sarkaCarsPerAnger"]=old; assert(s.Read("sarka",loaded)&&loaded->Get("sarkaAngerPerCar")== (old==0?0:3)); } for(float bad:{-1.0f,6.0f,0.5f}){ files["sarka"]=files["old"];files["sarka"]["sarkaCarsPerAnger"]=bad; assert(!s.Read("sarka",loaded)); } for(int points:{0,1,3,5,30}){ files["sarka"]=files["old"];files["sarka"]["sarkaCarsPerAnger"]=0; files["sarka"]["sarkaAngerPerCar"]=points; assert(s.Read("sarka",loaded)&&loaded->Get("sarkaAngerPerCar")==points); } for(float bad:{-1.0f,31.0f,0.5f}){ files["sarka"]=files["old"];files["sarka"]["sarkaAngerPerCar"]=bad; assert(!s.Read("sarka",loaded)); } assert(!s.Read("missing",loaded)); } ''') assert 'm_Values.Set("automaticConvoyEnabled", AddedDefault("automaticConvoyEnabled"));' in P.read_text() admin=body(BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c','void Execute(') assert 'if (key == "automaticConvoyEnabled") line += " (saved for next round; current startup unchanged)";' in admin provider=BASE/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQAdminProvider.c' helper=body(provider,'protected void ConvoyAutoSetting(') for forbidden in ('StartConvoy','StartScenarioConvoy','Spawn','m_HQ.'): assert forbidden not in helper # Do not advertise this command-only setting as an editable field absent from the actual fixed menu. menu=body(ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenuFields.c','static array Keys(') assert 'automaticConvoyEnabled' not in menu assert body(provider,'override bool Help(').count('replies.Insert(')<=5 print('PASS: actual read migrates old valid settings, preserves explicit disabled state, rejects invalid data; next-round feedback and fixed menu remain honest')