"""Execute actual activity terminal-event ownership, detach and Update methods. Invoker/engine boundaries are adapters; completion must still be observed natively. """ import re from policy_harness import ROOT, body, run p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_MortarBattery.c' def adapt(signature): s=body(p,signature).replace('protected ','').replace('null','nullptr') s=s.replace('SCR_AIActionBase action','Action* action').replace('EAIActionState ', 'State ').replace('EAIActionState.', 'State::') s=s.replace('ERR_MortarSupplyState ', 'Supply ').replace('ERR_MortarSupplyState.', 'Supply::').replace('ERR_MortarSalvo.', 'Salvo::') s=s.replace('string reason', 'std::string reason') s=s.replace('new ERR_MortarStation(this)', 'new Station(this)') for name in ['Activity','m_ObservedActivity','m_Transfer','m_Readiness','m_Station']: s=s.replace(name+'.',name+'->') for name in ['OnActivityCompleted','OnActivityFailed']: s=s.replace('.Insert('+name+')', '.Insert(this, &Battery::'+name+')').replace('.Remove('+name+')', '.Remove(this, &Battery::'+name+')') s=re.sub(r'^.*(?:Audit|Trace)\([^\n]*$', ';', s, flags=re.M) return s methods='\n'.join(adapt(s) for s in ['protected void WatchActivity(', 'protected void DetachActivity(', 'protected void OnActivityCompleted(', 'protected void OnActivityFailed(', 'protected void ObserveActivityTerminal(', 'protected EAIActionState ActivityTerminal(', 'void Update(']) cancel=adapt('void Cancel(') # Capture the existing cancellation reason before testing its unchanged body. cancel=cancel.replace('{','{\n lastCancel = reason;',1) run(r''' #include #include enum class State {EVALUATED,RUNNING,SUSPENDED,COMPLETED,FAILED}; enum class Supply {FAILED=-1,PENDING,READY}; struct Salvo {static constexpr float TIMEOUT_MS=120000;}; struct Text:std::string {using std::string::string;bool IsEmpty(){return empty();}}; struct Action;struct Battery; struct Station {Station(Battery*){} void Update(float){} void Stop(){}}; struct Invoker {Battery* owner=nullptr;void(Battery::*callback)(Action*)=nullptr;int inserts=0,removes=0,calls=0; void Insert(Battery* b,void(Battery::*f)(Action*)){owner=b;callback=f;inserts++;} void Remove(Battery* b,void(Battery::*f)(Action*)){if(owner==b&&callback==f){owner=nullptr;callback=nullptr;removes++;}} void Invoke(Action* a){if(owner&&callback){calls++;(owner->*callback)(a);}} }; struct Action {State state=State::RUNNING;Invoker m_OnActionCompleted,m_OnActionFailed;int completes=0; State GetActionState(){return state;} void Complete(){completes++;state=State::COMPLETED;m_OnActionCompleted.Invoke(this);state=State::EVALUATED;} void Fail(){state=State::FAILED;m_OnActionFailed.Invoke(this);state=State::EVALUATED;} }; struct Transfer {int invalidates=0;void Invalidate(){invalidates++;}void Poll(float){}}; struct Readiness {Supply Poll(float){return Supply::READY;}}; struct Battery { bool m_Stopped=false,CommandOpen=true,StartupActivated=true,operational=true,RefreshRejected=false,Released=false; Action* Activity=nullptr;Action* m_ObservedActivity=nullptr;int OrderId=1,m_ObservedActivityOrder=0,OrderShots=8,TotalShots=8; State m_ActivityTerminal=State::EVALUATED;float OrderedAt=0,AimedAt=100,AimValidatedAt=100; Text SpawnFailure;std::string lastCancel;Readiness readiness;Readiness* m_Readiness=&readiness;Transfer transfer;Transfer* m_Transfer=&transfer;Station* m_Station=nullptr; void* RadioOrder=nullptr;void* PendingRadioReport=nullptr;void* PendingSupplyReport=nullptr;void* Refresh=nullptr;void* Report=this;void* AimSolver=this;void* AimSolution=this; bool Operational(){return operational;}std::string OperationalReason(){return "physical crew unavailable";}void SpawnFailed(const char*){}void UpdateCrewDetail(float){}void FlushTraces(){} '''+methods+cancel+r''' }; int main(){ Action a;Battery b;b.Activity=&a;b.WatchActivity();assert(a.m_OnActionCompleted.inserts==1&&a.m_OnActionFailed.inserts==1); a.Complete();assert(a.GetActionState()==State::EVALUATED&&b.m_ActivityTerminal==State::COMPLETED); assert(b.CommandOpen&&b.Activity==&a&&a.completes==1); // no cancellation/reentrancy in callback b.Update(100000);assert(!b.CommandOpen&&!b.Activity&&b.lastCancel=="native artillery activity COMPLETED (shot count is separate)"); assert(a.m_OnActionCompleted.calls==1&&a.m_OnActionCompleted.removes==1&&a.m_OnActionFailed.removes==1); assert(a.completes==2&&b.OrderShots==8&&b.TotalShots==8); // cleanup Complete cannot create a second observed event Action f;Battery failed;failed.Activity=&f;failed.WatchActivity();f.Fail();failed.Update(100000); assert(failed.lastCancel=="native artillery activity FAILED"&&f.m_OnActionFailed.calls==1&&f.m_OnActionCompleted.calls==0); Action noEvent;Battery timeout;timeout.Activity=&noEvent;timeout.WatchActivity();noEvent.state=State::EVALUATED;timeout.Update(120001); assert(timeout.lastCancel=="120s artillery salvo timeout"); // eight physical shots alone are not a completion event Action early;Battery partial;partial.Activity=&early;partial.OrderShots=2;partial.TotalShots=2;partial.WatchActivity();early.Complete();partial.Update(50000); assert(partial.OrderShots==2&&partial.TotalShots==2&&partial.lastCancel=="native artillery activity COMPLETED (shot count is separate)"); // fixture must still reject incomplete salvo Action old,current;Battery replacement;replacement.Activity=&old;replacement.WatchActivity();replacement.Cancel("replace");replacement.OrderId=2;replacement.CommandOpen=true;replacement.Activity=¤t;replacement.WatchActivity(); replacement.OnActivityCompleted(&old);assert(replacement.m_ActivityTerminal==State::EVALUATED&¤t.completes==0); replacement.OrderId=3;replacement.OnActivityCompleted(¤t);assert(replacement.m_ActivityTerminal==State::EVALUATED); // captured order mismatch replacement.OrderId=2;current.Complete();replacement.OrderId=3;assert(replacement.ActivityTerminal()==State::EVALUATED); // old latch cannot escape to another order Action foreign;replacement.OrderId=2;replacement.DetachActivity();replacement.WatchActivity();replacement.OnActivityCompleted(&foreign);assert(replacement.m_ActivityTerminal==State::EVALUATED); Action cancelled;Battery explicitCancel;explicitCancel.Activity=&cancelled;explicitCancel.WatchActivity();explicitCancel.Cancel("HQ stopped"); assert(cancelled.m_OnActionCompleted.calls==0&&cancelled.m_OnActionCompleted.removes==1&&explicitCancel.m_ActivityTerminal==State::EVALUATED); explicitCancel.OnActivityCompleted(&cancelled);assert(explicitCancel.m_ActivityTerminal==State::EVALUATED); Action first;Battery precedence;precedence.Activity=&first;precedence.WatchActivity();first.Fail();precedence.OnActivityCompleted(&first);assert(precedence.ActivityTerminal()==State::FAILED); Action stopped;Battery halted;halted.Activity=&stopped;halted.WatchActivity();halted.m_Stopped=true;stopped.Complete();assert(halted.m_ActivityTerminal==State::EVALUATED);halted.Update(120001);assert(halted.Activity==&stopped);halted.Cancel("stop cleanup"); Action polled;Battery fallback;fallback.Activity=&polled;fallback.WatchActivity();polled.state=State::COMPLETED;fallback.Update(100000);assert(fallback.lastCancel=="native artillery activity COMPLETED (shot count is separate)"); } ''') order=body(p,'void Order(');cancel_source=body(p,'void Cancel(') assert order.index('WatchActivity();')