Files
EscapistsReintroduced/tests/test_native_local_handoff_probe.py
T

83 lines
8.8 KiB
Python

"""Actual staged fixture transitions, immutable comparison and cleanup; no native proof."""
import os,re,sys
from pathlib import Path
REPO=Path(os.environ.get('ERR_REPO','/home/jakub/devel/escapistsrereintroduced'));sys.path.insert(0,str(REPO/'tests'))
from policy_harness import body,run
P=Path(os.environ.get('ERR_LOCAL_PROBE_SOURCE',REPO/'addons/chat/Scripts/Game/ERR/ERR_NativeLocalHandoffProbe.c'))
s='\n'.join(body(P,signature) for signature in ('protected bool SameSnapshot(','int Tick(ERR_HQ hq, float now)','void Stop()'))
s=s.replace('protected ','').replace('null','nullptr').replace('bool restore;', 'bool restore = false;')
s=s.replace('ERR_GameMode.Get()', 'Mode::Get()')
for a,b in [('ERR_HQReport report','Report* report'),('ERR_HQ hq','HQ* hq'),('ERR_HQBelief before','Belief* before'),('ERR_HQBelief belief','Belief* belief'),('IEntity caller','Actor* caller')]:s=s.replace(a,b)
for v in ('report','m_Payload','m_Source','m_Recipient','m_Mode','m_HQ','m_Route','hq','before','belief','m_Witness'):s=s.replace(v+'.',v+'->')
s=s.replace('Memory().','Memory()->').replace('Nodes[0].','Nodes[0]->').replace('PendingReport.','PendingReport->')
run(r'''
#include <cassert>
#include <string>
#include <vector>
struct Group{};
struct Actor{int moved=0,last=0;void SetOrigin(int value){moved++;last=value;}};
struct Mode{static inline Mode* current=nullptr;bool active=true;bool Active(){return active;}static Mode* Get(){return current;}};
struct Report{int MessageId=1,Position=20,ObservedAt=0;Group* Source=nullptr;Mode* Target=nullptr;};
struct Node{struct Group* Group;Actor* Carrier;};
struct Nodes:std::vector<Node*>{int Count(){return size();}};
struct Route{Group* LocalSource=nullptr;Actor* LocalWitness=nullptr;struct Nodes Nodes;bool Originated=false,Sending=false,Failed=false;int Cursor=0,result=0;int Advance(float,int){return result;}};
struct Unit{struct Group* Group;Route* Uplink=nullptr;Report* PendingReport=nullptr;};
struct Belief{int MessageId=0;struct Report* Report=nullptr;};
struct Memory{Belief* belief=nullptr;Belief* Get(Mode*){return belief;}};
struct HQ{struct Memory memory;int sourceToken=220001,recipientToken=220001;Group* source;int releases=0;int Journal(){return 0;}struct Memory* Memory(){return &memory;}int LogisticsToken(Group* g){return g==source?sourceToken:recipientToken;}void ReleaseLogistics(Group*g,int token){releases++;int& held=g==source?sourceToken:recipientToken;if(held==token)held=0;}bool ERR_ProbeSquadsReady(){return true;}};
struct Probe{static constexpr int TOKEN=220001;Mode* m_Mode;HQ* m_HQ;Unit* m_Source;Unit* m_Recipient;Actor* m_Witness;Actor* m_Radio;Route* m_Route;Report* m_Payload;int m_OriginalPosition=7,m_DeliveredId=0,m_State=1;float m_Wait=0,m_Deadline=0;bool m_Stopped=false,m_Failed=false,m_Positioned=true,owned=true,canQueue=true;int removals=0;bool Enabled(){return true;}bool Original(Unit*,Actor*){return owned&&m_Mode&&m_Mode==Mode::Get()&&m_Mode->active;}bool Select(){return true;}bool Arrange(){return true;}bool Queue(float){if(canQueue){m_Source->Uplink=m_Route;m_Source->PendingReport=m_Payload;}return canQueue;}bool Check(bool value,std::string){if(!value){m_Failed=true;Stop();}return value;}int RemoveRadios(Unit*,Actor*){removals++;return 1;}
METHODS
};
struct F{Mode mode;Group source,recipient;Actor witness,radio;Node node{&recipient,&radio};Route route;Report report;Unit src{&source},dst{&recipient};HQ hq;Probe p;Belief belief;F():p{&mode,&hq,&src,&dst,&witness,&radio,&route,&report}{Mode::current=&mode;hq.source=&source;route.LocalSource=&source;route.LocalWitness=&witness;route.Nodes.push_back(&node);report.Source=&source;report.Target=&mode;belief.MessageId=1;belief.Report=&report;}};
int main(){
{F f;assert(f.p.Tick(&f.hq,0)==0&&f.p.m_State==2&&!f.p.m_Failed);assert(f.p.Tick(&f.hq,1000)==0&&f.p.m_State==2);f.route.Originated=true;f.route.Cursor=1;f.hq.memory.belief=&f.belief;assert(f.p.Tick(&f.hq,6000)==0&&f.p.m_State==3&&f.p.m_DeliveredId==1);Report second=f.report;second.MessageId=2;second.ObservedAt=7000;Route retry=f.route;retry.Originated=false;retry.Cursor=0;f.p.m_Payload=&second;f.p.m_Route=&retry;assert(f.p.Tick(&f.hq,7000)==0&&f.p.m_State==4&&f.p.removals==1);assert(f.p.Tick(&f.hq,8000)==0);retry.Failed=true;assert(f.p.Tick(&f.hq,9000)==1&&f.p.m_Stopped&&f.hq.sourceToken==0&&f.hq.recipientToken==0&&f.witness.moved==1);}
// An HQ belief appearing before the actual radio chain finishes cannot pass.
{F f;f.p.Tick(&f.hq,0);f.hq.memory.belief=&f.belief;assert(f.p.Tick(&f.hq,1000)==-1&&f.p.m_Failed);}
{F f;f.p.Tick(&f.hq,0);Report altered=f.report;altered.Position++;f.src.PendingReport=&altered;assert(f.p.Tick(&f.hq,1000)==-1);}
{F f;Report other=f.report;other.Source=&f.recipient;assert(!f.p.SameSnapshot(&other));other=f.report;other.ObservedAt++;assert(!f.p.SameSnapshot(&other));}
// A newer second belief invalidates interruption proof even if route failed.
{F f;f.p.m_State=4;f.p.m_DeliveredId=1;f.belief.MessageId=2;f.hq.memory.belief=&f.belief;f.route.Failed=true;assert(f.p.Tick(&f.hq,0)==-1);}
{F f;f.src.Uplink=&f.route;f.src.PendingReport=&f.report;f.p.Stop();assert(!f.src.Uplink&&!f.src.PendingReport&&f.witness.moved==1);int calls=f.hq.releases;f.p.Stop();assert(f.hq.releases==calls&&f.witness.moved==1);}
{F f;Route other;f.src.Uplink=&other;f.src.PendingReport=&f.report;f.hq.sourceToken=99;f.hq.recipientToken=98;f.p.Stop();assert(f.src.Uplink==&other&&f.src.PendingReport==&f.report&&f.hq.sourceToken==99&&f.hq.recipientToken==98&&f.witness.moved==0);}
{F f;Mode::current=nullptr;f.p.Stop();assert(f.witness.moved==0);}
{F f;f.p.owned=false;f.p.Stop();assert(f.witness.moved==0);}
}
'''.replace('METHODS',s))
assert 'ObservationRoute(unit)' in body(P,'ERR_HQRoute ERR_LocalFixtureQueue(')
assert 'arranged.Snapshot()' in body(P,'ERR_HQRoute ERR_LocalFixtureQueue(')
assert 'm_Wait = now + 1500' in body(P,'int Tick(ERR_HQ hq, float now)')
print('PASS: actual fixture local/radio gating, immutable source/time/position, interrupted-report exclusion and exact-owner cleanup')
# Invalid fixture launch requests stop during argument validation, before any runtime work.
import subprocess
runner=Path(os.environ.get('ERR_LOCAL_RUNNER_SOURCE',REPO/'tools/headless_workbench.py'))
code="import sys,runpy;sys.path.insert(0,"+repr(str(REPO/'tools'))+");p=sys.argv.pop(1);runpy.run_path(p,run_name='__main__')"
for project,rounds,timeout in [('core','1','360'),('chat','2','360'),('chat','1','359')]:
result=subprocess.run([sys.executable,'-c',code,str(runner),'--compat','/tmp/unused-local-handoff','--profile','/tmp/unused-local-handoff-profile','--smoke','hq-local-handoff','--project',project,'--rounds',rounds,'--timeout',timeout],capture_output=True,text=True)
assert result.returncode==2 and 'hq-local-handoff requires Chat, one round and timeout at least360 seconds' in result.stderr,result.stderr
print('PASS: local-handoff runner refuses wrong project/round count/insufficient deadline before runtime work')
# Fixture radio deletion must wait for allocator ownership to end, not merely
# for a radio to appear in inventory or a late callback to claim completion.
settled=body(P,'bool ERR_LocalFixtureSettled()')
lookup=body(P,'bool ERR_LocalFixtureRadiosSettled(SCR_AIGroup group)')
lookup=lookup.replace('SCR_AIGroup group','Group* group').replace('ERR_NativeLocalHandoffProbe.Enabled()','Enabled()')
lookup=lookup.replace('foreach (ERR_EnemyEquipmentGroup state : m_Groups)','for (State* state : m_Groups)').replace('state.','state->').replace('Radio.','Radio->')
run(r'''
#include <cassert>
#include <vector>
struct Radio{bool m_Stopped=false,m_Submitted=false;
SETTLED
};
struct Group{};
struct State{struct Group* Group;struct Radio* Radio;};
struct Equipment{bool enabled=true;std::vector<State*> m_Groups;bool Enabled(){return enabled;}
LOOKUP
};
int main(){Radio radio;assert(!radio.ERR_LocalFixtureSettled());radio.m_Submitted=true;assert(!radio.ERR_LocalFixtureSettled());radio.m_Stopped=true;assert(!radio.ERR_LocalFixtureSettled());radio.m_Submitted=false;assert(radio.ERR_LocalFixtureSettled());Group group,unknown;State state{&group,&radio};Equipment e;e.m_Groups.push_back(&state);assert(e.ERR_LocalFixtureRadiosSettled(&group));assert(!e.ERR_LocalFixtureRadiosSettled(&unknown));assert(!e.ERR_LocalFixtureRadiosSettled(nullptr));radio.m_Submitted=true;assert(!e.ERR_LocalFixtureRadiosSettled(&group));state.Radio=nullptr;assert(e.ERR_LocalFixtureRadiosSettled(&group));e.enabled=false;assert(!e.ERR_LocalFixtureRadiosSettled(&group));}
'''.replace('SETTLED',settled).replace('LOOKUP',lookup))
selection=body(P,'protected bool Select()')
assert selection.index('ERR_LocalFixtureRadiosSettled(recipient)')<selection.index('ReserveLogistics(recipient.Group')
assert selection.index('ERR_LocalFixtureRadiosSettled(source)')<selection.index('ReserveLogistics(source.Group')
print('PASS: actual fixture allocator terminal/unsubmitted guard, original-group lookup and both candidate gates')