Capture current mission, AI, map and administration development

Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
2026-10-13 22:52:53 +02:00
parent a951a96edc
commit 6a667d8deb
8104 changed files with 2124945 additions and 2061 deletions
@@ -0,0 +1,56 @@
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c
@@ -7,6 +7,7 @@
protected ERR_ConvoyTask m_MountedInterceptTask;
protected ref ERR_HQRoute m_MountedInterceptRoute;
protected bool m_MountedInterceptDelivered;
+ protected int m_MountedSurveyCursor;
protected ref array<int> m_MountedAttemptBeliefs = {};
protected ref array<int> m_MountedAttemptRevisions = {};
@@ -140,9 +141,16 @@
array<vector> reserved = {}; int first = -1; int second = -1;
foreach (ERR_HQUnit known : m_Units) ReserveUnit(known, reserved, first, second);
// At most two bounded native road queries per normal five-second HQ pass.
+ // Continue the next pass after the last surveyed track, so unsuitable roads
+ // for older reports cannot indefinitely hide later delivered tracks.
+ int count = m_Memory.Count();
+ if (count == 0) return;
+ int start = m_MountedSurveyCursor % count;
int queries;
- for (int b = 0; b < m_Memory.Count() && queries < 2; b++)
+ for (int scanned = 0; scanned < count && queries < 2; scanned++)
{
+ int b = (start + scanned) % count;
+ m_MountedSurveyCursor = (b + 1) % count;
ERR_HQBelief belief = m_Memory.At(b);
if (!ERR_HQMountedInterceptRules.Eligible(belief, now) || MountedInterceptAttempted(belief.BeliefId, belief.BeliefRevision)) continue;
vector destination; queries++;
--- a/tests/test_hq_mounted_dispatch.py
+++ b/tests/test_hq_mounted_dispatch.py
@@ -69,6 +69,7 @@
virtual void ReserveUnit(ERR_HQUnit*,Array<vector>&r,int&,int&){for(auto p:destinations)r.Insert(p);}virtual void ReleaseUnit(ERR_HQUnit*,float){releaseUnits++;}virtual void Stop(){stops++;}};
struct HQ:Base{
ERR_ConvoyInterceptOrder*m_MountedIntercept=nullptr;ERR_HQUnit*m_MountedInterceptUnit=nullptr;ERR_ConvoyTask*m_MountedInterceptTask=nullptr;ERR_HQRoute*m_MountedInterceptRoute=nullptr;bool m_MountedInterceptDelivered=false,m_Stopping=false,active=true;
+ int m_MountedSurveyCursor=0;
Array<int>m_MountedAttemptBeliefs,m_MountedAttemptRevisions;Array<ERR_HQUnit*>m_Units;Watch*m_Watch;Mode*m_Mode;Service*m_Convoys;Memory*m_Memory;Journal*m_Journal;Network*m_Network;
bool MissionActive(){return active;}bool MountedLogisticsEligible(ERR_HQUnit*u){return u&&u->eligible&&!u->LogisticsReservation;}bool ConvoyContact(ERR_HQUnit*u,float){return u->contact;}
'''+methods+r'''
@@ -103,7 +104,16 @@
for(int kind=0;kind<14;kind++){Fixture f;if(kind==0)f.vehicle.Index=1;if(kind==1)f.vehicle.Index=2;if(kind==2)f.vehicle.Arrived=true;if(kind==3)f.vehicle.SupplyToken=1;if(kind==4)f.combat.Held=true;if(kind==5)f.unit.LogisticsReservation=300001;if(kind==6)f.sampler.roster=false;if(kind==7)f.entry.Position={2000,0};if(kind==8)f.entry.ObservedAt=4000;if(kind==9)f.entry.LastDeliveredAt=0;if(kind==10)f.ammo.Weapons=0;if(kind==11)f.ammo.Rounds=0;if(kind==12)f.entry.Count=2;if(kind==13)f.entry.Closed=true;f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
{Fixture f;f.service.reserve=false;f.plan();assert(!f.unit.LogisticsReservation&&!f.h.m_MountedIntercept);f.service.reserve=true;f.plan();assert(f.service.reserves==1);}
{Fixture f;for(int i=0;i<256;i++){f.h.m_MountedAttemptBeliefs.Insert(i+20);f.h.m_MountedAttemptRevisions.Insert(1);}f.plan();assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
- {Fixture f;ERR_HQBelief b2,b3;f.belief.select=b2.select=false;b2.BeliefId=2;b3.BeliefId=3;f.memory.entries={&f.belief,&b2,&b3};int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects-before==2&&f.service.reserves==0);}
+ {Fixture f;ERR_HQBelief b2,b3;f.belief.select=b2.select=false;b2.BeliefId=2;b3.BeliefId=3;f.memory.entries={&f.belief,&b2,&b3};int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects-before==2&&f.service.reserves==0);assert(f.h.m_MountedAttemptBeliefs.empty());int after=ERR_HQMountedInterceptRules::selects;f.h.PlanMountedIntercept(8000);assert(ERR_HQMountedInterceptRules::selects-after==1);assert(f.h.m_MountedIntercept&&f.h.m_MountedIntercept->BeliefId==3&&f.service.reserves==1);}
+ // Cursor normalization handles track expiry/removal without out-of-range access.
+ {Fixture f;ERR_HQBelief second;second.BeliefId=2;f.belief.select=second.select=false;f.memory.entries={&f.belief,&second};f.plan();assert(!f.h.m_MountedIntercept);f.h.m_MountedSurveyCursor=31;f.memory.entries={&f.belief};f.belief.select=true;f.h.PlanMountedIntercept(8000);assert(f.h.m_MountedIntercept&&f.h.m_MountedIntercept->BeliefId==1);}
+ // Skipped ineligible/already-attempted tracks do not spend the two-road-query budget.
+ {Fixture f;ERR_HQBelief second,third;second.BeliefId=2;third.BeliefId=3;f.belief.eligible=false;f.h.m_MountedAttemptBeliefs.Insert(2);f.h.m_MountedAttemptRevisions.Insert(second.BeliefRevision);f.memory.entries={&f.belief,&second,&third};int before=ERR_HQMountedInterceptRules::selects;f.plan();assert(ERR_HQMountedInterceptRules::selects-before==1&&f.h.m_MountedIntercept&&f.h.m_MountedIntercept->BeliefId==3);}
+ // No eligible reports and no reports both produce zero native road queries.
+ {Fixture f;f.belief.eligible=false;int before=ERR_HQMountedInterceptRules::selects;f.plan();f.memory.entries.clear();f.plan();assert(ERR_HQMountedInterceptRules::selects==before&&!f.h.m_MountedIntercept&&f.service.reserves==0);}
+ // Full bounded memory: every pass has at most two surveys, no failed revision is consumed.
+ {Fixture f;ERR_HQBelief beliefs[64];f.memory.entries.clear();for(int i=0;i<64;i++){beliefs[i].BeliefId=i+1;beliefs[i].select=false;f.memory.entries.Insert(&beliefs[i]);}for(int pass=0;pass<32;pass++){int before=ERR_HQMountedInterceptRules::selects;float now=3000+pass*5000;f.entry.ObservedAt=f.entry.LastDeliveredAt=now;f.h.PlanMountedIntercept(now);assert(ERR_HQMountedInterceptRules::selects-before==2&&f.h.m_MountedSurveyCursor==((pass+1)*2)%64&&f.h.m_MountedAttemptBeliefs.empty());}assert(!f.h.m_MountedIntercept&&f.service.reserves==0);}
+
}
''')
print('actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed')
@@ -0,0 +1 @@
actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed
@@ -0,0 +1,19 @@
check: /tmp/err-policy-hhovsptp/check.cpp:276: int main(): Assertion `ERR_HQMountedInterceptRules::selects-after==1' failed.
Traceback (most recent call last):
File "/tmp/erri-hq-gap-1456/tests/test_hq_mounted_dispatch.py", line 24, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<91 lines>...
}
^
''')
^^^^
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-hhovsptp/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1 @@
actual mounted HQ dispatch planning, radio, ownership and lifecycle checks passed
@@ -0,0 +1,28 @@
{
"verdict": "approved",
"results": {
"baseline": {
"exit": 1,
"source_sha256": "8650c09afc46439a74e17cdf968b09d238ec9437113ffad784a031cddd65440a",
"log": "/tmp/erri-hq-gap-1456/independent-baseline.log",
"assertion_failure": true
},
"proposal": {
"exit": 0,
"source_sha256": "e98e931e010124ba567f415690999b96868f32a18edc7c4e1f21c0775b3c095f",
"log": "/tmp/erri-hq-gap-1456/independent-proposal.log",
"assertion_failure": false
}
},
"test_sha256": "2b2ebf810ef0084bad5f4cfaf3d155ecac0f002b82bbd54d9756a9592d364fa9",
"findings": [
"Persistent cursor remains nonnegative and less than previously observed count; modulo current nonzero count handles shrink. Empty list exits before modulo.",
"Cursor advances for every inspected entry, including ineligible or previously attempted records. Survey counter increments only for actual eligible Select calls, cap2 unchanged.",
"Each pass inspects at most count entries; full64-entry regression retains2surveys each pass and visits successive pairs over32passes.",
"All order construction, expiry, radio, force claims, attempted-revision bookkeeping and native Select policy remain unchanged. Memory mutates outside this synchronous selection loop; Select does not mutate HQ memory.",
"Baseline compiles and fails actual late-third-belief assertion; proposal completes all existing and added cases."
],
"limitations": [
"No engine execution. Test doubles native Select; native road reachability remains outside this source correction."
]
}
@@ -0,0 +1,19 @@
check: /tmp/err-policy-mbg6kx6k/check.cpp:276: int main(): Assertion `ERR_HQMountedInterceptRules::selects-after==1' failed.
Traceback (most recent call last):
File "/tmp/erri-hq-gap-1456/tests/test_hq_mounted_dispatch.py", line 24, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<91 lines>...
}
^
''')
^^^^
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-mbg6kx6k/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,10 @@
{
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQMountedDispatch.c": {
"baseline_sha256": "8650c09afc46439a74e17cdf968b09d238ec9437113ffad784a031cddd65440a",
"proposal_sha256": "e98e931e010124ba567f415690999b96868f32a18edc7c4e1f21c0775b3c095f"
},
"tests/test_hq_mounted_dispatch.py": {
"baseline_sha256": "27c0bcd6edf33f4455276a3a4e791e08b8a14c09aa8f5506ae175496406f1531",
"proposal_sha256": "2b2ebf810ef0084bad5f4cfaf3d155ecac0f002b82bbd54d9756a9592d364fa9"
}
}