Files
EscapistsReintroduced/tests/test_native_mounted_swap_probe.py
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

154 lines
12 KiB
Python

"""Execute dry native eligibility/slot proof; never launch the runner or inventory operation."""
import ast,os,re
from pathlib import Path
from types import SimpleNamespace
from policy_harness import ROOT,body,run
R=Path(os.environ.get('ERR_NATIVE_SWAP_ROOT',ROOT))
p=Path(os.environ.get('ERR_NATIVE_SWAP_SOURCE',R/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedSwapProbe.c'))
s=p.read_text();fixture=(R/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedAmmoProbe.c').read_text()
smoke=(R/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeSmoke.c').read_text()
runner=(R/'tools/headless_workbench.py').read_text()
assert s.startswith('#ifdef WORKBENCH') and s.rstrip().endswith('#endif')
for forbidden in ('TrySwap','TryMove','TrySpawn','Spawn(', 'SetAmmoCount','SetOrigin','DeleteEntity','.Reserve(','.Commit(','.BindVehicle(','.Sample(','.Advance(','.Ingest(','.AddWaypoint(','.RemoveWaypoint('):
assert forbidden not in s,forbidden
assert s.count('.CanSwapItemStorages(')==1
assert 'Collect(vehicle.Entity, shape, allowed, weapons)' in s
assert 'GetChildren(' not in s and 'GetSibling(' not in s # exact shared production collector, no parallel traversal
for guard in ('index < 6','weapons.Count() > 32','muzzles.Count() > 8','seen.Count() >= 128','cargo.Count() > 128','stores.Count() > 64','InitialStrength() > 32','CanAttribute(slot, worker, false)','ValidateBoundVehicle(vehicle.Entity)','cargo.Contains(outgoing)','ExactGun()'):
assert guard in s,guard
assert 'now + 180000' in fixture and 'now > m_Deadline' in fixture
assert fixture.index('if (m_SwapEligibility)\n {') < fixture.index('Capture(hq, task.Vehicles[slot], now)')
assert 'if (!m_ProofTask) m_ProofTask = Candidate(hq)' in fixture
assert 'native mounted swap eligibility only; no transfer or radio proof' in smoke
assert "['-errMountedSwapEligibility', '1']" in runner
node=next(n for n in ast.parse(runner).body if isinstance(n,ast.If) and 'a.mounted_swap_eligibility' in ast.unparse(n.test))
condition=compile(ast.Expression(node.test),'<flag>','eval')
for flag,outcome,bad in [(True,'mounted-ammo',False),(False,'convoy',False),(True,'convoy',True)]:
assert eval(condition,{'a':SimpleNamespace(mounted_swap_eligibility=flag,smoke=outcome)})==bad
# Check exact dry call/body with controlled native result and original inventory slots.
code='\n'.join(body(p,x) for x in ('protected static bool Attached(', 'protected int DryRun(', 'void Stop('))
code=code.replace('protected ','').replace('InventoryItemComponent.Cast(item.FindComponent(InventoryItemComponent))','item->inventory')
for t in ('IEntity','InventoryStorageSlot','InventoryStorageManagerComponent','BaseMuzzleComponent','InventoryItemComponent'):
code=re.sub(r'\b'+t+r' (?=\w)',t+'* ',code)
for var in ('slot','inventoryItem','source','muzzle'):code=code.replace(var+'.',var+'->')
code=code.replace('GetMagazine().','GetMagazine()->')
run(r'''
#include <cassert>
#include <functional>
struct IEntity;struct InventoryStorageSlot;
struct InventoryItemComponent{InventoryStorageSlot* slot=nullptr;InventoryStorageSlot* GetParentSlot(){return slot;}};
struct IEntity{InventoryItemComponent* inventory=nullptr;};
struct InventoryStorageSlot{IEntity* attached=nullptr;IEntity* GetAttachedEntity(){return attached;}};
struct Magazine{IEntity* owner=nullptr;IEntity* GetOwner(){return owner;}};
struct BaseMuzzleComponent{Magazine* loaded=nullptr;Magazine* GetMagazine(){return loaded;}};
struct InventoryStorageManagerComponent{int calls=0;bool result=true;std::function<void()> effect;
bool CanSwapItemStorages(IEntity* incoming,IEntity* outgoing){assert(incoming&&outgoing);calls++;if(effect)effect();return result;}};
template<class... T>void PrintFormat(T...){}
struct Probe{bool m_Stopped=false,m_Called=false;int m_Result=0;
'''+code+r'''
};
struct Fixture{IEntity incoming,outgoing,foreign;InventoryItemComponent a,b;InventoryStorageSlot from,to,other;
Magazine magazine;BaseMuzzleComponent muzzle;InventoryStorageManagerComponent source;Probe p;
Fixture(){incoming.inventory=&a;outgoing.inventory=&b;a.slot=&from;b.slot=&to;from.attached=&incoming;to.attached=&outgoing;magazine.owner=&outgoing;muzzle.loaded=&magazine;}
int Run(){return p.DryRun(&source,&incoming,&outgoing,&from,&to,&muzzle);}
};
int main(){
{Fixture f;assert(f.Run()==1&&f.source.calls==1&&f.p.m_Called);assert(f.Run()==1&&f.source.calls==1);
assert(f.from.attached==&f.incoming&&f.to.attached==&f.outgoing&&f.muzzle.loaded==&f.magazine);}
{Fixture f;f.source.result=false;assert(f.Run()==-1&&f.source.calls==1);f.source.result=true;assert(f.Run()==-1&&f.source.calls==1);}
for(int bad=0;bad<6;bad++){
Fixture f;if(bad==0){f.incoming.inventory=nullptr;}
if(bad==1){f.a.slot=&f.other;}
if(bad==2){f.from.attached=&f.foreign;}
if(bad==3){f.to.attached=&f.foreign;}
if(bad==4){f.muzzle.loaded=nullptr;}
if(bad==5){f.magazine.owner=&f.foreign;}
assert(f.Run()==-1&&f.source.calls==0&&!f.p.m_Called);}
// Even a positive native query cannot pass if exact ownership changes during it.
for(int changed=0;changed<3;changed++){
Fixture f;f.source.effect=[&](){if(changed==0)f.from.attached=&f.foreign;if(changed==1)f.b.slot=&f.other;if(changed==2)f.muzzle.loaded=nullptr;};
assert(f.Run()==-1&&f.source.calls==1&&f.p.m_Called);assert(f.Run()==-1&&f.source.calls==1);}
{Fixture f;f.p.Stop();assert(f.Run()==-1&&f.source.calls==0);}
{Fixture f;assert(f.Run()==1);f.p.Stop();assert(f.Run()==-1&&f.source.calls==1);}
{Fixture f;assert(f.p.DryRun(nullptr,&f.incoming,&f.outgoing,&f.from,&f.to,&f.muzzle)==-1&&f.source.calls==0);}
}
''')
print('PASS: actual once-only native dry query, exact before/after slots, native refusal, shutdown; shared bounded discovery and distinct opt-in verdict')
# Execute the actual final-completion selector; the step verdict alone does not
# prevent the round-end branch from wrongly claiming radio delivery.
completion=body(ROOT/'addons/core/Scripts/Game/ERR/40_Chat/ERR_NativeMountedAmmoProbe.c','static string CompletionText(')
run('#include <cassert>\n#include <string>\nusing string=std::string;\n'+completion+r'''
int main(){
auto ordinary=CompletionText(false),swap=CompletionText(true);
assert(ordinary.find("actual radio delivery")!=string::npos);
assert(swap.find("eligibility only")!=string::npos);
assert(swap.find("no transfer or radio proof")!=string::npos);
assert(swap.find("actual radio delivery")==string::npos);
assert(swap!=ordinary);
}
''')
terminal=smoke.split('if (m_ERRTestMountedAmmo)\n\t\t{',1)[1].split('if (m_ERRTestConvoy)',1)[0]
assert terminal.index('CompletionText(m_ERRMountedAmmo && m_ERRMountedAmmo.SwapEligibility())') < terminal.index('ERR_StopHQPatrol()') < terminal.index('Print(mountedCompletion)')
assert 'actual radio delivery' not in terminal
print('PASS: actual completion selector and pre-cleanup variant capture preserve separate radio and eligibility verdicts')
# Exercise the production single-pass candidate classifier, not only its log text.
stage='\n'.join(body(p,x) for x in ('protected static bool Attached(', 'protected int CargoStage('))
stage=stage.replace('protected ','').replace('null','nullptr')
stage=stage.replace('BaseMagazineComponent.Cast(candidate.FindComponent(BaseMagazineComponent))','candidate->magazine')
stage=stage.replace('InventoryItemComponent.Cast(candidate.FindComponent(InventoryItemComponent))','candidate->inventory')
stage=stage.replace('InventoryItemComponent.Cast(item.FindComponent(InventoryItemComponent))','item->inventory')
stage=stage.replace('array<BaseInventoryStorageComponent> stores','Array<BaseInventoryStorageComponent*>& stores')
for typ in ('BaseMagazineComponent','InventoryStorageSlot','BaseInventoryStorageComponent'):
stage=stage.replace('out '+typ+' ',typ+'*& ')
for typ in ('IEntity','InventoryItemComponent','InventoryStorageSlot','BaseInventoryStorageComponent'):
stage=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',stage)
for name in ('candidate','magazine','item','from','storage','slot','inventoryItem','m_Muzzle'):
stage=stage.replace(name+'.',name+'->')
stage=stage.replace('GetMagazineWell().','GetMagazineWell()->').replace('ERR_InfantryAmmoTransfer.','ERR_InfantryAmmoTransfer::')
run(r'''
#include <cassert>
#include <algorithm>
#include <vector>
struct IEntity;struct InventoryStorageSlot;struct BaseMagazineComponent;struct InventoryItemComponent;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;bool Contains(T value){return std::find(this->begin(),this->end(),value)!=this->end();}};
struct Well{int kind=1;int Type(){return kind;}bool IsInherited(int type){return kind==type;}};
struct IEntity{BaseMagazineComponent* magazine=nullptr;InventoryItemComponent* inventory=nullptr;IEntity* truck=nullptr;};
struct BaseMagazineComponent{IEntity* owner=nullptr;Well* well=nullptr;bool used=false;int rounds=100,capacity=100;
IEntity* GetOwner(){return owner;}bool IsUsed(){return used;}int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}Well* GetMagazineWell(){return well;}};
struct BaseInventoryStorageComponent{IEntity* owner=nullptr;IEntity* GetOwner(){return owner;}};
struct InventoryStorageSlot{IEntity* attached=nullptr;BaseInventoryStorageComponent* storage=nullptr;IEntity* GetAttachedEntity(){return attached;}BaseInventoryStorageComponent* GetStorage(){return storage;}};
struct InventoryItemComponent{InventoryStorageSlot* slot=nullptr;InventoryStorageSlot* GetParentSlot(){return slot;}};
struct Muzzle{Well* well=nullptr;Well* GetMagazineWell(){return well;}};
namespace ERR_InfantryAmmoTransfer{int checks=0;bool StoredUnder(IEntity* owner,IEntity* truck){checks++;return owner&&owner->truck==truck;}}
struct Probe{Muzzle* m_Muzzle=nullptr;
'''+stage+r'''
};
struct Fixture{IEntity candidate,truck,storageOwner,foreign;Well well,wrong;BaseMagazineComponent magazine;
BaseInventoryStorageComponent storage;InventoryStorageSlot slot,other;InventoryItemComponent item;Muzzle muzzle;Probe probe;
Array<BaseInventoryStorageComponent*> stores;BaseMagazineComponent* selected=nullptr;InventoryStorageSlot* from=nullptr;BaseInventoryStorageComponent* selectedStorage=nullptr;
Fixture(){wrong.kind=2;candidate.magazine=&magazine;candidate.inventory=&item;magazine.owner=&candidate;magazine.well=&well;
storageOwner.truck=&truck;storage.owner=&storageOwner;slot.attached=&candidate;slot.storage=&storage;item.slot=&slot;stores={&storage};muzzle.well=&well;probe.m_Muzzle=&muzzle;}
int Run(){return probe.CargoStage(&candidate,&truck,stores,selected,from,selectedStorage);}
};
int main(){
{Fixture f;assert(f.Run()==4&&f.selected==&f.magazine&&f.from==&f.slot&&f.selectedStorage==&f.storage);}
for(int bad=0;bad<2;bad++){Fixture f;if(bad==0){f.candidate.magazine=nullptr;}else f.magazine.owner=&f.foreign;
assert(f.Run()==0&&f.from==nullptr&&f.selectedStorage==nullptr);}
for(int bad=0;bad<5;bad++){Fixture f;if(bad==0){f.magazine.used=true;}if(bad==1){f.magazine.rounds=0;}
if(bad==2){f.magazine.capacity=0;}if(bad==3){f.magazine.capacity=100001;}if(bad==4){f.magazine.rounds=101;}
ERR_InfantryAmmoTransfer::checks=0;assert(f.Run()==1&&f.from==nullptr&&ERR_InfantryAmmoTransfer::checks==0);}
for(int bad=0;bad<3;bad++){Fixture f;if(bad==0){f.magazine.well=nullptr;}if(bad==1){f.muzzle.well=nullptr;}if(bad==2){f.magazine.well=&f.wrong;}
ERR_InfantryAmmoTransfer::checks=0;assert(f.Run()==2&&f.from==nullptr&&ERR_InfantryAmmoTransfer::checks==0);}
for(int bad=0;bad<6;bad++){Fixture f;if(bad==0){f.candidate.inventory=nullptr;}if(bad==1){f.item.slot=nullptr;}
if(bad==2){f.slot.storage=nullptr;}if(bad==3){f.stores.clear();}if(bad==4){f.storageOwner.truck=&f.foreign;}if(bad==5){f.slot.attached=&f.foreign;}
assert(f.Run()==3);}
// Exact counters are cumulative stages; classification neither transfers nor consumes.
Fixture valid;assert(valid.Run()==4&&!valid.magazine.used&&valid.magazine.rounds==100&&valid.slot.attached==&valid.candidate);
// Reusing output arguments cannot accidentally retain a prior eligible slot/storage.
valid.candidate.magazine=nullptr;assert(valid.Run()==0&&valid.from==nullptr&&valid.selectedStorage==nullptr);
}
''')
print('PASS: actual cargo diagnostic stages separate absent/foreign magazines, used/empty/invalid rounds, well mismatch and exact truck storage/attachment without mutation')