"""Execute actual HQ assignment ownership and failed-radio backoff bodies.""" from policy_harness import ROOT, body, run import os,re from pathlib import Path p=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c')) unit='\n'.join(body(p,s) for s in ('void CancelPending(', 'ERR_HQTask PlannedTask(', 'void ReleaseAssignment(', 'void Release()')) lost='\n'.join(body(p,s) for s in ('protected void FinishWatch(', 'protected void LostOrder(')).replace('protected ', '') if 'protected float HuntCommitment(' in p.read_text(): lost=body(p,'protected float HuntCommitment(').replace('protected ', '')+'\n'+lost if 'protected void ReconsiderHunt(' in p.read_text(): lost=body(p,'protected void ReconsiderHunt(').replace('protected ', '')+'\n'+lost if 'protected void RememberCompletedHunt(' in p.read_text(): lost=body(p,'protected void RememberCompletedHunt(').replace('protected ', '')+'\n'+lost retask=body(p,'protected void Retask(') queue=retask[retask.index(' ERR_HQHuntWindow hunt = Hunt(belief.BeliefId);'):] queue=queue[:queue.index(';',queue.index('if (hunt)'))+1] def adapt(code): code=code.replace('EngineerMine.', 'EngineerMine->') code=code.replace('ref ', '') code=code.replace('.IsEmpty()', '.empty()') code=code.replace('null','nullptr').replace('Math.','Math::').replace('ERR_GameMode.Get()', 'ERR_GameMode::Get()') code=code.replace('SCR_EntityHelper.DeleteEntityAndChildren', 'SCR_EntityHelper::DeleteEntityAndChildren') code=code.replace('ERR_HQUnit unit', 'ERR_HQUnit* unit') code=code.replace('ERR_HQTask ', 'ERR_HQTask* ').replace('ERR_GameMode ', 'ERR_GameMode* ') code=code.replace('ERR_ForceRecord record;', 'ERR_ForceRecord* record=nullptr;') code=code.replace('ERR_HQHuntWindow hunt', 'ERR_HQHuntWindow* hunt') code=code.replace('foreach (ERR_HQUnit other : m_Units)', 'for (ERR_HQUnit* other : m_Units)') code=re.sub(r'\b(mode|record|Group|unit|other|task|planned|hunt)\.', r'\1->', code) code=code.replace('mode->Forces().FindGroup', 'mode->Forces()->FindGroup') return code.replace('other->Task.', 'other->Task->') run(r''' #include #include #include #include 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 {int OperationId=0,ExecutionId=0;float ExpiresAt=120000;bool ReleaseOnly=false;int BeliefId=0;std::string WatchSourceId,SecuritySite;int WatchMessageId=0,WatchRequestId=0;bool Investigation(){return !WatchSourceId.empty();}}; struct ERR_HQReport {}; struct ERR_HQRoute {}; struct AIWaypoint {int deleted=0;}; struct SCR_AIGroup {int removals=0; AIWaypoint* last=nullptr; void RemoveWaypoint(AIWaypoint* p){removals++;last=p;}}; struct ERR_ForceRecord {bool Eliminated=false;int Duty=2,HomeDuty=1,DutyTarget=200,HomePosition=100;}; struct Ledger {ERR_ForceRecord* record=nullptr;ERR_ForceRecord* FindGroup(SCR_AIGroup*){return record;}}; struct ERR_GameMode {Ledger ledger;static ERR_GameMode* current; static ERR_GameMode* Get(){return current;} Ledger* Forces(){return &ledger;}}; ERR_GameMode* ERR_GameMode::current=nullptr; struct SCR_EntityHelper {static void DeleteEntityAndChildren(AIWaypoint* p){p->deleted++;}}; struct ERR_HQHuntWindow {float CompletedUntil=0;float NextHunt=0;}; struct MineJob {int stops=0;void Stop(){stops++;}}; struct TestWorld{operator bool(){return true;}float GetWorldTime(){return 0;}};struct TestGame{operator bool(){return true;}TestWorld GetWorld(){return {};}};TestGame GetGame(){return {};}struct ExecutionDouble{operator bool(){return false;}void Stop(float,bool){}void Receive(ERR_HQTask*,float){}}; struct ERR_HQUnit {ExecutionDouble Execution;int Id=1;MineJob* EngineerMine=nullptr; SCR_AIGroup* Group=nullptr;AIWaypoint* Waypoint=nullptr; ERR_HQTask *Task=nullptr,*PendingTask=nullptr; ERR_HQReport *Assignment=nullptr,*PendingOrder=nullptr; void* PendingConvoyOrder=nullptr; ERR_HQRoute* Downlink=nullptr;bool PendingCheckin=false; '''+adapt(unit)+r'''}; struct Watch {int calls=0;std::string source;int message=0,request=0; bool FinishInvestigation(std::string s,int m,int r,float){calls++;source=s;message=m;request=r;return true;}}; struct HQ {struct Operations{void Release(int,int,float){}}m_Operations;Watch m_Watch;std::vector m_Units;ERR_HQHuntWindow window; ERR_HQHuntWindow* Hunt(int){return &window;} '''+adapt(lost)+'\nvoid QueueHorizon(ERR_HQTask* task,int id){struct Belief{int BeliefId;};Belief belief{id};'+adapt(queue)+'}'+r'''}; int main(){ ERR_GameMode mode;ERR_GameMode::current=&mode;ERR_ForceRecord record;mode.ledger.record=&record; SCR_AIGroup group;ERR_HQReport oldReport,newReport;ERR_HQTask oldTask,newTask;ERR_HQRoute route; oldTask.BeliefId=1;newTask.BeliefId=2;AIWaypoint oldWaypoint; ERR_HQUnit u;u.Group=&group;u.Waypoint=&oldWaypoint;u.Assignment=&oldReport;u.Task=&oldTask; u.PendingOrder=&newReport;u.PendingTask=&newTask;u.Downlink=&route; assert(u.PlannedTask()==&newTask); u.CancelPending(); assert(!u.PendingOrder&&!u.PendingTask&&!u.Downlink); assert(u.Task==&oldTask&&u.Assignment==&oldReport&&u.Waypoint==&oldWaypoint); assert(oldWaypoint.deleted==0&&group.removals==0&&record.Duty==2); assert(u.PlannedTask()==&oldTask); // Expiry of the current task must leave its replacement radio transaction intact. u.PendingOrder=&newReport;u.PendingTask=&newTask;u.Downlink=&route; MineJob miner;u.EngineerMine=&miner; u.ReleaseAssignment();assert(miner.stops==1); assert(!u.Assignment&&!u.Task&&!u.Waypoint); assert(u.PendingOrder==&newReport&&u.PendingTask==&newTask&&u.Downlink==&route); assert(oldWaypoint.deleted==1&&group.removals==1&&group.last==&oldWaypoint); assert(record.Duty==record.HomeDuty&&record.DutyTarget==record.HomePosition); u.ReleaseAssignment();assert(oldWaypoint.deleted==1&&group.removals==1); u.Release();assert(!u.PendingOrder&&!u.PendingTask&&!u.Downlink); // Elimination and absent group/mode cannot rewrite casualty state or double-delete. AIWaypoint detached;u.Waypoint=&detached;u.Group=nullptr;record.Eliminated=true;record.Duty=9; ERR_GameMode::current=nullptr;u.Release();assert(detached.deleted==1&&record.Duty==9); ERR_GameMode::current=&mode;u.Group=&group;u.ReleaseAssignment();assert(record.Duty==9); // A failed initial dispatch can retry after a short backoff. HQ hq;hq.m_Units={&u};hq.window.NextHunt=300000; u.PendingOrder=&newReport;u.PendingTask=&newTask;u.Downlink=&route; hq.LostOrder(&u,1000);assert(hq.window.NextHunt==16000&&!u.PendingOrder); // Failed retask preserves delivered assignment and its active hunt window. u.Task=&newTask;u.Assignment=&oldReport;u.Waypoint=&oldWaypoint; u.PendingOrder=&newReport;u.PendingTask=&newTask;u.Downlink=&route;hq.window.NextHunt=300000; hq.LostOrder(&u,1000);assert(hq.window.NextHunt==300000&&u.Task==&newTask&&u.Waypoint==&oldWaypoint); // Another group's pending hunt prevents duplicate retry commitments. u.Task=nullptr;u.Assignment=nullptr;u.Waypoint=nullptr; ERR_HQUnit peer;peer.PendingTask=&newTask;peer.PendingOrder=&newReport;hq.m_Units.push_back(&peer); u.PendingTask=&newTask;u.PendingOrder=&newReport;u.Downlink=&route; hq.LostOrder(&u,2000);assert(hq.window.NextHunt==300000); // Execute the actual Retask deadline extension, then fail its downlink. // The existing short delivered hunt must not inherit the failed long horizon. {HQ h;ERR_HQUnit owner;ERR_HQTask delivered,pending;ERR_HQReport r;ERR_HQRoute link;AIWaypoint w; delivered.BeliefId=7;delivered.ExpiresAt=120000;pending.BeliefId=7;pending.ExpiresAt=600000; owner.Task=&delivered;owner.Assignment=&r;owner.Waypoint=&w; owner.PendingTask=&pending;owner.PendingOrder=&r;owner.Downlink=&link;h.m_Units={&owner};h.window.NextHunt=300000; h.QueueHorizon(&pending,7);assert(h.window.NextHunt==780000); h.LostOrder(&owner,30000);assert(h.window.NextHunt==300000); assert(owner.Task==&delivered&&owner.Assignment==&r&&owner.Waypoint==&w&&!owner.PendingTask); // A surviving peer's longer pending order remains fully reserved. ERR_HQUnit peer;ERR_HQTask active,replacement; active.BeliefId=7;active.ExpiresAt=400000;replacement.BeliefId=7;replacement.ExpiresAt=700000; peer.Task=&active;peer.Assignment=&r;peer.PendingTask=&replacement;peer.PendingOrder=&r;h.m_Units.push_back(&peer); owner.PendingTask=&pending;owner.PendingOrder=&r;h.window.NextHunt=1000000; h.LostOrder(&owner,30000);assert(h.window.NextHunt==880000&&peer.PendingTask==&replacement&&peer.Task==&active); // An unrelated pending replacement cannot conceal its original same-track task. replacement.BeliefId=8;owner.PendingTask=&pending;owner.PendingOrder=&r;h.window.NextHunt=1000000; h.LostOrder(&owner,30000);assert(h.window.NextHunt==580000); // Orphan pointers and non-pursuit orders cannot hold a hunt. A still-owned // expired active search preserves its completed interval. owner.Task=nullptr;owner.Assignment=nullptr;peer.Assignment=nullptr;peer.PendingOrder=nullptr; owner.PendingTask=&pending;owner.PendingOrder=&r;h.window.NextHunt=1000000; h.LostOrder(&owner,30000);assert(h.window.NextHunt==45000); for(int kind=0;kind<4;kind++){ peer.Assignment=&r;active.ExpiresAt=400000;active.ReleaseOnly=false;active.SecuritySite="";active.WatchSourceId=""; if(kind==0){active.ReleaseOnly=true;}if(kind==1){active.SecuritySite="site";}if(kind==2){active.WatchSourceId="patrol";}if(kind==3){active.ExpiresAt=30000;} owner.PendingTask=&pending;owner.PendingOrder=&r;h.window.NextHunt=1000000; h.LostOrder(&owner,30000);assert(h.window.NextHunt==(kind==3?210000:45000));} // Clamping only: even a surviving long commitment must not raise earlier pacing. active.ExpiresAt=400000;owner.PendingTask=&pending;owner.PendingOrder=&r;h.window.NextHunt=40000; h.LostOrder(&owner,30000);assert(h.window.NextHunt==40000); } // A failed pending watch investigation completes only that pending lineage. ERR_HQTask activeWatch,pendingWatch;activeWatch.WatchSourceId="patrol-A";activeWatch.WatchMessageId=41;activeWatch.WatchRequestId=1; pendingWatch.WatchSourceId="patrol-B";pendingWatch.WatchMessageId=42;pendingWatch.WatchRequestId=2; u.Task=&activeWatch;u.Waypoint=&oldWaypoint;u.Assignment=&oldReport; u.PendingTask=&pendingWatch;u.PendingOrder=&newReport;u.Downlink=&route; hq.LostOrder(&u,3000);assert(hq.m_Watch.calls==1&&hq.m_Watch.source=="patrol-B"&&hq.m_Watch.message==42&&hq.m_Watch.request==2); assert(u.Task==&activeWatch&&u.Waypoint==&oldWaypoint&&u.Assignment==&oldReport); assert(!u.PendingTask&&!u.PendingOrder&&!u.Downlink&&hq.window.NextHunt==300000); // Dropped status-check-in transport cannot finish the delivered investigation. u.PendingCheckin=true;u.Downlink=&route;hq.LostOrder(&u,4000); assert(!u.PendingCheckin&&!u.Downlink&&u.Task==&activeWatch&&hq.m_Watch.calls==1); ERR_HQTask recall;recall.ReleaseOnly=true;u.PendingTask=&recall;u.PendingOrder=&newReport;u.Downlink=&route; hq.LostOrder(&u,5000);assert(!u.PendingTask&&u.Task==&activeWatch&&u.Waypoint==&oldWaypoint&&hq.m_Watch.calls==1); u.Task=nullptr;u.Waypoint=nullptr;u.Assignment=nullptr; ERR_HQTask security;security.SecuritySite="radio-1";u.PendingTask=&security;u.PendingOrder=&newReport;u.Downlink=&route; hq.window.NextHunt=300000;hq.LostOrder(&u,5000); assert(!u.PendingTask&&!u.Downlink&&hq.window.NextHunt==300000&&hq.m_Watch.calls==1); // Unrelated belief cannot suppress bounded retry, and existing sooner retry remains sooner. peer.PendingTask=&oldTask;u.PendingTask=&newTask;hq.window.NextHunt=5000; hq.LostOrder(&u,2000);assert(hq.window.NextHunt==5000); } ''') print('PASS: actual failed-retask deadline rollback plus assignment cancellation/release preserves radio ownership, native waypoint lifecycle and failed-order retry boundaries')