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:
+22
@@ -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>.
|
||||
+1164
File diff suppressed because it is too large
Load Diff
+120
@@ -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
|
||||
+44
@@ -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"
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: production prison AI hold/release, repeat activation, authority/faction/player exclusion, dead actor and stop cleanup
|
||||
+2
@@ -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
|
||||
+39
@@ -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
|
||||
}
|
||||
+1165
File diff suppressed because it is too large
Load Diff
+157
@@ -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')
|
||||
+23
@@ -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>.
|
||||
+90
@@ -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);
|
||||
}
|
||||
}
|
||||
+118
@@ -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
|
||||
+1165
File diff suppressed because it is too large
Load Diff
+157
@@ -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')
|
||||
+23
@@ -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>.
|
||||
+5
@@ -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
|
||||
+22
@@ -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=℘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
|
||||
+53
@@ -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"
|
||||
}
|
||||
}
|
||||
+42
@@ -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
|
||||
}
|
||||
+105
@@ -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);
|
||||
}
|
||||
}
|
||||
+121
@@ -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
|
||||
+1167
File diff suppressed because it is too large
Load Diff
+211
@@ -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=℘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')
|
||||
Reference in New Issue
Block a user