Capture current mission, AI, map and administration development

Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
2026-10-13 22:52:53 +02:00
parent a951a96edc
commit 6a667d8deb
8104 changed files with 2124945 additions and 2061 deletions
@@ -0,0 +1,138 @@
"""Exercise real menu routing/capability bodies and the authored control contract.
This does not claim native focus, rendering or multiplayer RPC coverage.
"""
import re,os
from pathlib import Path
BASE=Path(os.environ.get("ERR_CONVOY_SETTINGS_ROOT", Path(__file__).resolve().parents[1]))
from policy_harness import ROOT, body, run
source = BASE / 'addons/core/Scripts/Game/ERR/00_Core/ERR_AdminMenu.c'
layout = BASE / 'addons/core/UI/layouts/ERR/AdminMenu.layout'
keys = re.findall(r'"([A-Za-z]+)"', body(source, 'static array<string> Keys('))
settings = re.findall(r'"([A-Za-z]+)"', body(BASE / 'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c', 'static array<string> Keys('))
assert len(keys) == 16 and set(settings) - set(keys) == {"automaticConvoyEnabled", "keepWeaponOnUnconscious", "automaticEquipmentChancePercent"}
assert set(keys) <= set(settings) # Every menu field must have a server setting.
# Native layouts place child widget declarations inside an anonymous Children
# block. A missing brace can preserve most widget names yet prevent menu load.
def validate_layout_structure(text):
tokens = re.findall(r'"(?:[^"\\]|\\.)*"|[{}]|[^\s{}"]+', text)
stack = []
prefix = []
for token in tokens:
if token.endswith('WidgetClass'):
assert not stack or stack[-1] == 'children', 'Widget outside parent Children block: ' + token
if token == '{':
kind = 'property'
if any(part.endswith('WidgetClass') for part in prefix):
kind = 'widget'
elif not prefix:
kind = 'children'
stack.append(kind)
prefix = []
elif token == '}':
assert stack, 'Unexpected closing brace'
stack.pop()
prefix = []
else:
prefix.append(token)
assert not stack, 'Unclosed layout block'
validate_layout_structure(layout.read_text())
try:
validate_layout_structure(layout.read_text().replace('Name "JournalState"', 'Name "JournalState" {', 1))
except AssertionError:
pass
else:
raise AssertionError('Malformed child structure must be rejected')
names = re.findall(r'\bName "([^"]+)"', layout.read_text())
assert 'RandomDay_startTimeMinutes' in names
assert len(names) == len(set(names)), 'Menu widget names must be unique'
for key in keys:
assert all(name in names for name in (key, 'Apply_' + key, 'Saved_' + key, 'Help_' + key))
if key != 'startMode': assert ('Default_' if key == 'viewDistanceMetres' else 'Random_') + key in names
else: assert 'Random_startMode' not in names
for name in ('Close', 'SettingsPage', 'HQPage', 'JournalPage', 'JournalText', 'JournalContent', 'JournalScroll', 'PauseJournal', 'LatestJournal', 'Status'):
assert name in names
for name in re.findall(r'"([A-Za-z]+)"', body(source, 'override void OnMenuOpen(').split('foreach')[0]):
assert name in names
journal = body(source, 'static string JournalText(').replace('ERR_JournalText.Menu', 'ERR_JournalText::Menu')
menu_format = body(ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_JournalText.c', 'static string Menu(')
follow = body(source, 'static bool FollowJournal(')
command = body(source, 'static string Command(')
available = body(source, 'static bool Available(').replace('Keys().Contains(key)', 'Known(key)')
known = ' || '.join('key == "' + key + '"' for key in keys)
run(r'''
#include <cassert>
#include <string>
#include <vector>
struct string : std::string {
using std::string::string;
string(std::string s):std::string(s){}
bool IsEmpty(){return empty();}
int Length(){return size();}
int IndexOf(string s){auto i=find(s);return i==npos?-1:int(i);}
string Substring(int i,int n){return substr(i,n);}
void Replace(string a,string b){size_t i=0;while((i=find(a,i))!=npos){replace(i,a.size(),b);i+=b.size();}}
string Trim(){auto a=find_first_not_of(" \t\r\n");if(a==npos)return "";auto b=find_last_not_of(" \t\r\n");return substr(a,b-a+1);}
};
struct ERR_JournalText {
''' + menu_format + r'''
};
struct Fields {
static bool Known(string key) {return ''' + known + ';}\n' + command + '\n' + available + '\n' + follow + '\n' + journal + r'''
};
int main() {
string raw="#24 t=7s M3 observe guard";
assert(Fields::JournalText(raw)=="No. 24 t=7s M3 observe guard");
assert(raw=="#24 t=7s M3 observe guard");
assert(Fields::JournalText("unprefixed event")=="unprefixed event");
assert(Fields::JournalText("#25 delivered #24")=="No. 25 delivered No. 24");
assert(Fields::JournalText("").empty());
assert(!Fields::FollowJournal(false,0.5,1000,400,false));
assert(!Fields::FollowJournal(false,0,1000,400,false));
assert(!Fields::FollowJournal(false,0.97,1000,400,false));
assert(Fields::FollowJournal(false,1,1000,400,false));
assert(Fields::FollowJournal(false,0,1000,400,true));
assert(Fields::FollowJournal(false,0,200,400,false));
assert(Fields::FollowJournal(true,0,0,400,false));
std::vector<std::pair<string,string>> cases={
{"startMode","mission start"},{"radioDefenceSeconds","mission radio"},{"aiIntelChancePercent","intel carrying"},
{"intelRevealChancePercent","intel reveal"},{"startTimeMinutes","mission time"},
{"timeMultiplier","mission speed"},{"startDateCode","mission date"},
{"weatherProfile","mission weather"},{"viewDistanceMetres","mission viewdistance"},{"reportIntervalSeconds","hq interval"},
{"heliTimerMinutes","hq heli timer"},{"supportSquadLimit","hq squads"},{"responseRadiusMetres","hq radius"},{"convoyCount","hq convoy count"},{"antiAirChancePercent","hq aa"},{"antiPersonnelMineChancePercent","hq mines"}};
for(auto pair:cases){
assert(Fields::Command(pair.first," 42 ")==pair.second+" set 42");
assert(Fields::Command(pair.first,"Random")==pair.second+" set Random");
assert(Fields::Command(pair.first,"").empty());
assert(Fields::Command(pair.first," \t ").empty());
assert(Fields::Command(pair.first,string(33,'x')).empty());
assert(Fields::Available(pair.first,true,true));
}
assert(Fields::Command("startTimeMinutes","14:25")=="mission time set 14:25");
assert(Fields::Command("startDateCode","2026-09-19")=="mission date set 2026-09-19");
assert(Fields::Command("weatherProfile","authored")=="mission weather set authored");
for(string key:{"automaticEquipmentChancePercent","heliTimer","prisonDistance","stingerChance","mission abandon","","bad"}){
assert(Fields::Command(key,"1").empty());assert(!Fields::Available(key,true,true));
}
for(string key:{"heliTimerMinutes","reportIntervalSeconds","supportSquadLimit","responseRadiusMetres","convoyCount","antiAirChancePercent","antiPersonnelMineChancePercent"}){
assert(!Fields::Available(key,false,true));assert(Fields::Available(key,true,false));
}
assert(!Fields::Available("aiIntelChancePercent",true,false));
assert(Fields::Available("aiIntelChancePercent",false,true));
for(string key:{"intelRevealChancePercent","radioDefenceSeconds","startTimeMinutes","timeMultiplier","startDateCode","weatherProfile"}) assert(Fields::Available(key,false,false));
}
''')
print('PASS: actual menu command routing covers all sixteen settings, rejects unsupported keys, gates companions, and matches unique authored widgets')
assert all(name in names for name in ('KeepWeaponOn', 'KeepWeaponOff', 'Saved_keepWeaponOnUnconscious'))
click=body(source,'protected void OnButton(')
assert 'm_Controller.ERR_AdminSubmit("mission keepweapon set " + retain, true)' in click
assert click.index('!m_Ready') < click.index('"mission keepweapon set "')
print('PASS: explicit weapon retention buttons use the same ready/authorized server command path')
# The edit page must carry every control bound by the extension, with a valid row.
items_menu = (ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_ServerItemsMenu.c').read_text()
for name in ('ItemsMod','ItemsShow','ItemsEnableAll','ItemsDisableAll','ItemsPrevious','ItemsNext','ItemsPresets','ItemsPresetLoad','ItemsPresetDelete','ItemsPresetName','ItemsPresetSave','ItemsStorage','ItemsContent'):
assert name in names, name
validate_layout_structure((ROOT/'addons/core/UI/layouts/ERR/ItemRow.layout').read_text())
print('PASS: item editor, preset controls and dynamic item-row layout are structurally valid')
@@ -0,0 +1,108 @@
"""Execute recovery spawn retries and retirement using production method bodies."""
from policy_harness import ROOT, body, run
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_BodyRecovery.c'
def adapt(s):
s=s.replace('Math.Max', 'std::max').replace('protected ', '').replace('ERR_BodyRecoveryJob job','Job* job').replace('AIAgent agent','Agent* agent')
s=s.replace('ChimeraAIWorld world = ChimeraAIWorld.Cast(GetGame().GetAIWorld());','World* world = GetGame()->GetAIWorld();')
s=s.replace('job.Team.GetOnAgentAdded().Remove(job.MemberAdded);','job.Team.GetOnAgentAdded()->Remove();')
s=s.replace('new array<ResourceName>()','Slots{0}').replace('array<AIAgent> agents = {};','Array<Agent*> agents;')
s=s.replace('foreach (','for (').replace('null','nullptr')
s=s.replace('ERR_HQ.Event','ERR_HQ::Event').replace('string.Format','string::Format')
for v in ('job','agent','world','m_HQ','m_Mode'):
s=s.replace(v+'.',v+'->')
s=s.replace('Team.','Team->').replace('Evidence().','Evidence()->')
return s
added=adapt(body(p,'void MemberAdded('))
retry=adapt(body(p,'protected void RetryEmptySpawn('))
refund=adapt(body(p,'protected void ReturnUnusedWave('))
preparation=adapt(body(p,'protected bool RetryPreparation('))
retire=adapt(body(p,'protected void RetireSpawn('))
finish=adapt(body(p,'protected void Finish('))
pins=adapt(body(p,'protected void ReleasePins('))
diagnostic=adapt(body(p,'protected void Diagnostic(')).split(' int agents, living;')[0]
diagnostic += ' diagnostics++;\n }'
reuse=adapt(body(p,'protected bool Reusable(')).replace('ERR_BodyRecoveryJob previous','Job* previous').replace('previous.', 'previous->')
run(r'''
#include <cassert>
#include <algorithm>
#include <string>
#include <vector>
struct string:std::string{using std::string::string;template<class...T>static string Format(string fmt,T...){return fmt;}};
template<class T>struct Array:std::vector<T>{int Count(){return this->size();}};
struct Agent{int lod=-1,activations=0;void SetPermanentLOD(int v){lod=v;}void ActivateAI(){activations++;}};
struct Slots{int size=4;int Count(){return size;}};
struct Hook{int removals=0;void Remove(){removals++;}};
struct Group{int count=0,lod=0,requests=0,wanted=0;bool immediately=true;int players=0,faction=1,living=4;int GetPlayerCount(bool){return players;}int GetFaction(){return faction;}Slots m_aUnitPrefabSlots;Hook hook;Array<Agent*> agents;
int GetAgentsCount(){return count;}void RequestSpawn(int n){requests++;wanted=n;}void SetSpawnImmediately(bool b){immediately=b;}
Hook* GetOnAgentAdded(){return &hook;}void SetPermanentLOD(int n){lod=n;}void GetAgents(Array<Agent*>& out){out=agents;}};
struct World{int purges=0;Group* last=nullptr;void PurgeSpawnRequestsForGroup(Group*g){purges++;last=g;}};
struct Game{World world;bool ai=true;World* GetAIWorld(){return ai?&world:nullptr;}} game;
bool gameAvailable=true;Game* GetGame(){return gameAvailable?&game:nullptr;}
struct Job{int Id=1,State=0,SpawnAttempts=1;float NextSpawnAttempt=10000;bool SpawnedMember=false,Delivered=false,Prepared=false,SpawnWave=false,HadTeam=false;int PreparationRetries=0;float RetryAt=0;float NextStatusDiagnostic=0,NextDiagnostic=0;string LastDiagnosticReason;Group* Team=nullptr;Array<string> Missing;int Position=99,Body=7;void* Order=nullptr;void* Report=nullptr;
'''+added+r'''
};
struct Evidence{int delivered=0;void Delivered(int){delivered++;}};
struct Mode{int EnemyFaction(){return 1;}int ForceLiving(Group*g){return g->living;}struct Evidence evidence;struct Evidence* Evidence(){return &evidence;}};
struct ERR_HQ{int compromised=0,released=0;static void Event(string,string){}void CompromisedSites(Array<string>,int){compromised++;}void ReleaseRecovery(Group*){released++;}};
struct Recovery{Mode* m_Mode;ERR_HQ* m_HQ;int removed=0,diagnostics=0,m_Waves=0,m_SpawnRequests=0;void RemoveWaypoint(Job*){removed++;}
'''+retry+retire+pins+refund+preparation+finish+reuse+diagnostic+r'''
};
int main(){
Mode mode;ERR_HQ hq;Recovery r{&mode,&hq};Group team;Job job;job.Team=&team;
r.RetryEmptySpawn(&job,9999);assert(team.requests==0&&job.SpawnAttempts==1);
r.RetryEmptySpawn(&job,10000);assert(team.requests==1&&team.wanted==4&&job.SpawnAttempts==2&&game.world.last==&team);
r.RetryEmptySpawn(&job,19999);assert(team.requests==1);
r.RetryEmptySpawn(&job,20000);assert(team.requests==2&&job.SpawnAttempts==3);
r.RetryEmptySpawn(&job,90000);assert(team.requests==2&&game.world.purges==2);
// Unchanged waits are rate limited; changed causes are visible immediately.
Job status;r.Diagnostic(&status,0,"roster");assert(r.diagnostics==1);
r.Diagnostic(&status,29999,"roster");assert(r.diagnostics==1);
r.Diagnostic(&status,29999,"radio");assert(r.diagnostics==2);
status.NextDiagnostic=1000000; // movement logs have a separate throttle
r.Diagnostic(&status,59999,"radio");assert(r.diagnostics==3);
// Never replenish a soldier that spawned and disappeared before the next poll.
Job lost;lost.Team=&team;Agent original;lost.MemberAdded(&original);assert(lost.SpawnedMember&&original.lod==0&&original.activations==1);
r.RetryEmptySpawn(&lost,90000);assert(team.requests==2);
Job partial;partial.Team=&team;team.count=1;r.RetryEmptySpawn(&partial,90000);assert(team.requests==2);team.count=0;
Job callback;callback.Team=&team;callback.MemberAdded(nullptr);r.RetryEmptySpawn(&callback,90000);assert(team.requests==2);
// Abort cancels pending spawn intent and clears orders, but never reports intel.
job.Order=&team;job.Report=&team;team.agents.push_back(&original);
r.Finish(&job,false,"preparation deadline");assert(job.State==4&&!job.Delivered&&!job.Order&&!job.Report);
assert(team.m_aUnitPrefabSlots.Count()==0&&!team.immediately&&game.world.purges==3&&team.hook.removals==1&&team.lod==-1&&original.lod==-1);
assert(!mode.evidence.delivered&&!hq.compromised&&hq.released==1);
// Only actual successful receipt calls the evidence/compromise boundary.
Job received;received.Team=&team;received.State=3;received.Missing.push_back("radio-1");
r.Finish(&received,true,"radio delivered");assert(received.Delivered&&mode.evidence.delivered==1&&hq.compromised==1);
assert(game.world.purges==3); // a completed registered team has no preparation queue to cancel
Job deleted;r.Finish(&deleted,false,"team deleted");assert(deleted.State==4&&!deleted.Delivered);
// A failed unregistered preparation may leave survivors, but cannot be reused
// as a registered team. Completed ready teams may be reused; ownership holds.
Job waiting,previous;previous.Team=&team;previous.State=4;
assert(!r.Reusable(&waiting,&previous));previous.Prepared=true;
assert(r.Reusable(&waiting,&previous)&&!r.Reusable(&previous,&previous));
previous.State=2;assert(!r.Reusable(&waiting,&previous));previous.State=4;
team.players=1;assert(!r.Reusable(&waiting,&previous));team.players=0;
team.faction=2;assert(!r.Reusable(&waiting,&previous));team.faction=1;
team.living=0;assert(!r.Reusable(&waiting,&previous));team.living=1;
assert(r.Reusable(&waiting,&previous));previous.Team=nullptr;assert(!r.Reusable(&waiting,&previous));
// Failed empty attempts do not consume the allowance for actual teams.
Job empty;empty.Team=&team;empty.SpawnWave=true;r.m_Waves=1;
r.ReturnUnusedWave(&empty);assert(r.m_Waves==0&&!empty.SpawnWave);
r.ReturnUnusedWave(&empty);assert(r.m_Waves==0);
empty.SpawnWave=true;empty.SpawnedMember=true;r.m_Waves=1;
r.ReturnUnusedWave(&empty);assert(r.m_Waves==1&&empty.SpawnWave);
empty.SpawnedMember=false;team.count=1;
r.ReturnUnusedWave(&empty);assert(r.m_Waves==1);team.count=0;
empty.PreparationRetries=2;assert(!r.RetryPreparation(&empty,100000));
empty.PreparationRetries=0;r.m_SpawnRequests=32;assert(!r.RetryPreparation(&empty,100000));
r.m_SpawnRequests=8;empty.SpawnedMember=true;assert(!r.RetryPreparation(&empty,100000));
empty.SpawnedMember=false;int purges=game.world.purges;
assert(r.RetryPreparation(&empty,100000));
assert(!empty.Team&&!empty.HadTeam&&empty.RetryAt==160000&&empty.PreparationRetries==1&&r.m_Waves==0);
assert(game.world.purges==purges+1);
// Teardown can retire intent after the game/AI-world accessor has disappeared.
Job stop;stop.Team=&team;gameAvailable=false;r.RetireSpawn(&stop);assert(!team.immediately);
gameAvailable=true;game.ai=false;int previousPurges=game.world.purges;r.RetireSpawn(&stop);assert(game.world.purges==previousPurges);
}
''')
print('PASS: bounded same-group empty-spawn retries, no casualty refill, queue retirement, delivery-gated intel, prepared-team reuse, throttled diagnostics and LOD release')
@@ -0,0 +1,51 @@
"""Execute production idle-team selection with explicit radio/status boundaries."""
import re
from policy_harness import ROOT, body, run
p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_BodyRecovery.c'
s=body(p,'SCR_AIGroup RecoveryTeam(')
s=s.replace('SCR_AIGroup RecoveryTeam','Group* RecoveryTeam').replace('array<SCR_AIGroup> blocked','Array<Group*>* blocked')
s=s.replace('ERR_HQUnit best;', 'Unit* best=nullptr;').replace('ERR_HQUnit unit : m_Units','Unit* unit : m_Units').replace('ERR_HQWatchEntry status','Status* status')
s=s.replace('foreach (','for (').replace('null','nullptr').replace('nullptrptr','nullptr')
for name in ('unit','status','best','blocked'):s=s.replace(name+'.',name+'->')
s=s.replace('vector.DistanceXZ','vector::DistanceXZ').replace('ERR_InfantryResupplyRules.Fresh','Rules::Fresh')
run(r'''
#include <cassert>
#include <vector>
#include <cmath>
#include <algorithm>
struct vector{float x=0;static float DistanceXZ(vector a,vector b){return std::abs(a.x-b.x);}};
struct Group{};
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}};
struct Status{vector Position;bool fresh=true;};
struct Unit{struct Group* Group=nullptr;bool eligible=true,HomeGuard=false,Assignment=false,PendingTask=false,PendingOrder=false,Downlink=false,fighting=false,Radio=true;int ForceSourceId=0;bool Eligible(void*){return eligible;}};
struct Watch{Status entries[3];Status* Find(int i){return &entries[i];}};
struct Network{bool available=true;bool Route(bool,bool){return available;}};
struct Rules{static bool Fresh(Status*s,float){return s&&s->fresh;}};
struct HQ{Array<Unit*> m_Units;Watch m_Watch;Network m_Network;void* m_Mode=nullptr;bool Fighting(Unit*u){return u->fighting;}
'''+s+r'''
};
int main(){
Group a,b;Unit first,second;first.Group=&a;second.Group=&b;second.ForceSourceId=1;
HQ h;h.m_Units={&first,&second};h.m_Watch.entries[0].Position.x=100;h.m_Watch.entries[1].Position.x=200;
assert(h.RecoveryTeam({},nullptr,1000)==&a);
Array<Group*> blocked{&a};assert(h.RecoveryTeam({},&blocked,1000)==&b);
blocked.push_back(&b);assert(!h.RecoveryTeam({},&blocked,1000));
h.m_Units={&first};
for(int fault=0;fault<10;fault++){
Unit clean=first;first=Unit{};first.Group=&a;
if(fault==0)first.eligible=false;
if(fault==1)first.HomeGuard=true;
if(fault==2)first.Assignment=true;
if(fault==3)first.PendingTask=true;
if(fault==4)first.PendingOrder=true;
if(fault==5)first.Downlink=true;
if(fault==6)first.fighting=true;
if(fault==7)first.Radio=false;
if(fault==8)h.m_Watch.entries[0].fresh=false;
if(fault==9)h.m_Network.available=false;
assert(!h.RecoveryTeam({},nullptr,1000));first=clean;h.m_Watch.entries[0].fresh=true;h.m_Network.available=true;
}
h.m_Watch.entries[0].Position.x=5000;assert(!h.RecoveryTeam({},nullptr,1000));
}
''')
print('PASS: nearest reported idle team, blocked teams, guards, active/pending orders, combat, radio routes, stale status and distance limit')
@@ -0,0 +1,124 @@
"""Actual start policy/setup/freeze methods; engine/resource/terrain boundaries doubled."""
import os,sys
from pathlib import Path
ROOT=Path(os.environ.get('ERR_REPO',Path(__file__).resolve().parents[1]))
P=Path(os.environ.get('ERR_CRASH_CORE_ROOT',ROOT))
sys.path.insert(0,str(ROOT/'tests'))
from policy_harness import body,run
C='addons/core/Scripts/Game/ERR/00_Core/'
def source(name):return P/C/name
def cpp(s):
for a,b in [('protected ',''),('ERR_ForceRules.','ERR_ForceRules::'),('ERR_StartRules.','Rules::'),('ERR_CrashSiteResources.','Resources::'),('ERR_LocationKind.','Kind::'),('Math.','Math::'),('vector.Zero','vector{}'),('out vector ','vector& '),('out float ','float& ')]:s=s.replace(a,b)
return s
rules='\n'.join(cpp(body(source('ERR_StartRules.c'),sig)) for sig in ['static bool Valid(','static bool Geometry('])
setup='\n'.join(cpp(body(source('ERR_StartSetup.c'),sig)) for sig in ['static bool IsCrash(','static bool Geometry(','static bool Configure('])
for a,b in [('ERR_SiteDefinition site','Site* site'),('ERR_SiteDefinition start;','Site* start=nullptr;'),('ERR_ScenarioDefinition definition','Definition* definition'),('ERR_SiteProp wreck','Prop* wreck'),('ref Prop*','Prop*'),('new ERR_SiteProp()','new Prop()'),('new array<ref ERR_SiteProp>()','new Array<Prop*>()'),('foreach (','for ('),('int count;','int count=0;')]:setup=setup.replace(a,b)
for var in ['site','start','definition','wreck']:setup=setup.replace(var+'.',var+'->')
setup=setup.replace('start->Props.Insert','start->Props->Insert').replace('.IsEmpty()','.empty()')
run(r'''
#include <cassert>
#include <cmath>
#include <limits>
#include <vector>
#include <string>
#define null nullptr
struct vector{float a[3]{};float&operator[](int i){return a[i];}float operator[](int i)const{return a[i];}float Length(){return std::sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2]);}};
vector Vector(float x,float y,float z){return{{x,y,z}};}vector operator+(vector a,vector b){return Vector(a[0]+b[0],a[1]+b[1],a[2]+b[2]);}vector operator*(vector a,float b){return Vector(a[0]*b,a[1]*b,a[2]*b);}
namespace ERR_ForceRules{bool Finite(float x){return std::isfinite(x);}}
namespace Math{float Ceil(float x){return std::ceil(x);}float Max(float x,float y){return std::fmax(x,y);}float AbsFloat(float x){return std::fabs(x);}}
struct Rules{RULES};
namespace Kind{constexpr int PRISON=0;}
template<class T>struct Array:std::vector<T>{using std::vector<T>::operator=;void Insert(T x){this->push_back(x);}};
struct Prop{std::string Prefab;vector Offset;};
struct Site{int Kind=0,GuardCount=6,ResponseCount=2;bool Guarded=true,AllowFactionPool=true,RandomClear=false;std::string Prefab="prison",Label="Prison",Guards="guards",Container="finite-bag",Id="original";float RootHeight=2;int*Patrol=(int*)1;void*Stock=(void*)2;vector ContainerOffset=Vector(-4,0,0);Array<Prop*>*Props=nullptr;};
struct Definition{Array<Site*>Sites;bool valid=true;int calls=0;bool Valid(){calls++;return valid;}};
namespace Resources{bool ready=true;vector low=Vector(-4,-1,-3),high=Vector(6,4,3);std::string SitePrefab(){return"crash-site";}std::string WreckPrefab(){return"wreck";}bool Bounds(vector&lo,vector&hi){lo=low;hi=high;return ready;}}
struct Setup{SETUP};
int main(){
assert(Rules::Valid(0)&&Rules::Valid(1)&&!Rules::Valid(-1)&&!Rules::Valid(2));
vector offset,center;float half,height;
assert(Rules::Geometry(Resources::low,Resources::high,offset,center,half,height));assert(offset[0]+Resources::low[0]==12&&offset[1]==1&&half==6&&height==6);
for(int a=0;a<3;a++)for(float bad:{std::numeric_limits<float>::infinity(),std::numeric_limits<float>::quiet_NaN()}){vector lo=Resources::low;lo[a]=bad;assert(!Rules::Geometry(lo,Resources::high,offset,center,half,height)&&half==0&&height==0&&offset[0]==0);}
assert(!Rules::Geometry(Vector(0,0,100),Vector(1,1,101),offset,center,half,height));assert(!Rules::Geometry(Vector(0,0,0),Vector(0,1,1),offset,center,half,height));assert(!Rules::Geometry(Vector(-50,0,0),Vector(-49,1,1),offset,center,half,height));assert(!Rules::Geometry(Vector(0,0,0),Vector(31,1,1),offset,center,half,height));assert(!Rules::Geometry(Vector(0,0,0),Vector(1,21,1),offset,center,half,height));
Site prison;Definition original;original.Sites={&prison};Resources::ready=false;assert(Setup::Configure(&original,0)&&prison.Guarded&&prison.Prefab=="prison"&&prison.ResponseCount==2&&prison.RootHeight==2&&original.calls==0);
assert(!Setup::Configure(&original,1)&&prison.Prefab=="prison");Resources::ready=true;
assert(Setup::Configure(&original,1)&&original.calls==1&&Setup::IsCrash(&prison));assert(!prison.Guarded&&prison.Guards.empty()&&prison.GuardCount==0&&prison.ResponseCount==0&&!prison.Patrol&&!prison.AllowFactionPool&&prison.RandomClear&&prison.RootHeight==0);
assert(prison.Stock==(void*)2&&prison.Container=="finite-bag"&&prison.Id=="original"&&prison.ContainerOffset[0]==-4&&prison.Props->size()==1&&(*prison.Props)[0]->Prefab=="wreck"&&(*prison.Props)[0]->Offset[0]==16);
for(int fault=0;fault<7;fault++){Site a,b;Definition d;d.Sites={&a};if(fault==0)d.Sites.clear();if(fault==1)d.Sites.push_back(&b);if(fault==2)a.Container="";if(fault==3)a.ContainerOffset[0]=9;if(fault==4)a.ContainerOffset[1]=2;if(fault==5)d.valid=false;assert(!Setup::Configure(fault==6?nullptr:&d,1));}
assert(!Setup::Configure(&original,-1));
}
'''.replace('RULES',rules).replace('SETUP',setup))
# Actual once-only server freeze, including failed settings and restart/new owner.
freeze=body(source('ERR_GameMode.c'),'protected bool FreezeStartMode(').replace('protected ','').replace('Replication.','Replication::').replace('ERR_Phase.','Phase::').replace('ERR_StartRules.','Rules::').replace('m_Settings.','m_Settings->')
run(r'''
#include <cassert>
#include <string>
namespace Replication{bool server=true;int bumps=0;bool IsServer(){return server;}void BumpMe(){bumps++;}}
namespace Phase{constexpr int PREPARING=0;}
namespace Rules{bool Valid(int n){return n==0||n==1;}}
struct Settings{int value=0,reads=0;int Get(std::string key){assert(key=="startMode");reads++;return value;}};
struct Mode{bool m_Stopped=false,m_NativeEndHandled=false,m_StartModeFrozen=false;int m_Phase=0,m_StartMode=0;Settings*m_Settings=nullptr;METHOD};
int main(){Settings s;Mode p;p.m_Settings=&s;assert(p.FreezeStartMode()&&p.m_StartMode==0);s.value=1;assert(p.FreezeStartMode()&&p.m_StartMode==0&&s.reads==1);Mode restart;restart.m_Settings=&s;assert(restart.FreezeStartMode()&&restart.m_StartMode==1);for(int f=0;f<6;f++){Mode x;x.m_Settings=&s;if(f==0)Replication::server=false;if(f==1)x.m_Stopped=true;if(f==2)x.m_NativeEndHandled=true;if(f==3)x.m_Phase=1;if(f==4)x.m_Settings=nullptr;if(f==5)s.value=2;assert(!x.FreezeStartMode()&&!x.m_StartModeFrozen);Replication::server=true;s.value=1;}}
'''.replace('METHOD',freeze))
# Actual transition policy retains all default edges; crash authorization adds
# only initial RADIO_SEARCH and cannot add a terminal or downstream shortcut.
policy='\n'.join(body(source('ERR_MissionRules.c'),sig) for sig in ['static bool Terminal(','static bool CanTransition(']).replace('ERR_Phase.','Phase::')
run(r'''
#include <cassert>
namespace Phase{enum{PREPARING,PRISON,RADIO_SEARCH,RADIO_DEFENCE,EXTRACTION,VICTORY,DEFEAT};}
POLICY
int main(){for(int from=0;from<=6;from++)for(int to=0;to<=6;to++){bool old=(from<5&&to==from+1)||(from>=1&&from<=4&&to==6);assert(CanTransition(from,to)==old);assert(CanTransition(from,to,true)==(from==0?to==2:old));}}
'''.replace('POLICY',policy))
mode=source('ERR_GameMode.c').read_text();scenario=source('ERR_ScenarioComponent.c').read_text()
transition=body(source('ERR_GameMode.c'),'bool Transition(')
assert 'm_StartModeFrozen && m_StartPrepared && m_StartMode == 1' in transition
assert 'm_Phase == ERR_Phase.PREPARING && !m_StartPrepared' in transition
start=body(source('ERR_GameMode.c'),'protected void StartScenario(')
assert start.index('FreezeStartMode()')<start.index('scenario.Prepare()')<start.index('m_StartPrepared = true;')<start.index('Transition(ERR_Phase.RADIO_SEARCH)')
assert start.index('m_Time.Start(this)')<start.index('m_StartPrepared = true;')
assert 'previous == ERR_Phase.PREPARING' in transition
assert '[RplProp()] protected int m_StartMode;' in mode
assert scenario.index('ERR_ConfigurePatrolHQFixture(fixture)')<scenario.index('ERR_StartSetup.Configure(')<scenario.index('m_Generated.Begin(')
assert 'historicalFixture != "crash-start"' in scenario and 'fixture != "crash-start"' in scenario
assert 'm_Mode.StartMode() != 0' in body(source('ERR_PrisonEscapeService.c'),'void PhaseChanged(')
# Both generated and terrain-independent placement dispatch through this exact
# shared Site method; actual Clear is native trace geometry (not simulated here).
survey=body(source('ERR_GeneratedSites.c'),'bool Site(')
assert 'ERR_StartSetup.IsCrash(site)' in survey and 'ERR_StartSetup.Geometry(offset, center, halfSize, height)' in survey
assert 'Clear(position + Rotate(center, heading), heading, halfSize, 0.6, wreckFloor, height)' in survey
assert 'm_Terrain.Site(site, position, heading, floor)' in (ROOT/C/'ERR_TerrainSites.c').read_text()
print('PASS: actual crash geometry, detached definition setup, frozen server choice and guarded phase policy; integration/source ordering checked')
# Execute actual GameMode transition admission prefix (effects after acceptance
# are existing mission hooks); callers cannot use selected crash mode before
# ordinary scenario/extraction/environment preparation has completed.
admit=body(source('ERR_GameMode.c'),'bool Transition(').split('\n\t\tERR_Phase previous',1)[0]+'\n return true;\n}'
admit=admit.replace('ERR_Phase phase','int phase').replace('Replication.','Replication::').replace('ERR_MissionRules.','Policy::').replace('ERR_Phase.','Phase::').replace('m_PreparationError.IsEmpty()','m_PreparationError.empty()')
run(r'''
#include <cassert>
#include <string>
namespace Replication{bool server=true;bool IsServer(){return server;}}
namespace Phase{enum{PREPARING,PRISON,RADIO_SEARCH,RADIO_DEFENCE,EXTRACTION,VICTORY,DEFEAT};}
namespace Policy{POLICY}
struct Mode{int m_Phase=0,m_StartMode=1;bool m_StartModeFrozen=false,m_StartPrepared=false;std::string m_PreparationError;ADMIT};
int main(){Mode crash;assert(!crash.Transition(2));crash.m_StartModeFrozen=true;assert(!crash.Transition(2));crash.m_StartPrepared=true;assert(crash.Transition(2)&&!crash.Transition(1));crash.m_PreparationError="failed";assert(!crash.Transition(2));crash.m_PreparationError="";Replication::server=false;assert(!crash.Transition(2));Replication::server=true;Mode prison;prison.m_StartMode=0;prison.m_StartModeFrozen=true;prison.m_StartPrepared=true;assert(prison.Transition(1)&&!prison.Transition(2));prison.m_Phase=1;assert(prison.Transition(2));}
'''.replace('POLICY',policy).replace('ADMIT',admit))
# Execute actual common site survey to prove both measured wreck and start/cache
# areas are required and that unknown measured bounds cannot fall back to10m.
site=body(source('ERR_GeneratedSites.c'),'bool Site(')
for a,b in [('ERR_SiteDefinition site','SiteData* site'),('site.','site->'),('foreach (','for ('),('ERR_LocationKind.','Kind::'),('ERR_StartSetup.','StartSetup::'),('SCR_AIWorld aiWorld = SCR_AIWorld.Cast(GetGame().GetAIWorld());','AIWorld* aiWorld = &world;'),('aiWorld.GetRoadNetworkManager()','aiWorld->GetRoadNetworkManager()'),('GetRoadNetworkManager().','GetRoadNetworkManager()->'),('BaseRoad road; float distance;','Road* road=nullptr; float distance=0;'),('skipNavlinks: true','true'),('out float ','float& '),('vector.DistanceXZ','DistanceXZ')]:site=site.replace(a,b)
run(r'''
#include <cassert>
#include <cmath>
#include <vector>
#define null nullptr
struct vector{float x=0,y=0,z=0;};vector operator+(vector a,vector b){return{a.x+b.x,a.y+b.y,a.z+b.z};}float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}
namespace Kind{constexpr int PRISON=0;}
struct SiteData{int Kind=0,ResponseCount=0;bool Guarded=false,crash=false;vector GuardOffset,ResponseOffset;};
namespace StartSetup{bool measured=true;bool IsCrash(SiteData*s){return s->crash;}bool Geometry(vector&offset,vector&center,float&half,float&height){offset={16,1,0};center={17,0,0};half=6;height=6;return measured;}}
struct Road{};struct RoadManager{void GetClosestRoad(vector,Road*&,float&,bool){}};struct AIWorld{RoadManager roads;RoadManager*GetRoadNetworkManager(){return &roads;}};
struct Call{vector p;float half,height;};
struct Survey{AIWorld world;std::vector<vector>m_Airfields;std::vector<Call>calls;int refuse=0;vector Rotate(vector p,float h){assert(h==90);return{p.z,p.y,-p.x};}bool Clear(vector p,float,float half,float,float&floor,float height=4){calls.push_back({p,half,height});floor=42;return int(calls.size())!=refuse;}SITE};
int main(){SiteData a;float floor=0;Survey normal;assert(normal.Site(&a,{100,0,200},90,floor)&&normal.calls.size()==1&&normal.calls[0].half==10&&normal.calls[0].height==4);a.crash=true;Survey crash;assert(crash.Site(&a,{100,0,200},90,floor)&&crash.calls.size()==2);assert(crash.calls[1].p.x==100&&crash.calls[1].p.z==183&&crash.calls[1].half==6&&crash.calls[1].height==6);for(int refusal:{1,2}){Survey blocked;blocked.refuse=refusal;assert(!blocked.Site(&a,{100,0,200},90,floor));}StartSetup::measured=false;Survey unknown;assert(!unknown.Site(&a,{100,0,200},90,floor)&&unknown.calls.size()==1);}
'''.replace('SITE',site))
print('PASS: actual GameMode readiness admission and common full-footprint survey require crash authorization and both surveyed areas')
@@ -0,0 +1,240 @@
"""Execute real engineer ownership, callback and Tick bodies; native boundaries are doubles.
No movement, Enforce compilation, pressure detonation or multiplayer proof.
"""
import os,re
from pathlib import Path
from policy_harness import ROOT,body,run
P=Path(os.environ.get('ERR_ENGINEER_MINE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c'))
def cpp(s):
s=s.replace('protected override ','').replace('protected ','').replace('null','nullptr').replace('ref ','')
s=s.replace('array<','Array<').replace(' = {};',';').replace('foreach (','for (')
s=s.replace('array<IEntity> attempted','Array<IEntity>* attempted')
s=s.replace('Array<vector> priorSites','Array<vector>* priorSites').replace('priorSites.','priorSites->').replace(': priorSites)',': *priorSites)')
s=s.replace(', m_PriorSites)',', &m_PriorSites)').replace('Math.Clamp','Math::Clamp')
for typ in ['ERR_ForceRecord','SCR_AIGroupUtilityComponent','ERR_GameMode','ERR_HQUnit','ERR_HQTask','SCR_ChimeraAIAgent','SCR_InventoryStorageManagerComponent','SCR_PressureTriggerComponent','InventoryItemComponent','DamageManagerComponent','FactionAffiliationComponent','Faction','Physics']:
s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
for ns in ['ERR_ForceRules','Replication','ERR_GameMode','ERR_HunterCombat','ERR_Actors','CompartmentAccessComponent','SCR_FactionManager','SCR_ChimeraAIAgent','SCR_AIUtils','ERR_EnemyLoadoutCatalog','ERR_Inventory','ERR_InfantryAmmoTransfer','InventoryItemComponent','DamageManagerComponent','FactionAffiliationComponent','ChimeraCharacter','Vehicle','SCR_InventoryStorageManagerComponent','SCR_PressureTriggerComponent','ERR_EngineerMineRules','ERR_HQ','vector']:
s=s.replace(ns+'.',ns+'::')
for obj in ['groupUtility','m_Mode','m_Unit','m_Group','m_Task','m_Assignment','m_Record','m_Actor','m_Mine','m_Trigger','m_Inventory','m_Utility','m_Action','m_Removal','m_Attempted','mode','unit','task','actor','mine','agent','inventory','trigger','item','entity','physics','damage','affiliation','faction']:
s=re.sub(r'\b'+obj+r'\.',obj+'->',s)
for field in ['StatusSampler','InfantryCombat','m_UtilityComponent']:
s=s.replace(field+'.',field+'->')
s=s.replace('InfantryCombat->Held','InfantryCombat.Held')
s=s.replace('GetPrefabData().','GetPrefabData()->').replace('EnemyFaction().','EnemyFaction()->').replace('Forces().','Forces()->').replace('MineIntel().','MineIntel()->')
s=s.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->')
s=re.sub(r'FindComponent\((\w+)\)',r'FindComponent("\1")',s)
s=s.replace('CheckFriendly, queryFlags: EQueryEntitiesFlags.ALL','[this](IEntity entity){return CheckFriendly(entity);}, 1')
s=s.replace('ERR_EngineerMine(Array<IEntity> attempted)','ERR_EngineerMine(Array<IEntity>* attempted)')
s=s.replace('Faction* faction;','Faction* faction=nullptr;').replace('Array<AIActionBase>', 'Array<AIActionBase*>').replace('void ERR_EngineerMine(', 'ERR_EngineerMine(')
return s
policies='\n'.join(body(P,x) for x in ['static bool CorridorClear(','static bool WithinWorkArea(','static bool Within(','static float PrepareSince(','static bool Prepared(','static bool MayArm('])
callback='\n'.join(body(P,x) for x in ['protected void Finish(','protected override void OnComplete(','protected override void OnAllComplete(','protected override void OnFailed(','protected override void OnAllFailed('])
methods='\n'.join(body(P,x) for x in ['void ERR_EngineerMine(','bool Finished(','bool BlocksNextPost(','protected bool Refuse(','protected bool Quiet(','protected bool Owned(','protected bool MineValid(','protected bool Carried(','protected bool Grounded(','protected bool Placed(','protected void ClearAction(','protected void Move(','protected void WaitAtMine(','protected bool CheckFriendly(','protected bool SelectActor(','bool Begin(','void Stop(','void Tick('])
run(r'''
#include <cassert>
#include <cmath>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
struct string:std::string {using std::string::string;using std::string::operator=;bool IsEmpty(){return empty();}};
struct vector {float x=0,y=0,z=0;float operator[](int i){return i==0?x:i==1?y:z;}float LengthSq(){return x*x+y*y+z*z;}
static float Distance(vector a,vector b){return std::sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));}
static float DistanceXZ(vector a,vector b){a.y=b.y=0;return Distance(a,b);}};
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}void Insert(T t){this->push_back(t);}};
namespace Math{float Clamp(float v,float a,float b){return std::max(a,std::min(v,b));}}
struct Entity;using IEntity=Entity*;struct SCR_AIGroup;struct SCR_ChimeraAIAgent;struct SCR_InventoryStorageManagerComponent;struct ERR_EngineerMineRemoval;
struct Faction {bool enemy=false;bool IsFactionEnemy(Faction* f){return f&&f->enemy;}};
struct Physics {vector velocity;vector GetVelocity(){return velocity;}};
struct Prefab {string name="PMN4";string GetPrefabName(){return name;}};
struct InventoryItemComponent {void* slot=nullptr;void* GetParentSlot(){return slot;}static InventoryItemComponent* Cast(void* p){return (InventoryItemComponent*)p;}};
struct DamageManagerComponent {bool destroyed=false;bool IsDestroyed(){return destroyed;}static DamageManagerComponent* Cast(void* p){return (DamageManagerComponent*)p;}};
struct FactionAffiliationComponent {Faction* faction=nullptr;Faction* GetAffiliatedFaction(){return faction;}static FactionAffiliationComponent* Cast(void* p){return (FactionAffiliationComponent*)p;}};
struct SCR_PressureTriggerComponent {bool active=false,byDefault=false,allow=true;int calls=0,users=0;IEntity user=nullptr;
bool IsActivated(){return active;}bool IsActivatedByDefault(){return byDefault;}void SetUser(IEntity e){users++;user=e;}void ActivateTrigger(){calls++;active=allow;}static SCR_PressureTriggerComponent* Cast(void* p){return (SCR_PressureTriggerComponent*)p;}};
struct Entity {vector position;bool alive=true,player=false,character=true,vehicle=false,stored=false;int moves=0;IEntity parent=nullptr,inVehicle=nullptr,storageOwner=nullptr;
Prefab* prefab=nullptr;Physics* physics=nullptr;InventoryItemComponent* item=nullptr;SCR_PressureTriggerComponent* trigger=nullptr;SCR_InventoryStorageManagerComponent* inventory=nullptr;DamageManagerComponent* damage=nullptr;FactionAffiliationComponent* affiliation=nullptr;Faction* faction=nullptr;SCR_AIGroup* group=nullptr;SCR_ChimeraAIAgent* agent=nullptr;
vector GetOrigin(){return position;}void SetOrigin(vector p){position=p;moves++;}IEntity GetParent(){return parent;}Prefab* GetPrefabData(){return prefab;}Physics* GetPhysics(){return physics;}
void* FindComponent(std::string type){if(type=="InventoryItemComponent")return item;if(type=="SCR_PressureTriggerComponent")return trigger;if(type=="SCR_InventoryStorageManagerComponent")return inventory;if(type=="DamageManagerComponent")return damage;if(type=="FactionAffiliationComponent")return affiliation;return nullptr;}
};
struct ChimeraCharacter {static bool Cast(IEntity e){return e&&e->character;}};struct Vehicle {static bool Cast(IEntity e){return e&&e->vehicle;}};
// Native group movement envelope boundary, not navigation/pathfinding.
struct AIWaypoint {virtual ~AIWaypoint()=default;};
struct SCR_DefendWaypoint:AIWaypoint {vector origin;float radius=30;vector GetOrigin(){return origin;}float GetCompletionRadius(){return radius;}static SCR_DefendWaypoint* Cast(AIWaypoint* p){return dynamic_cast<SCR_DefendWaypoint*>(p);}};
struct SCR_AIGroupUtilityComponent;
struct SCR_AIGroup {bool slave=false;int players=0;Faction* faction=nullptr;SCR_AIGroupUtilityComponent* utility=nullptr;AIWaypoint* waypoint=nullptr;vector center;
bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}Faction* GetFaction(){return faction;}SCR_AIGroupUtilityComponent* GetGroupUtilityComponent(){return utility;}AIWaypoint* GetCurrentWaypoint(){return waypoint;}vector GetCenterOfMass(){return center;}};
struct SCR_AIGroupUtilityComponent {SCR_AIGroup* m_Owner=nullptr;float m_fMaxAutonomousDistance=100;
// Matches installed SCR_AIGroupUtilityComponent.IsPositionAllowed semantics.
bool IsPositionAllowed(vector pos){auto wp=m_Owner->GetCurrentWaypoint();auto defendWp=SCR_DefendWaypoint::Cast(wp);if(defendWp)return vector::Distance(defendWp->GetOrigin(),pos)<defendWp->GetCompletionRadius();return vector::Distance(m_Owner->GetCenterOfMass(),pos)<m_fMaxAutonomousDistance;}
};
struct SCR_AIBehaviorBase {int completed=0;float priority=0;void Complete(){completed++;}void SetPriority(float p){priority=p;}};using AIActionBase=SCR_AIBehaviorBase;
struct SCR_AIUtilityComponent {bool m_AIInfo=true;Array<AIActionBase*> actions;int additions=0;void GetActions(Array<AIActionBase*>& out){out=actions;}void AddAction(AIActionBase* a){additions++;actions.Insert(a);}};
struct SCR_AIMoveIndividuallyBehavior:SCR_AIBehaviorBase {SCR_AIMoveIndividuallyBehavior(SCR_AIUtilityComponent*,void*,vector,float,float,void*,float){}};
struct SCR_AIWaitBehavior:SCR_AIBehaviorBase {SCR_AIWaitBehavior(SCR_AIUtilityComponent*,void*){}};
struct SCR_ChimeraAIAgent {SCR_AIUtilityComponent* m_UtilityComponent=nullptr;static SCR_ChimeraAIAgent* Cast(SCR_ChimeraAIAgent* p){return p;}};
struct SCR_InventoryStorageManagerComponent {Array<IEntity> items;int calls=0,inlineResult=0;bool accepted=true;ERR_EngineerMineRemoval* callback=nullptr;
bool TryRemoveItemFromInventory(IEntity,void*,ERR_EngineerMineRemoval*);static SCR_InventoryStorageManagerComponent* Cast(void* p){return (SCR_InventoryStorageManagerComponent*)p;}};
struct Sampler {Array<IEntity> members;bool owns=true,attribute=true;int InitialStrength(){return members.Count();}bool OwnsRoster(SCR_AIGroup*){return owns;}bool CanAttribute(int slot,IEntity e,bool){return attribute&&slot>=0&&slot<members.Count()&&members[slot]==e;}IEntity InitialMember(int slot){return members[slot];}};
struct ERR_HQTask {bool RoadIntercept=true,ReleaseOnly=false,investigation=false;string SecuritySite;vector Destination;float ExpiresAt=999999;bool Investigation(){return investigation;}};
struct ERR_HQReport{};struct ERR_ForceRecord {SCR_AIGroup* Group=nullptr;bool Physical=true,Eliminated=false;string SourceId="original";int InitialStrength=1;};
struct Ledger {ERR_ForceRecord* record=nullptr;ERR_ForceRecord* FindGroup(SCR_AIGroup*){return record;}};
struct ERR_HQUnit {SCR_AIGroup* Group=nullptr;ERR_HQTask* Task=nullptr;ERR_HQReport* Assignment=nullptr;ERR_HQTask* PendingTask=nullptr;ERR_HQReport* PendingOrder=nullptr;Sampler* StatusSampler=nullptr;struct{bool Held=false;}InfantryCombat;bool ReservedForSupport=false;int LogisticsReservation=0;string ForceSourceId="original";};
struct MineIntel {int candidates=0,seals=0;IEntity capturedActor=nullptr,sealedMine=nullptr;vector position;Array<IEntity> available,captured,sealed;
void Candidates(IEntity actor,Array<IEntity>&out){candidates++;capturedActor=actor;out=available;captured=out;}
void Seal(IEntity actor,IEntity mine,vector p,Array<IEntity>&folders){assert(actor==capturedActor&&mine&&mine->trigger&&mine->trigger->active);seals++;sealedMine=mine;position=p;sealed=folders;assert(folders==captured);}
};
struct ERR_GameMode {struct MineIntel mineIntel;bool haveMineIntel=true;struct MineIntel* MineIntel(){return haveMineIntel?&mineIntel:nullptr;}static inline ERR_GameMode* current=nullptr;bool active=true;Faction* faction=nullptr;Ledger* ledger=nullptr;static ERR_GameMode* Get(){return current;}bool Active(){return active;}Faction* EnemyFaction(){return faction;}Ledger* Forces(){return ledger;}};
namespace ERR_ForceRules {bool Finite(float f){return std::isfinite(f);}bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.y)&&Finite(v.z);}}
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace ERR_HunterCombat {bool engaged=false;bool Active(SCR_AIGroup*){return engaged;}}
namespace ERR_Actors {bool Alive(IEntity a){return a&&a->alive;}bool CanFight(IEntity a){return a&&a->alive;}bool Player(IEntity a){return a&&a->player;}SCR_AIGroup* Group(IEntity a){return a->group;}}
namespace CompartmentAccessComponent {IEntity GetVehicleIn(IEntity a){return a->inVehicle;}}
namespace SCR_FactionManager {Faction* SGetFaction(IEntity a){return a->faction;}}
namespace SCR_AIUtils {SCR_ChimeraAIAgent* GetAIAgent(IEntity a){return a->agent;}}
namespace ERR_EnemyLoadoutCatalog {const string MINE="PMN4";}
namespace ERR_Inventory {void Items(SCR_InventoryStorageManagerComponent* inv,Array<IEntity>& items){items=inv->items;}}
namespace ERR_InfantryAmmoTransfer {bool StoredUnder(IEntity mine,IEntity actor){return mine->stored&&mine->storageOwner==actor;}}
namespace ERR_HQ {int events=0;void Event(string,string){events++;}}
struct World {Array<IEntity> query;int queries=0;void QueryEntitiesBySphere(vector,float,std::function<bool(IEntity)> fn,int){queries++;for(auto e:query)if(!fn(e))break;}};
struct Game {World world;World* GetWorld(){return &world;}} game;Game* currentGame=&game;Game* GetGame(){return currentGame;}
struct ERR_EngineerMineRules {
'''+cpp(policies)+r'''};
struct ERR_EngineerMineRemoval {int Result=0;
'''+cpp(callback)+r'''};
bool SCR_InventoryStorageManagerComponent::TryRemoveItemFromInventory(IEntity,void*,ERR_EngineerMineRemoval* cb){calls++;callback=cb;if(inlineResult>0)cb->OnComplete();if(inlineResult<0)cb->OnFailed();return accepted;}
struct ERR_EngineerMine {
ERR_GameMode* m_Mode=nullptr;ERR_HQUnit* m_Unit=nullptr;SCR_AIGroup* m_Group=nullptr;ERR_HQTask* m_Task=nullptr;ERR_HQReport* m_Assignment=nullptr;ERR_ForceRecord* m_Record=nullptr;string m_SourceId;int m_Strength=0;IEntity m_Actor=nullptr;int m_Slot=0;IEntity m_Mine=nullptr;SCR_PressureTriggerComponent* m_Trigger=nullptr;SCR_InventoryStorageManagerComponent* m_Inventory=nullptr;SCR_AIUtilityComponent* m_Utility=nullptr;SCR_AIBehaviorBase* m_Action=nullptr;ERR_EngineerMineRemoval* m_Removal=nullptr;Array<IEntity>* m_Attempted=nullptr;Array<IEntity> m_IntelFolders;Array<vector> m_PriorSites;vector m_Position,m_Rally;float m_Started=-1,m_LastTick=-1,m_PreparedAt=-1,m_NextClearance=0,m_NextArmLog=0;int m_State=0,m_Checked=0;bool m_FriendlyNear=false,m_Begun=false,m_Stopped=false;
'''+cpp(methods)+r'''};
struct Fixture {Faction faction,hostile{true};SCR_AIGroup group;SCR_AIGroupUtilityComponent groupUtility;SCR_AIUtilityComponent utility;SCR_ChimeraAIAgent agent;SCR_InventoryStorageManagerComponent inventory;Entity actor,mine;Prefab prefab;Physics physics;InventoryItemComponent item;SCR_PressureTriggerComponent trigger;Sampler roster;ERR_HQTask task;ERR_HQReport report;ERR_ForceRecord record;Ledger ledger;ERR_GameMode mode;ERR_HQUnit unit;Array<IEntity> attempted;ERR_EngineerMine controller{&attempted};
Fixture(){ERR_GameMode::current=&mode;Replication::server=true;ERR_HunterCombat::engaged=false;currentGame=&game;game.world.query.clear();game.world.queries=0;
group.faction=&faction;group.utility=&groupUtility;groupUtility.m_Owner=&group;actor.group=&group;actor.faction=&faction;actor.agent=&agent;actor.inventory=&inventory;agent.m_UtilityComponent=&utility;
mine.prefab=&prefab;mine.physics=&physics;mine.item=&item;mine.trigger=&trigger;mine.stored=true;mine.storageOwner=&actor;item.slot=&inventory;inventory.items.Insert(&mine);roster.members.Insert(&actor);
record.Group=&group;ledger.record=&record;mode.faction=&faction;mode.ledger=&ledger;unit.Group=&group;unit.Task=&task;unit.Assignment=&report;unit.StatusSampler=&roster;}
void Begin(){assert(controller.Begin(&mode,&unit,&task,{30,0,0},1000));assert(attempted.Count()==1&&utility.additions==1&&mode.mineIntel.candidates==1&&mode.mineIntel.capturedActor==&actor&&mode.mineIntel.seals==0);}
void Prepare(){Begin();actor.position={30,0,0};controller.Tick(2000);assert(controller.m_State==1&&utility.additions==2);controller.Tick(12000);assert(inventory.calls==1);assert(controller.m_State==2||controller.Finished());}
void Drop(){mine.position=actor.position;mine.stored=false;mine.storageOwner=nullptr;item.slot=nullptr;inventory.items.clear();}
void Withdraw(){Prepare();Drop();inventory.callback->OnComplete();controller.Tick(12001);assert(controller.m_State==3&&mine.moves==1&&utility.additions==3);}
};
int main(){
// History geometry uses bounded detached coordinates, not live mine entities.
Array<vector> hazards;hazards.Insert({20,0,20});
assert(!ERR_EngineerMineRules::CorridorClear({0,0,0},{40,0,0},&hazards));hazards[0].z=20.001f;
assert(ERR_EngineerMineRules::CorridorClear({0,0,0},{40,0,0},&hazards));
hazards[0]={20,0,0};assert(!ERR_EngineerMineRules::CorridorClear({0,0,0},{0,0,0},&hazards));
assert(ERR_EngineerMineRules::WithinWorkArea({0,0,0},{60,100,0}));assert(!ERR_EngineerMineRules::WithinWorkArea({0,0,0},{60.001f,0,0}));
for(int invalid=0;invalid<3;invalid++){Fixture f;Array<vector> prior;if(invalid==0)prior.Insert({20,0,5});if(invalid==1)prior.resize(33);if(invalid==2)prior.Insert({NAN,0,0});f.actor.position={-20,0,0};assert(!f.controller.Begin(&f.mode,&f.unit,&f.task,{60,0,0},1000,&prior));assert(f.attempted.empty()&&!f.utility.additions&&!f.inventory.calls);}
{Fixture f;Array<vector> prior;prior.Insert({100,0,0});assert(f.controller.Begin(&f.mode,&f.unit,&f.task,{30,0,0},1000,&prior));prior[0]={30,0,0};f.controller.Tick(2000);assert(!f.controller.Finished()&&f.controller.m_PriorSites[0].x==100);f.actor.position={60,0,0};f.controller.Tick(3000);assert(!f.controller.Finished());auto action=f.controller.m_Action;f.actor.position={60.001f,0,0};f.controller.Tick(4000);assert(f.controller.Finished()&&action->completed==1&&!f.inventory.calls);}
{Fixture f;Array<vector> prior;prior.Insert({50,0,30});assert(f.controller.Begin(&f.mode,&f.unit,&f.task,{30,0,0},1000,&prior));f.actor.position={50,0,11};f.controller.Tick(2000);assert(f.controller.Finished()&&!f.inventory.calls&&!f.trigger.calls);}
// Active job blocks its exact original record. Stopped pending native removal
// remains blocking even if its surviving item is destroyed; callbacks only settle.
{Fixture f;ERR_ForceRecord other;f.Begin();assert(f.controller.BlocksNextPost(&f.record)&&!f.controller.BlocksNextPost(&other)&&!f.controller.BlocksNextPost(nullptr));f.controller.Stop();assert(!f.controller.BlocksNextPost(&f.record));}
for(int completion=0;completion<3;completion++){Fixture f;f.Prepare();f.controller.Stop();auto callback=f.inventory.callback;assert(f.controller.BlocksNextPost(&f.record));f.mine.alive=false;assert(f.controller.BlocksNextPost(&f.record));if(completion==0)callback->OnComplete();if(completion==1)callback->OnFailed();if(completion==2)f.controller.m_Mine=nullptr;assert(!f.controller.BlocksNextPost(&f.record));f.Drop();callback->OnComplete();f.controller.Tick(14000);assert(f.inventory.calls==1&&!f.mine.moves&&!f.trigger.calls&&f.attempted.Count()==1);}
// An exact different physical mine can be used at a distinct post, while the
// first exact entity remains in the shared lifetime tombstone.
{Fixture f;f.Begin();f.controller.Stop();Entity second=f.mine;SCR_PressureTriggerComponent secondTrigger;InventoryItemComponent secondItem;second.trigger=&secondTrigger;second.item=&secondItem;secondItem.slot=&f.inventory;f.inventory.items={&f.mine,&second};ERR_HQTask next;ERR_HQReport nextReport;next.Destination={200,0,0};f.unit.Task=&next;f.unit.Assignment=&nextReport;f.actor.position={200,0,0};f.group.center={200,0,0};Array<vector> prior;prior.Insert({30,0,0});ERR_EngineerMine job(&f.attempted);assert(job.Begin(&f.mode,&f.unit,&next,{230,0,0},2000,&prior));assert(job.m_Mine==&second&&f.attempted.Count()==2&&f.attempted[0]==&f.mine);}
// Current native defend radius is strict and three dimensional: the broad
// 25..60 m mission band does not authorize bypassing the group's own envelope.
for(int fault=0;fault<4;fault++){Fixture f;SCR_DefendWaypoint defend;f.group.waypoint=&defend;vector point={30,0,0};
if(fault==0)f.group.utility=nullptr;
if(fault==2)point={25,17,0};
if(fault==3){f.group.waypoint=nullptr;f.groupUtility.m_fMaxAutonomousDistance=30;}
assert(!f.controller.Begin(&f.mode,&f.unit,&f.task,point,1000));
assert(f.controller.Finished()&&f.attempted.empty()&&!f.utility.additions&&!f.inventory.calls&&!f.trigger.calls&&!f.mode.mineIntel.candidates);
}
for(int boundary=0;boundary<3;boundary++){Fixture f;SCR_DefendWaypoint defend;f.group.waypoint=&defend;vector point={29.999f,0,0};
if(boundary==1)point={25,16,0};
if(boundary==2){f.group.waypoint=nullptr;f.group.center={1,0,0};f.groupUtility.m_fMaxAutonomousDistance=30;point={30,0,0};}
assert(f.controller.Begin(&f.mode,&f.unit,&f.task,point,1000));assert(f.attempted.Count()==1&&f.utility.additions==1);
}
// Delivered road posts now grant the 70m native defend envelope. The
// engineer still cannot leave the independently enforced 60m work area.
for(float distance:{35.f,60.f}){Fixture f;SCR_DefendWaypoint defend;defend.radius=70;f.group.waypoint=&defend;
assert(f.controller.Begin(&f.mode,&f.unit,&f.task,{distance,10,0},1000));
f.actor.position={60.01f,0,0};f.controller.Tick(2000);assert(f.controller.Finished()&&!f.trigger.calls);}
// Revalidate the current utility and its current waypoint at every phase.
// Cancellation completes only our exact action and never resumes after a late
// inventory acknowledgement or a later enlargement of the movement envelope.
for(int phase=0;phase<4;phase++)for(int replacement=0;replacement<3;replacement++){Fixture f;
if(phase==3)f.Withdraw();else if(phase==2)f.Prepare();else{f.Begin();if(phase==1){f.actor.position={30,0,0};f.controller.Tick(2000);}}
auto own=f.controller.m_Action;SCR_AIBehaviorBase foreign;f.utility.actions.Insert(&foreign);
auto callback=f.inventory.callback;int calls=f.inventory.calls,additions=f.utility.additions,moves=f.mine.moves;
SCR_DefendWaypoint defend;defend.radius=29;SCR_AIGroupUtilityComponent newUtility;newUtility.m_Owner=&f.group;newUtility.m_fMaxAutonomousDistance=29;
if(replacement==0)f.group.waypoint=&defend;else if(replacement==1)f.group.utility=&newUtility;else f.group.utility=nullptr;
f.controller.Tick(13000);
assert(f.controller.Finished()&&own->completed==1&&!foreign.completed&&f.attempted.Count()==1);
assert(f.inventory.calls==calls&&f.utility.additions==additions&&f.mine.moves==moves&&!f.trigger.calls&&!f.mode.mineIntel.seals);
f.group.utility=&f.groupUtility;f.group.waypoint=nullptr;f.Drop();if(callback)callback->OnComplete();f.actor.position={0,0,0};f.controller.Tick(14000);
assert(f.controller.Finished()&&own->completed==1&&!foreign.completed&&f.inventory.calls==calls&&f.utility.additions==additions&&f.mine.moves==moves&&!f.trigger.calls);
}
// Candidate list is captured once at accepted Begin; only successful native
// activation seals that same original list, even if carried folders change.
Fixture evidence;Entity originalFolder,lateFolder;evidence.mode.mineIntel.available.Insert(&originalFolder);evidence.Withdraw();
evidence.mode.mineIntel.available={&lateFolder};evidence.actor.position={0,0,0};evidence.controller.Tick(13000);
assert(evidence.mode.mineIntel.candidates==1&&evidence.mode.mineIntel.seals==1&&evidence.mode.mineIntel.sealed==Array<IEntity>{&originalFolder});
assert(evidence.mode.mineIntel.sealedMine==&evidence.mine&&evidence.mode.mineIntel.position.x==30);evidence.controller.Stop();evidence.controller.Tick(14000);assert(evidence.mode.mineIntel.seals==1);
Fixture noService;noService.mode.haveMineIntel=false;assert(noService.controller.Begin(&noService.mode,&noService.unit,&noService.task,{30,0,0},1000));assert(!noService.mode.mineIntel.candidates);
// Immutable total deadline, rollback/NaN clock refusal and continuous dwell.
assert(ERR_EngineerMineRules::Within(1000,1000,180999));assert(!ERR_EngineerMineRules::Within(1000,1000,181000));
assert(!ERR_EngineerMineRules::Within(1000,2000,1999));assert(!ERR_EngineerMineRules::Within(1000,2000,NAN));
assert(ERR_EngineerMineRules::PrepareSince(1000,5000,false,false)==-1);assert(ERR_EngineerMineRules::PrepareSince(1000,5000,true,true)==-1);
assert(!ERR_EngineerMineRules::Prepared(1000,10999));assert(ERR_EngineerMineRules::Prepared(1000,11000));
Fixture dwell;dwell.Begin();dwell.actor.position={30,0,0};dwell.controller.Tick(2000);dwell.controller.Tick(11999);assert(!dwell.inventory.calls);
dwell.actor.position={34,0,0};dwell.controller.Tick(12000);assert(dwell.controller.m_PreparedAt==-1);
dwell.actor.position={30,0,0};dwell.controller.Tick(12001);dwell.controller.Tick(22000);assert(!dwell.inventory.calls);dwell.controller.Tick(22001);assert(dwell.inventory.calls==1&&dwell.controller.m_Started==1000);
// Accepted unknown removal cannot retry, place or arm; original deadline wins.
Fixture unknown;unknown.Prepare();unknown.Drop();unknown.controller.Tick(12001);unknown.controller.Tick(180999);assert(unknown.inventory.calls==1&&!unknown.mine.moves&&!unknown.trigger.calls);
auto held=unknown.inventory.callback;unknown.controller.Tick(181000);assert(unknown.controller.Finished());held->OnComplete();unknown.controller.Tick(181001);assert(!unknown.mine.moves&&!unknown.trigger.calls&&unknown.attempted.Count()==1&&unknown.mode.mineIntel.seals==0);
// Synchronous completion on a false native return is evidence, not a second call.
Fixture inlineOk;inlineOk.inventory.accepted=false;inlineOk.inventory.inlineResult=1;inlineOk.Prepare();inlineOk.Drop();inlineOk.controller.Tick(12001);assert(inlineOk.controller.m_State==3&&inlineOk.inventory.calls==1);
Fixture refused;refused.inventory.accepted=false;refused.Prepare();assert(refused.controller.Finished()&&refused.inventory.calls==1);refused.inventory.callback->OnComplete();refused.Drop();refused.controller.Tick(13000);assert(!refused.trigger.calls&&!refused.mine.moves);
Fixture failed;failed.Prepare();failed.inventory.callback->OnFailed();failed.inventory.callback->OnComplete();failed.Drop();failed.controller.Tick(12001);assert(failed.controller.Finished()&&failed.inventory.callback->Result==-1&&!failed.mine.moves);
// ACK alone cannot establish custody. A stolen item is never positioned/armed.
Fixture stolen;stolen.Prepare();stolen.inventory.callback->OnComplete();Entity thief;stolen.mine.parent=&thief;stolen.Drop();stolen.controller.Tick(12001);assert(stolen.controller.Finished()&&!stolen.mine.moves);stolen.controller.Tick(181000);assert(stolen.controller.Finished());
// Observed foreign custody is terminal even if the same exact item returns nearby.
// Exercise both an acknowledged removal and an accepted, still-pending removal.
for(int ack=0;ack<2;ack++)for(int slotOnly=0;slotOnly<2;slotOnly++){Fixture f;f.Prepare();f.Drop();Entity holder;if(slotOnly)f.item.slot=&holder;else f.mine.parent=&holder;f.mine.stored=true;f.mine.storageOwner=&holder;if(ack)f.inventory.callback->OnComplete();
f.controller.Tick(12001);assert(f.controller.Finished()&&!f.mine.moves&&!f.trigger.calls);
f.mine.parent=nullptr;f.Drop();f.inventory.callback->OnComplete();f.controller.Tick(12002);f.actor.position={0,0,0};f.controller.Tick(14000);assert(!f.mine.moves&&!f.trigger.calls&&f.inventory.calls==1);}
// Native ownership still attached to the original actor may settle normally.
Fixture settling;settling.Prepare();settling.inventory.callback->OnComplete();settling.controller.Tick(12001);assert(!settling.controller.Finished()&&!settling.mine.moves);settling.Drop();settling.controller.Tick(12002);assert(settling.controller.m_State==3&&settling.mine.moves==1);
// Cancellation retains callback and item tombstone and only completes own action.
Fixture stopped;stopped.Prepare();SCR_AIBehaviorBase unrelated;stopped.utility.actions.Insert(&unrelated);auto owned=stopped.controller.m_Action;stopped.controller.Stop();stopped.controller.Stop();stopped.Drop();stopped.inventory.callback->OnComplete();stopped.controller.Tick(13000);assert(owned->completed==1&&!unrelated.completed&&!stopped.mine.moves&&!stopped.trigger.calls&&stopped.attempted.Contains(&stopped.mine)&&stopped.mode.mineIntel.seals==0);
// Original identity changes abort before irreversible work, including replacement
// task/report/ledger, recycled formation slot, capture and changed inventory.
for(int fault=0;fault<13;fault++){Fixture f;f.Begin();ERR_HQTask replacementTask;ERR_HQReport replacementReport;ERR_ForceRecord replacementRecord;SCR_InventoryStorageManagerComponent replacementInventory;SCR_AIUtilityComponent replacementUtility;
switch(fault){case 0:f.actor.alive=false;break;case 1:f.actor.player=true;break;case 2:f.unit.Task=&replacementTask;break;case 3:f.unit.Assignment=&replacementReport;break;case 4:f.ledger.record=&replacementRecord;break;case 5:f.roster.attribute=false;break;case 6:f.group.players=1;break;case 7:f.actor.inventory=&replacementInventory;break;case 8:f.agent.m_UtilityComponent=&replacementUtility;break;case 9:f.unit.PendingTask=&replacementTask;break;case 10:f.unit.ReservedForSupport=true;break;case 11:f.unit.InfantryCombat.Held=true;break;case 12:f.mode.active=false;break;}
f.controller.Tick(2000);assert(f.controller.Finished()&&!f.inventory.calls&&!f.trigger.calls);}
// Ground item lost, moved or activated externally after acknowledged removal.
for(int fault=0;fault<3;fault++){Fixture f;f.Withdraw();if(fault==0)f.mine.parent=&f.actor;if(fault==1)f.mine.position={40,0,0};if(fault==2)f.trigger.active=true;f.actor.position={0,0,0};f.controller.Tick(13000);assert(f.controller.Finished()&&!f.trigger.calls);}
Fixture arm;arm.Withdraw();arm.actor.position={10.001f,0,0};arm.controller.Tick(13000);assert(!arm.trigger.calls);arm.actor.position={10,0,0};
Entity friendly;FactionAffiliationComponent affiliation;affiliation.faction=&arm.faction;friendly.affiliation=&affiliation;game.world.query.Insert(&friendly);arm.controller.Tick(14000);assert(!arm.trigger.calls);game.world.query.clear();arm.controller.Tick(14500);assert(!arm.trigger.calls);arm.controller.Tick(15000);assert(arm.trigger.calls==1&&arm.trigger.user==&arm.actor&&arm.controller.Finished());arm.controller.Tick(16000);assert(arm.trigger.calls==1);
Fixture rejected;rejected.Withdraw();rejected.trigger.allow=false;rejected.actor.position={0,0,0};rejected.controller.Tick(13000);assert(rejected.trigger.calls==1&&!rejected.trigger.active&&rejected.controller.Finished());rejected.controller.Tick(14000);assert(rejected.trigger.calls==1&&rejected.mode.mineIntel.seals==0);
Fixture moving;moving.Withdraw();moving.actor.position={0,0,0};moving.physics.velocity={1,0,0};moving.controller.Tick(13000);assert(!moving.trigger.calls);moving.physics.velocity={0,0,0};moving.controller.Tick(14000);assert(moving.trigger.calls==1);
Fixture bounded;bounded.Withdraw();bounded.actor.position={0,0,0};Entity prop;prop.character=false;for(int i=0;i<257;i++)game.world.query.Insert(&prop);bounded.controller.Tick(13000);assert(!bounded.trigger.calls&&bounded.controller.m_FriendlyNear);
// Exact original item stays quarantined across controllers; never adopted twice.
Fixture replay;replay.Begin();replay.controller.Stop();ERR_EngineerMine second{&replay.attempted};assert(!second.Begin(&replay.mode,&replay.unit,&replay.task,{30,0,0},2000)&&replay.utility.additions==1);
Fixture cap;cap.attempted.resize(256,nullptr);assert(!cap.controller.Begin(&cap.mode,&cap.unit,&cap.task,{30,0,0},1000)&&!cap.utility.additions);
// Utility replacement or action removal must not complete somebody else's work.
Fixture swapped;swapped.Begin();auto original=swapped.controller.m_Action;SCR_AIUtilityComponent foreign;swapped.agent.m_UtilityComponent=&foreign;swapped.controller.Stop();assert(!original->completed);
Fixture removed;removed.Begin();original=removed.controller.m_Action;removed.utility.actions.clear();removed.controller.Stop();assert(!original->completed);
// Acknowledgement does not authorize correcting a stolen/far/falling item.
for(int fault=0;fault<4;fault++){Fixture f;f.Prepare();f.Drop();f.inventory.callback->OnComplete();if(fault==0)f.mine.position={40,0,0};if(fault==1)f.mine.position={30,4,0};if(fault==2)f.actor.position={35,0,0};if(fault==3)f.mine.alive=false;f.controller.Tick(12001);assert(f.controller.Finished()&&!f.mine.moves&&!f.trigger.calls);}
// Every irreversible stage repeats original task/actor custody checks.
for(int fault=0;fault<7;fault++){Fixture f;f.Withdraw();f.actor.position={0,0,0};ERR_HQTask newer;ERR_HQReport newerReport;ERR_GameMode newerMode;SCR_PressureTriggerComponent foreignTrigger;
switch(fault){case 0:f.unit.Task=&newer;break;case 1:f.unit.Assignment=&newerReport;break;case 2:f.actor.alive=false;break;case 3:f.actor.player=true;break;case 4:ERR_GameMode::current=&newerMode;break;case 5:f.mine.trigger=&foreignTrigger;break;case 6:f.task.ExpiresAt=13000;break;}
f.controller.Tick(13000);assert(f.controller.Finished()&&!f.trigger.calls&&!foreignTrigger.calls&&f.mode.mineIntel.seals==0);}
// Unknown or unconscious friendlies still block; hostile bodies and dead bodies
// do not fabricate a friendly. Static scenery isn't a soldier or vehicle.
Fixture friendPolicy;friendPolicy.Begin();Entity unknownPerson,unconscious,dead,enemy,scenery;DamageManagerComponent destroyed;destroyed.destroyed=true;dead.damage=&destroyed;FactionAffiliationComponent enemyAff;enemyAff.faction=&friendPolicy.hostile;enemy.affiliation=&enemyAff;scenery.character=false;
friendPolicy.controller.m_FriendlyNear=false;assert(!friendPolicy.controller.CheckFriendly(&unknownPerson)&&friendPolicy.controller.m_FriendlyNear);
friendPolicy.controller.m_FriendlyNear=false;unconscious.alive=false;assert(!friendPolicy.controller.CheckFriendly(&unconscious)&&friendPolicy.controller.m_FriendlyNear);
friendPolicy.controller.m_FriendlyNear=false;assert(friendPolicy.controller.CheckFriendly(&dead)&&!friendPolicy.controller.m_FriendlyNear);assert(friendPolicy.controller.CheckFriendly(&enemy));assert(friendPolicy.controller.CheckFriendly(&scenery));
// Already-armed/default-live/unknown stock is not adopted or newly quarantined.
for(int fault=0;fault<5;fault++){Fixture f;if(fault==0)f.trigger.active=true;if(fault==1)f.trigger.byDefault=true;if(fault==2)f.mine.alive=false;if(fault==3)f.prefab.name="unrelated";if(fault==4)f.mine.storageOwner=nullptr;assert(!f.controller.Begin(&f.mode,&f.unit,&f.task,{30,0,0},1000));assert(f.attempted.empty()&&!f.utility.additions&&!f.inventory.calls&&f.mode.mineIntel.candidates==0&&f.mode.mineIntel.seals==0);}
Fixture clock;clock.Begin();clock.controller.Tick(2000);clock.controller.Tick(1999);assert(clock.controller.Finished());
Fixture server;server.Begin();Replication::server=false;server.controller.Tick(2000);assert(server.controller.Finished()&&!server.inventory.calls);Replication::server=true;
}
''')
print('PASS: actual engineer native group-envelope admission and phase cancellation, preparation/deadline, original custody/identity, once-only callback removal, cancellation quarantine, withdrawal/clearance, native refusal and owned-action cleanup; native execution untested')
@@ -0,0 +1,57 @@
"""Actual HQ admission with actual finite post ledger; native jobs are boundary doubles."""
from pathlib import Path
import os,re
from policy_harness import ROOT,body,run
from test_engineer_mine_posts import BOUNDARY,ledger_cpp
p=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
s=body(p,'protected void MaintainEngineerMines(')
s=s.replace('protected ','').replace('int active;','int active=0;').replace('bool blocked;','bool blocked=false;').replace('vector position;','vector position;')
s=s.replace('array<vector> priorSites = {};','Array<vector> priorSites;')
for typ in ['ERR_HQUnit','ERR_EngineerMine','ERR_ForceRecord']:s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
s=s.replace('foreach (','for (')
for name in ('ERR_HQ','ERR_ForceRules','Replication','vector','ERR_EngineerMineSite','ERR_GameMode'):s=s.replace(name+'.',name+'::')
for name in ('unit','m_Mode','job','previous','record'):s=re.sub(r'\b'+name+r'\.',name+'->',s)
for name in ['EngineerMine','Task','Group','StatusSampler']:s=s.replace('->'+name+'.','->'+name+'->')
s=s.replace('Forces().','Forces()->')
run(BOUNDARY+ledger_cpp()+r'''
struct ERR_HQ{static void Event(string,string){}};
struct Ledger{Array<ERR_ForceRecord*> records;ERR_ForceRecord* FindGroup(SCR_AIGroup* group){for(auto r:records)if(r->Group==group)return r;return nullptr;}};
struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
struct ERR_GameMode{static inline ERR_GameMode* current=nullptr;static ERR_GameMode* Get(){return current;}bool active=true;Ledger ledger;bool Active(){return active;}Ledger* Forces(){return &ledger;}};
using AttemptArray=Array<int>;
struct Sampler{bool owns=true;int strength=2;bool OwnsRoster(SCR_AIGroup*){return owns;}int InitialStrength(){return strength;}};
struct ERR_HQUnit;
struct ERR_EngineerMine{int ticks=0,stops=0;bool finished=false,unresolved=false;ERR_ForceRecord* record=nullptr;Array<vector> prior;AttemptArray& attempts;
ERR_EngineerMine(AttemptArray& a):attempts(a){} bool Begin(ERR_GameMode*,ERR_HQUnit*,ERR_HQTask*,vector,float,Array<vector> sites={});
void Tick(float){ticks++;}bool Finished(){return finished;}void Stop(){stops++;finished=true;}bool BlocksNextPost(ERR_ForceRecord* r){return r==record&&(!finished||unresolved);}};
struct ERR_HQUnit{ERR_EngineerMine* EngineerMine=nullptr;bool EngineerMineAttempted=false;ERR_HQTask* Task=nullptr;ERR_HQReport* Assignment=nullptr;void* PendingTask=nullptr;void* PendingOrder=nullptr;void* Downlink=nullptr;SCR_AIGroup* Group=nullptr;Sampler* StatusSampler=nullptr;string ForceSourceId;bool eligible=true,fighting=false,begin=true;bool Eligible(ERR_GameMode*){return eligible;}};
bool ERR_EngineerMine::Begin(ERR_GameMode* mode,ERR_HQUnit*unit,ERR_HQTask*,vector,float,Array<vector> sites){record=mode->Forces()->FindGroup(unit->Group);prior=sites;if(unit->begin)attempts.Insert(1);return unit->begin;}
struct HQ{Array<ERR_EngineerMine*> m_EngineerMineJobs;ERR_EngineerMinePosts m_EngineerMinePosts;bool m_Stopping=false;ERR_GameMode* m_Mode=nullptr;AttemptArray m_EngineerMineAttempts;Array<ERR_HQUnit*> m_Units;bool Fighting(ERR_HQUnit*u){return u->fighting;}
'''+s+r'''};
struct Fixture{ERR_GameMode mode;HQ h;ERR_HQTask tasks[40];ERR_HQReport reports[40];SCR_AIGroup groups[4];ERR_ForceRecord records[4];Sampler samplers[4];ERR_HQUnit units[4];
Fixture(){ERR_GameMode::current=&mode;h.m_Mode=&mode;ERR_EngineerMineSite::valid=true;for(int i=0;i<40;i++){tasks[i].Destination={float(i*1000),0,0};tasks[i].QueuedAt=10+i*10;tasks[i].BeliefRevision=i+1;reports[i].ObservedAt=5+i*10;reports[i].MessageId=i+1;}for(int i=0;i<4;i++){records[i].Group=&groups[i];records[i].SourceId=std::to_string(i);units[i].ForceSourceId=records[i].SourceId;units[i].Group=&groups[i];units[i].StatusSampler=&samplers[i];mode.ledger.records.Insert(&records[i]);NewPost(i,i);h.m_Units.Insert(&units[i]);}}
void NewPost(int u,int n){units[u].Task=&tasks[n];units[u].Assignment=&reports[n];groups[u].position=tasks[n].Destination;}
void Tick(){h.MaintainEngineerMines(1000);}
~Fixture(){for(auto j:h.m_EngineerMineJobs)delete j;}
};
int main(){
// Genuine second delivered post succeeds; no timer retry or replenishment at old post.
{Fixture f;f.h.m_Units.resize(1);f.Tick();auto first=f.units[0].EngineerMine;first->Stop();f.Tick();assert(f.h.m_EngineerMineJobs.Count()==1);f.NewPost(0,4);f.Tick();assert(f.h.m_EngineerMineJobs.Count()==2&&f.h.m_EngineerMineAttempts.Count()==2);assert(f.units[0].EngineerMine!=first&&f.units[0].EngineerMine->prior.size()==1&&f.units[0].EngineerMine->prior[0].x==30);}
{Fixture f;f.Tick();f.Tick();assert(f.h.m_EngineerMineJobs.Count()==2);int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls);f.units[0].EngineerMine->Stop();f.Tick();assert(f.h.m_EngineerMineJobs.Count()==3);}
// Settled old controller retained even after unit pointer disappears; pending receipt blocks exact original force.
{Fixture f;f.h.m_Units.resize(1);f.Tick();auto first=f.units[0].EngineerMine;first->Stop();first->unresolved=true;f.NewPost(0,4);f.units[0].EngineerMine=nullptr;int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls&&first->ticks==1);first->unresolved=false;f.Tick();assert(f.h.m_EngineerMineJobs.Count()==2);f.h.m_Units.Clear();f.Tick();assert(first->ticks==3);}
// Four consumed attempts survive current unit replacement. No-item Begin failures are still consumed.
{Fixture f;f.h.m_Units.resize(1);f.units[0].begin=false;for(int i=0;i<5;i++){f.NewPost(0,i*4);int before=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==before+(i<4));}assert(f.h.m_EngineerMinePosts.Count()==4&&f.h.m_EngineerMineJobs.Count()==4&&f.h.m_EngineerMineAttempts.empty());ERR_HQUnit replacement=f.units[0];f.h.m_Units[0]=&replacement;replacement.Task=&f.tasks[24];replacement.Assignment=&f.reports[24];f.groups[0].position=f.tasks[24].Destination;f.Tick();assert(f.h.m_EngineerMinePosts.Count()==4);}
{Fixture f;ERR_EngineerMineSite::valid=false;int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls+1&&f.h.m_EngineerMinePosts.Count()==1&&f.h.m_EngineerMineJobs.empty());f.Tick();assert(ERR_EngineerMineSite::calls==calls+2&&f.h.m_EngineerMinePosts.Count()==2);}
{Fixture f;f.h.m_Units.resize(1);ERR_EngineerMineSite::valid=false;f.Tick();int calls=ERR_EngineerMineSite::calls;ERR_EngineerMineSite::valid=true;f.Tick();assert(ERR_EngineerMineSite::calls==calls&&f.h.m_EngineerMinePosts.Count()==1);}
{Fixture f;for(int i=0;i<32;i++){auto j=new ERR_EngineerMine(f.h.m_EngineerMineAttempts);j->Stop();f.h.m_EngineerMineJobs.Insert(j);}int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls);}
{Fixture f;f.Tick();f.h.m_EngineerMineAttempts.resize(256);int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls&&f.units[0].EngineerMine->ticks==1);}
for(int fault=0;fault<12;fault++){Fixture f;f.h.m_Units.resize(1);auto&u=f.units[0];if(fault==0)u.Assignment=nullptr;if(fault==1)u.Task=nullptr;if(fault==2)f.tasks[0].RoadIntercept=false;if(fault==3)u.PendingTask=&f;if(fault==4)u.PendingOrder=&f;if(fault==5)u.Downlink=&f;if(fault==6)u.eligible=false;if(fault==7)u.fighting=true;if(fault==8)f.groups[0].position.x=21;if(fault==9)f.samplers[0].owns=false;if(fault==10)f.samplers[0].strength=1;if(fault==11)f.tasks[0].ExpiresAt=1000;int calls=ERR_EngineerMineSite::calls;f.Tick();assert(ERR_EngineerMineSite::calls==calls&&f.h.m_EngineerMinePosts.Count()==0&&!u.EngineerMine);}
{Fixture f;Replication::server=false;f.Tick();Replication::server=true;f.h.m_Stopping=true;f.Tick();f.h.m_Stopping=false;f.mode.active=false;f.Tick();f.mode.active=true;ERR_GameMode::current=nullptr;f.Tick();assert(f.h.m_EngineerMinePosts.Count()==0);}
}
''')
assert 'if (EngineerMine) EngineerMine.Stop();' in body(p,'void ReleaseAssignment(')
assert 'MaintainEngineerMines(now);' in body(p,'override void Tick(')
assert 'm_EngineerMineAttempts.Clear();' in body(p,'override void Stop(')
assert 'foreach (ERR_EngineerMine job : m_EngineerMineJobs) job.Stop();' in body(p,'override void Stop(')
print('PASS: actual HQ and post ledger: second delivered post, one query/tick,2 active/32 jobs,4 consumed lifetime attempts, no-stock/query refusal consumption, pending old job and original identity constraints')
@@ -0,0 +1,298 @@
"""Actual HQ convoy wrappers and authenticated command dispatch; native deployment is a double."""
from policy_harness import ROOT,body,run
import re,os
from pathlib import Path
H=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
P=Path(os.environ.get('ERR_HQ_ADMIN_SOURCE',str(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQAdminProvider.c')))
BASE=Path(os.environ.get('ERR_CONVOY_SETTINGS_ROOT', ROOT))
A=BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_AdminService.c'
def cpp(s):
for old,new in [('protected ',''),('override ',''),('null','nullptr'),('Replication.','Replication::'),
('ERR_GameMode.','ERR_GameMode::'),('ERR_ConvoyRoadPlan.','ERR_ConvoyRoadPlan::'),
('ERR_AdminMenuFields.','ERR_AdminMenuFields::'),('ERR_Settings.','ERR_Settings::'),('ERR_TimeService.','ERR_TimeService::'),('ERR_EnvironmentService.','ERR_EnvironmentService::'),('Math.','Math::'),('array<string>','Array<string>&')]:s=s.replace(old,new)
for typ in ('ERR_GameMode','ERR_ConvoyReceipt','ERR_ConvoyTask','ERR_SiteRuntime','ERR_HQUnit','ERR_AdminProvider'):
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
for var in ('m_SupplyConvoys','m_ConvoyStartup','m_Mode','m_Convoys','m_ConvoyPool','m_ConvoyTask','m_HQ','site','unit','receipt','mode','provider'):
s=s.replace(var+'.',var+'->')
s=s.replace('site->Definition.', 'site->Definition->')
for name in ('GetGame()','GetWorld()','Forces()','Sites()','Settings()','Recon()','AirAssault()','Airfield()','Recovery()'):s=s.replace(name+'.',name+'->')
s=s.replace('foreach (ERR_HQUnit* unit : m_Units)','for (ERR_HQUnit* unit : m_Units)')
s=s.replace('foreach (ERR_AdminProvider* provider : m_Providers)','for (ERR_AdminProvider* provider : m_Providers)')
s=s.replace('foreach (string key : ERR_Settings::Keys())','for (string key : ERR_Settings::Keys())')
s=s.replace('mode->Settings()->Get(key).ToString()','string(std::to_string(mode->Settings()->Get(key)))')
s=s.replace('bool handled;','bool handled=false;')
s=s.replace('Array<string>& parts = {};','Array<string> parts = {};').replace('Array<string>& countParts = {};','Array<string> countParts = {};').replace('ERR_CommandArguments.Selection','ERR_CommandArguments::Selection')
s=s.replace('Array<string>& timerParts = {};','Array<string> timerParts = {};')
s=s.replace('out int ', 'int& ').replace('ERR_CommandArguments.Integer', 'ERR_CommandArguments::Integer')
s=s.replace('value.ToString()', 'string(std::to_string(value))')
s=s.replace('"0123456789".IndexOf(', 'string("0123456789").IndexOf(')
return s
hq='\n'.join(cpp(body(H,s)) for s in ('static bool MissionActive(', 'bool PrepareConvoy(', 'bool StartConvoy(',
'bool StartScenarioConvoy(', 'string ConvoyStatus('))
provider=cpp(body(P,'protected void CountSetting('))+cpp(body(P,'protected void ConvoyAutoSetting('))+cpp(body(P,'protected void StingerSetting('))+cpp(body(P,'override bool Execute('))
integer=cpp(body(A,'static bool Integer('))
validator=cpp(body(BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c','static bool Valid('))
selection=cpp(body(A,'static bool Selection('))
selection_rules='\n'.join(cpp(body(BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c',sig)) for sig in ('static bool Randomizable(', 'static bool ValidSelection(', 'static string SelectionText('))
extension=cpp(body(A,'protected bool Extension('))
# Execute the exact complete auth/normalization/extension-dispatch region.
# Unrelated settings/help parsers after this boundary are covered by their own suites.
admin=body(A,'void Execute(')
admin=cpp(admin[:admin.index(' array<string> parts = {};')]+'\n }')
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <algorithm>
#include <cctype>
#include <map>
template<class T>struct Array;
struct string:std::string{
using std::string::string;string(const std::string& v):std::string(v){}
bool IsEmpty()const{return empty();}int Length()const{return size();}
string Substring(int begin,int count)const{return substr(begin,count);}int ToInt()const{return std::stoi(*this);}
void Split(const char* separator,Array<string>& parts,bool skipEmpty)const;
int IndexOf(const std::string& s)const{auto p=find(s);return p==npos?-1:int(p);}
string Trim()const{auto a=find_first_not_of(" \t\r\n");if(a==npos)return "";
return substr(a,find_last_not_of(" \t\r\n")-a+1);}
void ToLower(){std::transform(begin(),end(),begin(),[](unsigned char c){return std::tolower(c);});}
};
template<class T>struct Array:std::vector<T>{void Clear(){this->clear();}void Insert(T n){this->push_back(n);}
int Count(){return this->size();}bool IsEmpty(){return this->empty();}};
void string::Split(const char* separator,Array<string>& parts,bool skipEmpty)const{
parts.Clear();size_t begin=0;const size_t width=std::char_traits<char>::length(separator);assert(width>0);
for(;;){auto end=find(separator,begin);string part=substr(begin,end==npos?npos:end-begin);
if(!skipEmpty||!part.empty())parts.Insert(part);
if(end==npos)break;
begin=end+width;
}
}
struct vector{float x=0,y=0,z=0;};
struct Math{static int Max(int a,int b){return std::max(a,b);}};
struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
struct Group{};struct Record{};
struct Ledger{std::map<Group*,Record*>records;Record* FindGroup(Group* g){return records.count(g)?records[g]:nullptr;}};
struct ERR_HQUnit{struct Group* Group=nullptr;};
struct Definition{vector Position{12,3,45};};
struct ERR_SiteRuntime{struct Definition* Definition=nullptr;};
struct Sites{ERR_SiteRuntime* site=nullptr;int finds=0;string last;
ERR_SiteRuntime* Find(string id){finds++;last=id;return site;}};
struct ERR_Settings{
int automatic=1,stinger=15,convoy=3,writes=0;bool saveFails=false;
static Array<string> Keys(){return {}; }int Get(string key){if(key=="convoyCount")return convoy;return key=="antiAirChancePercent"?stinger:automatic;}
'''+validator+r'''
'''+selection_rules+r'''
bool Set(string key,int value){writes++;if(saveFails||!ValidSelection(key,value))return false;
if(key=="convoyCount"){convoy=value;return true;}
if(key=="automaticConvoyEnabled"){automatic=value;return true;}
assert(key=="antiAirChancePercent");stinger=value;return true;}
};
// Settings listing is outside these convoy assertions (Keys is empty); its actual formatter is covered by test_start_settings.
struct ERR_AdminMenuFields{static string StartSummary(int,int){return "start summary";}};
// Date/time branches are unrelated to these commands and deliberately refuse.
struct ERR_TimeService{static bool ParseClock(string,int&){return false;}};
struct ERR_EnvironmentService{static bool ParseDate(string,int&){return false;}static bool ParseProfile(string,int&){return false;}};
struct ERR_CommandArguments{
'''+integer+selection+r'''
};
struct ERR_GameMode{
inline static ERR_GameMode* current=nullptr;bool active=true,settingsReady=true;Ledger ledger;
struct Sites sites;ERR_Settings settings;bool sitesReady=true;
static ERR_GameMode* Get(){return current;}bool Active(){return active;}Ledger* Forces(){return &ledger;}
struct Sites* Sites(){return sitesReady?&sites:nullptr;}ERR_Settings* Settings(){return settingsReady?&settings:nullptr;}
int StartMode(){return 0;}string Status(){return "mission";}bool AbandonRound(){return false;}
};
struct Clock{float GetWorldTime(){return 123000;}Clock* GetWorld(){return this;}}clock_;
Clock* GetGame(){return &clock_;}
struct ERR_ForceRoute{};
struct ERR_ConvoyRoadPlan{
inline static bool allowed=true;inline static int calls=0;inline static vector lastSeed;
static bool Build(vector seed,Array<vector>& positions,Array<vector>& angles,ERR_ForceRoute*& route){
calls++;lastSeed=seed;assert(positions.IsEmpty()&&angles.IsEmpty()&&!route);
for(int i=0;i<6;i++){positions.Insert({float(i),0,0});angles.Insert({0,0,0});}
route=new ERR_ForceRoute();return allowed; // a rejected survey may still write all out parameters
}
};
struct ERR_ConvoyReceipt{bool Consumed=false;};
struct Pool{bool allowed=true;int calls=0,created=0;string source;ERR_ConvoyReceipt receipt;
ERR_ConvoyReceipt* Reserve(string id){calls++;source=id;if(!allowed)return nullptr;
if(created==0){created++;}return &receipt;}
};
struct ERR_ConvoyTask{bool Stopped=false;int retained=0;};
struct Service{
int calls=0,statusCalls=0;bool preflight=true,partial=false;ERR_ConvoyReceipt* last=nullptr;float lastTime=0;
ERR_ConvoyTask task;
ERR_ConvoyTask* Dispatch(ERR_ConvoyReceipt* receipt,Array<vector>& positions,Array<vector>& angles,
ERR_ForceRoute* route,float now){calls++;last=receipt;lastTime=now;
assert(positions.Count()==6&&angles.Count()==6&&route&&!receipt->Consumed);
if(!preflight)return nullptr;
receipt->Consumed=true;task.Stopped=partial;task.retained=partial?3:6;return &task;
}
int Count(){return 0;}ERR_ConvoyTask* At(int){return &task;}
string Status(){statusCalls++;return task.Stopped?"stopped; retained vehicles=3":"convoy service status";}
};
struct Supply{bool StartOne(){return false;}string Status(){return "supply";}};
struct ERR_ConvoyStartup{string Status(){return "startup snapshot";}};
struct ReconService{string Status(){return "recon";}void Return(){}bool SetDelay(int n){return n>=1&&n<=1440;}};
struct HQ{
ReconService recon;ReconService* Recon(){return &recon;}ReconService* AirAssault(){return &recon;}ReconService* Airfield(){return &recon;}ReconService* Recovery(){return &recon;}
Supply* m_SupplyConvoys=nullptr;bool ConvoyAdmissionReady(){return true;}
ERR_ConvoyStartup* m_ConvoyStartup=nullptr;
ERR_GameMode* m_Mode;Pool* m_ConvoyPool;Service* m_Convoys;ERR_ConvoyTask* m_ConvoyTask=nullptr;
Array<vector>m_ConvoyPositions,m_ConvoyAngles;ERR_ForceRoute* m_ConvoyRoute=nullptr;string m_ConvoyFailure;
Array<ERR_HQUnit*>m_Units;
HQ(ERR_GameMode* mode,Pool* pool,Service* service):m_Mode(mode),m_ConvoyPool(pool),m_Convoys(service){}
string Status(){return "hq status";}void MemoryStatus(Array<string>& r){r.Insert("memory");}
'''+hq+r'''
};
struct ERR_AdminProvider{virtual bool Execute(ERR_GameMode*,string,Array<string>&)=0;virtual ~ERR_AdminProvider()=default;};
struct Provider:ERR_AdminProvider{
HQ* m_HQ;Provider(HQ* hq):m_HQ(hq){}int JournalSequence(){return 0;}
bool ReadJournal(int,Array<string>& r,int& cursor){cursor=0;r.Insert("journal");return true;}
'''+provider+r'''
};
struct Admin{Array<ERR_AdminProvider*>m_Providers;
'''+extension+admin+r'''
};
int main(){
ERR_GameMode mode;ERR_GameMode::current=&mode;Pool pool;Service service;HQ hq(&mode,&pool,&service);
Provider provider(&hq);Admin admin;admin.m_Providers.Insert(&provider);Array<string> replies;
Definition definition;ERR_SiteRuntime site;site.Definition=&definition;mode.sites.site=&site;
// Core authenticated dispatch: no provider call, route scan or reservation without authority/server.
replies.Insert("old");admin.Execute(&mode,false,"hq convoy start",replies);
assert(replies.Count()==1&&replies[0]=="Administrator access required."&&ERR_ConvoyRoadPlan::calls==0&&pool.calls==0);
Replication::server=false;admin.Execute(&mode,true,"hq convoy start",replies);
assert(replies.IsEmpty()&&ERR_ConvoyRoadPlan::calls==0&&pool.calls==0);
assert(!hq.PrepareConvoy({})&&!hq.StartConvoy());Replication::server=true;
admin.Execute(nullptr,true,"hq convoy start",replies);assert(replies.IsEmpty()&&pool.calls==0);
admin.Execute(&mode,true,string(161,'x'),replies);assert(replies[0]=="Command is too long."&&pool.calls==0);
mode.settingsReady=false;admin.Execute(&mode,true,"hq convoy start",replies);
assert(replies[0]=="Mission settings are not ready."&&pool.calls==0);mode.settingsReady=true;
mode.active=false;admin.Execute(&mode,true,"hq convoy start",replies);
assert(pool.calls==0&&ERR_ConvoyRoadPlan::calls==0);mode.active=true;
ERR_GameMode::current=nullptr;assert(!hq.PrepareConvoy({})&&!hq.StartConvoy());ERR_GameMode::current=&mode;
// Read-only status and unsupported commands cannot dispatch.
admin.Execute(&mode,true,"hq convoy show",replies);assert(replies.Count()==1&&pool.calls==0);
assert(!provider.Execute(&mode,"hq convoy start extra",replies)&&pool.calls==0);
Provider unavailable(nullptr);replies.Clear();assert(unavailable.Execute(&mode,"hq convoy start",replies));
assert(replies[0]=="HQ unavailable.");
// Stinger commands pass the same actual Core authority and provider dispatch boundary.
admin.Execute(&mode,false,"hq stinger set 0",replies);assert(mode.settings.writes==0&&mode.settings.stinger==15);
Replication::server=false;admin.Execute(&mode,true,"hq stinger set 0",replies);
assert(mode.settings.writes==0&&replies.IsEmpty());Replication::server=true;
mode.settingsReady=false;admin.Execute(&mode,true,"hq stinger set 0",replies);
assert(mode.settings.writes==0&&replies[0]=="Mission settings are not ready.");mode.settingsReady=true;
admin.Execute(&mode,true," HQ STINGER SET 0 ",replies);
assert(mode.settings.writes==1&&mode.settings.stinger==0&&pool.calls==0);
assert(replies[0].find("have not rolled yet")!=string::npos&&replies[0].find("no reroll, removal or replenishment")!=string::npos);
admin.Execute(&mode,true,"hq stinger show",replies);
assert(mode.settings.writes==1&&replies[0].find("antiAirChancePercent=0")!=string::npos);
admin.Execute(&mode,true,"hq stinger help",replies);
assert(mode.settings.writes==1&&replies[0].find("0..100 percent")!=string::npos);
mode.settings.saveFails=true;admin.Execute(&mode,true,"hq stinger set 15",replies);
assert(mode.settings.stinger==0&&replies[0].find("previous setting retained")!=string::npos);mode.settings.saveFails=false;
int writes=mode.settings.writes;
admin.Execute(&mode,true,"hq stinger set 1 extra",replies);assert(mode.settings.writes==writes&&mode.settings.stinger==0);
admin.Execute(&mode,true,"hq stinger set 1junk",replies);assert(mode.settings.writes==writes&&mode.settings.stinger==0);
admin.Execute(&mode,true,"hq stinger set 10001",replies);assert(mode.settings.stinger==0&&replies[0].find("previous setting retained")!=string::npos);
replies.Clear();assert(unavailable.Execute(&mode,"hq stinger set 1",replies)&&replies[0]=="HQ unavailable.");
// Random uses the same parser and validation as numeric Stinger and convoy settings.
writes=mode.settings.writes;
admin.Execute(&mode,false,"hq stinger set random",replies);
assert(mode.settings.writes==writes&&mode.settings.stinger==0&&replies[0]=="Administrator access required.");
Replication::server=false;admin.Execute(&mode,true,"hq stinger set random",replies);
assert(mode.settings.writes==writes&&replies.IsEmpty());Replication::server=true;
admin.Execute(&mode,true," HQ STINGER SET RANDOM ",replies);
assert(mode.settings.writes==writes+1&&mode.settings.stinger==-1&&replies[0].find("antiAirChancePercent=Random saved.")!=string::npos);
admin.Execute(&mode,true,"hq stinger show",replies);assert(replies[0].find("antiAirChancePercent=Random.")!=string::npos);
writes=mode.settings.writes;
for(string bad:{"-1","random-day","random day","1junk"}){
admin.Execute(&mode,true,"hq stinger set "+bad,replies);assert(mode.settings.writes==writes&&mode.settings.stinger==-1);
}
admin.Execute(&mode,true,"hq stinger set 101",replies);assert(mode.settings.stinger==-1&&replies[0].find("previous setting retained")!=string::npos);
mode.settings.saveFails=true;admin.Execute(&mode,true,"hq stinger set 0",replies);assert(mode.settings.stinger==-1);
mode.settings.saveFails=false;admin.Execute(&mode,true,"hq stinger set 0",replies);assert(mode.settings.stinger==0);
mode.settings.saveFails=true;admin.Execute(&mode,true,"hq stinger set random",replies);
assert(mode.settings.stinger==0&&replies[0].find("previous setting retained")!=string::npos);mode.settings.saveFails=false;
assert(pool.calls==0&&service.calls==0&&ERR_ConvoyRoadPlan::calls==0);
// New automatic switch is saved only: it does not scan, reserve or launch now.
writes=mode.settings.writes;
admin.Execute(&mode,false,"hq convoy auto set 0",replies);assert(mode.settings.writes==writes&&mode.settings.automatic==1);
Replication::server=false;admin.Execute(&mode,true,"hq convoy auto set 0",replies);
assert(replies.IsEmpty()&&mode.settings.writes==writes);Replication::server=true;
mode.settingsReady=false;admin.Execute(&mode,true,"hq convoy auto set 0",replies);
assert(mode.settings.writes==writes&&replies[0]=="Mission settings are not ready.");mode.settingsReady=true;
admin.Execute(&mode,true," HQ CONVOY AUTO SET 0 ",replies);
assert(mode.settings.automatic==0&&pool.calls==0&&service.calls==0&&ERR_ConvoyRoadPlan::calls==0);
assert(replies[0].find("next round only")!=string::npos&&replies[0].find("manual convoy start unchanged")!=string::npos);
admin.Execute(&mode,true,"hq convoy auto show",replies);assert(replies[0].find("automaticConvoyEnabled=0 saved for next round")!=string::npos);
admin.Execute(&mode,true,"hq convoy auto help",replies);assert(replies[0].find("1 enabled (default)")!=string::npos);
writes=mode.settings.writes;
for(string bad:{"hq convoy auto set", "hq convoy auto set 1 extra", "hq convoy auto show extra", "hq convoy auto set -1", "hq convoy auto set 1.0", "hq convoy auto set 1junk"}){
admin.Execute(&mode,true,bad,replies);assert(mode.settings.writes==writes&&mode.settings.automatic==0);
}
admin.Execute(&mode,true,"hq convoy auto set 2",replies);assert(mode.settings.automatic==0&&replies[0].find("previous setting retained")!=string::npos);
mode.settings.saveFails=true;admin.Execute(&mode,true,"hq convoy auto set 1",replies);
assert(mode.settings.automatic==0&&replies[0].find("previous setting retained")!=string::npos);mode.settings.saveFails=false;
replies.Clear();assert(unavailable.Execute(&mode,"hq convoy auto set 1",replies)&&replies[0]=="HQ unavailable.");
// Disabled automatic startup does not disable manual canonical dispatch below.
assert(mode.settings.automatic==0&&pool.calls==0&&service.calls==0);
// Canonical authored site and preparation failures refuse without consuming the finite receipt.
mode.sitesReady=false;assert(!hq.StartScenarioConvoy());mode.sitesReady=true;
mode.sites.site=nullptr;assert(!hq.StartScenarioConvoy());mode.sites.site=&site;
site.Definition=nullptr;assert(!hq.StartScenarioConvoy());site.Definition=&definition;
ERR_ConvoyRoadPlan::allowed=false;admin.Execute(&mode,true,"hq convoy start",replies);
assert(pool.calls==0&&service.calls==0&&replies[0].find("No sufficiently long")!=string::npos);
assert(!hq.m_ConvoyRoute&&hq.m_ConvoyPositions.IsEmpty()&&hq.m_ConvoyAngles.IsEmpty());
assert(!hq.StartConvoy()&&pool.calls==0); // rejected builder output cannot become a deployment later
ERR_ConvoyRoadPlan::allowed=true;
// An asynchronously pending existing group blocks all new convoy reservations.
Group original;ERR_HQUnit unit;unit.Group=&original;hq.m_Units.Insert(&unit);
admin.Execute(&mode,true," HQ CONVOY START ",replies);
assert(pool.calls==0&&service.calls==0&&mode.sites.last=="everon-supply-1");
assert(ERR_ConvoyRoadPlan::lastSeed.x==12&&ERR_ConvoyRoadPlan::lastSeed.z==45);
assert(replies[0].find("Existing force admission is still pending")!=string::npos);
Record record;mode.ledger.records[&original]=&record;
// Pool refusal remains authoritative; the separate pool suite verifies the 59/60-body arithmetic.
pool.allowed=false;admin.Execute(&mode,true,"hq convoy start",replies);
assert(pool.calls==1&&pool.source=="everon-supply-convoy-1"&&service.calls==0&&!hq.m_ConvoyTask);
assert(replies[0].find("physical force budget is unavailable")!=string::npos);pool.allowed=true;
// Preflight failure retains one unconsumed canonical receipt; safe retry does not allocate a new allowance.
service.preflight=false;admin.Execute(&mode,true,"hq convoy start",replies);
assert(pool.created==1&&!pool.receipt.Consumed&&service.calls==1&&!hq.m_ConvoyTask);
assert(replies[0].find("Deployment preflight failed; no convoy created")!=string::npos);
service.preflight=true;admin.Execute(&mode,true,"hq convoy start",replies);
assert(service.calls==2&&pool.created==1&&pool.receipt.Consumed&&hq.m_ConvoyTask==&service.task);
assert(service.last==&pool.receipt&&service.lastTime==123000&&!service.task.Stopped);
int scans=ERR_ConvoyRoadPlan::calls,reserves=pool.calls;
for(int i=0;i<5;i++)admin.Execute(&mode,true,"hq convoy start",replies);
assert(service.calls==2&&pool.calls==reserves&&ERR_ConvoyRoadPlan::calls==scans);
// Partial native failure returns a stopped retained task. HQ must keep it, never retry/refund it.
Pool partialPool;Service partialService;partialService.partial=true;HQ partial(&mode,&partialPool,&partialService);
assert(!partial.StartScenarioConvoy()&&partial.m_ConvoyTask==&partialService.task);
assert(partialPool.receipt.Consumed&&partialService.task.retained==3&&partialService.calls==1);
assert(partial.ConvoyStatus().find("retained vehicles=3")!=string::npos);
scans=ERR_ConvoyRoadPlan::calls;
for(int i=0;i<5;i++)assert(!partial.StartScenarioConvoy()&&!partial.StartConvoy());
assert(partialService.calls==1&&partialPool.calls==1&&ERR_ConvoyRoadPlan::calls==scans);
// A preexisting consumed receipt is rejected even if the wrapper has no task handle.
Pool consumedPool;consumedPool.receipt.Consumed=true;consumedPool.created=1;
Service consumedService;HQ consumed(&mode,&consumedPool,&consumedService);
assert(!consumed.StartScenarioConvoy()&&!consumed.m_ConvoyTask&&consumedService.calls==0);
assert(consumed.ConvoyStatus().find("finite convoy allowance")!=string::npos);
for(string command:{"convoys set 0","hq convoy count set 20","convoys set random"}){
int calls=service.calls;replies.Clear();admin.Execute(&mode,true,command,replies);assert(replies.Count()==1&&replies[0].find("saved for next round")!=std::string::npos&&service.calls==calls);
}
assert(mode.settings.convoy==-1);writes=mode.settings.writes;admin.Execute(&mode,false,"convoys set 5",replies);assert(mode.settings.convoy==-1&&mode.settings.writes==writes);
for(string invalid:{"convoys set 21","convoys set -2","convoys set 1 extra"}){admin.Execute(&mode,true,invalid,replies);assert(mode.settings.convoy==-1);}
mode.settings.saveFails=true;admin.Execute(&mode,true,"convoys set 4",replies);assert(mode.settings.convoy==-1);mode.settings.saveFails=false;
admin.Execute(&mode,true,"convoys show",replies);assert(replies.Count()==1&&replies[0].find("Random")!=std::string::npos);
}
''')
for sig in ('bool PrepareConvoy(', 'bool StartConvoy(', 'bool StartScenarioConvoy('):
source=body(H,sig)
for forbidden in ('ERR_Actors.Spawn(', 'DeleteEntity', 'DespawnMembers(', 'SpawnMembers('):assert forbidden not in source
assert body(H,'bool StartConvoy(').count('everon-supply-convoy-1')==1
assert 'm_Convoys.Stop()' in body(H,'override void Stop(')
print('PASS: actual authenticated convoy dispatch, active/ready admission gates, canonical receipt, safe unconsumed preflight retry, pool refusal, retained partial failure and consumed no-retry')
@@ -0,0 +1,240 @@
"""Actual radio task delivery/ownership methods with native waypoint and memory doubles."""
from pathlib import Path
import os,re
from policy_harness import ROOT,body,run
P=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
ROAD=Path(os.environ.get('ERR_HQ_ROAD_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'))
def cpp(s):
s=s.replace('EngineerMine.', 'EngineerMine->')
s=s.replace('ref ', '')
s=s.replace('protected ','').replace('null','nullptr').replace('string.Format','Format')
for name in ('Math','ERR_GameMode','SCR_EntityHelper','ERR_HQPursuitRules','AIWaypoint','ERR_Actors','ERR_Resources','ERR_ForceDuty','ERR_HQRoadIntercept','ERR_ForceRules','ERR_HQPursuitRole','ERR_HQMemoryKind'):
s=s.replace(name+'.',name+'::')
for typ in ('ERR_HQUnit','ERR_HQTask','ERR_GameMode','ERR_HQReport','ERR_HQBelief','ERR_ForceRecord','ERR_HQHuntWindow','AIWaypoint'):
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
s=re.sub(r'foreach \((ERR_HQUnit\*) (\w+) : ([^)]+)\)',r'for (\1 \2 : \3)',s)
for name in ('unit','other','task','planned','order','belief','record','mode','Group','hunt','waypoint','m_Mode','lostActive','lostPending'):
s=s.replace(name+'.',name+'->')
s=s.replace('ResourceName ', 'int ').replace('.IsEmpty()', '.empty()')
s=s.replace('PendingOrder.', 'PendingOrder->').replace('Assignment.', 'Assignment->')
s=s.replace('PendingTask.', 'PendingTask->').replace('Task.', 'Task->')
s=s.replace('Downlink.', 'Downlink->').replace('Forces().','Forces()->').replace('ERR_ForceRecord* record;','ERR_ForceRecord* record=nullptr;')
return s
units='\n'.join(cpp(body(P,x)) for x in ('void CancelPending(', 'ERR_HQTask PlannedTask(', 'void ReleaseAssignment(', 'void Release()'))
methods='\n'.join(cpp(body(P,x)) for x in ('protected bool AlarmCommitted(', 'protected void FinishWatch(', 'protected void ReleaseUnit(', 'protected void LostOrder(', 'protected void DeliverOrder('))
if 'protected float HuntCommitment(' in P.read_text(): methods=cpp(body(P,'protected float HuntCommitment('))+'\n'+methods
if 'protected void ReconsiderHunt(' in P.read_text(): methods=cpp(body(P,'protected void ReconsiderHunt('))+'\n'+methods
if 'protected void RememberCompletedHunt(' in P.read_text(): methods=cpp(body(P,'protected void RememberCompletedHunt('))+'\n'+methods
if 'protected void ReleaseLostForce(' in P.read_text(): methods=cpp(body(P,'protected void ReleaseLostForce('))+'\n'+methods
missing=cpp(body(P,'if (!unit.Group && !unit.Uplink)')).replace('m_Units.Remove(i)', 'm_Units.erase(m_Units.begin()+i)')
cleanup=cpp(body(P,' foreach (ERR_HQUnit unit : m_Units)\n {\n if (!unit.Assignment && !unit.PendingOrder) continue;\n if (!unit.PlanEligible || assigned > globalLimit)'))
R=Path(os.environ.get('ERR_HQ_PURSUIT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c'))
rules='\n'.join(cpp(body(R,x)) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role('))
eligible=cpp(body(ROAD,'static bool Eligible('))
run(r'''
#include <cassert>
#include <vector>
#include <string>
#include <algorithm>
#include <cmath>
#include <limits>
using string=std::string;
template<class... T>string Format(T...){return "order";}
struct vector{float x=0,y=0,z=0;float& operator[](int i){return i==0?x:(i==1?y:z);}float Length(){return std::sqrt(x*x+y*y+z*z);}};
enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};
struct ERR_ForceRules{static bool Finite(float f){return std::isfinite(f);}};
struct ERR_HQMemoryKind{static const int SIGHTING=1,ALARM=4;};
struct ERR_HQPursuitRules{
'''+rules+r'''
};
struct Math{static float Min(float a,float b){return std::min(a,b);}static float Max(float a,float b){return std::max(a,b);}};
struct ERR_HQTask{string SecuritySite;bool RoadIntercept=false,ReleaseOnly=false;ERR_HQTask* ReplacesTask=nullptr;string WatchSourceId;int WatchMessageId=0,WatchRequestId=0;
int BeliefId=1,BeliefRevision=2;ERR_HQPursuitRole Role=ERR_HQPursuitRole::SEARCH;vector Destination{40,0,50};float SearchRadiusMetres=60,QueuedAt=0,ExpiresAt=60000;bool Investigation(){return !WatchSourceId.empty();}string Name(){return "search";}};
struct ERR_HQReport{string TravelMode="on foot";int MessageId=10;float ObservedAt=0;};
struct ERR_HQRoute{int MessageId=20;};
struct AIWaypoint{int resource=0,deletes=0,configured=0;float radius=30;void SetCompletionRadius(float r){assert(!configured);radius=r;configured++;}static AIWaypoint* Cast(AIWaypoint* p){return p;}};
struct SCR_AIGroup{int removals=0,additions=0;AIWaypoint* last=nullptr;void RemoveWaypoint(AIWaypoint* w){removals++;last=w;}void AddWaypointAt(AIWaypoint* w,int at){assert(at==0);additions++;last=w;}};
struct ERR_ForceRecord{bool Eliminated=false;int Duty=2,HomeDuty=1;vector DutyTarget,HomePosition;};
struct Ledger{ERR_ForceRecord record;ERR_ForceRecord* FindGroup(SCR_AIGroup*){return &record;}};
struct ERR_GameMode{static inline ERR_GameMode* current=nullptr;Ledger ledger;static ERR_GameMode* Get(){return current;}Ledger* Forces(){return &ledger;}};
struct SCR_EntityHelper{static void DeleteEntityAndChildren(AIWaypoint* p){p->deletes++;}};
struct ERR_Actors{static inline bool refuse=false;static inline int spawns=0;static AIWaypoint* Spawn(int resource,vector){spawns++;if(refuse)return nullptr;auto w=new AIWaypoint;w->resource=resource;return w;}static vector Ground(vector p){return p;}};
struct ERR_Resources{static int SearchWaypoint(){return 1;}static int DefendWaypoint(){return 2;}};
struct ERR_ForceDuty{static const int SEARCH=2;};
struct MineJob {int stops=0;void Stop(){stops++;}};
struct ERR_HQUnit{MineJob* EngineerMine=nullptr;int Id=1;string ForceSourceId="original";SCR_AIGroup* Group=nullptr;AIWaypoint* Waypoint=nullptr;
ERR_HQTask* Task=nullptr;ERR_HQTask* PendingTask=nullptr;ERR_HQReport* Assignment=nullptr;ERR_HQReport* PendingOrder=nullptr;
ERR_HQRoute* Downlink=nullptr;ERR_HQRoute* Uplink=nullptr;void* PendingConvoyOrder=nullptr;bool HomeGuard=false,PendingCheckin=false,eligible=true,fighting=false,PlanEligible=true;
bool Eligible(ERR_GameMode*){return eligible&&Group;}
'''+units+r'''
};
struct ERR_HQBelief{int BeliefId=1,BeliefRevision=2,Kind=1,SampleCount=2,MessageId=10;float Confidence=.8f,Radius=60,ObservedAt=0,LastDeliveredAt=0;string TravelMode="on foot";vector Velocity{0,0,3};bool HasVelocity=true;};
struct ERR_HQRoadIntercept{
'''+eligible+r'''
};
struct Memory{ERR_HQBelief* belief=nullptr;ERR_HQBelief* GetById(int id){return belief&&belief->BeliefId==id?belief:nullptr;}};
struct Watch{bool empty=false;int finished=0;bool ArmedSearchBlocked(string,float){return empty;}bool FinishInvestigation(string,int,int,float){finished++;return true;}};
struct Journal{int writes=0;void Write(string,string,int=0){writes++;}};
struct ERR_HQHuntWindow{float CompletedUntil=0;float NextHunt=200000;};
struct HQ{ERR_GameMode* m_Mode;Memory m_Memory;Watch m_Watch;Journal m_Journal;std::vector<ERR_HQUnit*>m_Units;ERR_HQHuntWindow hunt,otherHunt;bool watchValid=true;
bool Fighting(ERR_HQUnit* u){return u->fighting;}bool WatchTaskValid(ERR_HQTask*){return watchValid;}ERR_HQHuntWindow* Hunt(int id){return id==2?&otherHunt:&hunt;}
'''+methods+'\nvoid Cleanup(float now,int assigned,int globalLimit){'+cleanup+'}\nvoid RemoveMissing(float now){for(int i=int(m_Units.size())-1;i>=0;i--){ERR_HQUnit* unit=m_Units[i];'+missing+'}}'+r'''
};
struct Fixture{ERR_GameMode mode;HQ hq;ERR_HQUnit unit;SCR_AIGroup group;AIWaypoint previous;ERR_HQTask active,next;ERR_HQReport oldReport,newReport;ERR_HQRoute route;ERR_HQBelief belief;
Fixture(){ERR_GameMode::current=&mode;ERR_Actors::refuse=false;ERR_Actors::spawns=0;hq.m_Mode=&mode;hq.m_Units={&unit};hq.m_Memory.belief=&belief;
unit.Group=&group;unit.Task=&active;unit.Assignment=&oldReport;unit.Waypoint=&previous;unit.PendingTask=&next;unit.PendingOrder=&newReport;unit.Downlink=&route;active.BeliefId=9;}
void Retained(){assert(unit.Task==&active&&unit.Assignment==&oldReport&&unit.Waypoint==&previous&&previous.deletes==0&&group.removals==0&&!unit.PendingTask&&!unit.PendingOrder&&!unit.Downlink);}
};
int main(){
// The copied radius is used before installation even if current uncertainty grows.
for(float radius:{45.f,60.f,110.f}){Fixture f;f.next.SearchRadiusMetres=radius;f.belief.Radius=500;
float deadline=f.next.ExpiresAt;f.hq.DeliverOrder(&f.unit,1000);
assert(f.group.additions==1&&f.unit.Waypoint->radius==radius&&f.unit.Waypoint->configured==1);
assert(f.next.SearchRadiusMetres==radius&&f.next.ExpiresAt==deadline);}
// Malformed snapshot cannot spawn or replace the delivered previous assignment.
for(float radius:{0.f,44.99f,110.01f,-1.f,std::numeric_limits<float>::quiet_NaN(),std::numeric_limits<float>::infinity()}){
Fixture f;f.next.SearchRadiusMetres=radius;f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);}
// Missing patrol, road/security defend, interception and containment keep native defaults.
for(int kind=0;kind<5;kind++){Fixture f;f.next.SearchRadiusMetres=std::numeric_limits<float>::quiet_NaN();
if(kind==0)f.next.WatchSourceId="missing-original";
if(kind==1)f.next.RoadIntercept=true;
if(kind==2)f.next.SecuritySite="site";
if(kind==3)f.next.Role=ERR_HQPursuitRole::INTERCEPT;
if(kind==4){f.next.Role=ERR_HQPursuitRole::CONTAIN;f.belief.Confidence=.9f;}
f.hq.DeliverOrder(&f.unit,1000);assert(f.group.additions==1&&f.unit.Waypoint->radius==(kind==1?70:30)&&f.unit.Waypoint->configured==(kind==1?1:0));}
// Alarm-only deliveries retain their weak SEARCH/mobile policy even if a queued task is tampered with.
for(int invalid=0;invalid<5;invalid++){Fixture f;f.belief.Kind=ERR_HQMemoryKind::ALARM;
ERR_HQUnit other;ERR_HQTask otherTask;ERR_HQReport alarm;alarm.TravelMode="heard vehicle alarm";
if(invalid==0)f.unit.HomeGuard=true;
if(invalid==1)f.next.Role=ERR_HQPursuitRole::INTERCEPT;
if(invalid==2)f.next.RoadIntercept=true;
if(invalid==3){other.PendingTask=&otherTask;other.PendingOrder=&alarm;f.hq.m_Units.push_back(&other);}
if(invalid==4){other.Task=&otherTask;other.Assignment=&alarm;f.hq.m_Units.push_back(&other);}
f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);}
{Fixture f;f.belief.Kind=ERR_HQMemoryKind::ALARM;f.newReport.TravelMode="heard vehicle alarm";
assert(f.hq.AlarmCommitted()&&!f.hq.AlarmCommitted(&f.unit));
f.hq.DeliverOrder(&f.unit,1000);assert(ERR_Actors::spawns==1&&f.unit.Waypoint->resource==1&&f.hq.AlarmCommitted());
f.unit.ReleaseAssignment();assert(!f.hq.AlarmCommitted());}
// A changed belief revision or absent/stale/uncertain track cannot create a native destination.
for(int invalid=0;invalid<5;invalid++){Fixture f;
if(invalid==0){f.belief.BeliefRevision=3;}if(invalid==1){f.hq.m_Memory.belief=nullptr;}
if(invalid==2){f.belief.ObservedAt=2000;}if(invalid==3){f.belief.Confidence=.1f;}if(invalid==4){f.belief.Radius=2001;}
f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);}
// Exact absolute expiry boundary, combat, physical reservation eligibility and known-empty ammo remain gates.
for(int invalid=0;invalid<4;invalid++){Fixture f;
if(invalid==0){f.next.ExpiresAt=1000;}if(invalid==1){f.unit.fighting=true;}if(invalid==2){f.unit.eligible=false;}if(invalid==3){f.hq.m_Watch.empty=true;}
f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);}
// Native spawn refusal must leave the delivered old waypoint and duty intact.
{Fixture f;ERR_Actors::refuse=true;f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==1&&f.mode.ledger.record.Duty==2);}
// Commit swaps the old task only after native success. Duplicate delivery has no pending lineage and cannot spawn twice.
{Fixture f;f.hq.DeliverOrder(&f.unit,1000);assert(ERR_Actors::spawns==1&&f.previous.deletes==1&&f.group.removals==1&&f.group.additions==1);
assert(f.unit.Waypoint->resource==1);
assert(f.unit.Task==&f.next&&f.unit.Assignment==&f.newReport&&f.unit.Waypoint!=&f.previous&&!f.unit.PendingTask&&!f.unit.Downlink);
auto retained=f.unit.Waypoint;f.hq.DeliverOrder(&f.unit,1001);assert(ERR_Actors::spawns==1&&f.unit.Waypoint==retained&&f.group.additions==1);
assert(f.mode.ledger.record.DutyTarget.x==f.next.Destination.x);f.unit.ReleaseAssignment();assert(retained->deletes==1&&f.mode.ledger.record.Duty==f.mode.ledger.record.HomeDuty);}
// Actual assignment-pass expiry releases only the expired reservation, preserving the other lineage.
{Fixture f;f.active.ExpiresAt=1000;f.hq.Cleanup(1000,1,4);
assert(!f.unit.Task&&!f.unit.Assignment&&!f.unit.Waypoint&&f.previous.deletes==1&&f.unit.PendingTask==&f.next&&f.unit.Downlink==&f.route);
assert(f.mode.ledger.record.Duty==f.mode.ledger.record.HomeDuty);f.hq.Cleanup(1001,1,4);assert(f.previous.deletes==1&&f.unit.PendingTask==&f.next);}
{Fixture f;f.active.BeliefId=1;f.next.ExpiresAt=1000;f.hq.Cleanup(1000,1,4);f.Retained();}
{Fixture f;f.unit.PlanEligible=false;f.hq.Cleanup(1000,1,4);assert(!f.unit.Task&&!f.unit.PendingTask&&!f.unit.Downlink&&f.previous.deletes==1);}
// Loss of the only physical pursuit squad releases its unfulfilled long hold.
{Fixture f;f.unit.CancelPending();f.active.BeliefId=1;f.active.ExpiresAt=600000;f.hq.hunt.NextHunt=780000;f.unit.PlanEligible=false;
f.hq.Cleanup(10000,1,4);assert(f.hq.hunt.NextHunt==25000);assert(!f.unit.Task&&!f.unit.Assignment&&f.previous.deletes==1);}
// Native group deletion takes the earlier Tick cleanup path, before Assign.
{Fixture f;f.unit.CancelPending();f.active.BeliefId=1;f.active.ExpiresAt=600000;f.hq.hunt.NextHunt=780000;f.unit.Group=nullptr;
f.hq.RemoveMissing(10000);assert(f.hq.m_Units.empty()&&f.hq.hunt.NextHunt==25000&&!f.unit.Task&&f.previous.deletes==1);}
// Finishing an ordinary delivered search retains its completed-hunt cooldown.
{Fixture f;f.unit.CancelPending();f.active.BeliefId=1;f.active.ExpiresAt=10000;f.hq.hunt.NextHunt=190000;
f.hq.Cleanup(10000,1,4);assert(f.hq.hunt.NextHunt==190000&&!f.unit.Assignment);}
// Simultaneous expiry and unavailable-force detection cannot erase a completed
// hunt's quiet interval, including after loss of a pending same-belief retask.
for(int removed=0;removed<2;removed++)for(int pending=0;pending<3;pending++)for(int shorter=0;shorter<2;shorter++){
Fixture f;f.active.BeliefId=1;f.active.ExpiresAt=10000;f.hq.hunt.NextHunt=shorter?12000:190000;f.hq.otherHunt.NextHunt=780000;
if(pending==0)f.unit.CancelPending();else {f.next.BeliefId=pending==1?1:2;f.next.ExpiresAt=600000;}
f.unit.PlanEligible=false;if(removed){f.unit.Group=nullptr;f.hq.RemoveMissing(10000);}else f.hq.Cleanup(10000,1,4);
assert(f.hq.hunt.NextHunt==(shorter?12000:190000));assert(f.hq.otherHunt.NextHunt==(pending==2?25000:780000));
assert(!f.unit.Task&&!f.unit.PendingTask&&f.previous.deletes==1);
}
// A completed squad's floor survives a different lost squad in either cleanup
// order, whether the completed squad expires normally or is also unavailable.
for(int reverse=0;reverse<2;reverse++)for(int completedLost=0;completedLost<2;completedLost++){
Fixture f;f.unit.CancelPending();f.active.BeliefId=1;f.active.ExpiresAt=10000;f.unit.PlanEligible=!completedLost;f.hq.hunt.NextHunt=780000;
ERR_HQUnit lost;SCR_AIGroup lostGroup;ERR_HQTask lostTask;ERR_HQReport lostReport;lost.Group=&lostGroup;lost.Task=&lostTask;lost.Assignment=&lostReport;lost.PlanEligible=false;lostTask.BeliefId=1;lostTask.ExpiresAt=600000;
f.hq.m_Units.push_back(&lost);if(reverse)std::reverse(f.hq.m_Units.begin(),f.hq.m_Units.end());f.hq.Cleanup(10000,2,4);assert(f.hq.hunt.NextHunt==190000);
}
// A replacement can arrive before Assign processes the original expiry.
// Native commit must preserve completion history before clearing old ownership.
{Fixture f;f.active.BeliefId=1;f.active.ExpiresAt=10000;f.next.ExpiresAt=600000;f.hq.hunt.NextHunt=780000;
f.hq.DeliverOrder(&f.unit,10000);assert(f.unit.Task==&f.next&&f.hq.hunt.CompletedUntil==190000);
f.unit.PlanEligible=false;f.hq.Cleanup(11000,1,4);assert(f.hq.hunt.NextHunt==190000&&!f.unit.Task);}
// Only expired delivered pursuit work creates completion history.
for(int kind=0;kind<5;kind++){Fixture f;f.active.BeliefId=1;f.active.ExpiresAt=10000;
if(kind==0){f.active.ExpiresAt=10001;}if(kind==1){f.active.ReleaseOnly=true;}if(kind==2){f.active.SecuritySite="security";}if(kind==3){f.active.WatchSourceId="watch";}if(kind==4){f.unit.Assignment=nullptr;}
f.hq.ReleaseUnit(&f.unit,10000);assert(f.hq.hunt.CompletedUntil==0&&f.hq.otherHunt.CompletedUntil==0);}
// Capacity trimming is deliberately unchanged: it is not evidence of force loss.
{Fixture f;f.unit.CancelPending();f.active.BeliefId=1;f.active.ExpiresAt=600000;f.hq.hunt.NextHunt=780000;
f.hq.Cleanup(10000,2,1);assert(f.hq.hunt.NextHunt==780000&&!f.unit.Assignment);}
// Surviving active and pending same-belief commitments both retain their own
// horizon; foreign replacements cannot hide active work or prolong this hunt.
for(int scenario=0;scenario<5;scenario++){Fixture f;f.unit.CancelPending();f.active.BeliefId=1;f.active.ExpiresAt=600000;f.hq.hunt.NextHunt=780000;f.unit.PlanEligible=false;
ERR_HQUnit peer;SCR_AIGroup peerGroup;ERR_HQTask surviving,replacement;ERR_HQReport activeReport,pendingReport;peer.Group=&peerGroup;peer.Task=&surviving;peer.Assignment=&activeReport;peer.PendingTask=&replacement;peer.PendingOrder=&pendingReport;
surviving.BeliefId=1;surviving.ExpiresAt=120000;replacement.BeliefId=1;replacement.ExpiresAt=180000;
if(scenario==1)replacement.BeliefId=2;
if(scenario==2){surviving.SecuritySite="outpost";replacement.ReleaseOnly=true;}
if(scenario==3){peer.Assignment=nullptr;peer.PendingOrder=nullptr;}
if(scenario==4)f.hq.hunt.NextHunt=12000;
f.hq.m_Units.push_back(&peer);f.hq.Cleanup(10000,2,4);float expected=scenario==0?360000:scenario==1?300000:scenario==4?12000:25000;
assert(f.hq.hunt.NextHunt==expected);assert(peer.Task==&surviving);}
// Expired undelivered retasks use the same bounded rollback as lost radio.
// Both original and replacement expire here; no unfulfilled hunt owns 780s.
{Fixture f;f.active.ExpiresAt=120000;f.next.ExpiresAt=600000;f.hq.hunt.NextHunt=780000;
f.hq.Cleanup(600000,1,4);assert(f.hq.hunt.NextHunt==615000);
assert(!f.unit.Task&&!f.unit.PendingTask&&!f.unit.Downlink&&f.previous.deletes==1);}
// A still-live containment order also needs current authorization at delivery.
// The fixed task deadline is not permission to use an older/weaker movement belief.
for(int invalid=0;invalid<6;invalid++){Fixture f;f.next.Role=ERR_HQPursuitRole::CONTAIN;
f.next.QueuedAt=30000;f.next.ExpiresAt=120000;float now=45000;
if(invalid==0){now=45001;}
if(invalid==1){f.belief.Radius=100.01f;}
if(invalid==2){f.belief.Confidence=.799f;}
if(invalid==3){f.belief.HasVelocity=false;}
if(invalid==4){f.belief.Velocity={0,0,.49f};}
if(invalid==5){f.belief.Velocity={0,0,71};}
f.hq.DeliverOrder(&f.unit,now);f.Retained();assert(ERR_Actors::spawns==0);}
{Fixture f;f.next.Role=ERR_HQPursuitRole::CONTAIN;f.next.QueuedAt=30000;f.next.ExpiresAt=120000;
f.belief.Radius=100;f.belief.Confidence=.8f;f.belief.Velocity={0,0,.5f};
f.hq.DeliverOrder(&f.unit,45000);assert(ERR_Actors::spawns==1&&f.unit.Task==&f.next&&f.previous.deletes==1);
f.hq.DeliverOrder(&f.unit,45001);assert(ERR_Actors::spawns==1&&f.group.additions==1);}
// Radio latency may age a precision interception beyond its 45-second authorization.
// Eligibility uses the actual road helper and pursuit rules, never a test predicate.
for(int invalid=0;invalid<9;invalid++){Fixture f;f.next.RoadIntercept=true;float now=1000;
if(invalid==0)now=45001;
if(invalid==1)f.belief.LastDeliveredAt=1001;
if(invalid==2)f.belief.SampleCount=1;
if(invalid==3)f.belief.Velocity={0,0,1.99f};
if(invalid==4)f.belief.HasVelocity=false;
if(invalid==5)f.belief.TravelMode="aircraft";
if(invalid==6)f.belief.Kind=2;
if(invalid==7)f.belief.MessageId=0;
if(invalid==8){f.belief.ObservedAt=1;f.belief.LastDeliveredAt=0;}
f.hq.DeliverOrder(&f.unit,now);f.Retained();assert(ERR_Actors::spawns==0);}
{Fixture f;f.next.RoadIntercept=true;f.hq.DeliverOrder(&f.unit,45000);
assert(ERR_Actors::spawns==1&&f.unit.Waypoint&&f.unit.Waypoint->resource==2&&f.unit.Task==&f.next&&f.previous.deletes==1);
f.hq.DeliverOrder(&f.unit,45001);assert(ERR_Actors::spawns==1&&f.group.additions==1);
f.hq.Cleanup(60000,1,4);assert(!f.unit.Task&&!f.unit.Waypoint&&f.mode.ledger.record.Duty==f.mode.ledger.record.HomeDuty);}
// Security orders carry an inspected-site hypothesis, not a fabricated enemy track.
{Fixture f;f.next.SecuritySite="mortar-1";f.next.BeliefId=999;
f.hq.DeliverOrder(&f.unit,1000);assert(f.unit.Task==&f.next&&f.unit.Waypoint->resource==2);
f.hq.Cleanup(1001,1,4);assert(f.unit.Task==&f.next);
f.hq.Cleanup(60000,1,4);assert(!f.unit.Task&&!f.unit.Waypoint);}
// Cancelling a mismatched recall never releases a replacement task which arrived first.
{Fixture f;f.next.ReleaseOnly=true;f.next.ReplacesTask=nullptr;f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);}
{Fixture f;f.next.ReleaseOnly=true;f.next.ReplacesTask=&f.active;f.hq.DeliverOrder(&f.unit,1000);assert(!f.unit.Task&&!f.unit.Assignment&&!f.unit.Waypoint&&f.previous.deletes==1&&ERR_Actors::spawns==0);}
}
''')
print('PASS: actual delivered-task lineage, expiry, old-order retention, native commit, release, road precision expiry and duplicate delivery')
delivery=body(P,'protected void DeliverOrder(')
assert delivery.index('waypoint.SetCompletionRadius(task.SearchRadiusMetres)') < delivery.index('unit.Release();') < delivery.index('unit.Group.AddWaypointAt')
@@ -0,0 +1,220 @@
"""Execute actual delivered-belief pursuit rules as C++; no engine/AI behavior claim."""
import os, re
from pathlib import Path
from policy_harness import ROOT, body, run
P = Path(os.environ.get('ERR_HQ_PURSUIT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c'))
source = P.read_text()
# Exercise the real planner expression against the same actual policy methods.
HQ = Path(os.environ.get('ERR_HQ_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
assignment = body(HQ, 'protected void Assign(')
selection = re.search(r'ERR_HQPursuitRole role = (ERR_HQPursuitRules\.Role\([^;]+\));', assignment)
assert selection, 'missing actual Assign role selection'
planner_role = selection.group(1).replace('ERR_HQPursuitRules.', 'Rules::')
methods = '\n'.join(body(P, signature) for signature in (
'static bool Finite(', 'static bool PositionValid(', 'static int Limit(', 'static bool Actionable(',
'static bool Heading(', 'static ERR_HQPursuitRole Role(', 'static float LifetimeSeconds(',
'static float SearchRadius(', 'static float TaskLifetimeSeconds(', 'static bool CanCommit(', 'static int Bearing(', 'static bool BearingAllowed(', 'static vector Direction(',
'static vector Objective(', 'static float Score(', 'static bool Better('))
methods = methods.replace('Math.', 'Math::').replace('vector.', 'vector::')
methods = methods.replace('ERR_HQPursuitRole.', 'ERR_HQPursuitRole::')
methods = methods.replace('vector offset;', 'vector offset{};')
run(r'''
#include <cassert>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <limits>
#include <vector>
#include <set>
enum class ERR_HQPursuitRole { SEARCH, INTERCEPT, CONTAIN };
struct Math {
static float Clamp(float n,float lo,float hi){return std::clamp(n,lo,hi);}
static float AbsFloat(float n){return std::abs(n);}
static float Min(float a,float b){return std::min(a,b);}
};
struct vector {
float x=0,y=0,z=0;
vector()=default;
vector(float a,float b,float c):x(a),y(b),z(c){}
vector(const char* text){sscanf(text,"%f %f %f",&x,&y,&z);}
float& operator[](int i){return i==0?x:i==1?y:z;}
float operator[](int i)const{return i==0?x:i==1?y:z;}
vector operator+(vector b)const{return {x+b.x,y+b.y,z+b.z};}
vector operator-(vector b)const{return {x-b.x,y-b.y,z-b.z};}
vector operator*(float v)const{return {x*v,y*v,z*v};}
float Length()const{return std::sqrt(x*x+y*y+z*z);}
vector Normalized()const{float n=Length();return {x/n,y/n,z/n};}
static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}
static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}
};
vector Vector(float a,float b,float c){return {a,b,c};}
struct Rules {
'''+methods+r'''
};
struct Belief { float Confidence=.9f, Radius=50; vector Velocity; bool HasVelocity=true; };
ERR_HQPursuitRole PlannerRole(Belief belief,float age,int slot){return '''+planner_role+r''';}
bool near(float a,float b){return std::abs(a-b)<0.001f;}
int main(){
assert(Rules::SearchRadius(0)==45&&Rules::SearchRadius(44.99)==45&&Rules::SearchRadius(45)==45);
assert(Rules::SearchRadius(73)==73&&Rules::SearchRadius(110)==110&&Rules::SearchRadius(2000)==110);
assert(Rules::SearchRadius(-1)==0&&Rules::SearchRadius(std::numeric_limits<float>::quiet_NaN())==0&&Rules::SearchRadius(std::numeric_limits<float>::infinity())==0);
using Role=ERR_HQPursuitRole;
assert(Rules::Limit(-1)==0&&Rules::Limit(0)==0&&Rules::Limit(1)==1);
assert(Rules::Limit(3)==3&&Rules::Limit(8)==3);
assert(Rules::Actionable(.9,10,50)&&Rules::Actionable(.15,600,2000));
assert(!Rules::Actionable(.149,10,50)&&Rules::Actionable(.9,601,50));
assert(Rules::Actionable(.2,1200,1900)&&!Rules::Actionable(.2,1200,2001));
assert(!Rules::Actionable(.9,-1,50)&&!Rules::Actionable(.9,0,-1));
float nan=std::numeric_limits<float>::quiet_NaN();
float inf=std::numeric_limits<float>::infinity();
assert(!Rules::Actionable(nan,0,50)&&!Rules::Actionable(.9,inf,50));
assert(!Rules::Actionable(.9,0,nan)&&!Rules::Actionable(1.1,0,50));
vector north{0,0,5}, center{1000,12,1000};
assert(Rules::Heading(north,true)&&!Rules::Heading(north,false));
assert(!Rules::Heading({0,0,0},true)&&!Rules::Heading({0,0,71},true));
assert(!Rules::Heading({nan,0,5},true));
assert(Rules::Role(.9,10,50,true,0)==Role::INTERCEPT);
assert(Rules::Role(.9,10,50,true,1)==Role::SEARCH);
assert(Rules::Role(.9,10,50,true,2)==Role::CONTAIN);
// Memory can retain a valid slow-motion sample below the directed-task threshold.
// The actual Assign expression must not queue containment that delivery rejects.
Belief belief;
for(float speed:{0.f,.49f,70.01f})for(int slot=0;slot<3;slot++){
belief.Velocity={0,0,speed};
assert(PlannerRole(belief,10,slot)==Role::SEARCH);
}
for(float speed:{.5f,70.f}){
belief.Velocity={0,0,speed};
assert(PlannerRole(belief,10,0)==Role::INTERCEPT);
assert(PlannerRole(belief,10,1)==Role::SEARCH);
assert(PlannerRole(belief,10,2)==Role::CONTAIN);
}
belief.Velocity={nan,0,5};
assert(PlannerRole(belief,10,2)==Role::SEARCH);
belief.Velocity=north;belief.HasVelocity=false;
assert(PlannerRole(belief,10,0)==Role::SEARCH&&PlannerRole(belief,10,2)==Role::SEARCH);
for(int slot=0;slot<3;slot++){
assert(Rules::Role(.64,10,50,true,slot)==Role::SEARCH);
assert(Rules::Role(.9,46,50,true,slot)==Role::SEARCH);
assert(Rules::Role(.9,10,151,true,slot)==Role::SEARCH);
assert(Rules::Role(.9,10,50,false,slot)==Role::SEARCH);
}
assert(Rules::Role(.79,10,50,true,2)==Role::SEARCH);
assert(Rules::Role(.9,10,101,true,2)==Role::SEARCH);
assert(Rules::Role(.9,10,50,true,-1)==Role::SEARCH);
assert(Rules::LifetimeSeconds(Role::INTERCEPT)==60);
assert(Rules::LifetimeSeconds(Role::CONTAIN)==90);
assert(Rules::LifetimeSeconds(Role::SEARCH)==120);
assert(Rules::TaskLifetimeSeconds(Role::SEARCH,0)==120);
assert(near(Rules::TaskLifetimeSeconds(Role::SEARCH,2000),2000.f/3+45));
assert(Rules::TaskLifetimeSeconds(Role::SEARCH,5000)==900);
assert(Rules::TaskLifetimeSeconds(Role::INTERCEPT,0)==180);
assert(near(Rules::TaskLifetimeSeconds(Role::INTERCEPT,2000),2000.f/3+180));
assert(Rules::TaskLifetimeSeconds(Role::INTERCEPT,5000)==900);
assert(Rules::TaskLifetimeSeconds(Role::INTERCEPT,-1)==0);
assert(Rules::TaskLifetimeSeconds(Role::INTERCEPT,nan)==0);
assert(Rules::TaskLifetimeSeconds(Role::CONTAIN,2000)==90);
assert(Rules::TaskLifetimeSeconds(Role::SEARCH,-1)==0);
assert(Rules::TaskLifetimeSeconds(Role::SEARCH,nan)==0);
assert(Rules::TaskLifetimeSeconds(Role::SEARCH,inf)==0);
// The caller must already own a physical, eligible and unassigned formation.
assert(Rules::CanCommit(true,true,false,false,0,0,3));
assert(!Rules::CanCommit(false,true,false,false,0,0,3));
assert(!Rules::CanCommit(true,false,false,false,0,0,3));
assert(!Rules::CanCommit(true,true,true,false,0,0,3));
assert(!Rules::CanCommit(true,true,false,false,0,-1,3));
assert(!Rules::CanCommit(true,true,false,false,0,0,0));
assert(!Rules::CanCommit(true,true,false,false,0,3,99));
assert(!Rules::CanCommit(true,true,false,true,1,0,3));
int homeAvailable=3,assigned=0;
for(int i=0;i<5;i++)if(Rules::CanCommit(true,true,false,true,homeAvailable,assigned,3)){
homeAvailable--;assigned++;
}
assert(homeAvailable==1&&assigned==2);
// Pending orders spend the slot immediately, and each busy unit is ineligible.
bool busy=false;int commitments=0;
for(int i=0;i<5;i++)if(Rules::CanCommit(true,true,busy,false,1,commitments,3)){
busy=true;commitments++;
}
assert(commitments==1);
vector intercept=Rules::Objective(center,north,true,.9,10,50,Role::INTERCEPT,0);
assert(near(intercept.x,1000)&&near(intercept.z,1065)&&near(intercept.y,12));
vector contain=Rules::Objective(center,north,true,.9,10,50,Role::CONTAIN,2);
assert(near(contain.x,1080)&&near(contain.z,1080));
vector stale=Rules::Objective(center,north,true,.4,180,400,Role::INTERCEPT,0);
assert(near(stale.z,1250)); // degraded to a bounded search sector, no velocity lead
for(int slot=-8;slot<9;slot++)for(float radius:{0.f,50.f,200.f,2000.f}){
vector objective=Rules::Objective(center,north,true,.2,300,radius,Role::SEARCH,slot);
assert(vector::DistanceXZ(center,objective)<=300&&near(objective.y,center.y));
}
vector maximum=Rules::Objective(center,{0,0,70},true,.9,0,50,Role::INTERCEPT,0);
assert(near(maximum.z,1200)); // future lead capped even for a fast vehicle
vector noDoubleAge=Rules::Objective(center,north,true,.9,40,50,Role::INTERCEPT,0);
assert(near(noDoubleAge.z,1060)); // supplied position is already memory-predicted
vector first=Rules::Objective(center,north,false,.4,180,100,Role::SEARCH,0);
vector second=Rules::Objective(center,north,false,.4,180,100,Role::SEARCH,1);
vector third=Rules::Objective(center,north,false,.4,180,100,Role::SEARCH,2);
assert(vector::DistanceXZ(first,second)>100&&vector::DistanceXZ(second,third)>100);
// Nearby units already ahead win interception; nearby correct-flank units contain.
float ahead=Rules::Score(Role::INTERCEPT,center,north,{1000,0,1200},intercept);
float behind=Rules::Score(Role::INTERCEPT,center,north,{1000,0,800},intercept);
assert(ahead>=0&&behind<0);
float right=Rules::Score(Role::CONTAIN,center,north,{1100,0,1050},contain);
float left=Rules::Score(Role::CONTAIN,center,north,{900,0,1050},contain);
assert(right<left);
assert(Rules::Score(Role::INTERCEPT,center,north,{intercept.x+150,0,intercept.z},intercept)>=0);
assert(Rules::Score(Role::INTERCEPT,center,north,{intercept.x+151,0,intercept.z},intercept)<0);
assert(Rules::Score(Role::CONTAIN,center,north,{contain.x+240,0,contain.z},contain)>=0);
assert(Rules::Score(Role::CONTAIN,center,north,{contain.x+241,0,contain.z},contain)<0);
assert(Rules::Score(Role::SEARCH,center,north,{1000,0,1000},first)
<Rules::Score(Role::SEARCH,center,north,{2000,0,1000},first));
assert(Rules::Score(Role::SEARCH,{nan,0,0},north,center,first)<0);
assert(Rules::Better(100,2,100,5)&&!Rules::Better(100,5,100,2));
assert(Rules::Better(99,5,100,2)&&Rules::Better(100,1,-1,-1));
assert(!Rules::Better(-1,1,100,2)&&!Rules::Better(nan,1,100,2));
int firstBearing=-1,secondBearing=-1;std::set<int> bearings;
std::vector<vector> forces={{1000,0,1200},{1200,0,1000},{1000,0,800},{800,0,1000}};
for(vector force:forces){
int bearing=Rules::Bearing(force,center);
if(!Rules::BearingAllowed(bearing,firstBearing,secondBearing))continue;
bearings.insert(bearing);
if(firstBearing<0)firstBearing=bearing;
else if(bearing!=firstBearing&&secondBearing<0)secondBearing=bearing;
}
assert(bearings.size()==2);
assert(Rules::BearingAllowed(firstBearing,firstBearing,secondBearing));
assert(!Rules::BearingAllowed(2,0,1)&&!Rules::BearingAllowed(3,0,1));
assert(!Rules::BearingAllowed(-1,-1,-1)&&!Rules::BearingAllowed(2,-1,1));
assert(Rules::Bearing({nan,0,0},center)==-1);
}
''')
for forbidden in ('GetGame(', 'GetOrigin(', 'GetPlayers(', 'GetControlledEntity(',
'Spawn(', 'CallLater(', 'SCR_AIGroup', 'IEntity'):
assert forbidden not in source, f'pursuit must consume snapshots only: {forbidden}'
assert all(len(line)<120 for line in source.splitlines())
print('PASS: actual bounded pursuit roles/objectives/scores, stale search degradation, physical/busy/home guards, finite lifetime and two-bearing escape gap; no hidden player reads or spawning')
for signature in ('protected void Assign(', 'protected void Retask('):
section=body(HQ,signature)
line=next(line.strip() for line in section.splitlines() if 'task.SearchRadiusMetres =' in line)
line=line.replace('ERR_HQPursuitRules.', 'Rules::').replace('ERR_HQPursuitRole.', 'Role::')
run(r"""
#include <cassert>
#include <algorithm>
#include <cmath>
#include <limits>
enum class Role {SEARCH,INTERCEPT,CONTAIN};
struct Math{static float Clamp(float n,float a,float b){return std::clamp(n,a,b);}};
struct Rules{
"""+body(P,'static bool Finite(')+body(P,'static float SearchRadius(').replace('Math.','Math::')+r"""
};
struct Task{float SearchRadiusMetres=0;};struct Belief{float Radius=0;};
void Queue(Task& task,Belief& belief,Role role,bool roadIntercept){
"""+line+r"""
}
int main(){for(float radius:{0.f,60.f,500.f}){Task task;Belief belief{radius};Queue(task,belief,Role::SEARCH,false);float copied=Rules::SearchRadius(radius);assert(task.SearchRadiusMetres==copied);belief.Radius=1900;assert(task.SearchRadiusMetres==copied);}
"""+(r"""for(Role role:{Role::INTERCEPT,Role::CONTAIN}){Task task;Belief belief{100};Queue(task,belief,role,false);assert(task.SearchRadiusMetres==0);}Task road;Belief belief{100};Queue(road,belief,Role::SEARCH,true);assert(road.SearchRadiusMetres==0);""" if 'Assign' in signature else '')+r"""
}
""")
print('PASS: actual finite45..110 radius and both ordinary search queue snapshot expressions')
@@ -0,0 +1,90 @@
"""Actual delivered-memory road selector, geometry and native-query boundaries."""
from pathlib import Path
import os,re
from policy_harness import ROOT,body,run
p=Path(os.environ.get('ERR_ROAD_INTERCEPT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQRoadIntercept.c'))
source=p.read_text()
for forbidden in ('GetPlayer','GetControlledEntity','GetOrigin','Spawn(', 'AddWaypoint','SetOrigin','CallLater'):
assert forbidden not in source,forbidden
rules='\n'.join(body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c', x) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role(', 'static vector Direction(', 'static vector Objective('))
rules=rules.replace('Math.','Math::').replace('vector.','vector::').replace('ERR_HQPursuitRole.','ERR_HQPursuitRole::').replace('vector offset;','vector offset{};')
s=re.sub(r'//[^\n]*','',source).replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept')
s=s.replace('out vector','vector&').replace('out float','float&').replace('array<vector>','Array<vector>').replace('array<BaseRoad>','Array<BaseRoad*>').replace('foreach (','for (')
s=s.replace('SCR_AIWorld.Cast(GetGame().GetAIWorld())','GetGame()->GetAIWorld()')
for name in ('ERR_HQBelief','SCR_AIWorld','BaseRoad','TraceBox'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
for name in ('belief','world','road','body'):
s=s.replace(name+'.',name+'->')
s=s.replace('GetGame().','GetGame()->').replace('GetRoadNetworkManager().','GetRoadNetworkManager()->').replace('GetWorld().','GetWorld()->')
for name in ('ERR_ForceRules','ERR_HQPursuitRules','ERR_HQPursuitRole','ERR_HQMemoryKind','vector','Math','ERR_Actors','ChimeraWorldUtils','TraceFlags','Replication'):
s=s.replace(name+'.',name+'::')
s=s.replace('null','nullptr')
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('\n}\n','\n};\n')
run(r'''
#include <cassert>
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <limits>
#include <vector>
#include <string>
enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};enum class ERR_HQMemoryKind{SIGHTING,DISTRESS,BODY};
struct Math{static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}static float AbsFloat(float x){return std::abs(x);}};
struct vector{float x=0,y=0,z=0;vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}vector(const char*s){sscanf(s,"%f %f %f",&x,&y,&z);}float&operator[](int i){return i==0?x:i==1?y:z;}float operator[](int i)const{return i==0?x:i==1?y:z;}
vector operator+(vector b)const{return{x+b.x,y+b.y,z+b.z};}vector operator-(vector b)const{return{x-b.x,y-b.y,z-b.z};}vector operator*(float n)const{return{x*n,y*n,z*n};}bool operator==(vector b)const{return x==b.x&&y==b.y&&z==b.z;}
float Length()const{return std::sqrt(x*x+y*y+z*z);}vector Normalized()const{float n=Length();return{x/n,y/n,z/n};}static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}static vector Lerp(vector a,vector b,float n){return a+(b-a)*n;}static const vector Zero;};
const vector vector::Zero{};vector Vector(float a,float b,float c){return{a,b,c};}
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}void Insert(T v){this->push_back(v);}explicit operator bool(){return true;}};
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector p){return Finite(p.x)&&Finite(p.y)&&Finite(p.z);}};
struct ERR_HQPursuitRules{''' + rules + r'''};
struct ERR_HQBelief{ERR_HQMemoryKind Kind=ERR_HQMemoryKind::SIGHTING;int SampleCount=2,MessageId=1;float ObservedAt=5000,LastDeliveredAt=7000,Confidence=.9,Radius=40;std::string TravelMode="on foot";vector PredictedPosition={1000,10,1000},Velocity={0,0,4};bool HasVelocity=true;};
struct TraceFlags{static constexpr int WORLD=1,ENTS=2;};struct TraceBox{vector Start,End,Mins,Maxs;int Flags=0;};
struct NativeWorld{int traces=0;float fraction=1;float TraceMove(TraceBox* t,void*){traces++;assert(t->Flags==3&&t->Mins.y==-.9f);return fraction;}};
struct BaseRoad{float width=4;Array<vector>points={{900,10,1060},{1100,10,1060}};float GetWidth(){return width;}void GetPoints(Array<vector>&p){p=points;}};
struct RoadManager{Array<BaseRoad*>roads;int queries=0;void GetRoadsInAABB(vector low,vector high,Array<BaseRoad*>&out){queries++;assert(high.x-low.x==200&&high.z-low.z==200&&high.y-low.y==2000);out=roads;}};
struct SCR_AIWorld{RoadManager manager;bool available=true;RoadManager*GetRoadNetworkManager(){return available?&manager:nullptr;}};
struct Game{NativeWorld world;SCR_AIWorld ai;NativeWorld*GetWorld(){return &world;}SCR_AIWorld*GetAIWorld(){return &ai;}}game;Game*GetGame(){return &game;}
struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
struct ERR_Actors{static inline bool steep=false;static vector Ground(vector p){p.y=10;if(steep&&p.x>1000)p.y=11;return p;}};
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
''' + s + r'''
int main(){
using Road=ERR_HQRoadIntercept;ERR_HQBelief good;
assert(Road::Eligible(&good,10000));
auto vehicle=good;vehicle.TravelMode="vehicle";assert(Road::Eligible(&vehicle,10000));
vehicle.TravelMode="in vehicle";assert(Road::Eligible(&vehicle,10000));
for(int invalid=0;invalid<12;invalid++){
auto bad=good;
if(invalid==0){bad.Kind=ERR_HQMemoryKind::DISTRESS;}if(invalid==1){bad.SampleCount=1;}if(invalid==2){bad.MessageId=0;}
if(invalid==3){bad.LastDeliveredAt=10001;}if(invalid==4){bad.LastDeliveredAt=4999;}if(invalid==5){bad.TravelMode="aircraft";}
if(invalid==6){bad.HasVelocity=false;}if(invalid==7){bad.Velocity={0,0,1.99f};}if(invalid==8){bad.Confidence=.649f;}
if(invalid==9){bad.Radius=151;}if(invalid==10){bad.ObservedAt=-35001;}if(invalid==11){bad.Velocity.x=NAN;}
assert(!Road::Eligible(&bad,10000));
}
Array<vector> reserved;vector origin={1000,10,1000},velocity={0,0,4},seed={1000,10,1060},result;float score;
assert(Road::FromPolyline({{900,0,1060},{1100,0,1060}},origin,velocity,seed,reserved,result,score)&&result.x==1000&&result.z==1060&&score==0);
assert(!Road::Ahead(origin,velocity,{1000,10,1039.99f}));assert(Road::Ahead(origin,velocity,{1000,10,1040}));assert(!Road::Ahead(origin,velocity,{1000,10,1250.01f}));assert(!Road::Ahead(origin,velocity,{1000,10,940}));
reserved.Insert({1000,10,1060});assert(!Road::FromPolyline({{900,0,1060},{1100,0,1060}},origin,velocity,seed,reserved,result,score)&&result==vector::Zero);reserved.clear();
// Hairpin gaps are never joined by an offroad chord toward the desired center.
assert(Road::FromPolyline({{900,0,1040},{1100,0,1040},{1100,0,1160},{900,0,1160}},origin,velocity,{1000,10,1100},reserved,result,score));assert(result.z==1040||result.z==1160||result.x==1100);
assert(!Road::FromPolyline({{900,0,1060},{NAN,0,1060}},origin,velocity,seed,reserved,result,score)&&result==vector::Zero);
Array<vector> oversized(1025,seed);assert(!Road::FromPolyline(oversized,origin,velocity,seed,reserved,result,score));
BaseRoad road;game.ai.manager.roads={&road};assert(Road::Select(&good,10000,reserved,result)&&result==vector(1000,10,1060)&&game.world.traces==1);
// An elevated bridge must not be silently projected onto terrain beneath the actual road.
road.points={{900,20,1060},{1100,20,1060}};int groundTraces=game.world.traces;
assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero&&game.world.traces==groundTraces);
road.points={{900,12,1060},{1100,12,1060}};assert(Road::Select(&good,10000,reserved,result));
road.points={{900,10,1060},{1100,10,1060}};
// Refusal never leaves a seemingly usable destination from an earlier successful call.
game.world.fraction=.5;assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero);game.world.fraction=1;
ChimeraWorldUtils::water=true;assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero);ChimeraWorldUtils::water=false;
ERR_Actors::steep=true;assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero);ERR_Actors::steep=false;
road.width=3.99;assert(!Road::Select(&good,10000,reserved,result));road.width=4;
game.ai.manager.roads.resize(129,&road);assert(!Road::Select(&good,10000,reserved,result));game.ai.manager.roads={&road};
BaseRoad dense;for(int i=0;i<1024;i++)dense.points.push_back({float(i),0,1060});dense.points.erase(dense.points.begin(),dense.points.begin()+2);
game.ai.manager.roads={&dense,&dense,&dense,&dense,&dense};int before=game.world.traces;assert(!Road::Select(&good,10000,reserved,result)&&game.world.traces==before&&result==vector::Zero);
game.ai.manager.roads={&road};auto stale=good;stale.ObservedAt=-35001;before=game.ai.manager.queries;assert(!Road::Select(&stale,10000,reserved,result)&&game.ai.manager.queries==before);
game.world.fraction=NAN;assert(!Road::Select(&good,10000,reserved,result));
}
''')
print('PASS: actual delivered road eligibility, ahead projection without chords, reservations, stock road query bounds, terrain/water/obstruction refusal and clean outputs')
@@ -0,0 +1,70 @@
"""Run the production prison hold/release callbacks with engine boundaries stubbed."""
from policy_harness import ROOT, body, run
from pathlib import Path
import os
source = Path(os.environ.get('ERR_PRISON_AI_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c'))
def cpp(signature):
s=body(source,signature).replace('protected ','')
for ns in ('Replication','SCR_Global','ERR_GameMode','ERR_Phase','ERR_Actors','SCR_FactionManager'):
s=s.replace(ns+'.',ns+'::')
s=s.replace('ERR_GameMode mode =','ERR_GameMode* mode =').replace('IEntity actor =','Actor* actor =')
for var in ('mode','actor','m_ERRPrisonMode'):
s=s.replace(var+'.',var+'->')
s=s.replace('ERR_Phase previous, ERR_Phase current','int previous, int current')
for callback in ('ERR_PrisonPhaseChanged','ERR_PrisonStopped'):
s=s.replace('Insert('+callback+')','Insert("'+callback+'")').replace('Remove('+callback+')','Remove("'+callback+'")')
s=s.replace('OnPhaseChanged().','OnPhaseChanged()->').replace('OnStop().','OnStop()->')
return s
run(r'''
#include <cassert>
#include <set>
#include <string>
#define null nullptr
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace SCR_Global {bool edit=false;bool IsEditMode(){return edit;}}
namespace ERR_Phase {constexpr int PRISON=1,RADIO_SEARCH=2;}
struct Actor {bool player=false, fighting=true;int faction=2;};
namespace ERR_Actors {bool Player(Actor* a){return a&&a->player;}bool CanFight(Actor* a){return a&&a->fighting;}}
namespace SCR_FactionManager {int SGetFaction(Actor* a){return a->faction;}}
struct Hook {std::set<std::string> callbacks;int inserted=0;void Insert(std::string c){callbacks.insert(c);inserted++;}void Remove(std::string c){callbacks.erase(c);}};
struct ERR_GameMode {static ERR_GameMode* current;int phase=0,enemy=2;Hook phases,stops;
static ERR_GameMode* Get(){return current;}int Phase(){return phase;}int EnemyFaction(){return enemy;}bool IsMissionEnemy(int f){return enemy&&f>=enemy;}
Hook* OnPhaseChanged(){return &phases;}Hook* OnStop(){return &stops;}};
ERR_GameMode* ERR_GameMode::current=nullptr;
struct Agent {ERR_GameMode* m_ERRPrisonMode=nullptr;Actor* actor=nullptr;bool pending=false,active=true;int starts=0;
Actor* GetControlledEntity(){return actor;}bool IsPlayerPending_S(){return pending;}
void DeactivateAI(){active=false;}void ActivateAI(){active=true;starts++;}
'''+cpp('bool ERR_PrisonHeldForTest(')+cpp('void ERR_HoldForPrison(')+cpp('protected void ERR_PrisonUnsubscribe(')+cpp('protected void ERR_PrisonPhaseChanged(')+cpp('protected void ERR_PrisonStopped(')+r'''
};
int main(){
for(int phase=0;phase<=1;phase++){
ERR_GameMode mode;mode.phase=phase;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;
a.ERR_HoldForPrison();assert(a.ERR_PrisonHeldForTest());assert(!a.active&&mode.phases.callbacks.size()==1&&mode.stops.callbacks.size()==1);
a.active=true;a.ERR_HoldForPrison();assert(!a.active&&mode.phases.inserted==1);
a.ERR_PrisonPhaseChanged(0,1);assert(!a.active&&a.m_ERRPrisonMode==&mode);
mode.phase=2;a.ERR_PrisonPhaseChanged(phase,2);assert(a.active&&a.starts==1&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty()&&mode.stops.callbacks.empty());
a.ERR_HoldForPrison();assert(a.active&&mode.phases.inserted==1);
assert(!a.ERR_PrisonHeldForTest());a.DeactivateAI();assert(!a.active&&!a.ERR_PrisonHeldForTest());
}
for(int fault=0;fault<8;fault++){
ERR_GameMode mode;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;
if(fault==0)Replication::server=false;if(fault==1)SCR_Global::edit=true;
if(fault==2)ERR_GameMode::current=nullptr;if(fault==3)a.actor=nullptr;
if(fault==4)actor.player=true;if(fault==5)a.pending=true;
if(fault==6)actor.faction=1;if(fault==7)mode.enemy=0;
a.ERR_HoldForPrison();assert(a.active&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty());
Replication::server=true;SCR_Global::edit=false;
}
// Dead/unconscious, newly possessed, pending-player and removed actors cannot
// be activated by the release callback; all subscriptions are still removed.
for(int fault=0;fault<4;fault++){
ERR_GameMode mode;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;a.ERR_HoldForPrison();
if(fault==0)actor.fighting=false;if(fault==1)actor.player=true;
if(fault==2)a.pending=true;if(fault==3)a.actor=nullptr;
mode.phase=2;a.ERR_PrisonPhaseChanged(1,2);assert(!a.active&&a.starts==0&&mode.phases.callbacks.empty());
}
{ERR_GameMode mode;ERR_GameMode::current=&mode;Actor actor;Agent a;a.actor=&actor;a.ERR_HoldForPrison();
a.ERR_PrisonStopped();assert(!a.active&&a.starts==0&&!a.m_ERRPrisonMode&&mode.phases.callbacks.empty()&&mode.stops.callbacks.empty());}
}
'''.replace(';if(', ';\n if('))
print('PASS: production prison AI hold/release, repeat activation, authority/faction/player exclusion, dead actor and stop cleanup')
@@ -0,0 +1,75 @@
"""Execute actual task/guide copy for initial waiting and subsequent radio states."""
from policy_harness import ROOT, body, run
from pathlib import Path
import os
source = Path(os.environ.get('ERR_OBJECTIVE_TEXT_SOURCE', ROOT / 'addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveText.c'))
def translate(s):
s = s.replace('protected ', '').replace('ERR_GameMode mode,', 'ERR_GameMode* mode,')
s = s.replace('ERR_Phase phase', 'int phase').replace('mode.', 'mode->')
s = s.replace('ERR_RadioFeedback feedback', 'int feedback')
s = s.replace('array<ERR_LocationComponent>', 'std::vector<ERR_LocationComponent*>')
s = s.replace('foreach (ERR_LocationComponent location : locations)', 'for (ERR_LocationComponent* location : locations)')
s = s.replace('location.', 'location->').replace('bool roughRadio;', 'bool roughRadio = false;')
s = s.replace('.IsEmpty()', '.empty()').replace('string.Format(', 'Format(')
for name in ('ERR_Phase', 'ERR_LocationKind', 'ERR_RadioFeedback', 'Math'):
s = s.replace(name + '.', name + '::')
return s
methods = '\n'.join(translate(body(source, signature)) for signature in (
'static string RadioFeedbackMessage(', 'protected static string PhaseDescription('))
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <cmath>
using string=std::string;
namespace ERR_Phase {enum {PREPARING,PRISON,RADIO_SEARCH,RADIO_DEFENCE,EXTRACTION,VICTORY,DEFEAT};}
namespace ERR_LocationKind {enum {PRISON,RADIO,EXTRACTION,OUTPOST,DEPOT};}
namespace ERR_RadioFeedback {enum {INACTIVE,WAITING,TRANSMITTING,INTERRUPTED};}
namespace Math {float Ceil(float n){return std::ceil(n);}}
struct ERR_LocationComponent {int Kind(){return 0;}int Knowledge(){return 0;}};
struct ERR_GameMode {
int startMode=0;int StartMode(){return startMode;}
int phase=ERR_Phase::RADIO_DEFENCE, feedback=ERR_RadioFeedback::WAITING, remaining=180;
bool occupied=false;int Phase(){return phase;}string PreparationError(){return "";}
void Locations(std::vector<ERR_LocationComponent*>& locations){locations.clear();}
int RadioFeedback(){return feedback;}int DefenceSecondsRemaining(){return remaining;}
bool DefenceOccupied(){return occupied;}float EscapeRadius(){return 9000;}
};
string Format(string s,int value){auto p=s.find("%1");assert(p!=string::npos);return s.replace(p,2,std::to_string(value));}
string Extraction(float radius){assert(radius==9000);return "extraction";}
struct Actual {
''' + methods + r'''
};
bool has(string text,string needle){return text.find(needle)!=string::npos;}
int main(){
ERR_GameMode mode;
for(int duration : {30,180,1800}) {
mode.remaining=duration;
auto task=Actual::PhaseDescription(&mode,true);
assert(has(task,"Waiting for a conscious defender")&&!has(task,"interrupted"));
assert(has(task,std::to_string(duration)+" seconds")&&has(task,"Leaving resets progress"));
auto guide=Actual::PhaseDescription(&mode,false);
assert(has(guide,"Waiting for")&&!has(guide,"interrupted"));
}
mode.feedback=ERR_RadioFeedback::TRANSMITTING;mode.occupied=true;mode.remaining=179;
auto active=Actual::PhaseDescription(&mode,true);
assert(has(active,"up to 180 seconds remaining")&&!has(active,"Waiting")&&!has(active,"interrupted"));
assert(has(Actual::PhaseDescription(&mode,false),"Transmission in progress"));
mode.feedback=ERR_RadioFeedback::INTERRUPTED;mode.occupied=false;mode.remaining=180;
auto interrupted=Actual::PhaseDescription(&mode,true);
assert(has(interrupted,"Transmission interrupted")&&has(interrupted,"180 seconds"));
assert(!has(interrupted,"Waiting")&&has(Actual::PhaseDescription(&mode,false),"Progress has reset"));
mode.feedback=ERR_RadioFeedback::TRANSMITTING;mode.occupied=true;mode.remaining=174;
assert(has(Actual::PhaseDescription(&mode,true),"up to 175 seconds remaining"));
// Occupancy remains the progress authority; WAITING only selects unoccupied copy.
mode.feedback=ERR_RadioFeedback::WAITING;
assert(has(Actual::PhaseDescription(&mode,true),"seconds remaining"));
mode.occupied=false;
assert(has(Actual::PhaseDescription(&mode,true),"Waiting for a conscious defender"));
mode.phase=ERR_Phase::EXTRACTION;assert(Actual::PhaseDescription(&mode,true)=="extraction");
mode.phase=ERR_Phase::VICTORY;assert(Actual::PhaseDescription(&mode,true).empty());
mode.phase=ERR_Phase::DEFEAT;assert(Actual::PhaseDescription(&mode,true).empty());
}
''')
print('PASS: actual initial WAITING task/guide copy, configured duration, unchanged interruption and rounded progress; native task assertion pending')
@@ -0,0 +1,94 @@
"""Run actual shared objective selection and map grid methods; no native UI claim."""
from policy_harness import ROOT, body, run
from pathlib import Path
import os
source = Path(os.environ.get('ERR_OBJECTIVE_TEXT_SOURCE', ROOT / 'addons/core/Scripts/Game/ERR/00_Core/ERR_ObjectiveText.c'))
s = body(source, 'protected static string PhaseDescription(')
s = s.replace('protected ', '').replace('ERR_GameMode mode,', 'ERR_GameMode* mode,')
s = s.replace('ERR_Phase phase', 'int phase').replace('mode.', 'mode->')
s = s.replace('array<ERR_LocationComponent>', 'std::vector<ERR_LocationComponent*>')
s = s.replace('foreach (ERR_LocationComponent location : locations)', 'for (ERR_LocationComponent* location : locations)')
s = s.replace('location.', 'location->').replace('null', 'nullptr')
s = s.replace('bool roughRadio;', 'bool roughRadio = false;') # Enforce default initialization
s = s.replace('.IsEmpty()', '.empty()').replace('string.Format(', 'Format(')
for scope in ('ERR_Phase', 'ERR_LocationKind', 'ERR_RadioFeedback', 'Math'):
s = s.replace(scope + '.', scope + '::')
coordinate = body(ROOT / 'addons/core/Scripts/Game/ERR/00_Core/ERR_DiscoveryMap.c', 'static int Coordinate(').replace('Math.', 'Math::')
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <cmath>
using string = std::string;
namespace ERR_RadioFeedback {enum {INACTIVE,WAITING,TRANSMITTING,INTERRUPTED};}
namespace ERR_Phase {enum {PREPARING,PRISON,RADIO_SEARCH,RADIO_DEFENCE,EXTRACTION,VICTORY,DEFEAT};}
namespace ERR_LocationKind {enum {PRISON,RADIO,EXTRACTION,OUTPOST,DEPOT};}
namespace Math {float Ceil(float v){return std::ceil(v);}float Floor(float v){return std::floor(v);}float Round(float v){return std::round(v);}}
struct ERR_LocationComponent {int kind,knowledge;int Kind(){return kind;}int Knowledge(){return knowledge;}};
struct ERR_GameMode {
int startMode=0;int StartMode(){return startMode;}
int phase=ERR_Phase::RADIO_SEARCH;string error;bool occupied=false;
std::vector<ERR_LocationComponent*> locations;
int Phase(){return phase;}string PreparationError(){return error;}
void Locations(std::vector<ERR_LocationComponent*>& result){result=locations;}
int RadioFeedback(){return 7;}int DefenceSecondsRemaining(){return 30;}
bool DefenceOccupied(){return occupied;}float EscapeRadius(){return 9000;}
};
string Format(string value,int){return value;}
string RadioFeedbackMessage(int value){assert(value==7);return "radio feedback boundary";}
string Extraction(float value){assert(value==9000);return "extraction boundary";}
struct Actual {
''' + s + '\n' + coordinate + r'''
};
bool has(string text,string part){return text.find(part)!=string::npos;}
int main(){
ERR_GameMode mode;ERR_LocationComponent radio{ERR_LocationKind::RADIO,0};
mode.locations={&radio};
string unknown=Actual::PhaseDescription(&mode,true);
assert(has(unknown,"Choose either marked clue site")&&!has(unknown,"Intel recovered"));
mode.startMode=1;string crash=Actual::PhaseDescription(&mode,true);
assert(has(crash,"salvage backpack at the crash site")&&has(crash,unknown)&&!has(crash,"inside the prison"));
assert(Actual::PhaseDescription(&mode,false)==crash);mode.startMode=0;
radio.knowledge=1;
string rough=Actual::PhaseDescription(&mode,true);
assert(rough!=unknown&&has(rough,"Intel recovered")&&has(rough,"question mark"));
assert(!has(rough,"search square")&&has(rough,"different intelligence folder"));
assert(has(rough,"already recovered cannot provide another clue"));
assert(Actual::PhaseDescription(&mode,false)==rough); // Same task and guide copy.
for(int i=0;i<10;i++) assert(Actual::PhaseDescription(&mode,true)==rough);
radio.knowledge=2;
string exact=Actual::PhaseDescription(&mode,true);
assert(exact!=rough&&has(exact,"identified on your map")&&has(exact,"Contact extraction HQ"));
ERR_LocationComponent roughRadio{ERR_LocationKind::RADIO,1};
ERR_LocationComponent depot{ERR_LocationKind::DEPOT,1};
mode.locations={nullptr,&roughRadio,&depot,&radio};
assert(Actual::PhaseDescription(&mode,true)==exact);
mode.locations={&radio,&depot,&roughRadio,nullptr};
assert(Actual::PhaseDescription(&mode,true)==exact);
radio.knowledge=0;mode.locations={&depot,nullptr,&radio};
assert(Actual::PhaseDescription(&mode,true)==unknown);
mode.locations={};assert(Actual::PhaseDescription(&mode,true)==unknown);
mode.locations={&roughRadio};assert(Actual::PhaseDescription(&mode,true)==rough);
// Exact production grid dimensions agree with the player-facing 500 m claim.
assert(Actual::Coordinate(6500.0f,1)==6750);
assert(Actual::Coordinate(6999.9f,1)==6750);
assert(Actual::Coordinate(7000.0f,1)==7250);
assert(Actual::Coordinate(-.1f,1)==-250);
assert(Actual::Coordinate(6501.4f,2)==6501);
assert(Actual::PhaseDescription(nullptr,true).empty());
mode.phase=ERR_Phase::PREPARING;assert(has(Actual::PhaseDescription(&mode,true),"Preparing mission"));
mode.error="bad";assert(has(Actual::PhaseDescription(&mode,true),"could not start"));
mode.phase=ERR_Phase::PRISON;assert(has(Actual::PhaseDescription(&mode,true),"Pick the gate lock"));
mode.phase=ERR_Phase::RADIO_DEFENCE;assert(has(Actual::PhaseDescription(&mode,true),"interrupted"));
mode.occupied=true;assert(has(Actual::PhaseDescription(&mode,true),"in progress"));
assert(Actual::PhaseDescription(&mode,false)=="radio feedback boundary");
mode.phase=ERR_Phase::EXTRACTION;assert(Actual::PhaseDescription(&mode,true)=="extraction boundary");
mode.phase=ERR_Phase::VICTORY;assert(Actual::PhaseDescription(&mode,true).empty());
mode.phase=ERR_Phase::DEFEAT;assert(Actual::PhaseDescription(&mode,true).empty());
}
''')
print('PASS: actual rough-intel objective selection, exact precedence, source grid dimensions and unchanged other-phase dispatch; native UI pending')
assert "kilometres from the starting area:" in body(source,"static string Extraction(")
assert "kilometres from the prison:" not in body(source,"static string Extraction(")
@@ -0,0 +1,149 @@
from pathlib import Path
import importlib.util,json,tempfile,os,subprocess,sys
BASE=Path(os.environ.get("ERR_CONVOY_SETTINGS_ROOT", Path(__file__).resolve().parents[1]))
from policy_harness import ROOT,body,run
spec=importlib.util.spec_from_file_location('import_settings',BASE/'tools/import_settings.py')
m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
result,deferred=m.merge({'configVersion':1,'reportIntervalSeconds':20.0,'supportSquadLimit':2,'responseRadiusMetres':1500,'convoyCount':3},{'aiIntelChancePercent':0,'intelRevealChancePercent':100})
assert result==dict(startMode=0,keepWeaponOnUnconscious=1,reportIntervalSeconds=20,supportSquadLimit=2,responseRadiusMetres=1500,aiIntelChancePercent=0,intelRevealChancePercent=100,radioDefenceSeconds=180,startTimeMinutes=840,timeMultiplier=1,startDateCode=0,weatherProfile=0,viewDistanceMetres=0,automaticEquipmentChancePercent=25,antiAirChancePercent=7,antiPersonnelMineChancePercent=60,automaticConvoyEnabled=1,convoyCount=3,heliTimerMinutes=15)
assert deferred==[]
for count in (0,1,3,20):
values,notes=m.merge(ai={'convoyCount':count})
assert values['automaticConvoyEnabled']==int(count>0)
assert values['convoyCount']==count and not notes
for invalid in (-1,21,True,False,None,'1',1.5,float('nan'),float('inf')):
try:m.merge(ai={'convoyCount':invalid})
except ValueError:pass
else:raise AssertionError(invalid)
try:m.merge(ai={'convoyCount':0,'automaticConvoyEnabled':1})
except ValueError:pass
else:raise AssertionError('Ambiguous old/new settings accepted')
for old,percent in [(0,0),(1,100),(15,7),(37,3),(10000,0)]:
assert m.merge(ai={'stingerChanceOneIn':old})[0]['antiAirChancePercent']==percent
assert m.merge(ai={'stingerChanceOneIn':15,'antiAirChancePercent':100})[0]['antiAirChancePercent']==100
for invalid in (-1,10001,True,None,'15',1.5,float('nan')):
try:m.merge(ai={'stingerChanceOneIn':invalid})
except ValueError:pass
else:raise AssertionError(invalid)
assert m.merge(ai={'stingerChanceOneIn':0})[0]['antiAirChancePercent']==0
assert m.merge()[0]=={k:v[0] for k,v in m.SCHEMA.items()}
for key,(_,lo,hi) in m.SCHEMA.items():
if key not in m.AI_KEYS and key not in m.INTEL_KEYS:continue
for invalid in (lo-1,hi+1,True,False,None,'10',lo+0.5,float('nan'),float('inf')):
try:m.merge(**({'ai':{key:invalid}} if key in m.AI_KEYS else {'intel':{key:invalid}}))
except ValueError:pass
else:raise AssertionError((key,invalid))
with tempfile.TemporaryDirectory() as tmp:
dest=Path(tmp)/'rereintroduced.json'
m.write_new(dest,json.dumps(result));before=dest.read_bytes()
try:m.write_new(dest,'overwrite')
except FileExistsError:pass
else:raise AssertionError('Existing destination overwritten')
assert dest.read_bytes()==before
assert sorted(p.name for p in Path(tmp).iterdir())==['rereintroduced.json']
legacy=Path(tmp)/'legacy-ai.json';legacy.write_text('{"convoyCount":3}');original=legacy.read_bytes()
preview=subprocess.run([sys.executable,str(BASE/'tools/import_settings.py'),'--ai',str(legacy)],capture_output=True,text=True,check=True)
assert json.loads(preview.stdout)['automaticConvoyEnabled']==1
assert json.loads(preview.stdout)['convoyCount']==3 and not preview.stderr and legacy.read_bytes()==original
# Test the actual runtime validator against the migration schema at each boundary.
validator=body(BASE/'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c','static bool Valid(').replace('string key','std::string key')
checks=[]
for key,(_,lo,hi) in m.SCHEMA.items():
for value,expected in [(lo-1,False),(lo,True),(hi,True),(hi+1,False)]:
checks.append(f'assert(Valid("{key}",{value})=={str(expected).lower()});')
run('#include <cassert>\n#include <string>\n'+validator+'\nint main(){'+''.join(checks)+'assert(!Valid("unknown",1));}\n')
print('PASS: legacy values preserved, unsupported fields reported, invalid data rejected, existing files protected, runtime bounds aligned')
# Actual settings transaction: failures during final file promotion must not
# poison the next round when there was no previous settings file.
source = Path(os.environ.get('ERR_SETTINGS_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Settings.c'))
method = body(source, 'bool Set(')
method = (method.replace('string ', 'std::string ').replace('Replication.', 'Replication::')
.replace('FileIO.', 'FileIO::').replace('ERR_ProfileConfig.', 'ERR_ProfileConfig::').replace('foreach (', 'for (')
.replace('JsonSaveContext json = new JsonSaveContext();', 'JsonSaveContext json;')
.replace('map<string, int> ', 'Values ').replace('map<std::string, int> ', 'Values '))
run(r'''
#include <cassert>
#include <map>
#include <string>
#include <vector>
using string = std::string;
struct Values {
std::map<string,int> data;
int Get(string key){return data.at(key);}
void Remove(string key){data.erase(key);}
void Set(string key,int value){data[key]=value;}
};
struct File {bool valid=true;Values values;};
std::map<string,File> disk;
int fault=0;
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace FileIO {
bool FileExists(string path){return disk.count(path);}
bool DeleteFile(string path){return disk.erase(path);}
bool CopyFile(string from,string to){
if(!disk.count(from))return false;
disk[to]=disk[from];
if(from=="settings.tmp"){
if(fault==1){disk[to].valid=false;return false;} // partial copy reports failure
if(fault==2)disk[to].valid=false; // copy succeeds, readback fails
if(fault==3)disk[to].values.data["radio"]=31; // valid but incorrect contents
}
return true;
}
}
struct JsonSaveContext {
File pending;
void EnableTypeDiscriminator(bool value){}
void WriteValue(string key,int value){pending.values.data[key]=value;}
bool SaveToFile(string path){disk[path]=pending;return true;}
};
// This fixture exercises the custom-path transaction; unified storage has its own fixture.
const string PATH="$profile:rereintroduced.json";
namespace ERR_ProfileConfig {
bool Exists(string path){return FileIO::FileExists(path);}
bool Save(string,JsonSaveContext&){assert(false);return false;}
}
struct Settings {
bool m_ReadOnly=false;
string m_Path="settings";
Values m_Values{{{"radio",180},{"limit",4}}},m_Resolved{{{"radio",60}}};
std::vector<string> Keys(){return {"radio","limit"};}
bool ValidSelection(string key,int value){return (key=="radio"&&value>=30&&value<=1800)||(key=="limit"&&value>=0&&value<=8);}
int Get(string key){return m_Values.Get(key);}
bool Read(string path,Values& values){
if(!disk.count(path)||!disk[path].valid)return false;
values=disk[path].values;return true;
}
bool Restart(){
Values loaded;
if(Read(m_Path,loaded)||Read(m_Path+".bak",loaded))m_Values=loaded;
else if(FileIO::FileExists(m_Path)||FileIO::FileExists(m_Path+".bak"))m_ReadOnly=true;
return !m_ReadOnly;
}
'''+method+r'''
};
int main(){
for(int injected : {1,2,3}){
// A failed first save must not install a partial or rejected file for restart.
disk.clear();fault=injected;Settings initial;
assert(!initial.Set("radio",300));
assert(initial.Get("radio")==180&&initial.m_Resolved.Get("radio")==60);
assert(!FileIO::FileExists("settings"));
Settings restarted;assert(restarted.Restart());assert(restarted.Get("radio")==180);
fault=0;assert(restarted.Set("radio",450));assert(restarted.Get("radio")==450);
Settings persisted;assert(persisted.Restart()&&persisted.Get("radio")==450);
// An existing valid save is restored from the newly captured backup.
disk.clear();fault=0;Settings saved;assert(saved.Set("radio",600));
fault=injected;assert(!saved.Set("radio",900));assert(saved.Get("radio")==600);
Settings restored;assert(restored.Restart()&&restored.Get("radio")==600);
// A preexisting fallback backup remains authoritative when primary is absent.
disk.clear();disk["settings.bak"]={true,Values{{{"radio",750},{"limit",4}}}};
Settings fallback;assert(fallback.Restart()&&fallback.Get("radio")==750);
assert(!fallback.Set("radio",900));
Settings recovered;assert(recovered.Restart()&&recovered.Get("radio")==750);
}
}
''')
print('PASS: failed initial settings promotion leaves defaults writable after restart; prior/fallback saves survive copy, readback and mismatch faults')
@@ -0,0 +1,81 @@
"""Actual start setting parse/save and admin command bodies; native IO/UI are doubled.
Frozen-mode lifecycle itself is tested by the separate core start fixture.
"""
import os,re
from pathlib import Path
from policy_harness import ROOT,body,run
BASE=Path(os.environ.get('ERR_START_SETTINGS_ROOT',ROOT));CORE=BASE/'addons/core/Scripts/Game/ERR/00_Core'
S=Path(os.environ.get('ERR_SETTINGS_SOURCE', CORE/'ERR_Settings.c'));A=CORE/'ERR_AdminService.c';M=CORE/'ERR_AdminMenu.c'
def cpp(s):
s=s.replace('protected ','').replace('out int ','int& ').replace('out map<string, int> values','Values& values').replace('new map<string, int>()','Values()').replace('map<string, int>','Values')
s=s.replace('array<string> parts','Array<string> parts').replace('array<string> replies','Array<string>& replies').replace('array<string>','Array<string>').replace('foreach (','for (')
s=s.replace('JsonLoadContext json = new JsonLoadContext();','JsonLoadContext json;').replace('JsonSaveContext json = new JsonSaveContext();','JsonSaveContext json;').replace('JsonLoadContext json,','JsonLoadContext& json,')
s=s.replace('ERR_GameMode mode','Mode* mode').replace('mode.','mode->').replace('Settings().','Settings()->').replace('Math.Round','std::round')
for n in ['ERR_Settings','ERR_ProfileConfig','ERR_CommandArguments','ERR_AdminMenuFields','ERR_TimeService','ERR_EnvironmentService','Replication','FileIO']:s=s.replace(n+'.',n+'::')
s=s.replace('"0123456789".IndexOf','string("0123456789").IndexOf')
for n in ['value','saved','current']:s=s.replace(n+'.ToString()','string(std::to_string('+n+'))')
return s
settings='\n'.join(cpp(body(S,sig)) for sig in ['static array<string> Keys(','static bool Valid(','static bool Randomizable(','static bool ValidSelection(','static string SelectionText(','static int AddedDefault(','protected bool Read(','static bool Parse(','bool Set('])
args='\n'.join(cpp(body(A,sig)) for sig in ['static bool Integer(','static bool Selection('])
fields='\n'.join(cpp(body(M,sig)) for sig in ['static string Display(','static string StartSummary('])
command=cpp(body(A,'protected bool StartCommand('))
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <map>
#include <cmath>
#include <algorithm>
struct string:std::string{using std::string::string;string(std::string s):std::string(s){}bool IsEmpty(){return empty();}int Length(){return size();}int ToInt(){return std::stoi(*this);}int IndexOf(string x){auto n=find(x);return n==npos?-1:int(n);}string Substring(int a,int n){return substr(a,n);}};
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}};
struct Values{std::map<string,int> data;int Get(string k){return data.at(k);}void Set(string k,int v){data[k]=v;}void Remove(string k){data.erase(k);}};
std::map<string,std::map<string,float>> disk;bool failSave=false;
struct JsonLoadContext{std::map<string,float> data;bool DoesKeyExist(string k){return data.count(k);}bool ReadValue(string k,float& v){if(!data.count(k))return false;v=data[k];return true;}};
struct JsonSaveContext{std::map<string,float> data;void EnableTypeDiscriminator(bool){}void WriteValue(string k,int v){data[k]=v;}bool SaveToFile(string p){if(failSave)return false;disk[p]=data;return true;}};
namespace ERR_ProfileConfig{bool Load(string p,JsonLoadContext& j){if(!disk.count(p))return false;j.data=disk[p];return true;}bool Save(string p,JsonSaveContext&j){return j.SaveToFile(p);}bool Exists(string p){return disk.count(p);}}
namespace FileIO{bool CopyFile(string a,string b){if(!disk.count(a))return false;disk[b]=disk[a];return true;}void DeleteFile(string p){disk.erase(p);}}
namespace Replication{bool server=true;bool IsServer(){return server;}}
struct ERR_Settings{static constexpr auto PATH="settings";string m_Path=PATH;bool m_ReadOnly=false;Values m_Values,m_Resolved;int Get(string k){return m_Values.Get(k);}
'''+settings+r'''
};
namespace ERR_TimeService{bool ParseClock(string,int&){return false;}string Clock(int){return "clock";}}
namespace ERR_EnvironmentService{bool ParseDate(string,int&){return false;}bool ParseProfile(string,int&){return false;}string Date(int){return "date";}string ProfileName(int){return "weather";}}
struct ERR_CommandArguments{
'''+args+r'''
};
struct ERR_AdminMenuFields{
'''+fields+r'''
};
struct Mode{ERR_Settings* settings;int frozen;ERR_Settings*Settings(){return settings;}int StartMode(){return frozen;}};
struct Admin{
'''+command+r'''
};
int main(){
assert(ERR_Settings::AddedDefault("startMode")==0);
assert(!ERR_Settings::Randomizable("startMode")&&!ERR_Settings::ValidSelection("startMode",-1)&&!ERR_Settings::ValidSelection("startMode",-2));
disk["old"]={{"reportIntervalSeconds",10},{"supportSquadLimit",4},{"responseRadiusMetres",2000},{"aiIntelChancePercent",60},{"intelRevealChancePercent",15},{"radioDefenceSeconds",180}};
ERR_Settings settings;assert(settings.Read("old",settings.m_Values)&&settings.Get("startMode")==0);
for(float bad:{-1.f,-2.f,2.f,.5f}){disk["bad"]=disk["old"];disk["bad"]["startMode"]=bad;Values ignored;assert(!settings.Read("bad",ignored));}
Mode mode{&settings,0};Admin admin;Array<string> replies;
assert(!admin.StartCommand(&mode,{"mission","radio","show"},replies));
assert(admin.StartCommand(&mode,{"mission","start","set","crash"},replies));
assert(settings.Get("startMode")==1&&mode.StartMode()==0&&disk["settings"]["startMode"]==1);
assert(replies.back().find("Next start: Crash | Current round: Prison")!=string::npos);
assert(replies.back().find("Saved for next round")!=string::npos);
ERR_Settings restarted;assert(restarted.Read("settings",restarted.m_Values));Mode next{&restarted,restarted.Get("startMode")};assert(next.StartMode()==1);
auto saved=disk["settings"];failSave=true;admin.StartCommand(&mode,{"mission","start","set","prison"},replies);assert(settings.Get("startMode")==1&&disk["settings"]==saved&&mode.StartMode()==0);failSave=false;
for(string bad:{"random","random-day","2","-1","0.5","crashjunk"}){admin.StartCommand(&mode,{"mission","start","set",bad},replies);assert(settings.Get("startMode")==1&&disk["settings"]==saved);}
admin.StartCommand(&mode,{"mission","start","set","prison","extra"},replies);assert(settings.Get("startMode")==1);
Replication::server=false;admin.StartCommand(&mode,{"mission","start","set","prison"},replies);assert(settings.Get("startMode")==1);Replication::server=true;
settings.m_ReadOnly=true;admin.StartCommand(&mode,{"mission","start","set","prison"},replies);assert(settings.Get("startMode")==1);settings.m_ReadOnly=false;
admin.StartCommand(&mode,{"mission","start","set","0"},replies);assert(settings.Get("startMode")==0&&mode.StartMode()==0);
admin.StartCommand(&next,{"mission","start","show"},replies);assert(replies.back()=="Next start: Crash | Current round: Crash");
}
''')
assert 'm_Values.Set("startMode", AddedDefault("startMode"));' in S.read_text()
execute=body(A,'void Execute(');assert execute.index('if (!authorized)')<execute.index('StartCommand(mode, parts, replies)')
assert 'StartSummary(' in body(M,'void ReceiveSnapshot(')
assert 'm_Mode.StartMode()' in body(M,'void ReceiveSnapshot(')
assert 'ERR_Settings.Randomizable(key)' in body(M,'override void OnMenuOpen(')
assert 'mission start show|set prison|crash' in body(A,'protected void Help(')
print('PASS: actual start defaults/parse/save, failure retention and authorized command wiring; current/next feedback, aliases and no random choice; native UI and core mode freezing are separate')
@@ -0,0 +1,60 @@
"""Execute the production editor handover; engine delivery is checked separately."""
from policy_harness import ROOT, body, run
core = ROOT / 'addons/core/Scripts/Game/ERR/00_Core'
method = body(core / 'ERR_TerrainTakeoverManagers.c', 'void ERR_SetTerrainModeEvents(')
method = (method.replace('SCR_BaseGameMode mode', 'SCR_BaseGameMode* mode')
.replace('mode.', 'mode->').replace('Replication.', 'Replication::'))
run(r'''
#include <cassert>
#include <vector>
#include <algorithm>
namespace Replication {bool server=true; bool IsServer(){return server;}}
void Print(const char*){}
enum {OnPlayerRegistered=1,OnPlayerDisconnected,OnPlayerSpawn,OnPlayerKilled,OnPlayerDeleted,OnPlayerRoleChange};
struct Invoker {
std::vector<int> callbacks{99};
void Remove(int cb){callbacks.erase(std::remove(callbacks.begin(),callbacks.end(),cb),callbacks.end());}
void Insert(int cb){callbacks.push_back(cb);}
int Count(int cb){return std::count(callbacks.begin(),callbacks.end(),cb);}
};
struct SCR_BaseGameMode {
Invoker events[6];
Invoker& GetOnPlayerRegistered(){return events[0];}
Invoker& GetOnPostCompPlayerDisconnected(){return events[1];}
Invoker& GetOnPlayerSpawned(){return events[2];}
Invoker& GetOnPlayerKilled(){return events[3];}
Invoker& GetOnPlayerDeleted(){return events[4];}
Invoker& GetOnPlayerRoleChange(){return events[5];}
};
''' + method + r'''
void check(SCR_BaseGameMode& mode,int expected){
for(int i=0;i<6;i++){
assert(mode.events[i].Count(i+1)==expected);
assert(mode.events[i].Count(99)==1); // Preserve unrelated systems' listeners.
}
}
int main(){
SCR_BaseGameMode original,replacement;
ERR_SetTerrainModeEvents(nullptr,true);
ERR_SetTerrainModeEvents(&original,true);check(original,1);
ERR_SetTerrainModeEvents(&original,false);check(original,0);
ERR_SetTerrainModeEvents(&replacement,true);check(replacement,1);
ERR_SetTerrainModeEvents(&replacement,true);check(replacement,1);
Replication::server=false;
ERR_SetTerrainModeEvents(&replacement,false);check(replacement,1);
ERR_SetTerrainModeEvents(&original,true);check(original,0);
Replication::server=true;
ERR_SetTerrainModeEvents(&replacement,false);check(replacement,0);
ERR_SetTerrainModeEvents(&replacement,false);check(replacement,0);
ERR_SetTerrainModeEvents(&replacement,true);check(replacement,1);
}
''')
# Subscription transfer must precede deleting the old owner and replaying held admissions.
takeover = core / 'ERR_TerrainTakeover.c'
drain = body(takeover, 'protected void ERR_DrainTerrainMode(')
create = body(takeover, 'protected void ERR_CreateTerrainMode(')
assert drain.index('ERR_SetTerrainModeEvents(m_ERRTerrainOriginal, false)') < drain.index('DeleteEntityAndChildren')
assert create.index('ERR_SetTerrainModeEvents(mode, true)') < create.index('ERR_StartTerrainTakeover') < create.index('mode.OnPlayerAuditSuccess')
print('PASS: six editor listeners transferred idempotently, unrelated handlers retained, authority guard and lifecycle ordering')