Files
EscapistsReintroduced/tests/test_mortar_logistics.py
T
jakub 1b1a8b5173 Add mission customization, HQ support and aggression-driven AI
Integrate faction and random settings menus, generated objectives, scouts, convoys, air support, mortar stations and body-intel recovery. Add identified-body aggression, finite hunter UAZ deployment, dedicated objective defence and maximum AI skill with separate reduced aim accuracy. Include policy tests, native probes, evidence and documentation.
2026-09-23 15:07:14 +02:00

176 lines
12 KiB
Python

"""Execute production finite-courier policies and native-operation observation.
API doubles do not establish engine inventory, navigation, radio or MP behavior.
"""
from policy_harness import ROOT, body, run
import re
r=ROOT/'addons/core/Scripts/Game/ERR/10_AI'
p=r/'ERR_MortarLogistics.c'; t=r/'ERR_MortarCourierTransfer.c'
# C++ would accept an Enforce ownership annotation as an identifier.
# Reject that native compile regression before executing the source body.
assert not re.search(r'\b(?:bool|int|float|string)\s+(?:owned|event|autoptr|typename|proto|modded|notnull)\b', p.read_text())
policies='\n'.join(body(p,s) for s in ('static bool Fresh(', 'static bool Need(', 'static bool NewRequest(', 'static bool Near(', 'static bool ActiveTrip('))
run('''#include <cassert>
'''+policies+'''
int main(){
assert(Fresh(100,100)&&Fresh(100,180100));
assert(Fresh(0,0)&&Fresh(0,180000)&&!Fresh(0,180001));
assert(!Fresh(-1,-1)&&!Fresh(-1,0)&&!Fresh(-1,100)&&!Fresh(-0.001f,0));
assert(!Fresh(100,99)&&!Fresh(100,180101));
assert(Need(0)&&Need(6)&&!Need(-1)&&!Need(7));
assert(NewRequest(4,3,true)&&!NewRequest(4,4,true)&&!NewRequest(3,4,true));
assert(!NewRequest(1,0,false)&&!NewRequest(0,0,true));
assert(Near(0)&&Near(12)&&!Near(12.001)&&!Near(-1));
assert(ActiveTrip(true,false,1,1,true,true));
assert(!ActiveTrip(false,false,1,1,true,true));
assert(!ActiveTrip(true,true,1,1,true,true));
assert(!ActiveTrip(true,false,0,0,true,true));
assert(!ActiveTrip(true,false,1,2,true,true));
assert(!ActiveTrip(true,false,1,1,false,true));
assert(!ActiveTrip(true,false,1,1,true,false));
}
''')
poll=body(t,'int Poll(')
poll=poll.replace('bool exact;','bool exact=false;').replace('InventoryItemComponent item;','InventoryItemComponent* item=nullptr;')
poll=poll.replace('if (m_Shell) item = InventoryItemComponent.Cast(m_Shell.FindComponent(InventoryItemComponent));','if (m_Shell) item = m_Shell;')
poll=poll.replace('item.GetParentSlot().GetStorage()','item->GetParentSlot()->GetStorage()').replace('item.GetParentSlot()','item->GetParentSlot()')
poll=poll.replace('m_Service.TransferAllowed','m_Service->TransferAllowed')
run('''#include <cassert>
struct Slot {void* value;void* GetStorage(){return value;}};
struct InventoryItemComponent {Slot* slot;Slot* GetParentSlot(){return slot;}};
struct Service {bool valid=true;int token=1;bool TransferAllowed(int captured,InventoryItemComponent*,int,int,float){return valid&&token==captured;}};
'''+body(t,'static bool Proven(')+'''
struct Move {
Service service; Service* m_Service=&service;
bool source=true,destination=false,m_Accepted=true,m_Invalidated=false,m_Resolved=false;
int m_Acknowledgement=0,m_Trip=1,m_From=1,m_To=2,m_Source=1,m_Destination=2;
float m_Deadline=8000;void* m_Storage=this;
Slot slot{this};InventoryItemComponent item{&slot};InventoryItemComponent* m_Shell=&item;
bool Contains(int owner){return m_Shell&&(owner==1?source:destination);}
'''+body(t,'void Acknowledge(')+body(t,'void Invalidate(')+body(t,'bool Unresolved(')+poll+'''
};
int main(){
for(int bits=0;bits<16;bits++)assert(Proven(bits&1,bits&2,bits&4,bits&8)==(bits==13));
Move pending;assert(pending.Poll(0)==0&&pending.Unresolved());
pending.Acknowledge(true);pending.Acknowledge(false);assert(pending.m_Acknowledgement==1);
assert(pending.Poll(1)==0);pending.destination=true;assert(pending.Poll(2)==0);
pending.source=false;pending.slot.value=nullptr;assert(pending.Poll(3)==0);
pending.slot.value=pending.m_Storage;assert(pending.Poll(4)==1&&!pending.Unresolved());
Move canceled;canceled.Invalidate();assert(canceled.Poll(1)==-1&&canceled.Unresolved());
canceled.source=false;canceled.destination=true;canceled.Acknowledge(true);
assert(canceled.Poll(2)==-1&&!canceled.Unresolved()); // late physical success releases reservation, never authority
Move expired;assert(expired.Poll(8000)==-1&&expired.Unresolved());
expired.source=false;expired.destination=true;expired.Acknowledge(true);
assert(expired.Poll(9000)==-1&&!expired.Unresolved());
Move replaced;replaced.service.token=2;replaced.source=false;replaced.destination=true;replaced.Acknowledge(true);
assert(replaced.Poll(1)==-1); // callback from previous trip cannot supply new trip
Move captured;captured.service.valid=false;assert(captured.Poll(1)==-1&&captured.Unresolved());
Move rejected;rejected.Acknowledge(false);assert(rejected.Poll(1)==-1&&!rejected.Unresolved());
Move lost;lost.m_Shell=nullptr;lost.Acknowledge(true);assert(lost.Poll(1)==-1&&!lost.Unresolved());assert(lost.Poll(8000)==-1&&!lost.Unresolved());
}
''')
accept=body(p,'bool AcceptsStatus(').replace('ERR_MortarSupplyStatus report','ERR_MortarSupplyStatus* report').replace('m_Battery.','m_Battery->').replace('report.','report->').replace('ERR_MortarLogisticsRules.','Rules::')
run('''#include <cassert>
struct Rules {'''+policies+'''};
struct ERR_MortarSupplyStatus {void* Group;void* Gun;float ObservedAt;int MessageId;int Shells;};
struct Battery {void* Group;void* Gun;};
struct Service {bool m_Stopped=false;int m_State=1,m_ConsumedMessage=0;Battery* m_Battery=nullptr;
'''+accept+'''};
int main(){int group,gun,other;Battery battery{&group,&gun};Service service;service.m_Battery=&battery;
ERR_MortarSupplyStatus report{&group,&gun,100,1,0};assert(service.AcceptsStatus(&report,101));
assert(!service.AcceptsStatus(nullptr,101));
report.Gun=&other;assert(!service.AcceptsStatus(&report,101));report.Gun=&gun;
report.Group=&other;assert(!service.AcceptsStatus(&report,101));report.Group=&group;
report.Shells=7;assert(!service.AcceptsStatus(&report,101));report.Shells=0;
assert(!service.AcceptsStatus(&report,180101));assert(!service.AcceptsStatus(&report,99));
report.ObservedAt=-1;assert(!service.AcceptsStatus(&report,100));report.ObservedAt=100;
service.m_ConsumedMessage=1;assert(!service.AcceptsStatus(&report,101));report.MessageId=2;assert(service.AcceptsStatus(&report,101));
service.m_State=3;assert(!service.AcceptsStatus(&report,101));service.m_State=1;
service.m_Stopped=true;assert(!service.AcceptsStatus(&report,101));
}
''')
print('PASS: actual finite-courier age, token, exact ownership/callback and delivered-status admission reject stale, replayed, captured, canceled and ambiguous work')
# Run the actual gun-capture gate with narrow native identity/compartment doubles.
target=body(p,'bool TargetAvailable(')
target=target.replace('m_Battery.','m_Battery->').replace('m_HQ.Mortars().FindGun','m_HQ->Mortars()->FindGun').replace('m_Mode.EnemyFaction()','m_Mode->EnemyFaction()')
target=target.replace('SCR_FactionManager.SGetFaction','SCR_FactionManager::SGetFaction').replace('ERR_Actors.','ERR_Actors::')
target=target.replace('SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_OriginalGun.FindComponent(SCR_AIVehicleUsageComponent));','Usage* usage = m_OriginalGun->usage;')
target=target.replace('IEntity occupant','Actor* occupant').replace('usage.GetTurretCompartmentSlot().GetOccupant()','usage->GetTurretCompartmentSlot()->GetOccupant()').replace('usage.GetTurretCompartmentSlot()','usage->GetTurretCompartmentSlot()')
run('''#include <cassert>
struct Usage;
struct Actor {void* faction;void* group;bool alive=true;bool player=false;Usage* usage=nullptr;};
struct Slot {Actor* occupant=nullptr;Actor* GetOccupant(){return occupant;}};
struct Usage {Slot slot;bool present=true;Slot* GetTurretCompartmentSlot(){return present?&slot:nullptr;}};
struct Battery {Actor* Gun;void* Group;bool StartupActivated=true;bool operational=true;bool stopped=false;bool Stopped(){return stopped;}bool Operational(){return operational;}};
struct Registry {Battery* battery;Battery* FindGun(Actor* gun){return battery&&battery->Gun==gun?battery:nullptr;}};
struct HQ {Registry registry;Registry* Mortars(){return &registry;}};
struct Mode {void* faction;void* EnemyFaction(){return faction;}};
struct SCR_FactionManager {static void* SGetFaction(Actor* actor){return actor->faction;}};
struct ERR_Actors {static bool CanFight(Actor* a){return a->alive;}static bool Player(Actor* a){return a->player;}static void* Group(Actor* a){return a->group;}};
struct Members {Actor* member;bool IsEmpty(){return !member;}bool Contains(Actor* a){return a==member;}};
struct Service {Battery* m_Battery;Actor* m_OriginalGun;void* m_OriginalBatteryGroup;HQ* m_HQ;Mode* m_Mode;Members m_BatteryMembers;
'''+target+'''
};
int main(){int faction,group,foreign;Usage usage;Actor gun{&faction,&group};gun.usage=&usage;Actor original{&faction,&group};
Battery battery{&gun,&group};HQ hq{{&battery}};Mode mode{&faction};Service service{&battery,&gun,&group,&hq,&mode,{&original}};
assert(service.TargetAvailable());usage.slot.occupant=&original;assert(service.TargetAvailable());
original.player=true;assert(!service.TargetAvailable());original.player=false;
original.alive=false;assert(!service.TargetAvailable());original.alive=true;
original.group=&foreign;assert(!service.TargetAvailable());original.group=&group;
original.faction=&foreign;assert(!service.TargetAvailable());original.faction=&faction;
Actor stranger{&faction,&group};usage.slot.occupant=&stranger;assert(!service.TargetAvailable());usage.slot.occupant=&original;
gun.faction=&foreign;assert(!service.TargetAvailable());gun.faction=&faction;
usage.present=false;assert(!service.TargetAvailable());usage.present=true;
battery.operational=false;assert(!service.TargetAvailable());battery.operational=true;
battery.stopped=true;assert(!service.TargetAvailable());battery.stopped=false;
battery.Gun=&stranger;assert(!service.TargetAvailable());battery.Gun=&gun;
hq.registry.battery=nullptr;assert(!service.TargetAvailable());hq.registry.battery=&battery;
service.m_BatteryMembers.member=nullptr;assert(!service.TargetAvailable());
}
''')
print('PASS: actual native-gun ownership gate rejects player/foreign/replaced/dead occupant, faction change, missing seat and missing registry')
# Exercise the production between-trip target selector with four batteries.
select = body(p, 'protected void SelectBattery(')
select = select.replace('protected ', '').replace('ERR_MortarLogisticsRules.', 'Rules::').replace('vector.DistanceXZ', 'Distance')
select = select.replace('array<ref ERR_MortarBattery> batteries = {};', 'Array<Battery*> batteries;').replace('ERR_MortarBattery selected;', 'Battery* selected=nullptr;')
select = select.replace('foreach (ERR_MortarBattery candidate : batteries)', 'for (Battery* candidate : batteries)').replace('float.MAX', '999999')
select = select.replace('m_HQ.Mortars().Batteries(batteries)', 'system.Batteries(batteries)')
for name in ('m_Battery', 'candidate', 'selected', 'm_Cache'):
select = select.replace(name+'.', name+'->')
run(r'''
#include <cassert>
#include <cmath>
#include <vector>
using std::abs;
struct Battery { bool StartupActivated=true, stopped=false, operational=true; int shells=0, Gun=1, Group=1; float Position=0;
bool Stopped(){return stopped;} bool Operational(){return operational;} int CountShells(){return shells;} };
template<class T> struct Array:std::vector<T>{using std::vector<T>::operator=;void Clear(){this->clear();}};
struct System {Array<Battery*> all; void Batteries(Array<Battery*>& out){out=all;}};
struct Cache {float GetOrigin(){return 0;}};
float Distance(float a,float b){return abs(a-b);}
namespace Rules {bool Need(int n){return n>=0&&n<=6;}}
struct Courier {int m_State=1;bool m_Report=false,m_Move=false,m_Order=false; Battery* m_Battery=nullptr;
int m_OriginalGun=0,m_OriginalBatteryGroup=0;Array<int> m_BatteryMembers;float m_NextReport=99;
Cache cache;Cache* m_Cache=&cache;System system;
bool TargetAvailable(){return m_Battery&&m_Battery->StartupActivated&&!m_Battery->Stopped()&&m_Battery->Operational();}
''' + select + r'''
};
int main(){
Battery a,b,c,d;a.Position=200;b.Position=100;c.Position=300;d.Position=400;
Courier s;s.system.all={&a,&b,&c,&d};s.SelectBattery();assert(s.m_Battery==&b);
// Keep an active low-ammo recipient; a new nearer request cannot retarget it.
a.Position=1;s.SelectBattery();assert(s.m_Battery==&b);
// Once replenished, choose another depleted battery and reset only binding.
b.shells=12;s.m_BatteryMembers={9};s.SelectBattery();assert(s.m_Battery==&a&&s.m_BatteryMembers.empty()&&s.m_NextReport==0);
a.operational=false;s.SelectBattery();assert(s.m_Battery==&c);
c.stopped=true;d.StartupActivated=false;s.SelectBattery();assert(s.m_Battery==&c);
d.StartupActivated=true;
for(int gate=0;gate<4;gate++){
s.m_State=gate==0?3:1;s.m_Report=gate==1;s.m_Move=gate==2;s.m_Order=gate==3;
s.SelectBattery();assert(s.m_Battery==&c);
}
s.m_Order=false;s.SelectBattery();assert(s.m_Battery==&d);
}
''')
print('PASS: four-battery courier selection preserves pending trip/report/transfer identity, skips unavailable crews, and selects the next depleted battery without new stock')