Route delivered HQ tracks to temporary road interception and refresh playtest packages
This commit is contained in:
@@ -331,20 +331,30 @@ class FailureGraceTests(unittest.TestCase):
|
||||
self.assertEqual(monitor.timeout_result(), 'timeout')
|
||||
|
||||
def test_grace_bounds_rejected_before_any_native_mutation(self):
|
||||
for grace in (-1, 31):
|
||||
for grace in (-1, 121):
|
||||
with self.subTest(grace=grace):
|
||||
with self.assertRaisesRegex(ValueError, 'failure-grace 0..30'):
|
||||
with self.assertRaisesRegex(ValueError, 'failure-grace 0..120'):
|
||||
runner.StartupMonitor(100, 180, 15, grace)
|
||||
class Args:
|
||||
steam=Path('/steam'); compat=Path('/compat'); profile=Path('/profile')
|
||||
timeout=180; settle=15; failure_grace=grace
|
||||
with patch.object(runner, 'assert_no_game') as processes, patch.object(runner.subprocess, 'Popen') as spawn:
|
||||
with self.assertRaisesRegex(ValueError, 'failure-grace 0..30'):
|
||||
with self.assertRaisesRegex(ValueError, 'failure-grace 0..120'):
|
||||
runner.run(Args())
|
||||
processes.assert_not_called(); spawn.assert_not_called()
|
||||
|
||||
def test_longer_diagnostic_grace_remains_failed_and_absolute_bounded(self):
|
||||
monitor = runner.StartupMonitor(100, 180, 15, 120)
|
||||
self.assertIsNone(monitor.observe({'failed': True}, 105))
|
||||
self.assertIsNone(monitor.observe({'ready': True}, 224))
|
||||
self.assertEqual(monitor.observe({'ready': True}, 225), 'game-failed')
|
||||
self.assertFalse(monitor.annotate({'ready': True})['ready'])
|
||||
monitor = runner.StartupMonitor(100, 60, 15, 120)
|
||||
self.assertIsNone(monitor.observe({'failed': True}, 159))
|
||||
self.assertEqual(monitor.observe({'ready': True}, 160), 'game-failed')
|
||||
|
||||
def test_cli_default_and_explicit_grace(self):
|
||||
for extra, expected in (([], 0), (['--failure-grace', '30'], 30)):
|
||||
for extra, expected in (([], 0), (['--failure-grace', '30'], 30), (['--failure-grace', '120'], 120)):
|
||||
with self.subTest(extra=extra), patch.object(sys, 'argv', ['headless_game.py', '--compat', '/unused', *extra]):
|
||||
with patch.object(runner, 'run', return_value=1) as run:
|
||||
self.assertEqual(runner.main(), 1)
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
"""Actual radio task delivery/ownership methods with native waypoint and memory doubles."""
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
from policy_harness import ROOT,body,run
|
||||
P=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQ.c'))
|
||||
ROAD=Path(os.environ.get('ERR_HQ_ROAD_SOURCE',ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQRoadIntercept.c'))
|
||||
def cpp(s):
|
||||
s=s.replace('protected ','').replace('null','nullptr').replace('string.Format','Format')
|
||||
for name in ('Math','ERR_GameMode','SCR_EntityHelper','ERR_HQPursuitRules','AIWaypoint','ERR_Actors','ERR_Resources','ERR_ForceDuty','ERR_HQRoadIntercept','ERR_ForceRules','ERR_HQPursuitRole','ERR_HQMemoryKind'):
|
||||
s=s.replace(name+'.',name+'::')
|
||||
for typ in ('ERR_HQUnit','ERR_HQTask','ERR_GameMode','ERR_HQReport','ERR_HQBelief','ERR_ForceRecord','ERR_HQHuntWindow','AIWaypoint'):
|
||||
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
s=re.sub(r'foreach \((ERR_HQUnit\*) (\w+) : ([^)]+)\)',r'for (\1 \2 : \3)',s)
|
||||
for name in ('unit','other','task','planned','order','belief','record','mode','Group','hunt','m_Mode'):
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('ResourceName ', 'int ')
|
||||
s=s.replace('PendingTask.', 'PendingTask->').replace('Task.', 'Task->')
|
||||
s=s.replace('Downlink.', 'Downlink->').replace('Forces().','Forces()->').replace('ERR_ForceRecord* record;','ERR_ForceRecord* record=nullptr;')
|
||||
return s
|
||||
units='\n'.join(cpp(body(P,x)) for x in ('void CancelPending(', 'ERR_HQTask PlannedTask(', 'void ReleaseAssignment(', 'void Release()'))
|
||||
methods='\n'.join(cpp(body(P,x)) for x in ('protected void FinishWatch(', 'protected void ReleaseUnit(', 'protected void LostOrder(', 'protected void DeliverOrder('))
|
||||
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/ai/Scripts/Game/ERR/ERR_HQPursuit.c'
|
||||
rules='\n'.join(cpp(body(R,x)) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role('))
|
||||
eligible=cpp(body(ROAD,'static bool Eligible('))
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
using string=std::string;
|
||||
template<class... T>string Format(T...){return "order";}
|
||||
struct vector{float x=0,y=0,z=0;float& operator[](int i){return i==0?x:(i==1?y:z);}float Length(){return std::sqrt(x*x+y*y+z*z);}};
|
||||
enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};
|
||||
struct ERR_ForceRules{static bool Finite(float f){return std::isfinite(f);}};
|
||||
struct ERR_HQMemoryKind{static const int SIGHTING=1;};
|
||||
struct ERR_HQPursuitRules{
|
||||
'''+rules+r'''
|
||||
};
|
||||
struct Math{static float Min(float a,float b){return std::min(a,b);}};
|
||||
struct ERR_HQTask{bool RoadIntercept=false,ReleaseOnly=false;ERR_HQTask* ReplacesTask=nullptr;string WatchSourceId;int WatchMessageId=0,WatchRequestId=0;
|
||||
int BeliefId=1,BeliefRevision=2,Role=0;vector Destination{40,0,50};float QueuedAt=0,ExpiresAt=60000;bool Investigation(){return !WatchSourceId.empty();}string Name(){return "search";}};
|
||||
struct ERR_HQReport{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 SCR_AIGroup{int removals=0,additions=0;AIWaypoint* last=nullptr;void RemoveWaypoint(AIWaypoint* w){removals++;last=w;}void AddWaypointAt(AIWaypoint* w,int at){assert(at==0);additions++;last=w;}};
|
||||
struct ERR_ForceRecord{bool Eliminated=false;int Duty=2,HomeDuty=1;vector DutyTarget,HomePosition;};
|
||||
struct Ledger{ERR_ForceRecord record;ERR_ForceRecord* FindGroup(SCR_AIGroup*){return &record;}};
|
||||
struct ERR_GameMode{static inline ERR_GameMode* current=nullptr;Ledger ledger;static ERR_GameMode* Get(){return current;}Ledger* Forces(){return &ledger;}};
|
||||
struct SCR_EntityHelper{static void DeleteEntityAndChildren(AIWaypoint* p){p->deletes++;}};
|
||||
struct ERR_Actors{static inline bool refuse=false;static inline int spawns=0;static AIWaypoint* Spawn(int resource,vector){spawns++;if(refuse)return nullptr;auto w=new AIWaypoint;w->resource=resource;return w;}static vector Ground(vector p){return p;}};
|
||||
struct ERR_Resources{static int SearchWaypoint(){return 1;}static int DefendWaypoint(){return 2;}};
|
||||
struct ERR_ForceDuty{static const int SEARCH=2;};
|
||||
struct ERR_HQUnit{int Id=1;string ForceSourceId="original";SCR_AIGroup* Group=nullptr;AIWaypoint* Waypoint=nullptr;
|
||||
ERR_HQTask* Task=nullptr;ERR_HQTask* PendingTask=nullptr;ERR_HQReport* Assignment=nullptr;ERR_HQReport* PendingOrder=nullptr;
|
||||
ERR_HQRoute* Downlink=nullptr;void* PendingConvoyOrder=nullptr;bool PendingCheckin=false,eligible=true,fighting=false,PlanEligible=true;
|
||||
bool Eligible(ERR_GameMode*){return eligible&&Group;}
|
||||
'''+units+r'''
|
||||
};
|
||||
struct ERR_HQBelief{int BeliefId=1,BeliefRevision=2,Kind=1,SampleCount=2,MessageId=10;float Confidence=.8f,Radius=60,ObservedAt=0,LastDeliveredAt=0;string TravelMode="on foot";vector Velocity{0,0,3};bool HasVelocity=true;};
|
||||
struct ERR_HQRoadIntercept{
|
||||
'''+eligible+r'''
|
||||
};
|
||||
struct Memory{ERR_HQBelief* belief=nullptr;ERR_HQBelief* GetById(int id){return belief&&belief->BeliefId==id?belief:nullptr;}};
|
||||
struct Watch{bool empty=false;int finished=0;bool ArmedSearchBlocked(string,float){return empty;}bool FinishInvestigation(string,int,int,float){finished++;return true;}};
|
||||
struct Journal{int writes=0;void Write(string,string,int=0){writes++;}};
|
||||
struct ERR_HQHuntWindow{float NextHunt=200000;};
|
||||
struct HQ{ERR_GameMode* m_Mode;Memory m_Memory;Watch m_Watch;Journal m_Journal;std::vector<ERR_HQUnit*>m_Units;ERR_HQHuntWindow hunt;bool watchValid=true;
|
||||
bool Fighting(ERR_HQUnit* u){return u->fighting;}bool WatchTaskValid(ERR_HQTask*){return watchValid;}ERR_HQHuntWindow* Hunt(int){return &hunt;}
|
||||
'''+methods+'\nvoid Cleanup(float now,int assigned,int globalLimit){'+cleanup+'}'+r'''
|
||||
};
|
||||
struct Fixture{ERR_GameMode mode;HQ hq;ERR_HQUnit unit;SCR_AIGroup group;AIWaypoint previous;ERR_HQTask active,next;ERR_HQReport oldReport,newReport;ERR_HQRoute route;ERR_HQBelief belief;
|
||||
Fixture(){ERR_GameMode::current=&mode;ERR_Actors::refuse=false;ERR_Actors::spawns=0;hq.m_Mode=&mode;hq.m_Units={&unit};hq.m_Memory.belief=&belief;
|
||||
unit.Group=&group;unit.Task=&active;unit.Assignment=&oldReport;unit.Waypoint=&previous;unit.PendingTask=&next;unit.PendingOrder=&newReport;unit.Downlink=&route;active.BeliefId=9;}
|
||||
void Retained(){assert(unit.Task==&active&&unit.Assignment==&oldReport&&unit.Waypoint==&previous&&previous.deletes==0&&group.removals==0&&!unit.PendingTask&&!unit.PendingOrder&&!unit.Downlink);}
|
||||
};
|
||||
int main(){
|
||||
// A changed belief revision or absent/stale/uncertain track cannot create a native destination.
|
||||
for(int invalid=0;invalid<5;invalid++){Fixture f;
|
||||
if(invalid==0){f.belief.BeliefRevision=3;}if(invalid==1){f.hq.m_Memory.belief=nullptr;}
|
||||
if(invalid==2){f.belief.ObservedAt=2000;}if(invalid==3){f.belief.Confidence=.1f;}if(invalid==4){f.belief.Radius=2001;}
|
||||
f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);}
|
||||
// Exact absolute expiry boundary, combat, physical reservation eligibility and known-empty ammo remain gates.
|
||||
for(int invalid=0;invalid<4;invalid++){Fixture f;
|
||||
if(invalid==0){f.next.ExpiresAt=1000;}if(invalid==1){f.unit.fighting=true;}if(invalid==2){f.unit.eligible=false;}if(invalid==3){f.hq.m_Watch.empty=true;}
|
||||
f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);}
|
||||
// Native spawn refusal must leave the delivered old waypoint and duty intact.
|
||||
{Fixture f;ERR_Actors::refuse=true;f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==1&&f.mode.ledger.record.Duty==2);}
|
||||
// Commit swaps the old task only after native success. Duplicate delivery has no pending lineage and cannot spawn twice.
|
||||
{Fixture f;f.hq.DeliverOrder(&f.unit,1000);assert(ERR_Actors::spawns==1&&f.previous.deletes==1&&f.group.removals==1&&f.group.additions==1);
|
||||
assert(f.unit.Waypoint->resource==1);
|
||||
assert(f.unit.Task==&f.next&&f.unit.Assignment==&f.newReport&&f.unit.Waypoint!=&f.previous&&!f.unit.PendingTask&&!f.unit.Downlink);
|
||||
auto retained=f.unit.Waypoint;f.hq.DeliverOrder(&f.unit,1001);assert(ERR_Actors::spawns==1&&f.unit.Waypoint==retained&&f.group.additions==1);
|
||||
assert(f.mode.ledger.record.DutyTarget.x==f.next.Destination.x);f.unit.ReleaseAssignment();assert(retained->deletes==1&&f.mode.ledger.record.Duty==f.mode.ledger.record.HomeDuty);}
|
||||
// Actual assignment-pass expiry releases only the expired reservation, preserving the other lineage.
|
||||
{Fixture f;f.active.ExpiresAt=1000;f.hq.Cleanup(1000,1,4);
|
||||
assert(!f.unit.Task&&!f.unit.Assignment&&!f.unit.Waypoint&&f.previous.deletes==1&&f.unit.PendingTask==&f.next&&f.unit.Downlink==&f.route);
|
||||
assert(f.mode.ledger.record.Duty==f.mode.ledger.record.HomeDuty);f.hq.Cleanup(1001,1,4);assert(f.previous.deletes==1&&f.unit.PendingTask==&f.next);}
|
||||
{Fixture f;f.active.BeliefId=1;f.next.ExpiresAt=1000;f.hq.Cleanup(1000,1,4);f.Retained();}
|
||||
{Fixture f;f.unit.PlanEligible=false;f.hq.Cleanup(1000,1,4);assert(!f.unit.Task&&!f.unit.PendingTask&&!f.unit.Downlink&&f.previous.deletes==1);}
|
||||
// Radio latency may age a precision interception beyond its 45-second authorization.
|
||||
// Eligibility uses the actual road helper and pursuit rules, never a test predicate.
|
||||
for(int invalid=0;invalid<9;invalid++){Fixture f;f.next.RoadIntercept=true;float now=1000;
|
||||
if(invalid==0)now=45001;
|
||||
if(invalid==1)f.belief.LastDeliveredAt=1001;
|
||||
if(invalid==2)f.belief.SampleCount=1;
|
||||
if(invalid==3)f.belief.Velocity={0,0,1.99f};
|
||||
if(invalid==4)f.belief.HasVelocity=false;
|
||||
if(invalid==5)f.belief.TravelMode="aircraft";
|
||||
if(invalid==6)f.belief.Kind=2;
|
||||
if(invalid==7)f.belief.MessageId=0;
|
||||
if(invalid==8){f.belief.ObservedAt=1;f.belief.LastDeliveredAt=0;}
|
||||
f.hq.DeliverOrder(&f.unit,now);f.Retained();assert(ERR_Actors::spawns==0);}
|
||||
{Fixture f;f.next.RoadIntercept=true;f.hq.DeliverOrder(&f.unit,45000);
|
||||
assert(ERR_Actors::spawns==1&&f.unit.Waypoint&&f.unit.Waypoint->resource==2&&f.unit.Task==&f.next&&f.previous.deletes==1);
|
||||
f.hq.DeliverOrder(&f.unit,45001);assert(ERR_Actors::spawns==1&&f.group.additions==1);
|
||||
f.hq.Cleanup(60000,1,4);assert(!f.unit.Task&&!f.unit.Waypoint&&f.mode.ledger.record.Duty==f.mode.ledger.record.HomeDuty);}
|
||||
// Cancelling a mismatched recall never releases a replacement task which arrived first.
|
||||
{Fixture f;f.next.ReleaseOnly=true;f.next.ReplacesTask=nullptr;f.hq.DeliverOrder(&f.unit,1000);f.Retained();assert(ERR_Actors::spawns==0);}
|
||||
{Fixture f;f.next.ReleaseOnly=true;f.next.ReplacesTask=&f.active;f.hq.DeliverOrder(&f.unit,1000);assert(!f.unit.Task&&!f.unit.Assignment&&!f.unit.Waypoint&&f.previous.deletes==1&&ERR_Actors::spawns==0);}
|
||||
}
|
||||
''')
|
||||
print('PASS: actual delivered-task lineage, expiry, old-order retention, native commit, release, road precision expiry and duplicate delivery')
|
||||
@@ -0,0 +1,88 @@
|
||||
"""Actual delivered-memory road selector, geometry and native-query boundaries."""
|
||||
from pathlib import Path
|
||||
import os,re
|
||||
from policy_harness import ROOT,body,run
|
||||
p=Path(os.environ.get('ERR_ROAD_INTERCEPT_SOURCE',ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQRoadIntercept.c'))
|
||||
source=p.read_text()
|
||||
for forbidden in ('GetPlayer','GetControlledEntity','GetOrigin','Spawn(', 'AddWaypoint','SetOrigin','CallLater'):
|
||||
assert forbidden not in source,forbidden
|
||||
rules='\n'.join(body(ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQPursuit.c', x) for x in ('static bool Finite(', 'static bool PositionValid(', 'static bool Actionable(', 'static bool Heading(', 'static ERR_HQPursuitRole Role(', 'static vector Direction(', 'static vector Objective('))
|
||||
rules=rules.replace('Math.','Math::').replace('vector.','vector::').replace('ERR_HQPursuitRole.','ERR_HQPursuitRole::').replace('vector offset;','vector offset{};')
|
||||
s=re.sub(r'//[^\n]*','',source).replace('class ERR_HQRoadIntercept','struct ERR_HQRoadIntercept')
|
||||
s=s.replace('out vector','vector&').replace('out float','float&').replace('array<vector>','Array<vector>').replace('array<BaseRoad>','Array<BaseRoad*>').replace('foreach (','for (')
|
||||
s=s.replace('SCR_AIWorld.Cast(GetGame().GetAIWorld())','GetGame()->GetAIWorld()')
|
||||
for name in ('ERR_HQBelief','SCR_AIWorld','BaseRoad','TraceBox'):
|
||||
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
|
||||
for name in ('belief','world','road','body'):
|
||||
s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('GetGame().','GetGame()->').replace('GetRoadNetworkManager().','GetRoadNetworkManager()->').replace('GetWorld().','GetWorld()->')
|
||||
for name in ('ERR_ForceRules','ERR_HQPursuitRules','ERR_HQPursuitRole','ERR_HQMemoryKind','vector','Math','ERR_Actors','ChimeraWorldUtils','TraceFlags','Replication'):
|
||||
s=s.replace(name+'.',name+'::')
|
||||
s=s.replace('null','nullptr')
|
||||
s=re.sub(r'\b(bool|int|float) (\w+);',r'\1 \2{};',s).replace('\n}\n','\n};\n')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
enum class ERR_HQPursuitRole{SEARCH,INTERCEPT,CONTAIN};enum class ERR_HQMemoryKind{SIGHTING,DISTRESS,BODY};
|
||||
struct Math{static float Clamp(float x,float a,float b){return std::clamp(x,a,b);}static float AbsFloat(float x){return std::abs(x);}};
|
||||
struct vector{float x=0,y=0,z=0;vector()=default;vector(float a,float b,float c):x(a),y(b),z(c){}vector(const char*s){sscanf(s,"%f %f %f",&x,&y,&z);}float&operator[](int i){return i==0?x:i==1?y:z;}float operator[](int i)const{return i==0?x:i==1?y:z;}
|
||||
vector operator+(vector b)const{return{x+b.x,y+b.y,z+b.z};}vector operator-(vector b)const{return{x-b.x,y-b.y,z-b.z};}vector operator*(float n)const{return{x*n,y*n,z*n};}bool operator==(vector b)const{return x==b.x&&y==b.y&&z==b.z;}
|
||||
float Length()const{return std::sqrt(x*x+y*y+z*z);}vector Normalized()const{float n=Length();return{x/n,y/n,z/n};}static float Dot(vector a,vector b){return a.x*b.x+a.y*b.y+a.z*b.z;}static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}static vector Lerp(vector a,vector b,float n){return a+(b-a)*n;}static const vector Zero;};
|
||||
const vector vector::Zero{};vector Vector(float a,float b,float c){return{a,b,c};}
|
||||
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}void Insert(T v){this->push_back(v);}explicit operator bool(){return true;}};
|
||||
struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}static bool FiniteVector(vector p){return Finite(p.x)&&Finite(p.y)&&Finite(p.z);}};
|
||||
struct ERR_HQPursuitRules{''' + rules + r'''};
|
||||
struct ERR_HQBelief{ERR_HQMemoryKind Kind=ERR_HQMemoryKind::SIGHTING;int SampleCount=2,MessageId=1;float ObservedAt=5000,LastDeliveredAt=7000,Confidence=.9,Radius=40;std::string TravelMode="on foot";vector PredictedPosition={1000,10,1000},Velocity={0,0,4};bool HasVelocity=true;};
|
||||
struct TraceFlags{static constexpr int WORLD=1,ENTS=2;};struct TraceBox{vector Start,End,Mins,Maxs;int Flags=0;};
|
||||
struct NativeWorld{int traces=0;float fraction=1;float TraceMove(TraceBox* t,void*){traces++;assert(t->Flags==3&&t->Mins.y==-.9f);return fraction;}};
|
||||
struct BaseRoad{float width=4;Array<vector>points={{900,10,1060},{1100,10,1060}};float GetWidth(){return width;}void GetPoints(Array<vector>&p){p=points;}};
|
||||
struct RoadManager{Array<BaseRoad*>roads;int queries=0;void GetRoadsInAABB(vector low,vector high,Array<BaseRoad*>&out){queries++;assert(high.x-low.x==200&&high.z-low.z==200&&high.y-low.y==2000);out=roads;}};
|
||||
struct SCR_AIWorld{RoadManager manager;bool available=true;RoadManager*GetRoadNetworkManager(){return available?&manager:nullptr;}};
|
||||
struct Game{NativeWorld world;SCR_AIWorld ai;NativeWorld*GetWorld(){return &world;}SCR_AIWorld*GetAIWorld(){return &ai;}}game;Game*GetGame(){return &game;}
|
||||
struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
|
||||
struct ERR_Actors{static inline bool steep=false;static vector Ground(vector p){p.y=10;if(steep&&p.x>1000)p.y=11;return p;}};
|
||||
struct ChimeraWorldUtils{static inline bool water=false;static bool TryGetWaterSurfaceSimple(NativeWorld*,vector){return water;}};
|
||||
''' + s + r'''
|
||||
int main(){
|
||||
using Road=ERR_HQRoadIntercept;ERR_HQBelief good;
|
||||
assert(Road::Eligible(&good,10000));
|
||||
for(int invalid=0;invalid<12;invalid++){
|
||||
auto bad=good;
|
||||
if(invalid==0){bad.Kind=ERR_HQMemoryKind::DISTRESS;}if(invalid==1){bad.SampleCount=1;}if(invalid==2){bad.MessageId=0;}
|
||||
if(invalid==3){bad.LastDeliveredAt=10001;}if(invalid==4){bad.LastDeliveredAt=4999;}if(invalid==5){bad.TravelMode="aircraft";}
|
||||
if(invalid==6){bad.HasVelocity=false;}if(invalid==7){bad.Velocity={0,0,1.99f};}if(invalid==8){bad.Confidence=.649f;}
|
||||
if(invalid==9){bad.Radius=151;}if(invalid==10){bad.ObservedAt=-35001;}if(invalid==11){bad.Velocity.x=NAN;}
|
||||
assert(!Road::Eligible(&bad,10000));
|
||||
}
|
||||
Array<vector> reserved;vector origin={1000,10,1000},velocity={0,0,4},seed={1000,10,1060},result;float score;
|
||||
assert(Road::FromPolyline({{900,0,1060},{1100,0,1060}},origin,velocity,seed,reserved,result,score)&&result.x==1000&&result.z==1060&&score==0);
|
||||
assert(!Road::Ahead(origin,velocity,{1000,10,1039.99f}));assert(Road::Ahead(origin,velocity,{1000,10,1040}));assert(!Road::Ahead(origin,velocity,{1000,10,1250.01f}));assert(!Road::Ahead(origin,velocity,{1000,10,940}));
|
||||
reserved.Insert({1000,10,1060});assert(!Road::FromPolyline({{900,0,1060},{1100,0,1060}},origin,velocity,seed,reserved,result,score)&&result==vector::Zero);reserved.clear();
|
||||
// Hairpin gaps are never joined by an offroad chord toward the desired center.
|
||||
assert(Road::FromPolyline({{900,0,1040},{1100,0,1040},{1100,0,1160},{900,0,1160}},origin,velocity,{1000,10,1100},reserved,result,score));assert(result.z==1040||result.z==1160||result.x==1100);
|
||||
assert(!Road::FromPolyline({{900,0,1060},{NAN,0,1060}},origin,velocity,seed,reserved,result,score)&&result==vector::Zero);
|
||||
Array<vector> oversized(1025,seed);assert(!Road::FromPolyline(oversized,origin,velocity,seed,reserved,result,score));
|
||||
BaseRoad road;game.ai.manager.roads={&road};assert(Road::Select(&good,10000,reserved,result)&&result==vector(1000,10,1060)&&game.world.traces==1);
|
||||
// An elevated bridge must not be silently projected onto terrain beneath the actual road.
|
||||
road.points={{900,20,1060},{1100,20,1060}};int groundTraces=game.world.traces;
|
||||
assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero&&game.world.traces==groundTraces);
|
||||
road.points={{900,12,1060},{1100,12,1060}};assert(Road::Select(&good,10000,reserved,result));
|
||||
road.points={{900,10,1060},{1100,10,1060}};
|
||||
// Refusal never leaves a seemingly usable destination from an earlier successful call.
|
||||
game.world.fraction=.5;assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero);game.world.fraction=1;
|
||||
ChimeraWorldUtils::water=true;assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero);ChimeraWorldUtils::water=false;
|
||||
ERR_Actors::steep=true;assert(!Road::Select(&good,10000,reserved,result)&&result==vector::Zero);ERR_Actors::steep=false;
|
||||
road.width=3.99;assert(!Road::Select(&good,10000,reserved,result));road.width=4;
|
||||
game.ai.manager.roads.resize(129,&road);assert(!Road::Select(&good,10000,reserved,result));game.ai.manager.roads={&road};
|
||||
BaseRoad dense;for(int i=0;i<1024;i++)dense.points.push_back({float(i),0,1060});dense.points.erase(dense.points.begin(),dense.points.begin()+2);
|
||||
game.ai.manager.roads={&dense,&dense,&dense,&dense,&dense};int before=game.world.traces;assert(!Road::Select(&good,10000,reserved,result)&&game.world.traces==before&&result==vector::Zero);
|
||||
game.ai.manager.roads={&road};auto stale=good;stale.ObservedAt=-35001;before=game.ai.manager.queries;assert(!Road::Select(&stale,10000,reserved,result)&&game.ai.manager.queries==before);
|
||||
game.world.fraction=NAN;assert(!Road::Select(&good,10000,reserved,result));
|
||||
}
|
||||
''')
|
||||
print('PASS: actual delivered road eligibility, ahead projection without chords, reservations, stock road query bounds, terrain/water/obstruction refusal and clean outputs')
|
||||
@@ -55,7 +55,7 @@ struct Sampler{int observations=0,samples=0;bool valid=true;void ObserveDeaths()
|
||||
ERR_HQStatusReport* Sample(float now,int id,void*){samples++;if(!valid)return nullptr;auto r=new ERR_HQStatusReport;r->MessageId=id;r->ObservedAt=now;return r;}};
|
||||
struct ERR_HQSearchReport{int SourceId=0,MessageId=0,WatchMessageId=0,WatchRequestId=0,BeliefId=0,BeliefRevision=0;
|
||||
string WatchSourceId;vector Position;float ObservedAt=0;bool SweepCompleted=false,ContactFound=false;};
|
||||
struct ERR_HQTask{bool SweepQueued=false;float ArrivedAt=-1;vector Destination;
|
||||
struct ERR_HQTask{bool RoadIntercept=false,SweepQueued=false;float ArrivedAt=-1;vector Destination;
|
||||
string WatchSourceId;int WatchMessageId=0,WatchRequestId=0,BeliefId=1,BeliefRevision=2;};
|
||||
struct ERR_HQRoute{Group* LocalSource=nullptr;int MessageId=0;string Payload;};
|
||||
struct Network{bool available=true;int routes=0;ERR_HQRoute* Route(void*){routes++;return available?new ERR_HQRoute:nullptr;}};
|
||||
@@ -145,6 +145,10 @@ int main(){
|
||||
qt.ArrivedAt=0;q.ownUtility.visible=false;quiet.m_Bodies.visible=true;
|
||||
quiet.QueueReports(&q,50000);assert(q.PendingReport&&!q.PendingSweep);
|
||||
|
||||
// A staffed road hold must never manufacture negative search evidence from idle dwell.
|
||||
HQ road;ERR_HQUnit held;ERR_HQTask hold;hold.RoadIntercept=true;hold.ArrivedAt=0;
|
||||
held.Task=&hold;held.Assignment=&hold;held.Waypoint=&hold;held.ownUtility.visible=false;
|
||||
road.Sweep(&held,60000);assert(!held.PendingSweep&&!held.Uplink&&!hold.SweepQueued);
|
||||
}
|
||||
''')
|
||||
print('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')
|
||||
|
||||
@@ -359,6 +359,55 @@ class NativePackageTests(unittest.TestCase):
|
||||
self.assertTrue(summary['stopped_reason'])
|
||||
self.assertEqual(len(summary['skipped_projects']), 4)
|
||||
|
||||
def cleaned_report(self, **changes):
|
||||
values = dict(completion_observed='native-or-launcher-exited-prefix-active',
|
||||
native_exit_code=0, launcher_exit_code=0,
|
||||
cleanup_report={'cleanup_confirmed': True, 'idle_verified': True, 'errors': []})
|
||||
values.update(changes)
|
||||
return self.completed_report(**values)
|
||||
|
||||
def test_verified_post_cleanup_allows_only_diagnostic_later_jobs(self):
|
||||
reports = [self.cleaned_report()] + [self.completed_report() for _ in range(4)]
|
||||
code, summary, calls = self.orchestration_double(reports)
|
||||
self.assertEqual(len(calls), 5)
|
||||
self.assertEqual(code, 1)
|
||||
self.assertEqual(summary['projects'], reports)
|
||||
self.assertFalse(summary['all_candidates_created'])
|
||||
self.assertTrue(summary['diagnostic_continuations'][0]['failed_verdict_retained'])
|
||||
self.assertIsNone(summary['stopped_reason'])
|
||||
code, summary, calls = self.orchestration_double([self.cleaned_report()], False)
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertEqual(summary['diagnostic_continuations'], [])
|
||||
|
||||
def test_cleanup_diagnostic_refuses_uncertain_exit_or_cleanup(self):
|
||||
for change in ({'native_exit_code': None}, {'native_exit_code': 1}, {'native_exit_code': False},
|
||||
{'launcher_exit_code': 1}, {'cleanup_report': None},
|
||||
{'cleanup_report': {'cleanup_confirmed': True, 'idle_verified': False, 'errors': []}},
|
||||
{'cleanup_report': {'cleanup_confirmed': True, 'idle_verified': True, 'errors': ['problem']}}):
|
||||
with self.subTest(change=change):
|
||||
code, summary, calls = self.orchestration_double([self.cleaned_report(**change)])
|
||||
self.assertEqual(code, 1)
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertTrue(summary['stopped_reason'])
|
||||
self.assertEqual(summary['diagnostic_continuations'], [])
|
||||
|
||||
def test_cleanup_diagnostic_rechecks_prefix_before_later_job(self):
|
||||
code, summary, calls = self.orchestration_double([self.cleaned_report()],
|
||||
idle=[None, ValueError('prefix busy again')])
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn('diagnostic continuation refused', summary['stopped_reason'])
|
||||
self.assertEqual(summary['diagnostic_continuations'], [])
|
||||
|
||||
def test_cleanup_diagnostic_rechecks_repository_and_copied_sources(self):
|
||||
for method in ('inventory', 'copied_authored_snapshot'):
|
||||
with self.subTest(method=method), patch.object(n, method, return_value={}):
|
||||
code, summary, calls = self.orchestration_double([self.cleaned_report()])
|
||||
self.assertEqual(len(calls), 1)
|
||||
self.assertEqual(code, 1)
|
||||
self.assertIn('authored inputs changed', summary['stopped_reason'])
|
||||
self.assertEqual(summary['diagnostic_continuations'], [])
|
||||
|
||||
def test_prefix_becoming_busy_between_addons_stops_before_next_launch(self):
|
||||
# First probe owns idle prefix before copy; second is between addons.
|
||||
code, summary, calls = self.orchestration_double(
|
||||
|
||||
Reference in New Issue
Block a user