Files
EscapistsReintroduced/tests/test_casualty_reservation_death.py
T

135 lines
8.5 KiB
Python

"""Exercise the production structural-ticket check after post-commit death.
No synthetic fixture is claimed as network/owner-RPC proof.
"""
from policy_harness import ROOT, body, run
path = ROOT/'addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingService.c'
physical = body(path, 'protected bool PhysicalTicket(')
physical = (physical.replace('protected ', '').replace('ERR_CasualtyLoadTicket ticket', 'Ticket* ticket')
.replace('ticket.', 'ticket->').replace('m_Mode.', 'm_Mode->')
.replace('ERR_Phase.EXTRACTION', '4').replace('ERR_Actors.Alive', 'Alive')
.replace('ERR_CasualtyLoadingRules.TransportBench', 'TransportBench').replace('null', 'nullptr'))
policy = '\n'.join(body(path, signature) for signature in ['static bool ObserveTicket(', 'static bool Within('])
run('''#include <cassert>
struct Entity { bool alive=true; };
bool Alive(Entity* entity){return entity && entity->alive;}
bool TransportBench(Entity* seat,Entity* aircraft){return seat && aircraft;}
struct Ticket { Entity* Aircraft; Entity* Casualty; Entity* Seat; };
struct Mode {
Entity* aircraft; bool active=true; int phase=4;
bool Active(){return active;} int Phase(){return phase;}
Entity* ExtractionVehicle(){return aircraft;}
};
''' + policy + '''
struct Service { Mode* m_Mode;
''' + physical + '''
};
int main(){
Entity aircraft, body, bench; Mode mode{&aircraft}; Service service{&mode};
Ticket ticket{&aircraft,&body,&bench};
assert(service.PhysicalTicket(&ticket));
body.alive=false; // server death after authorization; owner commit can still be in flight
assert(service.PhysicalTicket(&ticket));
assert(ObserveTicket(true,service.PhysicalTicket(&ticket),false));
assert(!ObserveTicket(false,service.PhysicalTicket(&ticket),false));
assert(Within(1000,8999,8000)); // keep observation reservation through its budget
assert(!Within(1000,9000,8000)); // eventually release, without another owner command
ticket.Casualty=nullptr; assert(!service.PhysicalTicket(&ticket));
ticket.Casualty=&body; ticket.Seat=nullptr; assert(!service.PhysicalTicket(&ticket));
ticket.Seat=&bench; aircraft.alive=false; assert(!service.PhysicalTicket(&ticket));
aircraft.alive=true; mode.active=false; assert(!service.PhysicalTicket(&ticket));
mode.active=true; mode.phase=5; assert(!service.PhysicalTicket(&ticket));
}
''')
print('PASS: actual committed-ticket structure preserves reservation after death, rejects missing bodies/seats and ends at observation expiry')
# Exercise production queue ownership and the complete pending loop. The fake
# queue invokes the actual callback while no ordinary service Tick is called.
begin = body(path, 'bool Begin(')
assert begin.index('ticket.Seat.IsReservedBy(casualty)') < begin.index('ScheduleMaintenance();') < begin.index('controller.ERR_PrepareCasualtyLoad(')
methods = '\n'.join(body(path, signature) for signature in [
'protected void ScheduleMaintenance(', 'protected void CancelMaintenance(',
'protected void MaintainPending(', 'protected void Resolve(', 'void Stop('])
methods = (methods.replace('protected ', '').replace('ERR_CasualtyLoadTicket ticket', 'Ticket* ticket')
.replace('array<ref ERR_CasualtyLoadTicket> snapshot = {}', 'Array<Ticket*> snapshot')
.replace('foreach (ERR_CasualtyLoadTicket item : m_Pending)', 'for (Ticket* item : m_Pending)')
.replace('foreach (Ticket* ticket : snapshot)', 'for (Ticket* ticket : snapshot)')
.replace('m_Mode.', 'm_Mode->').replace('ticket.', 'ticket->')
.replace('ticket->Seat.', 'ticket->Seat->').replace('m_OnResolved.', 'm_OnResolved->')
.replace('GetGame().GetCallqueue().', 'GetGame()->GetCallqueue()->')
.replace('CallLater(MaintainPending, 250, true)', 'CallLater([this](){MaintainPending();}, 250, true)')
.replace('Remove(MaintainPending)', 'Remove(0)').replace('null', 'nullptr'))
for namespace in ['Replication', 'System', 'ERR_MissionRules', 'ERR_CasualtyLoadingRules', 'CompartmentAccessComponent', 'ERR_Actors']:
methods = methods.replace(namespace + '.', namespace + '::')
run('''#include <cassert>
#include <vector>
#include <algorithm>
#include <functional>
template<class T> struct Array {
std::vector<T> data;
bool IsEmpty(){return data.empty();} int Count(){return data.size();}
void Insert(T item){data.push_back(item);} void Clear(){data.clear();}
int Find(T item){auto it=std::find(data.begin(),data.end(),item);return it==data.end()?-1:it-data.begin();}
void Remove(int index){data.erase(data.begin()+index);} T operator[](int index){return data[index];}
auto begin(){return data.begin();} auto end(){return data.end();}
};
struct Entity {bool alive=true; Entity* vehicle=nullptr;};
struct Seat {Entity* reserved=nullptr;Entity* occupant=nullptr;
bool IsReservedBy(Entity* entity){return reserved==entity;}
void SetReserved(Entity* entity){reserved=entity;} Entity* GetOccupant(){return occupant;}
};
struct Ticket {Entity* Casualty;Entity* Aircraft;struct Seat* Seat;bool Committed=true;int CommittedAt=1000;int Started=1000;Entity* Caller=nullptr;};
struct Mode {int phase=4;int Phase(){return phase;}};
namespace Replication {bool server=true;bool IsServer(){return server;}}
namespace System {int now=1000;int GetTickCount(){return now;}}
namespace ERR_MissionRules {bool Terminal(int phase){return phase>=5;}}
namespace ERR_Actors {bool Alive(Entity* actor){return actor&&actor->alive;}}
namespace CompartmentAccessComponent {Entity* GetVehicleIn(Entity* actor){return actor?actor->vehicle:nullptr;}}
namespace ERR_CasualtyLoadingRules {
''' + policy + '''
bool Nearby(Mode*,Entity*,Entity*){return true;}
}
struct Queue {
bool active=false;int starts=0,removes=0;std::function<void()> callback;
void CallLater(std::function<void()> fn,int interval,bool repeat){assert(interval==250&&repeat);assert(!active);active=true;starts++;callback=fn;}
void Remove(int){assert(active);active=false;removes++;}
void Fire(){if(active){auto retained=callback;retained();}}
};
struct Game {Queue queue;Queue* GetCallqueue(){return &queue;}} game;
Game* GetGame(){return &game;}
struct Invoker {int calls=0,successes=0;std::function<void()> observer;
void Invoke(Entity*,bool success){calls++;successes+=success;if(observer){auto retained=observer;retained();}}
};
struct Service {
Mode* m_Mode;bool m_Stopped=false,m_MaintenanceQueued=false;
Array<Ticket*> m_Pending;Array<int> m_Settling;Invoker* m_OnResolved;
bool PhysicalTicket(Ticket* ticket){return ticket->Casualty&&ticket->Aircraft&&ticket->Seat;}
bool ValidTicket(Ticket*){return false;} // disconnected participants cannot authorize again
''' + methods + '''
void Add(Ticket* ticket){m_Pending.Insert(ticket);ticket->Seat->SetReserved(ticket->Casualty);ScheduleMaintenance();}
};
int main(){
Mode mode;Entity casualty,aircraft;Seat seat;Invoker events;Service service{&mode,false,false,{}, {},&events};
Ticket first{&casualty,&aircraft,&seat};
service.Add(&first);service.ScheduleMaintenance();assert(game.queue.starts==1);
System::now=8999;game.queue.Fire();assert(service.m_Pending.Count()==1&&seat.IsReservedBy(&casualty)&&events.calls==0);
System::now=9000;game.queue.Fire();assert(service.m_Pending.IsEmpty()&&!seat.reserved&&events.calls==1&&!events.successes&&!game.queue.active);
game.queue.Fire();service.MaintainPending();assert(events.calls==1&&game.queue.removes==1);
// Before commit the disconnected life is invalid immediately, without waiting
// for a roster callback or granting a command from maintenance.
Ticket uncommitted{&casualty,&aircraft,&seat};uncommitted.Committed=false;
service.Add(&uncommitted);game.queue.Fire();assert(events.calls==2&&!game.queue.active&&!seat.reserved);
// Resolve cancels its callback before notifying; a reentrant authorized request
// may arm a fresh callback without that callback being removed afterwards.
Ticket next{&casualty,&aircraft,&seat};next.CommittedAt=10000;
Ticket ending{&casualty,&aircraft,&seat};ending.CommittedAt=1000;
events.observer=[&](){events.observer=nullptr;service.Add(&next);};
service.Add(&ending);System::now=10000;game.queue.Fire();
assert(service.m_Pending.Count()==1&&game.queue.active&&seat.IsReservedBy(&casualty));
service.Stop();assert(service.m_Stopped&&!game.queue.active&&!seat.reserved&&service.m_Pending.IsEmpty());
int resolved=events.calls,removed=game.queue.removes;
service.Stop();game.queue.Fire();assert(events.calls==resolved&&game.queue.removes==removed);
assert(events.successes==0); // no acknowledgement or expired command manufactures loading
}
''')
print('PASS: actual pending callback expires an empty-server reservation once, preserves its full lease, and cancels on completion/Stop')