"""Actual HQ report methods, with deterministic radio/perception boundary doubles. Native radio and perception remain explicit boundary doubles. """ from pathlib import Path import os,re from policy_harness import ROOT,body,run P=Path(os.environ.get('ERR_HQ_SOURCE',str(ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQ.c'))) original=(ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQ.c').read_text() legacy='Observe(unit, now);\n ObserveBody(unit, now);\n StatusReport(unit, now);\n Sweep(unit, now);' assert legacy in original or 'QueueReports(unit, now);' in original def cpp(s): s=s.replace('protected ','').replace('array','Array') for t in ('ERR_HQUnit','ERR_HQVisualContact','ERR_HQRoute','ERR_HQReport','ERR_HQStatusReport','ERR_HQTask','ERR_HQSearchReport'): s=re.sub(r'\b'+t+r' (?=\w)',t+'* ',s) s=s.replace('ERR_HQReport* report;','ERR_HQReport* report = nullptr;') for v in ('unit','route','report','seen','task'): s=s.replace(v+'.',v+'->') for v in ('Utility','ReportSelection','StatusSampler','Group'): s=s.replace('->'+v+'.','->'+v+'->') s=s.replace('m_Mode.Settings().Get(', 'm_Mode->Settings()->Get(') s=s.replace('ERR_HQObservation.','ERR_HQObservation::').replace('vector.DistanceXZ','vector::DistanceXZ').replace('string.Format','Format') return s.replace('AmmoState.', 'AmmoState->') methods='\n'.join(cpp(body(P,s)) for s in ('protected void Observe(', 'protected void ObserveBody(', 'protected void StatusReport(', 'protected void Sweep(', 'protected void QueueReports(')) run(r''' #include #include #include #include using string=std::string; templatestring Format(T... args){return "diagnostic";} struct vector{float x=0,z=0;static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}}; templatestruct Array:std::vector{bool IsEmpty(){return this->empty();}}; struct Group{vector position;vector GetOrigin(){return position;}}; struct Settings{int interval=5;int Get(string){return interval;}}; struct Mode{struct Settings settings;struct Settings* Settings(){return &settings;}}; struct ERR_HQVisualContact{void* Entity=this;vector Position;float ObservedAt=0;}; struct Utility{bool visible=true;}; struct ERR_HQObservation{static void Gather(Utility* u,float now,Array& a){ static ERR_HQVisualContact contact;if(u->visible){contact.ObservedAt=now;a.push_back(&contact);}} templatestatic void Describe(T*,Array&,float){} }; struct Selection{int attempts=0;ERR_HQVisualContact* Select(Array& a){return a.front();}void Queued(void*){attempts++;}}; struct ERR_HQReport{int MessageId=0;void* Target=nullptr;Group* Source=nullptr;vector Position;float ObservedAt=0; string Description(float){return "contact";}ERR_HQReport* Snapshot(){return new ERR_HQReport(*this);}}; struct AmmoSnapshot{bool Complete=false;int SmallArmsRounds=0,SmallArmsChambers=0;}; struct ERR_HQStatusReport{AmmoSnapshot* AmmoState=nullptr;int MessageId=0,Count=4,Fit=4,ConfirmedLosses=0;string SourceId="own-1";float ObservedAt=0;}; struct Sampler{int observations=0,samples=0;bool valid=true;void ObserveDeaths(){observations++;} 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; string WatchSourceId;int WatchMessageId=0,WatchRequestId=0,BeliefId=1,BeliefRevision=2;}; struct ERR_HQRoute{int MessageId=0;string Payload;}; struct Network{bool available=true;int routes=0;ERR_HQRoute* Route(void*){routes++;return available?new ERR_HQRoute:nullptr;}}; struct Bodies{bool visible=false;bool Observe(Group*,float now,ERR_HQReport*& r){if(!visible)return false;r=new ERR_HQReport;r->ObservedAt=now;return true;}}; struct Journal{int seq=0;int Message(){return ++seq;}void Write(string,string,int){}}; struct ERR_HQUnit{int Id=1;struct Group ownGroup;struct Utility ownUtility;Selection selection;Sampler sampler; struct Group* Group=&ownGroup;struct Utility* Utility=&ownUtility;void* Radio=this; Selection* ReportSelection=&selection;Sampler* StatusSampler=&sampler; ERR_HQRoute* Uplink=nullptr;ERR_HQReport* PendingReport=nullptr;ERR_HQStatusReport* PendingStatus=nullptr;ERR_HQSearchReport* PendingSweep=nullptr; float NextReport=0,NextStatus=0;bool fighting=false,eligible=true; ERR_HQTask* Task=nullptr;void* Assignment=nullptr;void* Waypoint=nullptr; bool Eligible(Mode*){return eligible;} }; struct HQ{Mode mode;Mode* m_Mode=&mode;Network m_Network;Bodies m_Bodies;Journal m_Journal; bool Fighting(ERR_HQUnit* u){return u->fighting;} '''+methods+r''' void Legacy(ERR_HQUnit* unit,float now){ '''+legacy+r''' } }; struct Stats{int sight=0,status=0,sweep=0;}; void Finish(ERR_HQUnit& u,Stats& s,bool failed=false){ assert(u.Uplink);if(u.PendingStatus){if(!failed)s.status++;} else if(u.PendingSweep){if(!failed)s.sweep++;else u.Task->SweepQueued=false;} else {assert(u.PendingReport);if(!failed)s.sight++;} u.Uplink=nullptr;u.PendingStatus=nullptr;u.PendingSweep=nullptr;u.PendingReport=nullptr; } Stats Simulate(bool legacy){HQ h;ERR_HQUnit u;Stats counts;float due=-1; // Model three native two-second legs; real transport is intentionally outside this harness. for(int now=0;now<=600000;now+=1000){ if(u.Uplink&&now>=due)Finish(u,counts); bool busy=u.Uplink;if(legacy)h.Legacy(&u,now);else h.QueueReports(&u,now); if(!busy&&u.Uplink)due=now+6000; } assert(u.sampler.observations==601);return counts; } int main(){ auto before=Simulate(true);assert(before.sight==100&&before.status==0); auto after=Simulate(false);assert(after.status>=4&&after.sight>=95); HQ h;ERR_HQUnit u;Stats counts; // Queueing creates no delivered roster knowledge; exact sampled time stays on the pending report. h.QueueReports(&u,0);assert(u.PendingStatus&&u.PendingStatus->ObservedAt==0&&counts.status==0); auto original=u.Uplink;h.QueueReports(&u,1000);assert(u.Uplink==original&&u.sampler.observations==2); Finish(u,counts);assert(counts.status==1);h.QueueReports(&u,6000);assert(u.PendingReport); // HQ check-in may be delivered while sighting uplink is busy; next free slot must carry status. u.NextStatus=7000;h.QueueReports(&u,7000);assert(u.PendingReport&&!u.PendingStatus); Finish(u,counts);h.QueueReports(&u,12000);assert(u.PendingStatus&&u.PendingStatus->ObservedAt==12000); // Quiet-but-visible contact must suppress negative evidence, even though Fighting is false. Finish(u,counts);ERR_HQTask task;task.ArrivedAt=0;u.Task=&task;u.Assignment=&task;u.Waypoint=&task; h.QueueReports(&u,30000);assert(u.PendingReport&&!u.PendingSweep&&task.ArrivedAt==-1&&!u.fighting); // Start dwell only once native perception sees no actual contact. Finish(u,counts);u.ownUtility.visible=false;h.QueueReports(&u,36000); assert(task.ArrivedAt==36000&&!u.Uplink); // While a status frame is busy, newly visible contact still resets the dwell clock. u.NextStatus=37000;h.QueueReports(&u,37000);assert(u.PendingStatus); u.ownUtility.visible=true;h.QueueReports(&u,38000); assert(task.ArrivedAt==-1&&u.PendingStatus&&!u.PendingReport); // Clearing vision begins a fresh 30s dwell even while the existing uplink remains occupied. u.ownUtility.visible=false;h.QueueReports(&u,39000);assert(task.ArrivedAt==39000); Finish(u,counts);h.QueueReports(&u,43000);assert(!u.Uplink); h.QueueReports(&u,68000);assert(!u.PendingSweep); h.QueueReports(&u,69000);assert(u.PendingSweep&&u.PendingSweep->ObservedAt==69000&&task.SweepQueued); // Delivered search is never queued a second time. Finish(u,counts);h.QueueReports(&u,75000);assert(!u.Uplink&&counts.sweep==1); // A failed negative retry remains subordinate to current positive observation. task.SweepQueued=false;u.ownUtility.visible=true;h.QueueReports(&u,80000); assert(u.PendingReport&&!u.PendingSweep&&task.ArrivedAt==-1); // Quiet vision is insufficient while Fighting: combat and movement each reset uninterrupted dwell. Finish(u,counts);u.ownUtility.visible=false;u.fighting=true;task.ArrivedAt=0; h.QueueReports(&u,90000);assert(task.ArrivedAt==-1&&!u.PendingSweep); u.fighting=false;h.QueueReports(&u,91000);assert(task.ArrivedAt==91000); u.ownGroup.position.x=100;h.QueueReports(&u,92000);assert(task.ArrivedAt==-1&&!u.PendingSweep); u.ownGroup.position.x=0;h.QueueReports(&u,93000);assert(task.ArrivedAt==93000); h.QueueReports(&u,122000);assert(!u.PendingSweep);h.QueueReports(&u,123000);assert(u.PendingSweep); // Route loss or an unavailable status sampler must not fabricate a report or block useful contacts. HQ lost;ERR_HQUnit v;lost.m_Network.available=false;lost.QueueReports(&v,0); assert(!v.Uplink&&v.NextStatus==0&&v.selection.attempts==0); lost.m_Network.available=true;v.sampler.valid=false;lost.QueueReports(&v,6000);assert(v.PendingReport&&!v.PendingStatus); // An unavailable radio cannot manufacture a frame or a delivery. HQ quiet;ERR_HQUnit q;q.Radio=nullptr;quiet.QueueReports(&q,48000);assert(!q.Uplink); // Body evidence still gets its physical radio frame ahead of a completed negative sweep. ERR_HQTask qt;q.Radio=&q;q.NextStatus=999999;q.Task=&qt;q.Assignment=&qt;q.Waypoint=&qt; qt.ArrivedAt=0;q.ownUtility.visible=false;quiet.m_Bodies.visible=true; quiet.QueueReports(&q,50000);assert(q.PendingReport&&!q.PendingSweep); } ''') 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')