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,22 @@
check: /tmp/err-policy-phq1o06u/check.cpp:23: ERR_NativePatrolProbe::ERR_NativePatrolProbe(Fixture*): Assertion `owner->phase==2&&owner->active&&owner->unlocks==1' failed.
PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines
PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths
PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged
Traceback (most recent call last):
File "/tmp/erri-patrol-start-1659/tests/test_native_memory_deadlines.py", line 133, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<20 lines>...
}
^
'''.replace('BRANCH',patrol_branch))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-phq1o06u/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,120 @@
"""Actual native-fixture startup/deadline gate, engine boundaries doubled."""
import os,sys,re
from pathlib import Path
REPO=Path(os.environ.get('ERR_REPO', Path(__file__).resolve().parents[1]))
sys.path.insert(0,str(REPO/'tests'))
from policy_harness import body,run
p=Path(os.environ.get('ERR_MEMORY_PROBE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c'))
s=body(p,'int Tick(ERR_HQ hq, float now)')
# Execute the unchanged deadline/startup prefix; payload/native radio assertions
# after this gate remain the full fixture's responsibility.
s=s[:s.index(' if (!m_ValueChecked)')]+ ' }\n return 1;\n }'
s=s.replace('ERR_HQ hq','ERR_HQ* hq').replace('hq.','hq->')
run(r'''
#include <cassert>
#include <string>
using string = std::string;
struct CLI {string outcome="hq-memory"; void GetCLIParam(string,string& value){value=outcome;}} System;
struct ERR_HQ{bool ready=true;bool ERR_ProbeSquadsReady(){return ready;}};
struct Probe{bool enabled=true,m_Mode=true,m_Failed=false,m_AutoChecked=false;int m_Step=0,automatic=0,checks=0;float m_Deadline=0;bool Enabled(){return enabled;}void Check(bool,std::string){checks++;m_Failed=true;}int AutomaticConvoy(ERR_HQ*){if(automatic==1)m_AutoChecked=true;return automatic;}
METHOD
};
int main(){ERR_HQ hq;
Probe p;assert(p.Tick(&hq,1000)==0&&p.m_Deadline==151000);assert(p.Tick(&hq,130000)==0&&p.m_Deadline==151000);p.automatic=1;assert(p.Tick(&hq,145000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,200000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,265000)==1);assert(p.Tick(&hq,265001)==-1&&p.checks==1);
Probe missing;assert(missing.Tick(&hq,0)==0);assert(missing.Tick(&hq,150001)==-1&&missing.checks==1);
Probe refused;refused.Tick(&hq,0);refused.automatic=-1;assert(refused.Tick(&hq,40000)==-1&&refused.m_Deadline==150000&&!refused.m_AutoChecked);
Probe unavailable;hq.ready=false;assert(unavailable.Tick(&hq,0)==0);assert(unavailable.Tick(&hq,150001)==-1);hq.ready=true;
System.outcome="hq-awareness"; hq.ready=false; Probe awareness; assert(awareness.Tick(&hq,1000)==1 && !awareness.m_AutoChecked && awareness.m_Deadline==151000); assert(awareness.Tick(&hq,151001)==-1);
Probe invalid;invalid.enabled=false;assert(invalid.Tick(&hq,0)==-1&&invalid.m_Deadline==0);
}
'''.replace('METHOD',s))
smoke=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')).read_text()
assert 'if (m_ERRTestHQMemory) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 360000;' in smoke
assert 'if (m_ERRTestHQBody) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;' in smoke
# Execute the actual startup selector, so enlarged full-route budgets cannot
# accidentally extend a death/radio test or shorten an existing specialized run.
start=smoke.index('m_ERRTestLostHelicopter = System.GetCLIParam(')
end=smoke.index('OnStop().Insert(ERR_StopHQPatrol);',start)
selector=smoke[start:end].replace('System.GetCLIParam("errSmokeOutcome", outcome) && ', '')
selector=selector.replace('GetWorld().GetWorldTime()', 'now').replace('ERR_NativeConvoyCoverProbe.Enabled()', 'cover').replace('ERR_NativeMountedCourierProbe.Enabled()', 'courier').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance').replace('ERR_NativeMountedInterceptProbe.Enabled()', 'intercept')
flags=sorted(set(re.findall(r'\bm_ERRTest\w+',selector)))
run(r'''
#include <cassert>
#include <string>
using string = std::string;
float deadline(string outcome,float now,bool cover=false,bool courier=false,bool maintenance=false,bool intercept=false){
[[maybe_unused]] bool DECLARATIONS;
float m_ERRSmokeDeadline=0;
SELECTOR
return m_ERRSmokeDeadline;
}
int main(){
for(float now : {0.0f, 42000.0f}) {
for(string outcome : {"", "victory", "lost-helicopter"}) assert(deadline(outcome,now)==now+300000);
for(string outcome : {"escape-route", "normal-escape-route", "player-death", "radio-network", "hq-response", "map-ownership"}) assert(deadline(outcome,now)==now+150000);
assert(deadline("hq-memory",now)==now+360000);
assert(deadline("hq-body",now)==now+240000);
assert(deadline("convoy",now)==now+540000);
assert(deadline("convoy",now,false,false,false,true)==now+840000);
assert(deadline("supply-convoys",now)==now+840000);
assert(deadline("convoy",now,true)==now+840000);
assert(deadline("mounted-ammo",now)==now+330000);
assert(deadline("mounted-ammo",now,false,true)==now+750000);
assert(deadline("normal-escape-route",now,false,false,true)==now+330000);
}
}
'''.replace('DECLARATIONS', ','.join(flag+'=false' for flag in flags)).replace('SELECTOR',selector))
print('PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines')
# The new route diagnostic must leave the original administrator/reroll stress
# intact and may skip only those extras, never clue or radio progression gates.
route=body(Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')), 'bool ERR_CheckRouteExtras(float now)').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance')
run(r'''
#include <cassert>
#include <string>
struct Probe {
bool m_ERRTestEscapeRoute=false,rerollReady=false,maintenance=false,maintenanceReady=false;
int maintenanceCalls=0; bool ERR_RunMaintenance(float){maintenanceCalls++;return maintenanceReady;}
int adminResult=0,adminCalls=0,rerollCalls=0,failures=0;
int ERR_ProbeAdminMenu(float){adminCalls++;return adminResult;}
bool ERR_Check(bool result,std::string){if(!result)failures++;return result;}
bool ERR_CheckIntelReroll(float){rerollCalls++;return rerollReady;}
METHOD
};
int main(){
Probe route;route.m_ERRTestEscapeRoute=true;assert(route.ERR_CheckRouteExtras(100));
assert(route.adminCalls==0&&route.rerollCalls==0&&route.failures==0);
route.maintenance=true;assert(!route.ERR_CheckRouteExtras(101)&&route.maintenanceCalls==1);
route.maintenanceReady=true;assert(route.ERR_CheckRouteExtras(102)&&route.maintenanceCalls==2&&route.adminCalls==0&&route.rerollCalls==0);
Probe full;assert(!full.ERR_CheckRouteExtras(100)&&full.adminCalls==1&&full.rerollCalls==0);
full.adminResult=-1;assert(!full.ERR_CheckRouteExtras(200)&&full.failures==1&&full.rerollCalls==0);
full.adminResult=1;assert(!full.ERR_CheckRouteExtras(300)&&full.rerollCalls==1);
full.rerollReady=true;assert(full.ERR_CheckRouteExtras(400)&&full.rerollCalls==2);
}
'''.replace('METHOD',route))
assert 'if (!ERR_CheckRouteExtras(now)) return;' in smoke
assert 'if (!ERR_Check(m_ERRSmokeRadio.Knowledge() == 2,' in smoke
assert 'if (!ERR_Check(Phase() == ERR_Phase.EXTRACTION,' in smoke
print('PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths')
# Execute the actual scenario-selection expressions: normal route must retain
# the ordinary catalog AND random placement, while historical probes stay fixed.
scenario=(REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c').read_text()
conditions=[]
for variable in ('historicalFixture','fixture'):
line=next(line.strip() for line in scenario.splitlines() if line.strip().startswith('if (') and 'System.GetCLIParam("errSmokeOutcome", '+variable+')' in line)
expression=line[4:-1].replace('System.GetCLIParam("errSmokeOutcome", '+variable+')','supplied').replace(variable,'outcome')
conditions.append(expression)
run(r'''
#include <cassert>
#include <string>
using string=std::string;
bool legacy(bool supplied,string outcome,bool m_TerrainIndependent=false){return LEGACY;}
bool fixed(bool supplied,string outcome,bool m_TerrainIndependent=false){return FIXED;}
int main(){
for(string route : {"normal-escape-route","generated-sites","optional-site-skip"}) {assert(!legacy(true,route));assert(!fixed(true,route));}
for(string route : {"escape-route","victory","transport-survey"}) {assert(legacy(true,route));assert(fixed(true,route));}
assert(!legacy(false,"escape-route")&&!fixed(false,"escape-route"));
}
'''.replace('LEGACY',conditions[0]).replace('FIXED',conditions[1]))
print('PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged')
@@ -0,0 +1,18 @@
{
"conclusion": "Fixture starts patrol movement observation during intentional production prison freeze. Correct fixture through actual canonical UnlockPrison before creating movement probe. Not a demonstrated site-patrol movement or search-radius production defect.",
"evidence": [
"console has phase 0->1 and no RADIO_SEARCH entry before timeout",
"ERR_PrisonAI.ERR_HoldForPrison disables mission enemies through PRISON; callback releases on RADIO_SEARCH",
"original patrol-hq step0 branch creates ERR_NativePatrolProbe before unlock"
],
"change": "One WORKBENCH branch adds successful UnlockPrison(m_ERRSmokePlayer) and resulting RADIO_SEARCH check before allocation; failure returns without movement probe.",
"unchanged": [
"production prison AI/gameplay/mission rules",
"180000ms movement deadline",
"five ordered completions and >=8m displacement",
"native queue/route",
"existing observer damage protection",
"420000ms overall fixture deadline"
],
"scope": "New test executes exact branch; native unlock/damage/constructor boundaries doubled. Existing actual prison hold/release and canonical progression tests pass. Current native outcome remains unproven until root rerun. Earlier September passes are historical, not evidence against current opening policy."
}
@@ -0,0 +1,60 @@
--- a/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
@@ -698,6 +698,7 @@
if (m_ERRTestPatrolHQ)
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
+ if (!ERR_Check(UnlockPrison(m_ERRSmokePlayer) && Phase() == ERR_Phase.RADIO_SEARCH, "patrol HQ fixture leaves prison through canonical admission before observing movement")) return;
m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this);
m_ERRSmokeStep = 130;
return;
--- a/tests/test_native_memory_deadlines.py
+++ b/tests/test_native_memory_deadlines.py
@@ -52,6 +52,7 @@
for(float now : {0.0f, 42000.0f}) {
for(string outcome : {"", "victory", "lost-helicopter"}) assert(deadline(outcome,now)==now+300000);
for(string outcome : {"escape-route", "normal-escape-route", "player-death", "radio-network", "hq-response", "map-ownership"}) assert(deadline(outcome,now)==now+150000);
+ assert(deadline("patrol-hq",now)==now+420000);
assert(deadline("hq-memory",now)==now+360000);
assert(deadline("hq-body",now)==now+240000);
assert(deadline("convoy",now)==now+540000);
@@ -118,3 +119,39 @@
}
'''.replace('LEGACY',conditions[0]).replace('FIXED',conditions[1]))
print('PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged')
+
+# Execute the actual patrol-hq admission branch. The existing production
+# prison-AI/progression tests cover canonical UnlockPrison and phase callbacks;
+# here native damage, allocation and unlock boundaries are explicit doubles.
+smoke_path=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c'))
+smoke_tick=body(smoke_path,'protected void ERR_SmokeTick(')
+a=smoke_tick.index('if (m_ERRTestPatrolHQ)'); b=smoke_tick.index('{',a); i=b+1; depth=1
+while depth:
+ depth+=(smoke_tick[i]=='{')-(smoke_tick[i]=='}');i+=1
+patrol_branch=smoke_tick[a:i]
+patrol_branch=patrol_branch.replace('DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);','ProtectOriginal(m_ERRSmokePlayer);').replace('ERR_Phase.RADIO_SEARCH','2')
+run(r'''
+#include <cassert>
+#include <string>
+struct Fixture;
+struct ERR_NativePatrolProbe {explicit ERR_NativePatrolProbe(Fixture* owner);};
+struct Fixture {
+ bool m_ERRTestPatrolHQ=true,allowed=true,wrongPhase=false,active=false;
+ int m_ERRSmokePlayer=7,phase=1,unlocks=0,failures=0,allocations=0,protections=0,m_ERRSmokeStep=0;
+ ERR_NativePatrolProbe* m_ERRHQPatrolMovement=nullptr;
+ ~Fixture(){delete m_ERRHQPatrolMovement;}
+ void ProtectOriginal(int actor){assert(actor==7);protections++;}
+ bool UnlockPrison(int actor){assert(actor==7);unlocks++;if(!allowed)return false;if(!wrongPhase){phase=2;active=true;}return true;}
+ int Phase(){return phase;}
+ bool ERR_Check(bool ok,std::string){if(!ok)failures++;return ok;}
+ void Start(){BRANCH}
+};
+ERR_NativePatrolProbe::ERR_NativePatrolProbe(Fixture* owner){assert(owner->phase==2&&owner->active&&owner->unlocks==1);owner->allocations++;}
+int main(){
+ Fixture healthy;healthy.Start();assert(healthy.m_ERRSmokeStep==130&&healthy.allocations==1&&healthy.failures==0&&healthy.protections==1);
+ Fixture refused;refused.allowed=false;refused.Start();assert(refused.unlocks==1&&refused.failures==1&&refused.allocations==0&&refused.m_ERRSmokeStep==0&&!refused.active);
+ Fixture wrong;wrong.wrongPhase=true;wrong.Start();assert(wrong.unlocks==1&&wrong.failures==1&&wrong.allocations==0&&wrong.m_ERRSmokeStep==0);
+ Fixture other;other.m_ERRTestPatrolHQ=false;other.Start();assert(other.unlocks==0&&other.allocations==0&&other.protections==0&&other.m_ERRSmokeStep==0);
+}
+'''.replace('BRANCH',patrol_branch))
+print('PASS: actual patrol fixture leaves prison before movement observation and refuses failed transition')
@@ -0,0 +1,4 @@
PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines
PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths
PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged
PASS: actual patrol fixture leaves prison before movement observation and refuses failed transition
@@ -0,0 +1,44 @@
{
"approved": true,
"blockers": [],
"review_mode": "Read-only independent exact source/test/log inspection; no compiler or native rerun.",
"patch_sha256": "448bee9abebcb2c0a592715abb0b37a6da2ffe5ff5ffa3bb81d9571aa3503540",
"source_manifest_sha256": "1c29e6ea4382196591f3a4a3a6854554b870d30b2bf191d43e455f89f45c0105",
"files": {
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c": {
"baseline_sha256": "595fce681ec74e0d3ffe7a29c527c1551874469108ded479bed72a0fcf6c732e",
"proposed_sha256": "7d8dcb96efeeeb44f46f604f96bae337d3200fd15347a8019259d21a94f67d81"
},
"tests/test_native_memory_deadlines.py": {
"baseline_sha256": "f2d3f0feda862318ab936031487743218a8c8241d192b34e2b1ab63a5aa2cc48",
"proposed_sha256": "597d4f430a4fb9bd684958c60d44d98703410b9bab629290e21f857c001c7112"
}
},
"checks": [
"Only one WORKBENCH patrol-hq admission line changes: canonical original admitted observer unlock and resulting RADIO_SEARCH guard before probe allocation. Existing damage protection remains before transition.",
"Actual UnlockPrison validates server/current canonical fit player/faction/proximity and transitions only from PRISON. Nonterminal phase callback does not trigger smoke completion. Production prisoner AI policy releases only on RADIO_SEARCH and stays unchanged.",
"Actual extracted branch test covers successful transition before constructor, refused unlock, wrong resulting phase, and unselected fixture with no work. Baseline compiled then fails premature constructor phase assertion; corrected author program green.",
"Existing prison-AI and progression identity tests author logs pass. Exact180000ms native movement bound, five ordered completions,>=8m displacement,420000ms fixture lifetime and same original actors/route/probe remain unchanged.",
"Failed native evidence has only phase0->1 before180s route traversal failure; current fixture bypassed required unlock. This is fixture setup correction, not demonstrated production movement failure."
],
"limitations": [
"No native validation of corrected fixture yet; native actor movement and final patrol HQ preemption/resumption remain pending.",
"Native unlock/damage/allocation behavior is doubled in focused branch test; separate unchanged production method tests and source establish policy linkage."
],
"evidence_sha256": {
"green.log": "f0f1259c73095bf4ce66107a82bf3363d1d30a1bae1d14000c4ad2cd0629e3fb",
"baseline-red.log": "13065e36fc08d14024660a7e962250d5a73222d7be2136e50a0b3c96404b088d",
"prison-ai-green.log": "63ef1dee605d5ea050f379a0fdfd398c5593126bc0db3d7cb48e12e1151c1523",
"progression-green.log": "9e3c909c4acd90a8094c5bebf05b411775b0f676616f5bb2e5e8015b42c68a4e",
"finding.json": "3536550b06d42b21525e866889f9f810f64eecb61e836cd8d3eab64ac38f49bf"
},
"unchanged_production": {
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c": "8709753e86dc2ca777183f08cb8fbb9851e0c0de495ca01520ebcb1b3dfa59b9",
"addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c": "89a29c03ba225acbbb9fa3c58a721aced91a77bc0c77046d697005a6d7c48bb9",
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePatrolProbe.c": "5a2af71cc4cfccc7dd7c23584398a38290eba1a8894cd46f6967252ff3a7087c"
},
"native_failed_evidence": {
"report.json": "d8fc5e1cd3f946636f91ffd46226ca4d0217b47a11c3a38d55ff438d8076cd8b",
"console.log.txt": "d08c221dee2bff342e5c512a0284fef6ef6dbbc0ae9c978b19945438827a5080"
}
}
@@ -0,0 +1 @@
PASS: production prison AI hold/release, repeat activation, authority/faction/player exclusion, dead actor and stop cleanup
@@ -0,0 +1,2 @@
PASS: actual gate and pre-inventory document guards reject noncanonical/faction/life/disconnect/server mismatches; canonical reconnect and unconscious carried intel preserved
PASS: WB-only native fault injection restores both body mutations before checks/returns and requires eligible held-document negative/positive proof; runtime pending
@@ -0,0 +1,39 @@
{
"files": {
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c": {
"baseline_sha256": "595fce681ec74e0d3ffe7a29c527c1551874469108ded479bed72a0fcf6c732e",
"proposed_sha256": "7d8dcb96efeeeb44f46f604f96bae337d3200fd15347a8019259d21a94f67d81"
},
"tests/test_native_memory_deadlines.py": {
"baseline_sha256": "f2d3f0feda862318ab936031487743218a8c8241d192b34e2b1ab63a5aa2cc48",
"proposed_sha256": "597d4f430a4fb9bd684958c60d44d98703410b9bab629290e21f857c001c7112"
}
},
"patch_sha256": "448bee9abebcb2c0a592715abb0b37a6da2ffe5ff5ffa3bb81d9571aa3503540",
"tests": {
"proposed_existing_program_exit": 0,
"baseline_existing_program_exit": 1,
"baseline_failure": "compiled then SIGABRT at movement constructor assertion: phase==RADIO_SEARCH && active && unlocks==1",
"prison_ai_exit": 0,
"progression_identity_exit": 0
},
"evidence": {
"green.log": "f0f1259c73095bf4ce66107a82bf3363d1d30a1bae1d14000c4ad2cd0629e3fb",
"baseline-red.log": "13065e36fc08d14024660a7e962250d5a73222d7be2136e50a0b3c96404b088d",
"prison-ai-green.log": "63ef1dee605d5ea050f379a0fdfd398c5593126bc0db3d7cb48e12e1151c1523",
"progression-green.log": "9e3c909c4acd90a8094c5bebf05b411775b0f676616f5bb2e5e8015b42c68a4e",
"finding.json": "3536550b06d42b21525e866889f9f810f64eecb61e836cd8d3eab64ac38f49bf"
},
"unchanged_production": {
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c": "8709753e86dc2ca777183f08cb8fbb9851e0c0de495ca01520ebcb1b3dfa59b9",
"addons/core/Scripts/Game/ERR/00_Core/ERR_GameMode.c": "89a29c03ba225acbbb9fa3c58a721aced91a77bc0c77046d697005a6d7c48bb9",
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePatrolProbe.c": "5a2af71cc4cfccc7dd7c23584398a38290eba1a8894cd46f6967252ff3a7087c"
},
"native_failed_evidence": {
"report.json": "d8fc5e1cd3f946636f91ffd46226ca4d0217b47a11c3a38d55ff438d8076cd8b",
"console.log.txt": "d08c221dee2bff342e5c512a0284fef6ef6dbbc0ae9c978b19945438827a5080"
},
"native_runs": 0,
"live_edits": false,
"compiler_work_complete": true
}
@@ -0,0 +1,157 @@
"""Actual native-fixture startup/deadline gate, engine boundaries doubled."""
import os,sys,re
from pathlib import Path
REPO=Path(os.environ.get('ERR_REPO', Path(__file__).resolve().parents[1]))
sys.path.insert(0,str(REPO/'tests'))
from policy_harness import body,run
p=Path(os.environ.get('ERR_MEMORY_PROBE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c'))
s=body(p,'int Tick(ERR_HQ hq, float now)')
# Execute the unchanged deadline/startup prefix; payload/native radio assertions
# after this gate remain the full fixture's responsibility.
s=s[:s.index(' if (!m_ValueChecked)')]+ ' }\n return 1;\n }'
s=s.replace('ERR_HQ hq','ERR_HQ* hq').replace('hq.','hq->')
run(r'''
#include <cassert>
#include <string>
using string = std::string;
struct CLI {string outcome="hq-memory"; void GetCLIParam(string,string& value){value=outcome;}} System;
struct ERR_HQ{bool ready=true;bool ERR_ProbeSquadsReady(){return ready;}};
struct Probe{bool enabled=true,m_Mode=true,m_Failed=false,m_AutoChecked=false;int m_Step=0,automatic=0,checks=0;float m_Deadline=0;bool Enabled(){return enabled;}void Check(bool,std::string){checks++;m_Failed=true;}int AutomaticConvoy(ERR_HQ*){if(automatic==1)m_AutoChecked=true;return automatic;}
METHOD
};
int main(){ERR_HQ hq;
Probe p;assert(p.Tick(&hq,1000)==0&&p.m_Deadline==151000);assert(p.Tick(&hq,130000)==0&&p.m_Deadline==151000);p.automatic=1;assert(p.Tick(&hq,145000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,200000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,265000)==1);assert(p.Tick(&hq,265001)==-1&&p.checks==1);
Probe missing;assert(missing.Tick(&hq,0)==0);assert(missing.Tick(&hq,150001)==-1&&missing.checks==1);
Probe refused;refused.Tick(&hq,0);refused.automatic=-1;assert(refused.Tick(&hq,40000)==-1&&refused.m_Deadline==150000&&!refused.m_AutoChecked);
Probe unavailable;hq.ready=false;assert(unavailable.Tick(&hq,0)==0);assert(unavailable.Tick(&hq,150001)==-1);hq.ready=true;
System.outcome="hq-awareness"; hq.ready=false; Probe awareness; assert(awareness.Tick(&hq,1000)==1 && !awareness.m_AutoChecked && awareness.m_Deadline==151000); assert(awareness.Tick(&hq,151001)==-1);
Probe invalid;invalid.enabled=false;assert(invalid.Tick(&hq,0)==-1&&invalid.m_Deadline==0);
}
'''.replace('METHOD',s))
smoke=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')).read_text()
assert 'if (m_ERRTestHQMemory) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 360000;' in smoke
assert 'if (m_ERRTestHQBody) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;' in smoke
# Execute the actual startup selector, so enlarged full-route budgets cannot
# accidentally extend a death/radio test or shorten an existing specialized run.
start=smoke.index('m_ERRTestLostHelicopter = System.GetCLIParam(')
end=smoke.index('OnStop().Insert(ERR_StopHQPatrol);',start)
selector=smoke[start:end].replace('System.GetCLIParam("errSmokeOutcome", outcome) && ', '')
selector=selector.replace('GetWorld().GetWorldTime()', 'now').replace('ERR_NativeConvoyCoverProbe.Enabled()', 'cover').replace('ERR_NativeMountedCourierProbe.Enabled()', 'courier').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance').replace('ERR_NativeMountedInterceptProbe.Enabled()', 'intercept')
flags=sorted(set(re.findall(r'\bm_ERRTest\w+',selector)))
run(r'''
#include <cassert>
#include <string>
using string = std::string;
float deadline(string outcome,float now,bool cover=false,bool courier=false,bool maintenance=false,bool intercept=false){
[[maybe_unused]] bool DECLARATIONS;
float m_ERRSmokeDeadline=0;
SELECTOR
return m_ERRSmokeDeadline;
}
int main(){
for(float now : {0.0f, 42000.0f}) {
for(string outcome : {"", "victory", "lost-helicopter"}) assert(deadline(outcome,now)==now+300000);
for(string outcome : {"escape-route", "normal-escape-route", "player-death", "radio-network", "hq-response", "map-ownership"}) assert(deadline(outcome,now)==now+150000);
assert(deadline("patrol-hq",now)==now+420000);
assert(deadline("hq-memory",now)==now+360000);
assert(deadline("hq-body",now)==now+240000);
assert(deadline("convoy",now)==now+540000);
assert(deadline("convoy",now,false,false,false,true)==now+840000);
assert(deadline("supply-convoys",now)==now+840000);
assert(deadline("convoy",now,true)==now+840000);
assert(deadline("mounted-ammo",now)==now+330000);
assert(deadline("mounted-ammo",now,false,true)==now+750000);
assert(deadline("normal-escape-route",now,false,false,true)==now+330000);
}
}
'''.replace('DECLARATIONS', ','.join(flag+'=false' for flag in flags)).replace('SELECTOR',selector))
print('PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines')
# The new route diagnostic must leave the original administrator/reroll stress
# intact and may skip only those extras, never clue or radio progression gates.
route=body(Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')), 'bool ERR_CheckRouteExtras(float now)').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance')
run(r'''
#include <cassert>
#include <string>
struct Probe {
bool m_ERRTestEscapeRoute=false,rerollReady=false,maintenance=false,maintenanceReady=false;
int maintenanceCalls=0; bool ERR_RunMaintenance(float){maintenanceCalls++;return maintenanceReady;}
int adminResult=0,adminCalls=0,rerollCalls=0,failures=0;
int ERR_ProbeAdminMenu(float){adminCalls++;return adminResult;}
bool ERR_Check(bool result,std::string){if(!result)failures++;return result;}
bool ERR_CheckIntelReroll(float){rerollCalls++;return rerollReady;}
METHOD
};
int main(){
Probe route;route.m_ERRTestEscapeRoute=true;assert(route.ERR_CheckRouteExtras(100));
assert(route.adminCalls==0&&route.rerollCalls==0&&route.failures==0);
route.maintenance=true;assert(!route.ERR_CheckRouteExtras(101)&&route.maintenanceCalls==1);
route.maintenanceReady=true;assert(route.ERR_CheckRouteExtras(102)&&route.maintenanceCalls==2&&route.adminCalls==0&&route.rerollCalls==0);
Probe full;assert(!full.ERR_CheckRouteExtras(100)&&full.adminCalls==1&&full.rerollCalls==0);
full.adminResult=-1;assert(!full.ERR_CheckRouteExtras(200)&&full.failures==1&&full.rerollCalls==0);
full.adminResult=1;assert(!full.ERR_CheckRouteExtras(300)&&full.rerollCalls==1);
full.rerollReady=true;assert(full.ERR_CheckRouteExtras(400)&&full.rerollCalls==2);
}
'''.replace('METHOD',route))
assert 'if (!ERR_CheckRouteExtras(now)) return;' in smoke
assert 'if (!ERR_Check(m_ERRSmokeRadio.Knowledge() == 2,' in smoke
assert 'if (!ERR_Check(Phase() == ERR_Phase.EXTRACTION,' in smoke
print('PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths')
# Execute the actual scenario-selection expressions: normal route must retain
# the ordinary catalog AND random placement, while historical probes stay fixed.
scenario=(REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c').read_text()
conditions=[]
for variable in ('historicalFixture','fixture'):
line=next(line.strip() for line in scenario.splitlines() if line.strip().startswith('if (') and 'System.GetCLIParam("errSmokeOutcome", '+variable+')' in line)
expression=line[4:-1].replace('System.GetCLIParam("errSmokeOutcome", '+variable+')','supplied').replace(variable,'outcome')
conditions.append(expression)
run(r'''
#include <cassert>
#include <string>
using string=std::string;
bool legacy(bool supplied,string outcome,bool m_TerrainIndependent=false){return LEGACY;}
bool fixed(bool supplied,string outcome,bool m_TerrainIndependent=false){return FIXED;}
int main(){
for(string route : {"normal-escape-route","generated-sites","optional-site-skip"}) {assert(!legacy(true,route));assert(!fixed(true,route));}
for(string route : {"escape-route","victory","transport-survey"}) {assert(legacy(true,route));assert(fixed(true,route));}
assert(!legacy(false,"escape-route")&&!fixed(false,"escape-route"));
}
'''.replace('LEGACY',conditions[0]).replace('FIXED',conditions[1]))
print('PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged')
# Execute the actual patrol-hq admission branch. The existing production
# prison-AI/progression tests cover canonical UnlockPrison and phase callbacks;
# here native damage, allocation and unlock boundaries are explicit doubles.
smoke_path=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c'))
smoke_tick=body(smoke_path,'protected void ERR_SmokeTick(')
a=smoke_tick.index('if (m_ERRTestPatrolHQ)'); b=smoke_tick.index('{',a); i=b+1; depth=1
while depth:
depth+=(smoke_tick[i]=='{')-(smoke_tick[i]=='}');i+=1
patrol_branch=smoke_tick[a:i]
patrol_branch=patrol_branch.replace('DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);','ProtectOriginal(m_ERRSmokePlayer);').replace('ERR_Phase.RADIO_SEARCH','2')
run(r'''
#include <cassert>
#include <string>
struct Fixture;
struct ERR_NativePatrolProbe {explicit ERR_NativePatrolProbe(Fixture* owner);};
struct Fixture {
bool m_ERRTestPatrolHQ=true,allowed=true,wrongPhase=false,active=false;
int m_ERRSmokePlayer=7,phase=1,unlocks=0,failures=0,allocations=0,protections=0,m_ERRSmokeStep=0;
ERR_NativePatrolProbe* m_ERRHQPatrolMovement=nullptr;
~Fixture(){delete m_ERRHQPatrolMovement;}
void ProtectOriginal(int actor){assert(actor==7);protections++;}
bool UnlockPrison(int actor){assert(actor==7);unlocks++;if(!allowed)return false;if(!wrongPhase){phase=2;active=true;}return true;}
int Phase(){return phase;}
bool ERR_Check(bool ok,std::string){if(!ok)failures++;return ok;}
void Start(){BRANCH}
};
ERR_NativePatrolProbe::ERR_NativePatrolProbe(Fixture* owner){assert(owner->phase==2&&owner->active&&owner->unlocks==1);owner->allocations++;}
int main(){
Fixture healthy;healthy.Start();assert(healthy.m_ERRSmokeStep==130&&healthy.allocations==1&&healthy.failures==0&&healthy.protections==1);
Fixture refused;refused.allowed=false;refused.Start();assert(refused.unlocks==1&&refused.failures==1&&refused.allocations==0&&refused.m_ERRSmokeStep==0&&!refused.active);
Fixture wrong;wrong.wrongPhase=true;wrong.Start();assert(wrong.unlocks==1&&wrong.failures==1&&wrong.allocations==0&&wrong.m_ERRSmokeStep==0);
Fixture other;other.m_ERRTestPatrolHQ=false;other.Start();assert(other.unlocks==0&&other.allocations==0&&other.protections==0&&other.m_ERRSmokeStep==0);
}
'''.replace('BRANCH',patrol_branch))
print('PASS: actual patrol fixture leaves prison before movement observation and refuses failed transition')
@@ -0,0 +1,23 @@
check: /tmp/err-policy-n0hzu5dy/check.cpp:11: int main(): Assertion `d.site.ResponseCount==2&&d.site.ResponseOffset.x==-20&&d.site.ResponseOffset.z==-20&&d.site.ResponseOffset.y==0&&d.validations==1' failed.
PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines
PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths
PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged
PASS: actual patrol fixture leaves prison before responder HQ proof, followed by separate permanent-patrol observation
Traceback (most recent call last):
File "/tmp/erri-patrol-responders-1659/tests/test_native_memory_deadlines.py", line 177, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<13 lines>...
}
^
'''.replace('METHOD',configure))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-n0hzu5dy/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,90 @@
[ComponentEditorProps(category: "Reintroduced", description: "Authored scenario definition and finite population lifecycle")]
class ERR_ScenarioComponentClass : ScriptComponentClass {}
class ERR_ScenarioComponent : ScriptComponent
{
[Attribute("", UIWidgets.ResourceNamePicker, "Scenario site definitions", "conf")]
protected ResourceName m_Definition;
[Attribute("0", UIWidgets.CheckBox, "Generate all sites on this terrain; requires terrain-specific AI navigation")]
protected bool m_TerrainIndependent;
protected ref ERR_SiteService m_Sites;
protected bool m_Started;
protected ref ERR_ScenarioDefinition m_RuntimeDefinition;
protected ref ERR_GeneratedSites m_Generated;
protected bool m_Finished;
protected bool m_Failed;
ERR_SiteService Sites() { return m_Sites; }
string FailureReason()
{
ERR_HQ hq = ERR_HQ.Get();
if (hq && hq.Scouts() && !hq.Scouts().Error().IsEmpty()) return hq.Scouts().Error();
if (m_Generated && !m_Generated.Error().IsEmpty()) return m_Generated.Error();
if (m_Sites && !m_Sites.FailureReason().IsEmpty()) return m_Sites.FailureReason();
return "Scenario or additional force preparation failed.";
}
ERR_PrepareResult Prepare()
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!Replication.IsServer() || !mode || m_Failed) return ERR_PrepareResult.FAILED;
if (m_Finished) return ERR_PrepareResult.READY;
if (!m_Started)
{
m_Started = true;
#ifdef WORKBENCH
string historicalFixture;
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", historicalFixture) && historicalFixture != "normal-escape-route" && historicalFixture != "mounted-ammo" && historicalFixture != "alarm-hearing" && historicalFixture != "optional-site-skip" && historicalFixture != "infantry-combat" && historicalFixture != "site-garrisons" && historicalFixture != "generated-sites" && historicalFixture != "opening-tools" && historicalFixture != "airfield-markers" && historicalFixture != "air-recon" && historicalFixture != "air-logistics" && historicalFixture != "air-assault")
m_Definition = "{ACA98B44E2173605}Configs/ERR/Tests/LegacySites.conf";
#endif
m_RuntimeDefinition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab(m_Definition));
if (!m_RuntimeDefinition || !m_RuntimeDefinition.Valid()) { m_Failed = true; return ERR_PrepareResult.FAILED; }
#ifdef WORKBENCH
// Existing deterministic probes intentionally use authored coordinates.
string fixture;
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", fixture) && fixture != "normal-escape-route" && fixture != "mounted-ammo" && fixture != "alarm-hearing" && fixture != "optional-site-skip" && fixture != "infantry-combat" && fixture != "site-garrisons" && fixture != "generated-sites" && fixture != "airfield-markers" && fixture != "air-recon" && fixture != "air-logistics" && fixture != "air-assault")
foreach (ERR_SiteDefinition fixedSite : m_RuntimeDefinition.Sites) fixedSite.RandomClear = false;
#endif
if (m_TerrainIndependent) m_Generated = new ERR_TerrainSites();
else m_Generated = new ERR_GeneratedSites();
if (!m_Generated.Begin(m_RuntimeDefinition)) { m_Failed = true; return ERR_PrepareResult.FAILED; }
}
ERR_PrepareResult placement = m_Generated.Tick();
if (placement == ERR_PrepareResult.FAILED) m_Failed = true;
if (placement != ERR_PrepareResult.READY) return placement;
if (!m_Sites)
{
m_Sites = new ERR_SiteService(mode);
if (!m_Sites.Begin(m_RuntimeDefinition, FieldMap())) { m_Failed = true; return ERR_PrepareResult.FAILED; }
}
ERR_PrepareResult result = m_Sites.Prepare();
if (result != ERR_PrepareResult.READY) return result;
ERR_PrepareResult population = PrepareAdditionalPopulation();
if (population == ERR_PrepareResult.FAILED) { m_Failed = true; m_Sites.Rollback(); }
if (population != ERR_PrepareResult.READY) return population;
if (!PrepareAdditionalForces())
{
m_Failed = true;
m_Sites.Rollback();
return ERR_PrepareResult.FAILED;
}
mode.OnPhaseChanged().Insert(m_Sites.PhaseChanged);
mode.OnStop().Insert(m_Sites.Stop);
mode.OnTick().Insert(m_Sites.Tick);
GetGame().GetCallqueue().CallLater(ActivateSites, 2000, false);
m_Finished = true;
return ERR_PrepareResult.READY;
}
protected void ActivateSites()
{
ERR_GameMode mode = ERR_GameMode.Get();
if (mode && mode.Active() && m_Sites) m_Sites.Activate();
}
protected ERR_PrepareResult PrepareAdditionalPopulation() { return ERR_PrepareResult.READY; }
protected bool PrepareAdditionalForces() { return true; }
protected ResourceName FieldMap() { return ERR_Supplies.Map(); }
protected override void OnDelete(IEntity owner)
{
if (GetGame()) GetGame().GetCallqueue().Remove(ActivateSites);
if (m_Sites) m_Sites.Stop();
super.OnDelete(owner);
}
}
@@ -0,0 +1,118 @@
#ifdef WORKBENCH
// Test access only: trigger the real delivered-order method on an existing
// registered squad. Radio transit is intentionally not simulated by this test.
modded class ERR_HQ
{
bool ERR_TestPatrolSearch(SCR_AIGroup group, out AIWaypoint search)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group) continue;
if (unit.Assignment || unit.PendingOrder || unit.Waypoint || !unit.Eligible(m_Mode)) return false;
ERR_HQReport order = new ERR_HQReport();
order.Target = group;
order.Source = group;
order.Position = group.GetOrigin() + "40 0 0";
order.ObservedAt = GetGame().GetWorld().GetWorldTime();
order.MessageId = m_Journal.Message();
Accept(order, order.ObservedAt);
ERR_HQBelief belief = m_Memory.Get(group);
if (!belief) return false;
ERR_HQTask task = new ERR_HQTask();
task.BeliefId = belief.BeliefId; task.BeliefRevision = belief.BeliefRevision;
task.SearchRadiusMetres = ERR_HQPursuitRules.SearchRadius(belief.Radius);
task.Destination = order.Position; task.QueuedAt = order.ObservedAt;
task.ExpiresAt = order.ObservedAt + 120000;
unit.PendingTask = task;
unit.PendingOrder = order;
DeliverOrder(unit, order.ObservedAt);
search = unit.Waypoint;
return search && unit.Assignment == order;
}
return false;
}
bool ERR_TestPatrolRelease(SCR_AIGroup group, AIWaypoint search)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group || unit.Waypoint != search) continue;
unit.Release();
return !unit.Assignment && !unit.PendingOrder && !unit.Waypoint;
}
return false;
}
bool ERR_TestPatrolCombat(SCR_AIGroup group, AIWaypoint search, bool contact, float now, int expected)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group || unit.Waypoint != search) continue;
return unit.InfantryCombat.Update(unit, unit.Eligible(m_Mode), contact, now) == expected;
}
return false;
}
}
class ERR_NativePatrolHQProbe
{
protected ERR_GameMode m_Mode;
protected ERR_SiteRuntime m_Site;
protected SCR_AIGroup m_Group;
protected AIWaypoint m_Search;
protected ref array<AIWaypoint> m_Baseline = {};
protected int m_Step;
protected float m_Wait;
void ERR_NativePatrolHQProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: patrol HQ " + detail); return true; }
Print("ERR SMOKE FAIL: patrol HQ " + detail, LogLevel.ERROR); return false;
}
int Tick(float now)
{
if (now < m_Wait) return 0;
ERR_HQ hq = ERR_HQ.Get();
if (!Check(hq != null, "real HQ available")) return -1;
if (m_Step == 0)
{
m_Site = m_Mode.Sites().Find("everon-outpost-1");
if (!Check(m_Site && m_Site.Responders && m_Site.Responders.Group, "existing mobile outpost responders available")) return -1;
m_Group = m_Site.Responders.Group;
m_Group.GetWaypoints(m_Baseline);
if (!Check(!m_Baseline.IsEmpty() && hq.ERR_TestPatrolSearch(m_Group, m_Search), "real delivered-order method assigns existing squad")) return -1;
m_Step = 1; m_Wait = now + 750; return 0;
}
if (m_Step == 1)
{
array<AIWaypoint> queue = {};
m_Group.GetWaypoints(queue);
if (!Check(m_Search && !queue.IsEmpty() && queue[0] == m_Search && m_Group.GetCurrentWaypoint() == m_Search, "Search preempts native patrol")) return -1;
foreach (AIWaypoint waypoint : m_Baseline)
if (!Check(queue.Contains(waypoint), "preempted baseline route remains queued")) return -1;
if (!Check(hq.ERR_TestPatrolCombat(m_Group, m_Search, true, now, 1), "injected local contact pauses actual HQ search")) return -1;
queue.Clear(); m_Group.GetWaypoints(queue);
if (!Check(!queue.Contains(m_Search), "combat removes HQ search from native queue without deleting it")) return -1;
foreach (AIWaypoint home : m_Baseline)
if (!Check(queue.Contains(home), "combat preserves baseline patrol ownership")) return -1;
if (!Check(hq.ERR_TestPatrolCombat(m_Group, m_Search, false, now + 14999, 0), "quiet grace period prevents immediate marching")) return -1;
if (!Check(hq.ERR_TestPatrolCombat(m_Group, m_Search, false, now + 15000, 2), "quiet period resumes the same unexpired assignment")) return -1;
queue.Clear(); m_Group.GetWaypoints(queue);
if (!Check(!queue.IsEmpty() && queue[0] == m_Search, "resumed search is queued exactly once at the front")) return -1;
if (!Check(hq.ERR_TestPatrolRelease(m_Group, m_Search), "real HQ release removes only its assignment")) return -1;
m_Step = 2; m_Wait = now + 750; return 0;
}
if (m_Step == 2)
{
AIWaypoint current = m_Group.GetCurrentWaypoint();
if (!Check(current && m_Baseline.Contains(current), "native home defence returns after Search release")) return -1;
m_Step = 3; return 1;
}
return 1;
}
void Stop()
{
ERR_HQ hq = ERR_HQ.Get();
if (hq && m_Site && m_Search) hq.ERR_TestPatrolRelease(m_Group, m_Search);
}
void ~ERR_NativePatrolHQProbe() { Stop(); }
}
#endif
@@ -0,0 +1,157 @@
"""Actual native-fixture startup/deadline gate, engine boundaries doubled."""
import os,sys,re
from pathlib import Path
REPO=Path(os.environ.get('ERR_REPO', Path(__file__).resolve().parents[1]))
sys.path.insert(0,str(REPO/'tests'))
from policy_harness import body,run
p=Path(os.environ.get('ERR_MEMORY_PROBE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c'))
s=body(p,'int Tick(ERR_HQ hq, float now)')
# Execute the unchanged deadline/startup prefix; payload/native radio assertions
# after this gate remain the full fixture's responsibility.
s=s[:s.index(' if (!m_ValueChecked)')]+ ' }\n return 1;\n }'
s=s.replace('ERR_HQ hq','ERR_HQ* hq').replace('hq.','hq->')
run(r'''
#include <cassert>
#include <string>
using string = std::string;
struct CLI {string outcome="hq-memory"; void GetCLIParam(string,string& value){value=outcome;}} System;
struct ERR_HQ{bool ready=true;bool ERR_ProbeSquadsReady(){return ready;}};
struct Probe{bool enabled=true,m_Mode=true,m_Failed=false,m_AutoChecked=false;int m_Step=0,automatic=0,checks=0;float m_Deadline=0;bool Enabled(){return enabled;}void Check(bool,std::string){checks++;m_Failed=true;}int AutomaticConvoy(ERR_HQ*){if(automatic==1)m_AutoChecked=true;return automatic;}
METHOD
};
int main(){ERR_HQ hq;
Probe p;assert(p.Tick(&hq,1000)==0&&p.m_Deadline==151000);assert(p.Tick(&hq,130000)==0&&p.m_Deadline==151000);p.automatic=1;assert(p.Tick(&hq,145000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,200000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,265000)==1);assert(p.Tick(&hq,265001)==-1&&p.checks==1);
Probe missing;assert(missing.Tick(&hq,0)==0);assert(missing.Tick(&hq,150001)==-1&&missing.checks==1);
Probe refused;refused.Tick(&hq,0);refused.automatic=-1;assert(refused.Tick(&hq,40000)==-1&&refused.m_Deadline==150000&&!refused.m_AutoChecked);
Probe unavailable;hq.ready=false;assert(unavailable.Tick(&hq,0)==0);assert(unavailable.Tick(&hq,150001)==-1);hq.ready=true;
System.outcome="hq-awareness"; hq.ready=false; Probe awareness; assert(awareness.Tick(&hq,1000)==1 && !awareness.m_AutoChecked && awareness.m_Deadline==151000); assert(awareness.Tick(&hq,151001)==-1);
Probe invalid;invalid.enabled=false;assert(invalid.Tick(&hq,0)==-1&&invalid.m_Deadline==0);
}
'''.replace('METHOD',s))
smoke=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')).read_text()
assert 'if (m_ERRTestHQMemory) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 360000;' in smoke
assert 'if (m_ERRTestHQBody) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;' in smoke
# Execute the actual startup selector, so enlarged full-route budgets cannot
# accidentally extend a death/radio test or shorten an existing specialized run.
start=smoke.index('m_ERRTestLostHelicopter = System.GetCLIParam(')
end=smoke.index('OnStop().Insert(ERR_StopHQPatrol);',start)
selector=smoke[start:end].replace('System.GetCLIParam("errSmokeOutcome", outcome) && ', '')
selector=selector.replace('GetWorld().GetWorldTime()', 'now').replace('ERR_NativeConvoyCoverProbe.Enabled()', 'cover').replace('ERR_NativeMountedCourierProbe.Enabled()', 'courier').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance').replace('ERR_NativeMountedInterceptProbe.Enabled()', 'intercept')
flags=sorted(set(re.findall(r'\bm_ERRTest\w+',selector)))
run(r'''
#include <cassert>
#include <string>
using string = std::string;
float deadline(string outcome,float now,bool cover=false,bool courier=false,bool maintenance=false,bool intercept=false){
[[maybe_unused]] bool DECLARATIONS;
float m_ERRSmokeDeadline=0;
SELECTOR
return m_ERRSmokeDeadline;
}
int main(){
for(float now : {0.0f, 42000.0f}) {
for(string outcome : {"", "victory", "lost-helicopter"}) assert(deadline(outcome,now)==now+300000);
for(string outcome : {"escape-route", "normal-escape-route", "player-death", "radio-network", "hq-response", "map-ownership"}) assert(deadline(outcome,now)==now+150000);
assert(deadline("patrol-hq",now)==now+420000);
assert(deadline("hq-memory",now)==now+360000);
assert(deadline("hq-body",now)==now+240000);
assert(deadline("convoy",now)==now+540000);
assert(deadline("convoy",now,false,false,false,true)==now+840000);
assert(deadline("supply-convoys",now)==now+840000);
assert(deadline("convoy",now,true)==now+840000);
assert(deadline("mounted-ammo",now)==now+330000);
assert(deadline("mounted-ammo",now,false,true)==now+750000);
assert(deadline("normal-escape-route",now,false,false,true)==now+330000);
}
}
'''.replace('DECLARATIONS', ','.join(flag+'=false' for flag in flags)).replace('SELECTOR',selector))
print('PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines')
# The new route diagnostic must leave the original administrator/reroll stress
# intact and may skip only those extras, never clue or radio progression gates.
route=body(Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')), 'bool ERR_CheckRouteExtras(float now)').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance')
run(r'''
#include <cassert>
#include <string>
struct Probe {
bool m_ERRTestEscapeRoute=false,rerollReady=false,maintenance=false,maintenanceReady=false;
int maintenanceCalls=0; bool ERR_RunMaintenance(float){maintenanceCalls++;return maintenanceReady;}
int adminResult=0,adminCalls=0,rerollCalls=0,failures=0;
int ERR_ProbeAdminMenu(float){adminCalls++;return adminResult;}
bool ERR_Check(bool result,std::string){if(!result)failures++;return result;}
bool ERR_CheckIntelReroll(float){rerollCalls++;return rerollReady;}
METHOD
};
int main(){
Probe route;route.m_ERRTestEscapeRoute=true;assert(route.ERR_CheckRouteExtras(100));
assert(route.adminCalls==0&&route.rerollCalls==0&&route.failures==0);
route.maintenance=true;assert(!route.ERR_CheckRouteExtras(101)&&route.maintenanceCalls==1);
route.maintenanceReady=true;assert(route.ERR_CheckRouteExtras(102)&&route.maintenanceCalls==2&&route.adminCalls==0&&route.rerollCalls==0);
Probe full;assert(!full.ERR_CheckRouteExtras(100)&&full.adminCalls==1&&full.rerollCalls==0);
full.adminResult=-1;assert(!full.ERR_CheckRouteExtras(200)&&full.failures==1&&full.rerollCalls==0);
full.adminResult=1;assert(!full.ERR_CheckRouteExtras(300)&&full.rerollCalls==1);
full.rerollReady=true;assert(full.ERR_CheckRouteExtras(400)&&full.rerollCalls==2);
}
'''.replace('METHOD',route))
assert 'if (!ERR_CheckRouteExtras(now)) return;' in smoke
assert 'if (!ERR_Check(m_ERRSmokeRadio.Knowledge() == 2,' in smoke
assert 'if (!ERR_Check(Phase() == ERR_Phase.EXTRACTION,' in smoke
print('PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths')
# Execute the actual scenario-selection expressions: normal route must retain
# the ordinary catalog AND random placement, while historical probes stay fixed.
scenario=(REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c').read_text()
conditions=[]
for variable in ('historicalFixture','fixture'):
line=next(line.strip() for line in scenario.splitlines() if line.strip().startswith('if (') and 'System.GetCLIParam("errSmokeOutcome", '+variable+')' in line)
expression=line[4:-1].replace('System.GetCLIParam("errSmokeOutcome", '+variable+')','supplied').replace(variable,'outcome')
conditions.append(expression)
run(r'''
#include <cassert>
#include <string>
using string=std::string;
bool legacy(bool supplied,string outcome,bool m_TerrainIndependent=false){return LEGACY;}
bool fixed(bool supplied,string outcome,bool m_TerrainIndependent=false){return FIXED;}
int main(){
for(string route : {"normal-escape-route","generated-sites","optional-site-skip"}) {assert(!legacy(true,route));assert(!fixed(true,route));}
for(string route : {"escape-route","victory","transport-survey"}) {assert(legacy(true,route));assert(fixed(true,route));}
assert(!legacy(false,"escape-route")&&!fixed(false,"escape-route"));
}
'''.replace('LEGACY',conditions[0]).replace('FIXED',conditions[1]))
print('PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged')
# Execute the actual patrol-hq admission branch. The existing production
# prison-AI/progression tests cover canonical UnlockPrison and phase callbacks;
# here native damage, allocation and unlock boundaries are explicit doubles.
smoke_path=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c'))
smoke_tick=body(smoke_path,'protected void ERR_SmokeTick(')
a=smoke_tick.index('if (m_ERRTestPatrolHQ)'); b=smoke_tick.index('{',a); i=b+1; depth=1
while depth:
depth+=(smoke_tick[i]=='{')-(smoke_tick[i]=='}');i+=1
patrol_branch=smoke_tick[a:i]
patrol_branch=patrol_branch.replace('DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);','ProtectOriginal(m_ERRSmokePlayer);').replace('ERR_Phase.RADIO_SEARCH','2')
run(r'''
#include <cassert>
#include <string>
struct Fixture;
struct ERR_NativePatrolProbe {explicit ERR_NativePatrolProbe(Fixture* owner);};
struct Fixture {
bool m_ERRTestPatrolHQ=true,allowed=true,wrongPhase=false,active=false;
int m_ERRSmokePlayer=7,phase=1,unlocks=0,failures=0,allocations=0,protections=0,m_ERRSmokeStep=0;
ERR_NativePatrolProbe* m_ERRHQPatrolMovement=nullptr;
~Fixture(){delete m_ERRHQPatrolMovement;}
void ProtectOriginal(int actor){assert(actor==7);protections++;}
bool UnlockPrison(int actor){assert(actor==7);unlocks++;if(!allowed)return false;if(!wrongPhase){phase=2;active=true;}return true;}
int Phase(){return phase;}
bool ERR_Check(bool ok,std::string){if(!ok)failures++;return ok;}
void Start(){BRANCH}
};
ERR_NativePatrolProbe::ERR_NativePatrolProbe(Fixture* owner){assert(owner->phase==2&&owner->active&&owner->unlocks==1);owner->allocations++;}
int main(){
Fixture healthy;healthy.Start();assert(healthy.m_ERRSmokeStep==130&&healthy.allocations==1&&healthy.failures==0&&healthy.protections==1);
Fixture refused;refused.allowed=false;refused.Start();assert(refused.unlocks==1&&refused.failures==1&&refused.allocations==0&&refused.m_ERRSmokeStep==0&&!refused.active);
Fixture wrong;wrong.wrongPhase=true;wrong.Start();assert(wrong.unlocks==1&&wrong.failures==1&&wrong.allocations==0&&wrong.m_ERRSmokeStep==0);
Fixture other;other.m_ERRTestPatrolHQ=false;other.Start();assert(other.unlocks==0&&other.allocations==0&&other.protections==0&&other.m_ERRSmokeStep==0);
}
'''.replace('BRANCH',patrol_branch))
print('PASS: actual patrol fixture leaves prison before movement observation and refuses failed transition')
@@ -0,0 +1,23 @@
check: /tmp/err-policy-767sgjwj/check.cpp:11: int main(): Assertion `d.site.ResponseCount==2&&d.site.ResponseOffset.x==-20&&d.site.ResponseOffset.z==-20&&d.site.ResponseOffset.y==0&&d.validations==1' failed.
PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines
PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths
PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged
PASS: actual patrol fixture leaves prison before movement observation and refuses failed transition
Traceback (most recent call last):
File "/tmp/erri-patrol-responders-1659/tests/test_native_memory_deadlines.py", line 174, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<13 lines>...
}
^
'''.replace('METHOD',configure))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-767sgjwj/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,5 @@
PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines
PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths
PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged
PASS: actual patrol fixture leaves prison before movement observation and refuses failed transition
PASS: exact patrol-hq finite responder preparation and native search-radius receipt verification
@@ -0,0 +1,22 @@
{
"status": "scratch WORKBENCH fixture correction only; native rerun pending",
"trigger": "Corrected patrol phase run physically traversed five ordered native moves, then failed because LegacySites outpost has ResponseCount0 while current HQ probe uses separate mobile Responders.",
"changes": [
"Exact patrol-hq, non-terrain-independent preparation helper sets2 mobile responders before ordinary population and revalidates definition. Missing/wrong/unexpected definition fails. Other selectors and six permanent guards remain unchanged.",
"Responder offset(-20,0,-20) from site anchor is outside existing nearby cache/cover and eastern permanent patrol coordinates; geometry/navmesh clearance remains native-unqualified.",
"Initial canonical unlock preserved; start HQ responder stage131 immediately, followed by existing stage132 permanent movement proof. No reservations, cleared assignments, AI activation overrides or extra runtime spawner.",
"Native HQ delivery reads back GetCompletionRadius against scalar copied from queued task before DeliverOrder; requires two original living responders."
],
"claims": "Responder delivered-order queue/preemption/quiet-resume/home-defence restoration and native search radius are distinct from permanent guard physical patrol traversal. No same-group physical resumption, radio transit, search-area coverage or multiplayer proof.",
"tests": "Existing deadline program executes actual fixture setup method on legacy fields and actual receipt tail/native getter, with explicit definition validator/native allocation/engine doubles. Controls reject wrong selector/terrain mutation, missing/wrong/non-guarded/already-populated/invalid definition, unlock refusal/wrong phase, mismatched radius/assignment. Baseline setup has no mutation and compiles then fails expected2 assertion.",
"bounds_unchanged": [
"420000ms overall",
"180000ms movement",
"five ordered completions",
">=8m displacement",
"production HQ eligibility and assignments",
"site CreateResponders finite preparation/readiness/cleanup",
"prison AI hold policy"
],
"prior_patch": "/tmp/erri-patrol-start-1659 remains preserved; new baseline already includes its integrated unlock."
}
@@ -0,0 +1,165 @@
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c
@@ -22,6 +22,20 @@
if (m_Sites && !m_Sites.FailureReason().IsEmpty()) return m_Sites.FailureReason();
return "Scenario or additional force preparation failed.";
}
+ #ifdef WORKBENCH
+ // The historical catalog predates finite mobile site squads. Configure this
+ // isolated fixture before ordinary population, never borrow permanent guards.
+ protected bool ERR_ConfigurePatrolHQFixture(string outcome)
+ {
+ if (m_TerrainIndependent || outcome != "patrol-hq") return true;
+ if (!m_RuntimeDefinition) return false;
+ ERR_SiteDefinition site = m_RuntimeDefinition.Find("everon-outpost-1");
+ if (!site || site.Kind != ERR_LocationKind.OUTPOST || !site.Guarded || site.ResponseCount != 0) return false;
+ site.ResponseCount = 2;
+ site.ResponseOffset = "-20 0 -20";
+ return m_RuntimeDefinition.Valid();
+ }
+ #endif
ERR_PrepareResult Prepare()
{
ERR_GameMode mode = ERR_GameMode.Get();
@@ -42,6 +56,7 @@
string fixture;
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", fixture) && fixture != "normal-escape-route" && fixture != "mounted-ammo" && fixture != "alarm-hearing" && fixture != "optional-site-skip" && fixture != "infantry-combat" && fixture != "site-garrisons" && fixture != "generated-sites" && fixture != "airfield-markers" && fixture != "air-recon" && fixture != "air-logistics" && fixture != "air-assault")
foreach (ERR_SiteDefinition fixedSite : m_RuntimeDefinition.Sites) fixedSite.RandomClear = false;
+ if (!ERR_ConfigurePatrolHQFixture(fixture)) { m_Failed = true; return ERR_PrepareResult.FAILED; }
#endif
if (m_TerrainIndependent) m_Generated = new ERR_TerrainSites();
else m_Generated = new ERR_GeneratedSites();
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c
@@ -25,9 +25,11 @@
task.ExpiresAt = order.ObservedAt + 120000;
unit.PendingTask = task;
unit.PendingOrder = order;
+ float queuedRadius = task.SearchRadiusMetres;
DeliverOrder(unit, order.ObservedAt);
search = unit.Waypoint;
- return search && unit.Assignment == order;
+ if (search) PrintFormat("ERR PATROL HQ RADIUS: queued=%1 native=%2", queuedRadius, search.GetCompletionRadius());
+ return search && unit.Assignment == order && search.GetCompletionRadius() == queuedRadius;
}
return false;
}
@@ -77,6 +79,7 @@
m_Site = m_Mode.Sites().Find("everon-outpost-1");
if (!Check(m_Site && m_Site.Responders && m_Site.Responders.Group, "existing mobile outpost responders available")) return -1;
m_Group = m_Site.Responders.Group;
+ if (!Check(m_Site.Responders.InitialGuardCount == 2 && m_Mode.ForceLiving(m_Group) == 2, "two original mobile responders remain distinct from permanent guards")) return -1;
m_Group.GetWaypoints(m_Baseline);
if (!Check(!m_Baseline.IsEmpty() && hq.ERR_TestPatrolSearch(m_Group, m_Search), "real delivered-order method assigns existing squad")) return -1;
m_Step = 1; m_Wait = now + 750; return 0;
--- a/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
+++ b/addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c
@@ -699,8 +699,10 @@
{
DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);
if (!ERR_Check(UnlockPrison(m_ERRSmokePlayer) && Phase() == ERR_Phase.RADIO_SEARCH, "patrol HQ fixture leaves prison through canonical admission before observing movement")) return;
- m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this);
- m_ERRSmokeStep = 130;
+ // HQ tests the distinct mobile responders; the subsequent physical
+ // traversal still observes the untouched permanent guard patrol.
+ m_ERRHQPatrolOrder = new ERR_NativePatrolHQProbe(this);
+ m_ERRSmokeStep = 131;
return;
}
if (m_ERRTestMapOwnership)
--- a/tests/test_native_memory_deadlines.py
+++ b/tests/test_native_memory_deadlines.py
@@ -100,7 +100,7 @@
# Execute the actual scenario-selection expressions: normal route must retain
# the ordinary catalog AND random placement, while historical probes stay fixed.
-scenario=(REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c').read_text()
+scenario=Path(os.environ.get('ERR_SCENARIO_SOURCE',REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c')).read_text()
conditions=[]
for variable in ('historicalFixture','fixture'):
line=next(line.strip() for line in scenario.splitlines() if line.strip().startswith('if (') and 'System.GetCLIParam("errSmokeOutcome", '+variable+')' in line)
@@ -134,24 +134,78 @@
#include <cassert>
#include <string>
struct Fixture;
-struct ERR_NativePatrolProbe {explicit ERR_NativePatrolProbe(Fixture* owner);};
+struct ERR_NativePatrolHQProbe {explicit ERR_NativePatrolHQProbe(Fixture* owner);};
+struct ERR_NativePatrolProbe {explicit ERR_NativePatrolProbe(Fixture*){assert(false && "permanent patrol must follow responder HQ proof");}};
struct Fixture {
bool m_ERRTestPatrolHQ=true,allowed=true,wrongPhase=false,active=false;
int m_ERRSmokePlayer=7,phase=1,unlocks=0,failures=0,allocations=0,protections=0,m_ERRSmokeStep=0;
+ ERR_NativePatrolHQProbe* m_ERRHQPatrolOrder=nullptr;
ERR_NativePatrolProbe* m_ERRHQPatrolMovement=nullptr;
- ~Fixture(){delete m_ERRHQPatrolMovement;}
+ ~Fixture(){delete m_ERRHQPatrolOrder;delete m_ERRHQPatrolMovement;}
void ProtectOriginal(int actor){assert(actor==7);protections++;}
bool UnlockPrison(int actor){assert(actor==7);unlocks++;if(!allowed)return false;if(!wrongPhase){phase=2;active=true;}return true;}
int Phase(){return phase;}
bool ERR_Check(bool ok,std::string){if(!ok)failures++;return ok;}
void Start(){BRANCH}
};
-ERR_NativePatrolProbe::ERR_NativePatrolProbe(Fixture* owner){assert(owner->phase==2&&owner->active&&owner->unlocks==1);owner->allocations++;}
-int main(){
- Fixture healthy;healthy.Start();assert(healthy.m_ERRSmokeStep==130&&healthy.allocations==1&&healthy.failures==0&&healthy.protections==1);
+ERR_NativePatrolHQProbe::ERR_NativePatrolHQProbe(Fixture* owner){assert(owner->phase==2&&owner->active&&owner->unlocks==1);owner->allocations++;}
+int main(){
+ Fixture healthy;healthy.Start();assert(healthy.m_ERRSmokeStep==131&&healthy.allocations==1&&healthy.failures==0&&healthy.protections==1);
Fixture refused;refused.allowed=false;refused.Start();assert(refused.unlocks==1&&refused.failures==1&&refused.allocations==0&&refused.m_ERRSmokeStep==0&&!refused.active);
Fixture wrong;wrong.wrongPhase=true;wrong.Start();assert(wrong.unlocks==1&&wrong.failures==1&&wrong.allocations==0&&wrong.m_ERRSmokeStep==0);
Fixture other;other.m_ERRTestPatrolHQ=false;other.Start();assert(other.unlocks==0&&other.allocations==0&&other.protections==0&&other.m_ERRSmokeStep==0);
}
'''.replace('BRANCH',patrol_branch))
-print('PASS: actual patrol fixture leaves prison before movement observation and refuses failed transition')
+assert 'if (result == 1) { m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this); m_ERRSmokeStep = 132; }' in body(smoke_path,'protected void ERR_SmokeTick(')
+print('PASS: actual patrol fixture leaves prison before responder HQ proof, followed by separate permanent-patrol observation')
+
+# Execute the isolated definition mutation on the actual legacy outpost fields.
+# Definition.Valid is a boundary double here; full production authoring policy
+# remains enforced by the normal preparation path and repository checks.
+scenario_path=Path(os.environ.get('ERR_SCENARIO_SOURCE',REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c'))
+legacy=(REPO/'addons/core/Configs/ERR/Tests/LegacySites.conf').read_text()
+start=legacy.index('Id "everon-outpost-1"');end=legacy.index('\n }',start);outpost=legacy[start:end]
+assert 'ResponseCount' not in outpost and 'Guarded 1' in outpost and 'GuardOffset 16 0 0' in outpost
+assert outpost.count('ERR_SitePatrolPoint')==4
+if 'protected bool ERR_ConfigurePatrolHQFixture(' in scenario:
+ configure=body(scenario_path,'protected bool ERR_ConfigurePatrolHQFixture(').replace('protected ','').replace('ERR_SiteDefinition site =','Site* site =').replace('m_RuntimeDefinition.','m_RuntimeDefinition->').replace('site.','site->').replace('ERR_LocationKind.OUTPOST','3').replace('"-20 0 -20"','Vec{-20,0,-20}')
+ assert scenario.index('ERR_ConfigurePatrolHQFixture(fixture)') < scenario.index('m_Generated.Begin(m_RuntimeDefinition)') < scenario.index('m_Sites.Begin(m_RuntimeDefinition')
+else:
+ # Baseline has no prepopulation mutation; executing its unchanged definition
+ # must fail the required two-mobile-originals assertion below.
+ configure='bool ERR_ConfigurePatrolHQFixture(string){return true;}'
+run(r'''
+#include <cassert>
+#include <string>
+using string=std::string;
+struct Vec{float x,y,z;};
+struct Site {int Kind=3,ResponseCount=0,GuardCount=0,patrolPoints=4,stock=4;bool Guarded=true;Vec ResponseOffset{-12,0,0},GuardOffset{16,0,0};};
+struct Definition{Site site;bool present=true,valid=true;int validations=0;Site*Find(string id){assert(id=="everon-outpost-1");return present?&site:nullptr;}bool Valid(){validations++;return valid;}};
+struct Fixture {bool m_TerrainIndependent=false;Definition* m_RuntimeDefinition=nullptr;METHOD};
+int main(){
+ Definition d;Fixture f;f.m_RuntimeDefinition=&d;assert(f.ERR_ConfigurePatrolHQFixture("patrol-hq"));
+ assert(d.site.ResponseCount==2&&d.site.ResponseOffset.x==-20&&d.site.ResponseOffset.z==-20&&d.site.ResponseOffset.y==0&&d.validations==1);
+ assert(d.site.Guarded&&d.site.GuardCount==0&&d.site.GuardOffset.x==16&&d.site.patrolPoints==4&&d.site.stock==4);
+ for(string outcome:{"","normal-escape-route","patrol","victory","hq-memory"}){Definition other;Fixture g;g.m_RuntimeDefinition=&other;assert(g.ERR_ConfigurePatrolHQFixture(outcome)&&other.site.ResponseCount==0&&other.validations==0);}
+ Definition terrain;Fixture t;t.m_TerrainIndependent=true;t.m_RuntimeDefinition=&terrain;assert(t.ERR_ConfigurePatrolHQFixture("patrol-hq")&&terrain.site.ResponseCount==0);
+ for(int fault=0;fault<6;fault++){Definition bad;Fixture x;x.m_RuntimeDefinition=&bad;if(fault==0)x.m_RuntimeDefinition=nullptr;if(fault==1)bad.present=false;if(fault==2)bad.site.Kind=0;if(fault==3)bad.site.Guarded=false;if(fault==4)bad.site.ResponseCount=1;if(fault==5)bad.valid=false;assert(!x.ERR_ConfigurePatrolHQFixture("patrol-hq"));}
+}
+'''.replace('METHOD',configure))
+# Test actual final receipt decision. Delivery/native allocation are covered by
+# their own methods and the real opt-in run; this checks the native getter is
+# required instead of accepting an assignment with the untouched prefab radius.
+patrol_path=Path(os.environ.get('ERR_PATROL_HQ_SOURCE',REPO/'addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c'))
+patrol=body(patrol_path,'bool ERR_TestPatrolSearch(')
+assert 'float queuedRadius = task.SearchRadiusMetres;' in patrol
+assert patrol.index('float queuedRadius =') < patrol.index('DeliverOrder(unit,')
+tail=patrol[patrol.index(' search = unit.Waypoint;'):patrol.index('\n }',patrol.index(' search = unit.Waypoint;'))].replace('unit.','unit->').replace('search.','search->')
+run(r'''
+#include <cassert>
+#include <string>
+struct Waypoint{float radius=30;float GetCompletionRadius(){return radius;}};
+struct Unit{Waypoint*WaypointValue=nullptr;int Assignment=1;};
+int logs=0;void PrintFormat(std::string,float,float){logs++;}
+bool Delivered(Unit* unit,int order,float queuedRadius){Waypoint*search=nullptr;TAIL}
+int main(){Waypoint wp;Unit unit;unit.WaypointValue=&wp;assert(!Delivered(&unit,1,45));wp.radius=45;assert(Delivered(&unit,1,45));wp.radius=110;assert(Delivered(&unit,1,110));assert(!Delivered(&unit,1,45));unit.Assignment=2;assert(!Delivered(&unit,1,110));unit.WaypointValue=nullptr;int before=logs;assert(!Delivered(&unit,1,110)&&logs==before);}
+'''.replace('TAIL',tail.replace('unit->Waypoint','unit->WaypointValue')))
+print('PASS: exact patrol-hq finite responder preparation and native search-radius receipt verification')
@@ -0,0 +1,5 @@
PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines
PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths
PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged
PASS: actual patrol fixture leaves prison before responder HQ proof, followed by separate permanent-patrol observation
PASS: exact patrol-hq finite responder preparation and native search-radius receipt verification
@@ -0,0 +1,53 @@
{
"approved": true,
"blockers": [],
"review_mode": "Independent read-only source, test, evidence and hash review; no compiler/test/native runs by reviewer.",
"patch_sha256": "fa6dfb8a34a2d2f04aa2c9e19f70f31b42e3daebd4427cea8c9d3eeb3968d611",
"source_manifest_sha256": "ccffa8d016c46b053b881e54928006af26574a57364f1cc607f8f5f3c1433a73",
"files": {
"addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c": {
"baseline_sha256": "81bf30d358c7b100248be17cbc2e6beb2f107300c939aede8a92d273eb7dae1f",
"proposed_sha256": "7fe3e8ee04a15a50fe6d665027a5ef0ff945b4c587113713b9d462382b0eccd1"
},
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c": {
"baseline_sha256": "6f63edca662a6f83a0874a104b030823cbbe1c4260ec3b41368899f738981efb",
"proposed_sha256": "9784b551cbb91f017004fb728ce08b5c36bead0ffc83aa7149c2c46a7c4dc495"
},
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c": {
"baseline_sha256": "7d8dcb96efeeeb44f46f604f96bae337d3200fd15347a8019259d21a94f67d81",
"proposed_sha256": "bc548e03f217a6f07f1d0893157281521a531eb21c54763cabba16064a91532c"
},
"tests/test_native_memory_deadlines.py": {
"baseline_sha256": "597d4f430a4fb9bd684958c60d44d98703410b9bab629290e21f857c001c7112",
"proposed_sha256": "66cf766ff2477518610eaab231572633ae7c2920cb183909bef346f9835ba889"
}
},
"checks": [
"Scenario helper and invocation are WORKBENCH-only; exactpatrol-hq and non-terrain-independent gate. Other selectors return without mutation. Correct outpost/guarded/zeroexistingresponse policy required; finite2 responders and fixedoffset configured before generated-site and sitepopulation begin, followed by normal definition validation.",
"No production force/assignment eligibility or sitepopulation policy changed. Permanent guards, cache, route and response creation/cleanup remain existing systems. Added2 physical responders are declared fixture setup, not ordinary catalog behavior.",
"Canonical original observer unlock/phase guard remains before initial responder stage131. Successful responder test alone starts separate existing permanent-patrol stage132; terminal step133 still requires actual movement-probe success.",
"Native radius check retains queued scalar before real delivered-order method then reads GetCompletionRadius from assigned native waypoint; wrongradius or differentassignment refuses. It does not prove110m physical search coverage.",
"Existing permanent movement probe exact unchanged hash:180000ms,5 ordered completions and>=8m displacement; overall420000ms bound unchanged. No forced completion/teleport or assignment clearing added.",
"Actual setup helper, initial branch and delivery receipt tail execute in focused C++ harness with documented native/definition doubles. Final baseline isolates prior scenario definition and compiles then runtime-fails expected2responders assertion; final proposed log passes.",
"Exact frozen4file,patch,log,unchangedpolicy hashes verified; current integrated files equal proposal."
],
"qualification": [
"Pending third native run is NOT marked complete or passed in this review. Parent-reported queued45/native45 and responder queue/combat/home checks are partial progress only, not final traversal proof.",
"Responder HQ queue/preemption/synthetic contact quiet-resume/home defence and permanent guard physical patrol are separate groups. No same-group physical resumption or radio-transit claim.",
"Existing generic completion wording mentions physical resumption; evidence/user-facing summaries must preserve the separate-role qualification rather than infer broader behavior.",
"Offset(-20,0,-20) geometrical/path adequacy, native full outcome and multiplayer remain unqualified until actual evidence."
],
"evidence_sha256": {
"green.log": "23f87423bbf7f6d7cb18fdfda8c5298c1aa16e0c34cf3d5f4ff9152f92e4a6ec",
"baseline-red.log": "099937bd8f0be5536f1e58a3f0d9007655c6343adb6fcb709acd0f903a52f404",
"before-ordering-green.log": "c8bf4022e08ab0421fe8555adb5cb06afe9a52f66c1e0fac00110815d9a42e93",
"before-ordering-baseline-red.log": "925b6dd4cf0a20d3c6f80b0dfd0aeee68a21af59b6c7b3c01c53425328acf7ff",
"finding.json": "057a47fc85e09806d09a1f54bcec1922187593744033d519bcd69c7c4ca5f42a"
},
"unchanged_policy": {
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c": "15a28876954bbbe0e80d5b5b19e7336f154023cd05b67d65dd709ae7fd9ac712",
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePatrolProbe.c": "5a2af71cc4cfccc7dd7c23584398a38290eba1a8894cd46f6967252ff3a7087c",
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c": "8709753e86dc2ca777183f08cb8fbb9851e0c0de495ca01520ebcb1b3dfa59b9",
"addons/core/Configs/ERR/Tests/LegacySites.conf": "3864eb638d0ed4dd313013d2d787f975ae6ce4bbe238569919f76b73d5f7ca42"
}
}
@@ -0,0 +1,42 @@
{
"files": {
"addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c": {
"baseline_sha256": "81bf30d358c7b100248be17cbc2e6beb2f107300c939aede8a92d273eb7dae1f",
"proposed_sha256": "7fe3e8ee04a15a50fe6d665027a5ef0ff945b4c587113713b9d462382b0eccd1"
},
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c": {
"baseline_sha256": "6f63edca662a6f83a0874a104b030823cbbe1c4260ec3b41368899f738981efb",
"proposed_sha256": "9784b551cbb91f017004fb728ce08b5c36bead0ffc83aa7149c2c46a7c4dc495"
},
"addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c": {
"baseline_sha256": "7d8dcb96efeeeb44f46f604f96bae337d3200fd15347a8019259d21a94f67d81",
"proposed_sha256": "bc548e03f217a6f07f1d0893157281521a531eb21c54763cabba16064a91532c"
},
"tests/test_native_memory_deadlines.py": {
"baseline_sha256": "597d4f430a4fb9bd684958c60d44d98703410b9bab629290e21f857c001c7112",
"proposed_sha256": "66cf766ff2477518610eaab231572633ae7c2920cb183909bef346f9835ba889"
}
},
"patch_sha256": "fa6dfb8a34a2d2f04aa2c9e19f70f31b42e3daebd4427cea8c9d3eeb3968d611",
"results": {
"green": 0,
"baseline_scenario_red": 1,
"baseline_behavior": "compiled/runtime SIGABRT; current NativeSmoke/radius overlay isolates missing responder definition setup",
"compilers_finished": true
},
"logs": {
"green.log": "23f87423bbf7f6d7cb18fdfda8c5298c1aa16e0c34cf3d5f4ff9152f92e4a6ec",
"baseline-red.log": "099937bd8f0be5536f1e58a3f0d9007655c6343adb6fcb709acd0f903a52f404",
"before-ordering-green.log": "c8bf4022e08ab0421fe8555adb5cb06afe9a52f66c1e0fac00110815d9a42e93",
"before-ordering-baseline-red.log": "925b6dd4cf0a20d3c6f80b0dfd0aeee68a21af59b6c7b3c01c53425328acf7ff",
"finding.json": "057a47fc85e09806d09a1f54bcec1922187593744033d519bcd69c7c4ca5f42a"
},
"unchanged_policy": {
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c": "15a28876954bbbe0e80d5b5b19e7336f154023cd05b67d65dd709ae7fd9ac712",
"addons/core/Scripts/Game/ERR/00_Core/ERR_NativePatrolProbe.c": "5a2af71cc4cfccc7dd7c23584398a38290eba1a8894cd46f6967252ff3a7087c",
"addons/core/Scripts/Game/ERR/00_Core/ERR_PrisonAI.c": "8709753e86dc2ca777183f08cb8fbb9851e0c0de495ca01520ebcb1b3dfa59b9",
"addons/core/Configs/ERR/Tests/LegacySites.conf": "3864eb638d0ed4dd313013d2d787f975ae6ce4bbe238569919f76b73d5f7ca42"
},
"native_runs": 0,
"live_edits": false
}
@@ -0,0 +1,105 @@
[ComponentEditorProps(category: "Reintroduced", description: "Authored scenario definition and finite population lifecycle")]
class ERR_ScenarioComponentClass : ScriptComponentClass {}
class ERR_ScenarioComponent : ScriptComponent
{
[Attribute("", UIWidgets.ResourceNamePicker, "Scenario site definitions", "conf")]
protected ResourceName m_Definition;
[Attribute("0", UIWidgets.CheckBox, "Generate all sites on this terrain; requires terrain-specific AI navigation")]
protected bool m_TerrainIndependent;
protected ref ERR_SiteService m_Sites;
protected bool m_Started;
protected ref ERR_ScenarioDefinition m_RuntimeDefinition;
protected ref ERR_GeneratedSites m_Generated;
protected bool m_Finished;
protected bool m_Failed;
ERR_SiteService Sites() { return m_Sites; }
string FailureReason()
{
ERR_HQ hq = ERR_HQ.Get();
if (hq && hq.Scouts() && !hq.Scouts().Error().IsEmpty()) return hq.Scouts().Error();
if (m_Generated && !m_Generated.Error().IsEmpty()) return m_Generated.Error();
if (m_Sites && !m_Sites.FailureReason().IsEmpty()) return m_Sites.FailureReason();
return "Scenario or additional force preparation failed.";
}
#ifdef WORKBENCH
// The historical catalog predates finite mobile site squads. Configure this
// isolated fixture before ordinary population, never borrow permanent guards.
protected bool ERR_ConfigurePatrolHQFixture(string outcome)
{
if (m_TerrainIndependent || outcome != "patrol-hq") return true;
if (!m_RuntimeDefinition) return false;
ERR_SiteDefinition site = m_RuntimeDefinition.Find("everon-outpost-1");
if (!site || site.Kind != ERR_LocationKind.OUTPOST || !site.Guarded || site.ResponseCount != 0) return false;
site.ResponseCount = 2;
site.ResponseOffset = "-20 0 -20";
return m_RuntimeDefinition.Valid();
}
#endif
ERR_PrepareResult Prepare()
{
ERR_GameMode mode = ERR_GameMode.Get();
if (!Replication.IsServer() || !mode || m_Failed) return ERR_PrepareResult.FAILED;
if (m_Finished) return ERR_PrepareResult.READY;
if (!m_Started)
{
m_Started = true;
#ifdef WORKBENCH
string historicalFixture;
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", historicalFixture) && historicalFixture != "normal-escape-route" && historicalFixture != "mounted-ammo" && historicalFixture != "alarm-hearing" && historicalFixture != "optional-site-skip" && historicalFixture != "infantry-combat" && historicalFixture != "site-garrisons" && historicalFixture != "generated-sites" && historicalFixture != "opening-tools" && historicalFixture != "airfield-markers" && historicalFixture != "air-recon" && historicalFixture != "air-logistics" && historicalFixture != "air-assault")
m_Definition = "{ACA98B44E2173605}Configs/ERR/Tests/LegacySites.conf";
#endif
m_RuntimeDefinition = ERR_ScenarioDefinition.Cast(SCR_BaseContainerTools.CreateInstanceFromPrefab(m_Definition));
if (!m_RuntimeDefinition || !m_RuntimeDefinition.Valid()) { m_Failed = true; return ERR_PrepareResult.FAILED; }
#ifdef WORKBENCH
// Existing deterministic probes intentionally use authored coordinates.
string fixture;
if (!m_TerrainIndependent && System.GetCLIParam("errSmokeOutcome", fixture) && fixture != "normal-escape-route" && fixture != "mounted-ammo" && fixture != "alarm-hearing" && fixture != "optional-site-skip" && fixture != "infantry-combat" && fixture != "site-garrisons" && fixture != "generated-sites" && fixture != "airfield-markers" && fixture != "air-recon" && fixture != "air-logistics" && fixture != "air-assault")
foreach (ERR_SiteDefinition fixedSite : m_RuntimeDefinition.Sites) fixedSite.RandomClear = false;
if (!ERR_ConfigurePatrolHQFixture(fixture)) { m_Failed = true; return ERR_PrepareResult.FAILED; }
#endif
if (m_TerrainIndependent) m_Generated = new ERR_TerrainSites();
else m_Generated = new ERR_GeneratedSites();
if (!m_Generated.Begin(m_RuntimeDefinition)) { m_Failed = true; return ERR_PrepareResult.FAILED; }
}
ERR_PrepareResult placement = m_Generated.Tick();
if (placement == ERR_PrepareResult.FAILED) m_Failed = true;
if (placement != ERR_PrepareResult.READY) return placement;
if (!m_Sites)
{
m_Sites = new ERR_SiteService(mode);
if (!m_Sites.Begin(m_RuntimeDefinition, FieldMap())) { m_Failed = true; return ERR_PrepareResult.FAILED; }
}
ERR_PrepareResult result = m_Sites.Prepare();
if (result != ERR_PrepareResult.READY) return result;
ERR_PrepareResult population = PrepareAdditionalPopulation();
if (population == ERR_PrepareResult.FAILED) { m_Failed = true; m_Sites.Rollback(); }
if (population != ERR_PrepareResult.READY) return population;
if (!PrepareAdditionalForces())
{
m_Failed = true;
m_Sites.Rollback();
return ERR_PrepareResult.FAILED;
}
mode.OnPhaseChanged().Insert(m_Sites.PhaseChanged);
mode.OnStop().Insert(m_Sites.Stop);
mode.OnTick().Insert(m_Sites.Tick);
GetGame().GetCallqueue().CallLater(ActivateSites, 2000, false);
m_Finished = true;
return ERR_PrepareResult.READY;
}
protected void ActivateSites()
{
ERR_GameMode mode = ERR_GameMode.Get();
if (mode && mode.Active() && m_Sites) m_Sites.Activate();
}
protected ERR_PrepareResult PrepareAdditionalPopulation() { return ERR_PrepareResult.READY; }
protected bool PrepareAdditionalForces() { return true; }
protected ResourceName FieldMap() { return ERR_Supplies.Map(); }
protected override void OnDelete(IEntity owner)
{
if (GetGame()) GetGame().GetCallqueue().Remove(ActivateSites);
if (m_Sites) m_Sites.Stop();
super.OnDelete(owner);
}
}
@@ -0,0 +1,121 @@
#ifdef WORKBENCH
// Test access only: trigger the real delivered-order method on an existing
// registered squad. Radio transit is intentionally not simulated by this test.
modded class ERR_HQ
{
bool ERR_TestPatrolSearch(SCR_AIGroup group, out AIWaypoint search)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group) continue;
if (unit.Assignment || unit.PendingOrder || unit.Waypoint || !unit.Eligible(m_Mode)) return false;
ERR_HQReport order = new ERR_HQReport();
order.Target = group;
order.Source = group;
order.Position = group.GetOrigin() + "40 0 0";
order.ObservedAt = GetGame().GetWorld().GetWorldTime();
order.MessageId = m_Journal.Message();
Accept(order, order.ObservedAt);
ERR_HQBelief belief = m_Memory.Get(group);
if (!belief) return false;
ERR_HQTask task = new ERR_HQTask();
task.BeliefId = belief.BeliefId; task.BeliefRevision = belief.BeliefRevision;
task.SearchRadiusMetres = ERR_HQPursuitRules.SearchRadius(belief.Radius);
task.Destination = order.Position; task.QueuedAt = order.ObservedAt;
task.ExpiresAt = order.ObservedAt + 120000;
unit.PendingTask = task;
unit.PendingOrder = order;
float queuedRadius = task.SearchRadiusMetres;
DeliverOrder(unit, order.ObservedAt);
search = unit.Waypoint;
if (search) PrintFormat("ERR PATROL HQ RADIUS: queued=%1 native=%2", queuedRadius, search.GetCompletionRadius());
return search && unit.Assignment == order && search.GetCompletionRadius() == queuedRadius;
}
return false;
}
bool ERR_TestPatrolRelease(SCR_AIGroup group, AIWaypoint search)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group || unit.Waypoint != search) continue;
unit.Release();
return !unit.Assignment && !unit.PendingOrder && !unit.Waypoint;
}
return false;
}
bool ERR_TestPatrolCombat(SCR_AIGroup group, AIWaypoint search, bool contact, float now, int expected)
{
foreach (ERR_HQUnit unit : m_Units)
{
if (unit.Group != group || unit.Waypoint != search) continue;
return unit.InfantryCombat.Update(unit, unit.Eligible(m_Mode), contact, now) == expected;
}
return false;
}
}
class ERR_NativePatrolHQProbe
{
protected ERR_GameMode m_Mode;
protected ERR_SiteRuntime m_Site;
protected SCR_AIGroup m_Group;
protected AIWaypoint m_Search;
protected ref array<AIWaypoint> m_Baseline = {};
protected int m_Step;
protected float m_Wait;
void ERR_NativePatrolHQProbe(ERR_GameMode mode) { m_Mode = mode; }
protected bool Check(bool condition, string detail)
{
if (condition) { Print("ERR SMOKE PASS: patrol HQ " + detail); return true; }
Print("ERR SMOKE FAIL: patrol HQ " + detail, LogLevel.ERROR); return false;
}
int Tick(float now)
{
if (now < m_Wait) return 0;
ERR_HQ hq = ERR_HQ.Get();
if (!Check(hq != null, "real HQ available")) return -1;
if (m_Step == 0)
{
m_Site = m_Mode.Sites().Find("everon-outpost-1");
if (!Check(m_Site && m_Site.Responders && m_Site.Responders.Group, "existing mobile outpost responders available")) return -1;
m_Group = m_Site.Responders.Group;
if (!Check(m_Site.Responders.InitialGuardCount == 2 && m_Mode.ForceLiving(m_Group) == 2, "two original mobile responders remain distinct from permanent guards")) return -1;
m_Group.GetWaypoints(m_Baseline);
if (!Check(!m_Baseline.IsEmpty() && hq.ERR_TestPatrolSearch(m_Group, m_Search), "real delivered-order method assigns existing squad")) return -1;
m_Step = 1; m_Wait = now + 750; return 0;
}
if (m_Step == 1)
{
array<AIWaypoint> queue = {};
m_Group.GetWaypoints(queue);
if (!Check(m_Search && !queue.IsEmpty() && queue[0] == m_Search && m_Group.GetCurrentWaypoint() == m_Search, "Search preempts native patrol")) return -1;
foreach (AIWaypoint waypoint : m_Baseline)
if (!Check(queue.Contains(waypoint), "preempted baseline route remains queued")) return -1;
if (!Check(hq.ERR_TestPatrolCombat(m_Group, m_Search, true, now, 1), "injected local contact pauses actual HQ search")) return -1;
queue.Clear(); m_Group.GetWaypoints(queue);
if (!Check(!queue.Contains(m_Search), "combat removes HQ search from native queue without deleting it")) return -1;
foreach (AIWaypoint home : m_Baseline)
if (!Check(queue.Contains(home), "combat preserves baseline patrol ownership")) return -1;
if (!Check(hq.ERR_TestPatrolCombat(m_Group, m_Search, false, now + 14999, 0), "quiet grace period prevents immediate marching")) return -1;
if (!Check(hq.ERR_TestPatrolCombat(m_Group, m_Search, false, now + 15000, 2), "quiet period resumes the same unexpired assignment")) return -1;
queue.Clear(); m_Group.GetWaypoints(queue);
if (!Check(!queue.IsEmpty() && queue[0] == m_Search, "resumed search is queued exactly once at the front")) return -1;
if (!Check(hq.ERR_TestPatrolRelease(m_Group, m_Search), "real HQ release removes only its assignment")) return -1;
m_Step = 2; m_Wait = now + 750; return 0;
}
if (m_Step == 2)
{
AIWaypoint current = m_Group.GetCurrentWaypoint();
if (!Check(current && m_Baseline.Contains(current), "native home defence returns after Search release")) return -1;
m_Step = 3; return 1;
}
return 1;
}
void Stop()
{
ERR_HQ hq = ERR_HQ.Get();
if (hq && m_Site && m_Search) hq.ERR_TestPatrolRelease(m_Group, m_Search);
}
void ~ERR_NativePatrolHQProbe() { Stop(); }
}
#endif
@@ -0,0 +1,211 @@
"""Actual native-fixture startup/deadline gate, engine boundaries doubled."""
import os,sys,re
from pathlib import Path
REPO=Path(os.environ.get('ERR_REPO', Path(__file__).resolve().parents[1]))
sys.path.insert(0,str(REPO/'tests'))
from policy_harness import body,run
p=Path(os.environ.get('ERR_MEMORY_PROBE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeHQMemoryProbe.c'))
s=body(p,'int Tick(ERR_HQ hq, float now)')
# Execute the unchanged deadline/startup prefix; payload/native radio assertions
# after this gate remain the full fixture's responsibility.
s=s[:s.index(' if (!m_ValueChecked)')]+ ' }\n return 1;\n }'
s=s.replace('ERR_HQ hq','ERR_HQ* hq').replace('hq.','hq->')
run(r'''
#include <cassert>
#include <string>
using string = std::string;
struct CLI {string outcome="hq-memory"; void GetCLIParam(string,string& value){value=outcome;}} System;
struct ERR_HQ{bool ready=true;bool ERR_ProbeSquadsReady(){return ready;}};
struct Probe{bool enabled=true,m_Mode=true,m_Failed=false,m_AutoChecked=false;int m_Step=0,automatic=0,checks=0;float m_Deadline=0;bool Enabled(){return enabled;}void Check(bool,std::string){checks++;m_Failed=true;}int AutomaticConvoy(ERR_HQ*){if(automatic==1)m_AutoChecked=true;return automatic;}
METHOD
};
int main(){ERR_HQ hq;
Probe p;assert(p.Tick(&hq,1000)==0&&p.m_Deadline==151000);assert(p.Tick(&hq,130000)==0&&p.m_Deadline==151000);p.automatic=1;assert(p.Tick(&hq,145000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,200000)==1&&p.m_Deadline==265000);assert(p.Tick(&hq,265000)==1);assert(p.Tick(&hq,265001)==-1&&p.checks==1);
Probe missing;assert(missing.Tick(&hq,0)==0);assert(missing.Tick(&hq,150001)==-1&&missing.checks==1);
Probe refused;refused.Tick(&hq,0);refused.automatic=-1;assert(refused.Tick(&hq,40000)==-1&&refused.m_Deadline==150000&&!refused.m_AutoChecked);
Probe unavailable;hq.ready=false;assert(unavailable.Tick(&hq,0)==0);assert(unavailable.Tick(&hq,150001)==-1);hq.ready=true;
System.outcome="hq-awareness"; hq.ready=false; Probe awareness; assert(awareness.Tick(&hq,1000)==1 && !awareness.m_AutoChecked && awareness.m_Deadline==151000); assert(awareness.Tick(&hq,151001)==-1);
Probe invalid;invalid.enabled=false;assert(invalid.Tick(&hq,0)==-1&&invalid.m_Deadline==0);
}
'''.replace('METHOD',s))
smoke=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')).read_text()
assert 'if (m_ERRTestHQMemory) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 360000;' in smoke
assert 'if (m_ERRTestHQBody) m_ERRSmokeDeadline = GetWorld().GetWorldTime() + 240000;' in smoke
# Execute the actual startup selector, so enlarged full-route budgets cannot
# accidentally extend a death/radio test or shorten an existing specialized run.
start=smoke.index('m_ERRTestLostHelicopter = System.GetCLIParam(')
end=smoke.index('OnStop().Insert(ERR_StopHQPatrol);',start)
selector=smoke[start:end].replace('System.GetCLIParam("errSmokeOutcome", outcome) && ', '')
selector=selector.replace('GetWorld().GetWorldTime()', 'now').replace('ERR_NativeConvoyCoverProbe.Enabled()', 'cover').replace('ERR_NativeMountedCourierProbe.Enabled()', 'courier').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance').replace('ERR_NativeMountedInterceptProbe.Enabled()', 'intercept')
flags=sorted(set(re.findall(r'\bm_ERRTest\w+',selector)))
run(r'''
#include <cassert>
#include <string>
using string = std::string;
float deadline(string outcome,float now,bool cover=false,bool courier=false,bool maintenance=false,bool intercept=false){
[[maybe_unused]] bool DECLARATIONS;
float m_ERRSmokeDeadline=0;
SELECTOR
return m_ERRSmokeDeadline;
}
int main(){
for(float now : {0.0f, 42000.0f}) {
for(string outcome : {"", "victory", "lost-helicopter"}) assert(deadline(outcome,now)==now+300000);
for(string outcome : {"escape-route", "normal-escape-route", "player-death", "radio-network", "hq-response", "map-ownership"}) assert(deadline(outcome,now)==now+150000);
assert(deadline("patrol-hq",now)==now+420000);
assert(deadline("hq-memory",now)==now+360000);
assert(deadline("hq-body",now)==now+240000);
assert(deadline("convoy",now)==now+540000);
assert(deadline("convoy",now,false,false,false,true)==now+840000);
assert(deadline("supply-convoys",now)==now+840000);
assert(deadline("convoy",now,true)==now+840000);
assert(deadline("mounted-ammo",now)==now+330000);
assert(deadline("mounted-ammo",now,false,true)==now+750000);
assert(deadline("normal-escape-route",now,false,false,true)==now+330000);
}
}
'''.replace('DECLARATIONS', ','.join(flag+'=false' for flag in flags)).replace('SELECTOR',selector))
print('PASS: bounded HQ startup/memory windows and outcome-specific full-route deadlines')
# The new route diagnostic must leave the original administrator/reroll stress
# intact and may skip only those extras, never clue or radio progression gates.
route=body(Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c')), 'bool ERR_CheckRouteExtras(float now)').replace('ERR_NativeMaintenanceProbe.Enabled()', 'maintenance')
run(r'''
#include <cassert>
#include <string>
struct Probe {
bool m_ERRTestEscapeRoute=false,rerollReady=false,maintenance=false,maintenanceReady=false;
int maintenanceCalls=0; bool ERR_RunMaintenance(float){maintenanceCalls++;return maintenanceReady;}
int adminResult=0,adminCalls=0,rerollCalls=0,failures=0;
int ERR_ProbeAdminMenu(float){adminCalls++;return adminResult;}
bool ERR_Check(bool result,std::string){if(!result)failures++;return result;}
bool ERR_CheckIntelReroll(float){rerollCalls++;return rerollReady;}
METHOD
};
int main(){
Probe route;route.m_ERRTestEscapeRoute=true;assert(route.ERR_CheckRouteExtras(100));
assert(route.adminCalls==0&&route.rerollCalls==0&&route.failures==0);
route.maintenance=true;assert(!route.ERR_CheckRouteExtras(101)&&route.maintenanceCalls==1);
route.maintenanceReady=true;assert(route.ERR_CheckRouteExtras(102)&&route.maintenanceCalls==2&&route.adminCalls==0&&route.rerollCalls==0);
Probe full;assert(!full.ERR_CheckRouteExtras(100)&&full.adminCalls==1&&full.rerollCalls==0);
full.adminResult=-1;assert(!full.ERR_CheckRouteExtras(200)&&full.failures==1&&full.rerollCalls==0);
full.adminResult=1;assert(!full.ERR_CheckRouteExtras(300)&&full.rerollCalls==1);
full.rerollReady=true;assert(full.ERR_CheckRouteExtras(400)&&full.rerollCalls==2);
}
'''.replace('METHOD',route))
assert 'if (!ERR_CheckRouteExtras(now)) return;' in smoke
assert 'if (!ERR_Check(m_ERRSmokeRadio.Knowledge() == 2,' in smoke
assert 'if (!ERR_Check(Phase() == ERR_Phase.EXTRACTION,' in smoke
print('PASS: explicit escape-route skips only administrator stress, preserving original full-fixture wait/failure paths')
# Execute the actual scenario-selection expressions: normal route must retain
# the ordinary catalog AND random placement, while historical probes stay fixed.
scenario=Path(os.environ.get('ERR_SCENARIO_SOURCE',REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c')).read_text()
conditions=[]
for variable in ('historicalFixture','fixture'):
line=next(line.strip() for line in scenario.splitlines() if line.strip().startswith('if (') and 'System.GetCLIParam("errSmokeOutcome", '+variable+')' in line)
expression=line[4:-1].replace('System.GetCLIParam("errSmokeOutcome", '+variable+')','supplied').replace(variable,'outcome')
conditions.append(expression)
run(r'''
#include <cassert>
#include <string>
using string=std::string;
bool legacy(bool supplied,string outcome,bool m_TerrainIndependent=false){return LEGACY;}
bool fixed(bool supplied,string outcome,bool m_TerrainIndependent=false){return FIXED;}
int main(){
for(string route : {"normal-escape-route","generated-sites","optional-site-skip"}) {assert(!legacy(true,route));assert(!fixed(true,route));}
for(string route : {"escape-route","victory","transport-survey"}) {assert(legacy(true,route));assert(fixed(true,route));}
assert(!legacy(false,"escape-route")&&!fixed(false,"escape-route"));
}
'''.replace('LEGACY',conditions[0]).replace('FIXED',conditions[1]))
print('PASS: normal escape route keeps ordinary catalog and generated placement; historical selectors remain unchanged')
# Execute the actual patrol-hq admission branch. The existing production
# prison-AI/progression tests cover canonical UnlockPrison and phase callbacks;
# here native damage, allocation and unlock boundaries are explicit doubles.
smoke_path=Path(os.environ.get('ERR_SMOKE_SOURCE',REPO/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c'))
smoke_tick=body(smoke_path,'protected void ERR_SmokeTick(')
a=smoke_tick.index('if (m_ERRTestPatrolHQ)'); b=smoke_tick.index('{',a); i=b+1; depth=1
while depth:
depth+=(smoke_tick[i]=='{')-(smoke_tick[i]=='}');i+=1
patrol_branch=smoke_tick[a:i]
patrol_branch=patrol_branch.replace('DamageManagerComponent.Cast(m_ERRSmokePlayer.FindComponent(DamageManagerComponent)).EnableDamageHandling(false);','ProtectOriginal(m_ERRSmokePlayer);').replace('ERR_Phase.RADIO_SEARCH','2')
run(r'''
#include <cassert>
#include <string>
struct Fixture;
struct ERR_NativePatrolHQProbe {explicit ERR_NativePatrolHQProbe(Fixture* owner);};
struct ERR_NativePatrolProbe {explicit ERR_NativePatrolProbe(Fixture*){assert(false && "permanent patrol must follow responder HQ proof");}};
struct Fixture {
bool m_ERRTestPatrolHQ=true,allowed=true,wrongPhase=false,active=false;
int m_ERRSmokePlayer=7,phase=1,unlocks=0,failures=0,allocations=0,protections=0,m_ERRSmokeStep=0;
ERR_NativePatrolHQProbe* m_ERRHQPatrolOrder=nullptr;
ERR_NativePatrolProbe* m_ERRHQPatrolMovement=nullptr;
~Fixture(){delete m_ERRHQPatrolOrder;delete m_ERRHQPatrolMovement;}
void ProtectOriginal(int actor){assert(actor==7);protections++;}
bool UnlockPrison(int actor){assert(actor==7);unlocks++;if(!allowed)return false;if(!wrongPhase){phase=2;active=true;}return true;}
int Phase(){return phase;}
bool ERR_Check(bool ok,std::string){if(!ok)failures++;return ok;}
void Start(){BRANCH}
};
ERR_NativePatrolHQProbe::ERR_NativePatrolHQProbe(Fixture* owner){assert(owner->phase==2&&owner->active&&owner->unlocks==1);owner->allocations++;}
int main(){
Fixture healthy;healthy.Start();assert(healthy.m_ERRSmokeStep==131&&healthy.allocations==1&&healthy.failures==0&&healthy.protections==1);
Fixture refused;refused.allowed=false;refused.Start();assert(refused.unlocks==1&&refused.failures==1&&refused.allocations==0&&refused.m_ERRSmokeStep==0&&!refused.active);
Fixture wrong;wrong.wrongPhase=true;wrong.Start();assert(wrong.unlocks==1&&wrong.failures==1&&wrong.allocations==0&&wrong.m_ERRSmokeStep==0);
Fixture other;other.m_ERRTestPatrolHQ=false;other.Start();assert(other.unlocks==0&&other.allocations==0&&other.protections==0&&other.m_ERRSmokeStep==0);
}
'''.replace('BRANCH',patrol_branch))
assert 'if (result == 1) { m_ERRHQPatrolMovement = new ERR_NativePatrolProbe(this); m_ERRSmokeStep = 132; }' in body(smoke_path,'protected void ERR_SmokeTick(')
print('PASS: actual patrol fixture leaves prison before responder HQ proof, followed by separate permanent-patrol observation')
# Execute the isolated definition mutation on the actual legacy outpost fields.
# Definition.Valid is a boundary double here; full production authoring policy
# remains enforced by the normal preparation path and repository checks.
scenario_path=Path(os.environ.get('ERR_SCENARIO_SOURCE',REPO/'addons/core/Scripts/Game/ERR/00_Core/ERR_ScenarioComponent.c'))
legacy=(REPO/'addons/core/Configs/ERR/Tests/LegacySites.conf').read_text()
start=legacy.index('Id "everon-outpost-1"');end=legacy.index('\n }',start);outpost=legacy[start:end]
assert 'ResponseCount' not in outpost and 'Guarded 1' in outpost and 'GuardOffset 16 0 0' in outpost
assert outpost.count('ERR_SitePatrolPoint')==4
if 'protected bool ERR_ConfigurePatrolHQFixture(' in scenario:
configure=body(scenario_path,'protected bool ERR_ConfigurePatrolHQFixture(').replace('protected ','').replace('ERR_SiteDefinition site =','Site* site =').replace('m_RuntimeDefinition.','m_RuntimeDefinition->').replace('site.','site->').replace('ERR_LocationKind.OUTPOST','3').replace('"-20 0 -20"','Vec{-20,0,-20}')
assert scenario.index('ERR_ConfigurePatrolHQFixture(fixture)') < scenario.index('m_Generated.Begin(m_RuntimeDefinition)') < scenario.index('m_Sites.Begin(m_RuntimeDefinition')
else:
# Baseline has no prepopulation mutation; executing its unchanged definition
# must fail the required two-mobile-originals assertion below.
configure='bool ERR_ConfigurePatrolHQFixture(string){return true;}'
run(r'''
#include <cassert>
#include <string>
using string=std::string;
struct Vec{float x,y,z;};
struct Site {int Kind=3,ResponseCount=0,GuardCount=0,patrolPoints=4,stock=4;bool Guarded=true;Vec ResponseOffset{-12,0,0},GuardOffset{16,0,0};};
struct Definition{Site site;bool present=true,valid=true;int validations=0;Site*Find(string id){assert(id=="everon-outpost-1");return present?&site:nullptr;}bool Valid(){validations++;return valid;}};
struct Fixture {bool m_TerrainIndependent=false;Definition* m_RuntimeDefinition=nullptr;METHOD};
int main(){
Definition d;Fixture f;f.m_RuntimeDefinition=&d;assert(f.ERR_ConfigurePatrolHQFixture("patrol-hq"));
assert(d.site.ResponseCount==2&&d.site.ResponseOffset.x==-20&&d.site.ResponseOffset.z==-20&&d.site.ResponseOffset.y==0&&d.validations==1);
assert(d.site.Guarded&&d.site.GuardCount==0&&d.site.GuardOffset.x==16&&d.site.patrolPoints==4&&d.site.stock==4);
for(string outcome:{"","normal-escape-route","patrol","victory","hq-memory"}){Definition other;Fixture g;g.m_RuntimeDefinition=&other;assert(g.ERR_ConfigurePatrolHQFixture(outcome)&&other.site.ResponseCount==0&&other.validations==0);}
Definition terrain;Fixture t;t.m_TerrainIndependent=true;t.m_RuntimeDefinition=&terrain;assert(t.ERR_ConfigurePatrolHQFixture("patrol-hq")&&terrain.site.ResponseCount==0);
for(int fault=0;fault<6;fault++){Definition bad;Fixture x;x.m_RuntimeDefinition=&bad;if(fault==0)x.m_RuntimeDefinition=nullptr;if(fault==1)bad.present=false;if(fault==2)bad.site.Kind=0;if(fault==3)bad.site.Guarded=false;if(fault==4)bad.site.ResponseCount=1;if(fault==5)bad.valid=false;assert(!x.ERR_ConfigurePatrolHQFixture("patrol-hq"));}
}
'''.replace('METHOD',configure))
# Test actual final receipt decision. Delivery/native allocation are covered by
# their own methods and the real opt-in run; this checks the native getter is
# required instead of accepting an assignment with the untouched prefab radius.
patrol_path=Path(os.environ.get('ERR_PATROL_HQ_SOURCE',REPO/'addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c'))
patrol=body(patrol_path,'bool ERR_TestPatrolSearch(')
assert 'float queuedRadius = task.SearchRadiusMetres;' in patrol
assert patrol.index('float queuedRadius =') < patrol.index('DeliverOrder(unit,')
tail=patrol[patrol.index(' search = unit.Waypoint;'):patrol.index('\n }',patrol.index(' search = unit.Waypoint;'))].replace('unit.','unit->').replace('search.','search->')
run(r'''
#include <cassert>
#include <string>
struct Waypoint{float radius=30;float GetCompletionRadius(){return radius;}};
struct Unit{Waypoint*WaypointValue=nullptr;int Assignment=1;};
int logs=0;void PrintFormat(std::string,float,float){logs++;}
bool Delivered(Unit* unit,int order,float queuedRadius){Waypoint*search=nullptr;TAIL}
int main(){Waypoint wp;Unit unit;unit.WaypointValue=&wp;assert(!Delivered(&unit,1,45));wp.radius=45;assert(Delivered(&unit,1,45));wp.radius=110;assert(Delivered(&unit,1,110));assert(!Delivered(&unit,1,45));unit.Assignment=2;assert(!Delivered(&unit,1,110));unit.WaypointValue=nullptr;int before=logs;assert(!Delivered(&unit,1,110)&&logs==before);}
'''.replace('TAIL',tail.replace('unit->Waypoint','unit->WaypointValue')))
print('PASS: exact patrol-hq finite responder preparation and native search-radius receipt verification')