Files
EscapistsReintroduced/tests/test_casualty_reservation_death.py
T
jakub cab998fb64 Combine Workshop addon and fix prison startup
Merge owned systems into one addon while preserving resource identities. Gate player admission on the replicated prison scene, restore the original framed door, and hold enemy AI until escape. Correct scenario branding and author metadata, include publishing assets, and retain regression and native verification evidence.
2026-09-20 16:42:52 +02:00

136 lines
8.6 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/00_Core/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;}
void NotifyResolution(Ticket*,bool){} // presentation boundary covered by test_casualty_feedback.py
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')