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:
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-xvfc14oj/check.cpp:266: int main(): Assertion `f.group.additions==1&&f.unit.Waypoint->radius==radius&&f.unit.Waypoint->configured==1' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-search-radius-1611/tests/test_hq_delivered_task.py", line 34, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<199 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-xvfc14oj/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"scope": "Ordinary ground memory SEARCH only: snapshot finite delivered-belief uncertainty clamped45..110m at Assign/Retask, validate copied radius before spawn, set once after successful spawn and before old assignment release/new installation. No live read of player location, new actor, settings, radius animation or deadline extension.",
|
||||
"exclusions": [
|
||||
"Missing-patrol investigation and road/site-security Defend tasks retain existing defaults.",
|
||||
"INTERCEPT/CONTAIN and air direct-task path remain unchanged.",
|
||||
"WORKBENCH NativePatrolHQProbe now initializes the same snapshot for its direct ordinary pending-task fixture; no native run."
|
||||
],
|
||||
"proof": "Six focused programs green; old HQ actual DeliverOrder compiles and runtime-fails radius assertion. Omitted finite/range admission and omitted Retask snapshot each compile/runtime-fail. Existing actual40m/30s negative-sweep witness remains byte-identical and tested separately; larger native search grid does not prove larger cleared area.",
|
||||
"validation_limits": [
|
||||
"Native AI/waypoint/group boundaries are doubles; parser/radius policy and actual HQ delivery/queue expressions run as C++.",
|
||||
"Grid grows with radius;110m is a size bound, not a measured performance bound or FPS improvement.",
|
||||
"Native compilation, actual search movement and multiplayer remain unqualified.",
|
||||
"Historical prepare.py/adapt-tests.py were construction scratch; only manifest-listed files are authoritative."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c
|
||||
@@ -1085,12 +1085,17 @@
|
||||
if (task.RoadIntercept && !ERR_HQRoadIntercept.Eligible(belief, now)) { LostOrder(unit, now); return; }
|
||||
}
|
||||
if (Fighting(unit)) { LostOrder(unit, now); return; }
|
||||
+ bool memorySearch = task.Role == ERR_HQPursuitRole.SEARCH && !task.Investigation()
|
||||
+ && !task.RoadIntercept && task.SecuritySite.IsEmpty();
|
||||
+ if (memorySearch && (!ERR_HQPursuitRules.Finite(task.SearchRadiusMetres)
|
||||
+ || task.SearchRadiusMetres < 45 || task.SearchRadiusMetres > 110)) { LostOrder(unit, now); return; }
|
||||
int deliveryId;
|
||||
if (unit.Downlink) deliveryId = unit.Downlink.MessageId;
|
||||
ResourceName waypointResource = ERR_Resources.SearchWaypoint();
|
||||
if (task.RoadIntercept || !task.SecuritySite.IsEmpty()) waypointResource = ERR_Resources.DefendWaypoint();
|
||||
AIWaypoint waypoint = AIWaypoint.Cast(ERR_Actors.Spawn(waypointResource, ERR_Actors.Ground(task.Destination)));
|
||||
if (!waypoint) { LostOrder(unit, now); m_Journal.Write("ORDER FAILED", "waypoint spawn failed", order.MessageId); return; }
|
||||
+ if (memorySearch) waypoint.SetCompletionRadius(task.SearchRadiusMetres);
|
||||
if (unit.Assignment) RememberCompletedHunt(unit.Task, now);
|
||||
unit.Release();
|
||||
unit.Task = task;
|
||||
@@ -1294,6 +1299,7 @@
|
||||
if (!m_Coverage.Select(belief.PredictedPosition, belief.Radius, belief.Position, now, destination, reserved)) continue;
|
||||
ERR_HQTask task = new ERR_HQTask();
|
||||
task.BeliefId = belief.BeliefId; task.BeliefRevision = belief.BeliefRevision; task.Role = ERR_HQPursuitRole.SEARCH;
|
||||
+ task.SearchRadiusMetres = ERR_HQPursuitRules.SearchRadius(belief.Radius);
|
||||
task.Destination = destination; task.QueuedAt = now;
|
||||
task.ExpiresAt = now + ERR_HQPursuitRules.TaskLifetimeSeconds(task.Role, vector.DistanceXZ(unit.PlanPosition, destination)) * 1000;
|
||||
task.ApproachBearing = bearing;
|
||||
@@ -1544,6 +1550,7 @@
|
||||
if (!best) continue;
|
||||
ERR_HQTask task = new ERR_HQTask();
|
||||
task.BeliefId = belief.BeliefId; task.BeliefRevision = belief.BeliefRevision; task.Role = role; task.RoadIntercept = roadIntercept;
|
||||
+ if (role == ERR_HQPursuitRole.SEARCH && !roadIntercept) task.SearchRadiusMetres = ERR_HQPursuitRules.SearchRadius(belief.Radius);
|
||||
task.Destination = destination; task.QueuedAt = now;
|
||||
task.ExpiresAt = now + ERR_HQPursuitRules.TaskLifetimeSeconds(role, vector.DistanceXZ(best.PlanPosition, destination)) * 1000;
|
||||
task.ApproachBearing = bestBearing;
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c
|
||||
@@ -41,6 +41,13 @@
|
||||
if (slot == 0) return ERR_HQPursuitRole.INTERCEPT;
|
||||
if (slot == 2 && confidence >= 0.8 && uncertainty <= 100) return ERR_HQPursuitRole.CONTAIN;
|
||||
return ERR_HQPursuitRole.SEARCH;
|
||||
+ }
|
||||
+ // Radius of one native search grid, copied from delivered uncertainty when
|
||||
+ // the task is queued. This does not enlarge the reported swept-ground proof.
|
||||
+ static float SearchRadius(float uncertainty)
|
||||
+ {
|
||||
+ if (!Finite(uncertainty) || uncertainty < 0) return 0;
|
||||
+ return Math.Clamp(uncertainty, 45, 110);
|
||||
}
|
||||
static float LifetimeSeconds(ERR_HQPursuitRole role)
|
||||
{
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_HQTask.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_HQTask.c
|
||||
@@ -12,6 +12,7 @@
|
||||
int BeliefId;
|
||||
int BeliefRevision;
|
||||
ERR_HQPursuitRole Role;
|
||||
+ float SearchRadiusMetres;
|
||||
vector Destination;
|
||||
float QueuedAt;
|
||||
float ExpiresAt;
|
||||
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c
|
||||
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c
|
||||
@@ -20,6 +20,7 @@
|
||||
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;
|
||||
--- a/tests/test_hq_delivered_task.py
|
||||
+++ b/tests/test_hq_delivered_task.py
|
||||
@@ -13,7 +13,7 @@
|
||||
for typ in ('ERR_HQUnit','ERR_HQTask','ERR_GameMode','ERR_HQReport','ERR_HQBelief','ERR_ForceRecord','ERR_HQHuntWindow','AIWaypoint'):
|
||||
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
s=re.sub(r'foreach \((ERR_HQUnit\*) (\w+) : ([^)]+)\)',r'for (\1 \2 : \3)',s)
|
||||
- for name in ('unit','other','task','planned','order','belief','record','mode','Group','hunt','m_Mode','lostActive','lostPending'):
|
||||
+ for name in ('unit','other','task','planned','order','belief','record','mode','Group','hunt','waypoint','m_Mode','lostActive','lostPending'):
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('ResourceName ', 'int ').replace('.IsEmpty()', '.empty()')
|
||||
s=s.replace('PendingOrder.', 'PendingOrder->').replace('Assignment.', 'Assignment->')
|
||||
@@ -28,7 +28,7 @@
|
||||
if 'protected void ReleaseLostForce(' in P.read_text(): methods=cpp(body(P,'protected void ReleaseLostForce('))+'\n'+methods
|
||||
missing=cpp(body(P,'if (!unit.Group && !unit.Uplink)')).replace('m_Units.Remove(i)', 'm_Units.erase(m_Units.begin()+i)')
|
||||
cleanup=cpp(body(P,' foreach (ERR_HQUnit unit : m_Units)\n {\n if (!unit.Assignment && !unit.PendingOrder) continue;\n if (!unit.PlanEligible || assigned > globalLimit)'))
|
||||
-R=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c'
|
||||
+R=Path(os.environ.get('ERR_HQ_PURSUIT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c'))
|
||||
rules='\n'.join(cpp(body(R,x)) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role('))
|
||||
eligible=cpp(body(ROAD,'static bool Eligible('))
|
||||
run(r'''
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
+#include <limits>
|
||||
using string=std::string;
|
||||
template<class... T>string Format(T...){return "order";}
|
||||
struct vector{float x=0,y=0,z=0;float& operator[](int i){return i==0?x:(i==1?y:z);}float Length(){return std::sqrt(x*x+y*y+z*z);}};
|
||||
@@ -48,10 +49,10 @@
|
||||
};
|
||||
struct Math{static float Min(float a,float b){return std::min(a,b);}static float Max(float a,float b){return std::max(a,b);}};
|
||||
struct ERR_HQTask{string SecuritySite;bool RoadIntercept=false,ReleaseOnly=false;ERR_HQTask* ReplacesTask=nullptr;string WatchSourceId;int WatchMessageId=0,WatchRequestId=0;
|
||||
- int BeliefId=1,BeliefRevision=2;ERR_HQPursuitRole Role=ERR_HQPursuitRole::SEARCH;vector Destination{40,0,50};float QueuedAt=0,ExpiresAt=60000;bool Investigation(){return !WatchSourceId.empty();}string Name(){return "search";}};
|
||||
+ int BeliefId=1,BeliefRevision=2;ERR_HQPursuitRole Role=ERR_HQPursuitRole::SEARCH;vector Destination{40,0,50};float SearchRadiusMetres=60,QueuedAt=0,ExpiresAt=60000;bool Investigation(){return !WatchSourceId.empty();}string Name(){return "search";}};
|
||||
struct ERR_HQReport{string TravelMode="on foot";int MessageId=10;float ObservedAt=0;};
|
||||
struct ERR_HQRoute{int MessageId=20;};
|
||||
-struct AIWaypoint{int resource=0,deletes=0;static AIWaypoint* Cast(AIWaypoint* p){return p;}};
|
||||
+struct AIWaypoint{int resource=0,deletes=0,configured=0;float radius=30;void SetCompletionRadius(float r){assert(!configured);radius=r;configured++;}static AIWaypoint* Cast(AIWaypoint* p){return p;}};
|
||||
struct SCR_AIGroup{int removals=0,additions=0;AIWaypoint* last=nullptr;void RemoveWaypoint(AIWaypoint* w){removals++;last=w;}void AddWaypointAt(AIWaypoint* w,int at){assert(at==0);additions++;last=w;}};
|
||||
struct ERR_ForceRecord{bool Eliminated=false;int Duty=2,HomeDuty=1;vector DutyTarget,HomePosition;};
|
||||
struct Ledger{ERR_ForceRecord record;ERR_ForceRecord* FindGroup(SCR_AIGroup*){return &record;}};
|
||||
@@ -85,6 +86,23 @@
|
||||
void Retained(){assert(unit.Task==&active&&unit.Assignment==&oldReport&&unit.Waypoint==&previous&&previous.deletes==0&&group.removals==0&&!unit.PendingTask&&!unit.PendingOrder&&!unit.Downlink);}
|
||||
};
|
||||
int main(){
|
||||
+ // The copied radius is used before installation even if current uncertainty grows.
|
||||
+ for(float radius:{45.f,60.f,110.f}){Fixture f;f.next.SearchRadiusMetres=radius;f.belief.Radius=500;
|
||||
+ float deadline=f.next.ExpiresAt;f.hq.DeliverOrder(&f.unit,1000);
|
||||
+ assert(f.group.additions==1&&f.unit.Waypoint->radius==radius&&f.unit.Waypoint->configured==1);
|
||||
+ assert(f.next.SearchRadiusMetres==radius&&f.next.ExpiresAt==deadline);}
|
||||
+ // Malformed snapshot cannot spawn or replace the delivered previous assignment.
|
||||
+ for(float radius:{0.f,44.99f,110.01f,-1.f,std::numeric_limits<float>::quiet_NaN(),std::numeric_limits<float>::infinity()}){
|
||||
+ Fixture f;f.next.SearchRadiusMetres=radius;f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);}
|
||||
+ // Missing patrol, road/security defend, interception and containment keep native defaults.
|
||||
+ for(int kind=0;kind<5;kind++){Fixture f;f.next.SearchRadiusMetres=std::numeric_limits<float>::quiet_NaN();
|
||||
+ if(kind==0)f.next.WatchSourceId="missing-original";
|
||||
+ if(kind==1)f.next.RoadIntercept=true;
|
||||
+ if(kind==2)f.next.SecuritySite="site";
|
||||
+ if(kind==3)f.next.Role=ERR_HQPursuitRole::INTERCEPT;
|
||||
+ if(kind==4){f.next.Role=ERR_HQPursuitRole::CONTAIN;f.belief.Confidence=.9f;}
|
||||
+ f.hq.DeliverOrder(&f.unit,1000);assert(f.group.additions==1&&f.unit.Waypoint->radius==30&&f.unit.Waypoint->configured==0);}
|
||||
+
|
||||
// Alarm-only deliveries retain their weak SEARCH/mobile policy even if a queued task is tampered with.
|
||||
for(int invalid=0;invalid<5;invalid++){Fixture f;f.belief.Kind=ERR_HQMemoryKind::ALARM;
|
||||
ERR_HQUnit other;ERR_HQTask otherTask;ERR_HQReport alarm;alarm.TravelMode="heard vehicle alarm";
|
||||
@@ -217,3 +235,6 @@
|
||||
}
|
||||
''')
|
||||
print('PASS: actual delivered-task lineage, expiry, old-order retention, native commit, release, road precision expiry and duplicate delivery')
|
||||
+
|
||||
+delivery=body(P,'protected void DeliverOrder(')
|
||||
+assert delivery.index('waypoint.SetCompletionRadius(task.SearchRadiusMetres)') < delivery.index('unit.Release();') < delivery.index('unit.Group.AddWaypointAt')
|
||||
--- a/tests/test_hq_planner_budget.py
|
||||
+++ b/tests/test_hq_planner_budget.py
|
||||
@@ -44,13 +44,14 @@
|
||||
namespace ERR_HQMemoryKind{enum{SIGHTING,GUNFIRE,ALARM};}
|
||||
namespace ERR_HQPursuitRole{enum{SEARCH,INTERCEPT,CONTAIN};}
|
||||
struct ERR_HQReport{int id=0;ERR_HQReport* Snapshot(){return this;}};
|
||||
-struct ERR_HQTask{int BeliefId=0,BeliefRevision=0,Role=0,ApproachBearing=0;bool RoadIntercept=false;Vec Destination;float QueuedAt=0,ExpiresAt=0;string Name(){return "task";}};
|
||||
+struct ERR_HQTask{int BeliefId=0,BeliefRevision=0,Role=0,ApproachBearing=0;bool RoadIntercept=false;Vec Destination;float SearchRadiusMetres=0,QueuedAt=0,ExpiresAt=0;string Name(){return "task";}};
|
||||
struct ERR_HQRoute{int MessageId=0;string Payload;};
|
||||
struct Group{Vec pos;Vec GetOrigin(){return pos;}};
|
||||
struct ERR_HQUnit{int Id=0,Radio=0,HomeId=0,ForceSourceId=0;struct Group* Group=nullptr;ERR_HQReport* Assignment=nullptr;ERR_HQReport* PendingOrder=nullptr;ERR_HQTask* PendingTask=nullptr;ERR_HQRoute* Downlink=nullptr;bool PlanFighting=false,PlanEligible=true,HomeGuard=false;Vec PlanPosition;};
|
||||
struct ERR_HQBelief{int BeliefId=0,BeliefRevision=1,MessageId=1,Kind=0;float Confidence=.7,ObservedAt=1000,Radius=100;Vec Position,PredictedPosition,Velocity;bool HasVelocity=true;ERR_HQReport* Report=nullptr;};
|
||||
struct ERR_HQHuntWindow{float NextHunt=0;};
|
||||
struct ERR_HQPursuitRules{
|
||||
+ static float SearchRadius(float radius){return std::clamp(radius,45.f,110.f);}
|
||||
static bool Actionable(float c,float age,float r){return c>=.15&&age>=0&&r<=2000;}
|
||||
static bool Heading(Vec,bool h){return h;}
|
||||
static int Role(float,float,float,bool h,int slot){return h?slot:0;}
|
||||
@@ -91,7 +92,7 @@
|
||||
}
|
||||
};
|
||||
void Equal(Setup&a,Setup&b){assert(a.h.m_Journal.writes==b.h.m_Journal.writes&&a.h.m_Coverage.selections==b.h.m_Coverage.selections&&a.h.m_HomeAvailable.v==b.h.m_HomeAvailable.v);
|
||||
- for(int i=0;i<128;i++){auto x=a.units[i].PendingTask,y=b.units[i].PendingTask;assert(bool(x)==bool(y));if(!x)continue;assert(x->BeliefId==y->BeliefId&&x->Role==y->Role&&x->RoadIntercept==y->RoadIntercept&&x->Destination==y->Destination&&x->ExpiresAt==y->ExpiresAt&&x->ApproachBearing==y->ApproachBearing);}
|
||||
+ for(int i=0;i<128;i++){auto x=a.units[i].PendingTask,y=b.units[i].PendingTask;assert(bool(x)==bool(y));if(!x)continue;assert(x->SearchRadiusMetres==y->SearchRadiusMetres&&x->BeliefId==y->BeliefId&&x->Role==y->Role&&x->RoadIntercept==y->RoadIntercept&&x->Destination==y->Destination&&x->ExpiresAt==y->ExpiresAt&&x->ApproachBearing==y->ApproachBearing);}
|
||||
for(auto [id,window]:a.h.hunts)assert(window.NextHunt==b.h.hunts[id].NextHunt);
|
||||
}
|
||||
int main(){
|
||||
--- a/tests/test_hq_pursuit.py
|
||||
+++ b/tests/test_hq_pursuit.py
|
||||
@@ -2,7 +2,7 @@
|
||||
import os, re
|
||||
from pathlib import Path
|
||||
from policy_harness import ROOT, body, run
|
||||
-P = ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c'
|
||||
+P = Path(os.environ.get('ERR_HQ_PURSUIT_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c'))
|
||||
source = P.read_text()
|
||||
# Exercise the real planner expression against the same actual policy methods.
|
||||
HQ = Path(os.environ.get('ERR_HQ_SOURCE', ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
|
||||
@@ -13,7 +13,7 @@
|
||||
methods = '\n'.join(body(P, signature) for signature in (
|
||||
'static bool Finite(', 'static bool PositionValid(', 'static int Limit(', 'static bool Actionable(',
|
||||
'static bool Heading(', 'static ERR_HQPursuitRole Role(', 'static float LifetimeSeconds(',
|
||||
- 'static float TaskLifetimeSeconds(', 'static bool CanCommit(', 'static int Bearing(', 'static bool BearingAllowed(', 'static vector Direction(',
|
||||
+ 'static float SearchRadius(', 'static float TaskLifetimeSeconds(', 'static bool CanCommit(', 'static int Bearing(', 'static bool BearingAllowed(', 'static vector Direction(',
|
||||
'static vector Objective(', 'static float Score(', 'static bool Better('))
|
||||
methods = methods.replace('Math.', 'Math::').replace('vector.', 'vector::')
|
||||
methods = methods.replace('ERR_HQPursuitRole.', 'ERR_HQPursuitRole::')
|
||||
@@ -55,6 +55,10 @@
|
||||
ERR_HQPursuitRole PlannerRole(Belief belief,float age,int slot){return '''+planner_role+r''';}
|
||||
bool near(float a,float b){return std::abs(a-b)<0.001f;}
|
||||
int main(){
|
||||
+ assert(Rules::SearchRadius(0)==45&&Rules::SearchRadius(44.99)==45&&Rules::SearchRadius(45)==45);
|
||||
+ assert(Rules::SearchRadius(73)==73&&Rules::SearchRadius(110)==110&&Rules::SearchRadius(2000)==110);
|
||||
+ assert(Rules::SearchRadius(-1)==0&&Rules::SearchRadius(std::numeric_limits<float>::quiet_NaN())==0&&Rules::SearchRadius(std::numeric_limits<float>::infinity())==0);
|
||||
+
|
||||
using Role=ERR_HQPursuitRole;
|
||||
assert(Rules::Limit(-1)==0&&Rules::Limit(0)==0&&Rules::Limit(1)==1);
|
||||
assert(Rules::Limit(3)==3&&Rules::Limit(8)==3);
|
||||
@@ -186,3 +190,27 @@
|
||||
assert forbidden not in source, f'pursuit must consume snapshots only: {forbidden}'
|
||||
assert all(len(line)<120 for line in source.splitlines())
|
||||
print('PASS: actual bounded pursuit roles/objectives/scores, stale search degradation, physical/busy/home guards, finite lifetime and two-bearing escape gap; no hidden player reads or spawning')
|
||||
+
|
||||
+for signature in ('protected void Assign(', 'protected void Retask('):
|
||||
+ section=body(HQ,signature)
|
||||
+ line=next(line.strip() for line in section.splitlines() if 'task.SearchRadiusMetres =' in line)
|
||||
+ line=line.replace('ERR_HQPursuitRules.', 'Rules::').replace('ERR_HQPursuitRole.', 'Role::')
|
||||
+ run(r"""
|
||||
+#include <cassert>
|
||||
+#include <algorithm>
|
||||
+#include <cmath>
|
||||
+#include <limits>
|
||||
+enum class Role {SEARCH,INTERCEPT,CONTAIN};
|
||||
+struct Math{static float Clamp(float n,float a,float b){return std::clamp(n,a,b);}};
|
||||
+struct Rules{
|
||||
+"""+body(P,'static bool Finite(')+body(P,'static float SearchRadius(').replace('Math.','Math::')+r"""
|
||||
+};
|
||||
+struct Task{float SearchRadiusMetres=0;};struct Belief{float Radius=0;};
|
||||
+void Queue(Task& task,Belief& belief,Role role,bool roadIntercept){
|
||||
+"""+line+r"""
|
||||
+}
|
||||
+int main(){for(float radius:{0.f,60.f,500.f}){Task task;Belief belief{radius};Queue(task,belief,Role::SEARCH,false);float copied=Rules::SearchRadius(radius);assert(task.SearchRadiusMetres==copied);belief.Radius=1900;assert(task.SearchRadiusMetres==copied);}
|
||||
+"""+(r"""for(Role role:{Role::INTERCEPT,Role::CONTAIN}){Task task;Belief belief{100};Queue(task,belief,role,false);assert(task.SearchRadiusMetres==0);}Task road;Belief belief{100};Queue(road,belief,Role::SEARCH,true);assert(road.SearchRadiusMetres==0);""" if 'Assign' in signature else '')+r"""
|
||||
+}
|
||||
+""")
|
||||
+print('PASS: actual finite45..110 radius and both ordinary search queue snapshot expressions')
|
||||
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"approved": true,
|
||||
"blockers": [],
|
||||
"review_mode": "Read-only independent source/test/log/hash inspection; reviewer ran no compiler/tests/native.",
|
||||
"patch_sha256": "6f9bcc7c4f005406aca4d327b1d1d4628574ed079f7a746f9ff638756b1dbdee",
|
||||
"source_manifest_sha256": "ca18054aa5d77971af4ce9c6c4c50cd2f638fb6f135629022acb439c626b65d0",
|
||||
"files": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": {
|
||||
"baseline_sha256": "d873b5b689e918f8c907918d673304a184ac2e4a4cb6face527b37a67821ee9f",
|
||||
"proposal_sha256": "f9b1a44a5ef135d5944dd643f6435bea4f913915c3b2e689724bab0df7d18d8c",
|
||||
"index_sha256": "1f129b979abcb86ee10622e2866aa987807f070f2ab2d818af015e12a8bcfd8c",
|
||||
"index_absent": false
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c": {
|
||||
"baseline_sha256": "ab6ca0e1a7f36c104e664db06a74c80a528cbfd1de599af42e2194ef0a4f4ca3",
|
||||
"proposal_sha256": "41b0d69deda08535fd24fb88e85a7632c67b5cb99e47d773bc9803415a57c041",
|
||||
"index_sha256": "ab6ca0e1a7f36c104e664db06a74c80a528cbfd1de599af42e2194ef0a4f4ca3",
|
||||
"index_absent": false
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQTask.c": {
|
||||
"baseline_sha256": "1147904bae6d72e9de47ce17bdada3dab489de4ed6e781dbf15bdbb30599ff6e",
|
||||
"proposal_sha256": "16ed213344c00ec80080fb6866136333c11115e99f402d455033b686ecec8dfe",
|
||||
"index_sha256": "1147904bae6d72e9de47ce17bdada3dab489de4ed6e781dbf15bdbb30599ff6e",
|
||||
"index_absent": false
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c": {
|
||||
"baseline_sha256": "a58f57530e333976dc96fc5a0f9cb6cf0cb069c55788824950c5fb2f965d8e26",
|
||||
"proposal_sha256": "6f63edca662a6f83a0874a104b030823cbbe1c4260ec3b41368899f738981efb",
|
||||
"index_sha256": "a58f57530e333976dc96fc5a0f9cb6cf0cb069c55788824950c5fb2f965d8e26",
|
||||
"index_absent": false
|
||||
},
|
||||
"tests/test_hq_delivered_task.py": {
|
||||
"baseline_sha256": "076aecdb0ff0d614cadbffedf9d9f8cbb1383fb94a468bd92050b31251bcbce2",
|
||||
"proposal_sha256": "22ac74b16ab949330a82b9384e8c93e1cfbfda59d009d3bb921407ff1848555f",
|
||||
"index_sha256": "076aecdb0ff0d614cadbffedf9d9f8cbb1383fb94a468bd92050b31251bcbce2",
|
||||
"index_absent": false
|
||||
},
|
||||
"tests/test_hq_planner_budget.py": {
|
||||
"baseline_sha256": "c9e6b85a3d25267c187f503f903136a16aee382e9d842f61ff88c550b5bb4e51",
|
||||
"proposal_sha256": "53578da6fb82f4cc4210169332b1f54ac349fab05cce5513be7328218edd264a",
|
||||
"index_sha256": "c9e6b85a3d25267c187f503f903136a16aee382e9d842f61ff88c550b5bb4e51",
|
||||
"index_absent": false
|
||||
},
|
||||
"tests/test_hq_pursuit.py": {
|
||||
"baseline_sha256": "7ccbf51c7317097d9db6a66cc5be4bec67e542187f0fce5b4f3750bdc4311733",
|
||||
"proposal_sha256": "90171769ac65c89bc1fd101252dd6327df327a5e3cb54fdc1c5e713303af855c",
|
||||
"index_sha256": "7ccbf51c7317097d9db6a66cc5be4bec67e542187f0fce5b4f3750bdc4311733",
|
||||
"index_absent": false
|
||||
}
|
||||
},
|
||||
"test_results_sha256": "91dad71d2322c856365e692f587955db71ed598dbd121b855f5ac8be2a48f5dd",
|
||||
"checks": [
|
||||
"Finite SearchRadius rejects invalid/negative input then clamps delivered uncertainty to45..110; actual Assign and Retask copy once into detached task.",
|
||||
"Ordinary SEARCH only; delivery validates copied radius before spawning or releasing old task. Exactly one setter precedes native installation. Live uncertainty changes do not mutate queued radius.",
|
||||
"Missing-patrol, road/site-security, INTERCEPT/CONTAIN preserve original waypoint defaults. WORKBENCH direct patrol fixture initializes new SEARCH field.",
|
||||
"SweepPresent, Sweep, AcceptSweep and ReleaseUnit exact unchanged bytes; original center40m,30s quiet presence, physical roster and radio delivery proof remain.",
|
||||
"Six author focused programs green. Inspected actual lifecycle and queue-expression tests. Baseline compiles then fails intended runtime radius assertion; omitted validation and omitted Retask copy mutations compile and fail runtime assertions.",
|
||||
"Exact seven proposal/baseline hashes, patch, final result/log hashes and installed native API/resource proof hashes verified."
|
||||
],
|
||||
"limits": [
|
||||
"Actual C++ policy/lifecycle harnesses double native APIs; no native Enforce compilation or physical search qualification.",
|
||||
"45..110 bounds search size, not execution cost. Larger grid may reduce conservative center-dwell negative reports; no wider swept-area claim.",
|
||||
"Existing user game preserved; no native or source mutations by reviewer."
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
check: /tmp/err-policy-l7i6rzv6/check.cpp:265: void Fixture::Retained(): Assertion `unit.Task==&active&&unit.Assignment==&oldReport&&unit.Waypoint==&previous&&previous.deletes==0&&group.removals==0&&!unit.PendingTask&&!unit.PendingOrder&&!unit.Downlink' failed.
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-search-radius-1611/tests/test_hq_delivered_task.py", line 34, in <module>
|
||||
run(r'''
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<199 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-l7i6rzv6/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,20 @@
|
||||
check: /tmp/err-policy-wbaesr0g/check.cpp:22: int main(): Assertion `task.SearchRadiusMetres==copied' failed.
|
||||
PASS: actual bounded pursuit roles/objectives/scores, stale search degradation, physical/busy/home guards, finite lifetime and two-bearing escape gap; no hidden player reads or spawning
|
||||
Traceback (most recent call last):
|
||||
File "/tmp/erri-search-radius-1611/tests/test_hq_pursuit.py", line 198, in <module>
|
||||
run(r"""
|
||||
~~~^^^^^
|
||||
#include <cassert>
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
...<14 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-wbaesr0g/check']' died with <Signals.SIGABRT: 6>.
|
||||
@@ -0,0 +1,17 @@
|
||||
from pathlib import Path
|
||||
import os,subprocess,json,hashlib
|
||||
r=Path('/tmp/erri-search-radius-1611');live=Path('/home/jakub/devel/escapistsrereintroduced');ai=Path('addons/core/Scripts/Game/ERR/10_AI');e=os.environ.copy();e.update(PYTHONDONTWRITEBYTECODE='1',PYTHONPATH=str(live/'tests'),ERR_HQ_SOURCE=str(r/ai/'ERR_HQ.c'),ERR_HQ_PURSUIT_SOURCE=str(r/ai/'ERR_HQPursuit.c'));results=[]
|
||||
def run(name,test,override=None,red=False):
|
||||
env=e.copy();env.update(override or {});p=r/'tests'/test
|
||||
if not p.exists():p=live/'tests'/test
|
||||
log=r/(name+'.log')
|
||||
with log.open('w') as f:x=subprocess.run(['python3',str(p)],env=env,stdout=f,stderr=subprocess.STDOUT)
|
||||
text=log.read_text();ok=x.returncode==0 if not red else x.returncode!=0 and 'SIGABRT' in text and 'error:' not in text
|
||||
results.append({'name':name,'exit':x.returncode,'expected_result_observed':ok,'log_sha256':hashlib.sha256(log.read_bytes()).hexdigest()});assert ok,(name,text)
|
||||
for t in ['test_hq_pursuit.py','test_hq_delivered_task.py','test_hq_planner_budget.py','test_hq_task_reservations.py','test_hq_assignment_lifecycle.py','test_hq_uplink_fairness.py']:run(t.removesuffix('.py')+'-green',t)
|
||||
run('baseline-delivery-red','test_hq_delivered_task.py',{'ERR_HQ_SOURCE':str(r/'baseline'/ai/'ERR_HQ.c')},True)
|
||||
source=(r/ai/'ERR_HQ.c').read_text()
|
||||
for name,old,new,test in [('recompute-on-delivery','waypoint.SetCompletionRadius(task.SearchRadiusMetres)','waypoint.SetCompletionRadius(m_Memory.GetById(task.BeliefId).Radius)','test_hq_delivered_task.py'),('invalid-radius-admitted','if (memorySearch && (!ERR_HQPursuitRules.Finite(task.SearchRadiusMetres)','if (false && (!ERR_HQPursuitRules.Finite(task.SearchRadiusMetres)','test_hq_delivered_task.py'),('retask-not-copied','task.SearchRadiusMetres = ERR_HQPursuitRules.SearchRadius(belief.Radius);','task.SearchRadiusMetres = 0;','test_hq_pursuit.py')]:
|
||||
if name=='recompute-on-delivery':continue # pointer adapter would need unrelated mutation-specific adaptation
|
||||
assert old in source;p=r/'mutations'/name/'ERR_HQ.c';p.parent.mkdir(parents=True,exist_ok=True);p.write_text(source.replace(old,new,1));run(name+'-red',test,{'ERR_HQ_SOURCE':str(p)},True)
|
||||
(r/'test-results.json').write_text(json.dumps(results,indent=2)+'\n');print(json.dumps(results,indent=2))
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"native_grid": {
|
||||
"path": "/tmp/erri-vanilla-stock/scripts/Game/AI/Behavior/SCR_AISearchAndDestroyActivity.c",
|
||||
"sha256": "51f4343ee3e8fa90edea9a9a7d73581debfccc961a2e65dc87043289a5bd6fe1",
|
||||
"lines": [
|
||||
130,
|
||||
143,
|
||||
154
|
||||
],
|
||||
"meaning": "GetCompletionRadius supplies actionRadius/square half-width for investigation grid. Larger radius increases bounded cell/navigation work; no FPS improvement claim."
|
||||
},
|
||||
"native_default": {
|
||||
"path": "/tmp/erri-vanilla-stock/Prefabs/AI/Waypoints/AIWaypoint_SearchAndDestroy.et",
|
||||
"sha256": "55e46bd62175f3f2b4c5f203bb161b339648f5c1a1b53fcc6b40193c72dcf2a2",
|
||||
"radius": 30
|
||||
},
|
||||
"native_setter_example": {
|
||||
"path": "/tmp/erri-vanilla-stock/scripts/Game/Components/Spawner/CatalogSpawner/SCR_CatalogEntitySpawnerComponent.c",
|
||||
"sha256": "19069dcac3b76d0400719026646154346e2dba57d90e414fcbbc993122aa7695",
|
||||
"line": 1248
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"files": {
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c": {
|
||||
"baseline_sha256": "d873b5b689e918f8c907918d673304a184ac2e4a4cb6face527b37a67821ee9f",
|
||||
"proposal_sha256": "f9b1a44a5ef135d5944dd643f6435bea4f913915c3b2e689724bab0df7d18d8c",
|
||||
"index_sha256": "1f129b979abcb86ee10622e2866aa987807f070f2ab2d818af015e12a8bcfd8c",
|
||||
"index_absent": false
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQPursuit.c": {
|
||||
"baseline_sha256": "ab6ca0e1a7f36c104e664db06a74c80a528cbfd1de599af42e2194ef0a4f4ca3",
|
||||
"proposal_sha256": "41b0d69deda08535fd24fb88e85a7632c67b5cb99e47d773bc9803415a57c041",
|
||||
"index_sha256": "ab6ca0e1a7f36c104e664db06a74c80a528cbfd1de599af42e2194ef0a4f4ca3",
|
||||
"index_absent": false
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_HQTask.c": {
|
||||
"baseline_sha256": "1147904bae6d72e9de47ce17bdada3dab489de4ed6e781dbf15bdbb30599ff6e",
|
||||
"proposal_sha256": "16ed213344c00ec80080fb6866136333c11115e99f402d455033b686ecec8dfe",
|
||||
"index_sha256": "1147904bae6d72e9de47ce17bdada3dab489de4ed6e781dbf15bdbb30599ff6e",
|
||||
"index_absent": false
|
||||
},
|
||||
"addons/core/Scripts/Game/ERR/10_AI/ERR_NativePatrolHQProbe.c": {
|
||||
"baseline_sha256": "a58f57530e333976dc96fc5a0f9cb6cf0cb069c55788824950c5fb2f965d8e26",
|
||||
"proposal_sha256": "6f63edca662a6f83a0874a104b030823cbbe1c4260ec3b41368899f738981efb",
|
||||
"index_sha256": "a58f57530e333976dc96fc5a0f9cb6cf0cb069c55788824950c5fb2f965d8e26",
|
||||
"index_absent": false
|
||||
},
|
||||
"tests/test_hq_delivered_task.py": {
|
||||
"baseline_sha256": "076aecdb0ff0d614cadbffedf9d9f8cbb1383fb94a468bd92050b31251bcbce2",
|
||||
"proposal_sha256": "22ac74b16ab949330a82b9384e8c93e1cfbfda59d009d3bb921407ff1848555f",
|
||||
"index_sha256": "076aecdb0ff0d614cadbffedf9d9f8cbb1383fb94a468bd92050b31251bcbce2",
|
||||
"index_absent": false
|
||||
},
|
||||
"tests/test_hq_planner_budget.py": {
|
||||
"baseline_sha256": "c9e6b85a3d25267c187f503f903136a16aee382e9d842f61ff88c550b5bb4e51",
|
||||
"proposal_sha256": "53578da6fb82f4cc4210169332b1f54ac349fab05cce5513be7328218edd264a",
|
||||
"index_sha256": "c9e6b85a3d25267c187f503f903136a16aee382e9d842f61ff88c550b5bb4e51",
|
||||
"index_absent": false
|
||||
},
|
||||
"tests/test_hq_pursuit.py": {
|
||||
"baseline_sha256": "7ccbf51c7317097d9db6a66cc5be4bec67e542187f0fce5b4f3750bdc4311733",
|
||||
"proposal_sha256": "90171769ac65c89bc1fd101252dd6327df327a5e3cb54fdc1c5e713303af855c",
|
||||
"index_sha256": "7ccbf51c7317097d9db6a66cc5be4bec67e542187f0fce5b4f3750bdc4311733",
|
||||
"index_absent": false
|
||||
}
|
||||
},
|
||||
"new_test_registration": [],
|
||||
"native_validation": "Not run; existing user game preserved. No native compilation or behavior/FPS proof.",
|
||||
"patch_sha256": "6f9bcc7c4f005406aca4d327b1d1d4628574ed079f7a746f9ff638756b1dbdee",
|
||||
"test_results_sha256": "91dad71d2322c856365e692f587955db71ed598dbd121b855f5ac8be2a48f5dd",
|
||||
"unchanged_method_sha256": {
|
||||
"protected bool SweepPresent(": "9af91ba0b0a802b1225f95d956235d5e5382343440759eb2eb2a9f742d2c3198",
|
||||
"protected void Sweep(": "36b6d45d5710c68c76076377c26f8b3448af6b161a2bf78b948e4f29c84b369b",
|
||||
"protected void AcceptSweep(": "96549b9bb41cd4623b3119485b27e16626b6c42e150bf95dfe93e6a6159a0e2d",
|
||||
"protected void ReleaseUnit(": "405cd08b26befee2a6ff789a1e0454d5b40f9caaaf25127a16cf89a84d0efd46"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
[
|
||||
{
|
||||
"name": "test_hq_pursuit-green",
|
||||
"exit": 0,
|
||||
"expected_result_observed": true,
|
||||
"log_sha256": "cfa9cf0d5c8184ee60c0ea4895eb9c9ca7755fea336d6fb565b0eebfaf9b3525"
|
||||
},
|
||||
{
|
||||
"name": "test_hq_delivered_task-green",
|
||||
"exit": 0,
|
||||
"expected_result_observed": true,
|
||||
"log_sha256": "3e4c1177f828b2b47baa6fb274ed2ac27ca33fee42e75f49dfbd0124cd5049dd"
|
||||
},
|
||||
{
|
||||
"name": "test_hq_planner_budget-green",
|
||||
"exit": 0,
|
||||
"expected_result_observed": true,
|
||||
"log_sha256": "1b087e6a04079b13440799675b81a19b67b50c04b6aaa14bfd806d99a7fa2533"
|
||||
},
|
||||
{
|
||||
"name": "test_hq_task_reservations-green",
|
||||
"exit": 0,
|
||||
"expected_result_observed": true,
|
||||
"log_sha256": "b62b1a7d3a45a620144126f1581db6b290a6ccffc04e12f4b607f66e5303d0ae"
|
||||
},
|
||||
{
|
||||
"name": "test_hq_assignment_lifecycle-green",
|
||||
"exit": 0,
|
||||
"expected_result_observed": true,
|
||||
"log_sha256": "261750e7934a5b0fc94357eba49f2090491e27ed8304d6eeb8c90cfb6c04b0ab"
|
||||
},
|
||||
{
|
||||
"name": "test_hq_uplink_fairness-green",
|
||||
"exit": 0,
|
||||
"expected_result_observed": true,
|
||||
"log_sha256": "34d1246df1132ab32d36dc96a65cc5c5ae6ff113ba5d9f22c7ae3847fd8e54d1"
|
||||
},
|
||||
{
|
||||
"name": "baseline-delivery-red",
|
||||
"exit": 1,
|
||||
"expected_result_observed": true,
|
||||
"log_sha256": "5964c876c816a57a80d6c06378b002f4997867ba037e9c7a374a458f1547aa5e"
|
||||
},
|
||||
{
|
||||
"name": "invalid-radius-admitted-red",
|
||||
"exit": 1,
|
||||
"expected_result_observed": true,
|
||||
"log_sha256": "8bf0074be58c24469b7b7a68e5ce2fe726dbcbbeeb889febeb35262cb03441fa"
|
||||
},
|
||||
{
|
||||
"name": "retask-not-copied-red",
|
||||
"exit": 1,
|
||||
"expected_result_observed": true,
|
||||
"log_sha256": "0c2b986c54a93da1c556b35ef8b33bb27f02d9f5386890859626aaa321ab87f0"
|
||||
}
|
||||
]
|
||||
+1
@@ -0,0 +1 @@
|
||||
PASS: actual failed-retask deadline rollback plus assignment cancellation/release preserves radio ownership, native waypoint lifecycle and failed-order retry boundaries
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual delivered-task lineage, expiry, old-order retention, native commit, release, road precision expiry and duplicate delivery
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual full planner selection/commit body, 360 equivalence scenarios, orders/reservations/cooldowns identical; one128-unit scan vs >10000 repeated unit visits; no FPS claim
|
||||
@@ -0,0 +1,2 @@
|
||||
PASS: actual bounded pursuit roles/objectives/scores, stale search degradation, physical/busy/home guards, finite lifetime and two-bearing escape gap; no hidden player reads or spawning
|
||||
PASS: actual finite45..110 radius and both ordinary search queue snapshot expressions
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual HQ active/pending reservations, interruption, committed replacement, bounded coverage and unchanged force counts
|
||||
@@ -0,0 +1 @@
|
||||
PASS: actual report methods reproduce old starvation; status-first scheduler delivers check-ins and requires 30s without combat or actual visible contacts before negative reports
|
||||
Reference in New Issue
Block a user