Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
136 lines
8.6 KiB
Python
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;}
|
|
bool CasualtyLoadingTransport(Entity* value){return value==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 NearbyAircraft(Mode*,Entity*,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')
|