146 lines
12 KiB
Python
146 lines
12 KiB
Python
"""Execute production feedback control flow; no native UI or network claim."""
|
|
from policy_harness import ROOT, body, run
|
|
service = ROOT/'addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingService.c'
|
|
action = ROOT/'addons/core/Scripts/Game/ERR/ERR_LoadCasualtyAction.c'
|
|
feedback = ROOT/'addons/core/Scripts/Game/ERR/ERR_CasualtyLoadingFeedback.c'
|
|
def convert(s):
|
|
s=s.replace('protected ', '').replace('override ', '').replace('string.Empty','string{}').replace('.IsEmpty()', '.empty()')
|
|
for ns in ['Replication','System','ERR_CasualtyFeedback','ERR_Phase','ERR_CasualtyLoadingRules']:
|
|
s=s.replace(ns+'.',ns+'::')
|
|
s=s.replace('ERR_GameMode.Get()', 'Mode::Get()').replace('ERR_GameMode mode', 'Mode* mode').replace('ERR_GameMode.Cast','Mode::Cast')
|
|
s=s.replace('ERR_PlayerLife life','Life* life').replace('SCR_PlayerController controller = SCR_PlayerController.Cast(GetGame().GetPlayerManager().GetPlayerController(life.ConnectionId));','Controller* controller = GetGame()->GetPlayerManager()->GetPlayerController(life->ConnectionId);')
|
|
s=s.replace('ERR_CasualtyLoadTicket ticket','Ticket* ticket')
|
|
for v in ['mode','m_Mode','ticket','life','controller']:
|
|
s=s.replace(v+'.',v+'->')
|
|
s=s.replace('mode->CasualtyLoading().','mode->CasualtyLoading()->').replace('null','nullptr')
|
|
return s
|
|
methods='\n'.join(convert(body(service, sig)) for sig in ['string RejectionReason(', 'protected void NotifyCaller(', 'void Request(', 'protected void NotifyResolution('])
|
|
permission=convert(body(action,'override bool CanBePerformedScript('))
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <vector>
|
|
using string=std::string;
|
|
struct Actor{int identity=1;};using IEntity=Actor*;
|
|
namespace Replication{bool server=true;bool IsServer(){return server;}}
|
|
namespace System{int GetTickCount(){return 10000;}}
|
|
namespace ERR_Phase{enum{EXTRACTION=4};}
|
|
struct Mode;struct Service;
|
|
struct Life{int ConnectionId=1;};
|
|
struct Controller{IEntity actor=nullptr;int messages=0;string last;IEntity GetControlledEntity(){return actor;}void ERR_CasualtyFeedback(Mode*,IEntity a,string s){assert(a==actor);messages++;last=s;}};
|
|
struct Manager{Controller* current=nullptr;Controller* GetPlayerController(int){return current;}};
|
|
struct Game{Manager manager;Manager* GetPlayerManager(){return &manager;}} game;Game* GetGame(){return &game;}
|
|
struct Mode{bool active=true;int phase=4;Service* service=nullptr;static Mode* current;static Mode* Get(){return current;}bool Active(){return active;}int Phase(){return phase;}Service* CasualtyLoading(){return service;}int ExtractionVehicle(){return 1;}};Mode* Mode::current=nullptr;
|
|
namespace ERR_CasualtyFeedback{string physical;string PhysicalReason(Mode*,IEntity,IEntity){return physical;}}
|
|
struct Ticket{bool FeedbackSubmitted=false;IEntity Caller=nullptr;int CallerAccount=1,CallerConnection=1;};
|
|
struct Service{
|
|
Mode* m_Mode;bool m_Stopped=false,settled=true,pending=false,seat=true,beginOK=true,same=true;int begins=0;Life life;IEntity admittedCaller=nullptr,admittedCasualty=nullptr;
|
|
Life* CurrentLife(IEntity a){return a&&(a==admittedCaller||a==admittedCasualty)?&life:nullptr;}
|
|
bool Settled(IEntity,int){return settled;}bool PendingActor(IEntity,IEntity){return pending;}bool AvailableSeat(IEntity,int){return seat;}
|
|
bool Begin(IEntity,IEntity){begins++;return beginOK;}bool SameLife(IEntity a,int account,int connection){return same&&CurrentLife(a)&&account==1&&connection==1;}
|
|
''' + methods + r'''
|
|
};
|
|
struct Action{string reason="old";IEntity target=nullptr;IEntity GetOwner(){return target;}void SetCannotPerformReason(string s){reason=s;}
|
|
''' + permission + r'''
|
|
};
|
|
int main(){
|
|
Mode mode;Service svc{&mode};mode.service=&svc;Mode::current=&mode;Actor caller,patient,stranger;svc.admittedCaller=&caller;svc.admittedCasualty=&patient;Controller controller;controller.actor=&caller;game.manager.current=&controller;Action action;action.target=&patient;
|
|
assert(action.CanBePerformedScript(&caller)&&action.reason.empty());
|
|
ERR_CasualtyFeedback::physical="No accessible transport seat is available";
|
|
assert(!action.CanBePerformedScript(&caller)&&action.reason==ERR_CasualtyFeedback::physical);
|
|
svc.Request(&caller,&patient);assert(svc.begins==0&&controller.messages==1);
|
|
ERR_CasualtyFeedback::physical.clear();assert(action.CanBePerformedScript(&caller)&&action.reason.empty());
|
|
svc.settled=false;assert(!action.CanBePerformedScript(&caller)&&action.reason.find("five seconds")!=string::npos);svc.settled=true;
|
|
svc.pending=true;assert(svc.RejectionReason(&caller,&patient).find("already")!=string::npos);svc.pending=false;
|
|
svc.seat=false;assert(svc.RejectionReason(&caller,&patient).find("seat")!=string::npos);svc.seat=true;
|
|
assert(svc.RejectionReason(&stranger,&patient).find("connected")!=string::npos);
|
|
int before=controller.messages;svc.Request(&stranger,&patient);assert(controller.messages==before&&svc.begins==0);
|
|
svc.beginOK=false;svc.Request(&caller,&patient);assert(svc.begins==1&&controller.messages==before+1&&controller.last.find("could not start")!=string::npos);
|
|
svc.beginOK=true;before=controller.messages;svc.Request(&caller,&patient);assert(svc.begins==2&&controller.messages==before);
|
|
Ticket ticket;ticket.Caller=&caller;
|
|
svc.NotifyResolution(&ticket,false);assert(controller.messages==before); // failed reservation before submission: Request gives exactly one failure
|
|
ticket.FeedbackSubmitted=true;svc.NotifyResolution(&ticket,true);assert(controller.messages==before);
|
|
svc.NotifyResolution(&ticket,false);assert(controller.messages==before+1&&controller.last.find("could not be confirmed")!=string::npos);
|
|
before=controller.messages;
|
|
svc.same=false;svc.NotifyResolution(&ticket,false);assert(controller.messages==before);svc.same=true;
|
|
ticket.CallerAccount=2;svc.NotifyResolution(&ticket,false);assert(controller.messages==before);ticket.CallerAccount=1;
|
|
ticket.CallerConnection=2;svc.NotifyResolution(&ticket,false);assert(controller.messages==before);ticket.CallerConnection=1;
|
|
controller.actor=&stranger;svc.NotifyResolution(&ticket,false);assert(controller.messages==before);controller.actor=&caller;
|
|
svc.m_Stopped=true;svc.NotifyResolution(&ticket,false);assert(controller.messages==before);svc.m_Stopped=false;
|
|
mode.active=false;svc.NotifyResolution(&ticket,false);assert(controller.messages==before);mode.active=true;
|
|
mode.phase=5;svc.NotifyResolution(&ticket,false);assert(controller.messages==before);mode.phase=4;
|
|
Replication::server=false;svc.Request(&caller,&patient);assert(svc.begins==2&&controller.messages==before);
|
|
// Client presentation never evaluates authoritative roster/settling: physical facts only.
|
|
svc.settled=false;assert(action.CanBePerformedScript(&caller)&&action.reason.empty());
|
|
ERR_CasualtyFeedback::physical="The helicopter must be upright, grounded and stationary";assert(!action.CanBePerformedScript(&caller));
|
|
}
|
|
''')
|
|
# Guard the unchanged transfer path and exact recipient channel around the tested logic.
|
|
owner=feedback.read_text()
|
|
assert '[RplRpc(RplChannel.Reliable, RplRcver.Owner)]' in owner
|
|
assert 'RplRcver.Broadcast' not in owner and 'RplRcver.Server' not in owner
|
|
send=body(feedback,'void ERR_CasualtyFeedback(')
|
|
for guard in ['!Replication.IsServer()', 'mode != ERR_GameMode.Get()', '!mode.Players().CurrentActor(actor)', 'GetPlayerController(GetPlayerId()) != this']:
|
|
assert guard in send
|
|
receive=body(feedback,'protected void ERR_CasualtyFeedbackOwner(')
|
|
for guard in ['this != GetGame().GetPlayerController()', 'EntityId(mode) != modeId', 'EntityId(GetControlledEntity()) != actorId', 'mode.Phase() != ERR_Phase.EXTRACTION']:
|
|
assert guard in receive
|
|
assert receive.index('EntityId(GetControlledEntity()) != actorId') < receive.index('ShowCustom(')
|
|
resolve=body(service,'protected void Resolve(')
|
|
assert resolve.index('m_Pending.Remove(index)') < resolve.index('NotifyResolution(ticket, success)') < resolve.index('m_OnResolved.Invoke')
|
|
begin=body(service,'bool Begin(')
|
|
assert begin.index('IsReservedBy(casualty)') < begin.index('ticket.FeedbackSubmitted = true') < begin.index('ERR_PrepareCasualtyLoad(')
|
|
print('PASS: actual action denial/clearing, server request failure, single submitted-result feedback, current rescuer identity and terminal/stop suppression; native UI/RPC pending')
|
|
physical=convert(body(feedback,'static string PhysicalReason(')).replace('static ','')
|
|
physical=physical.replace('Vehicle aircraft','Actor* aircraft').replace('aircraft.FindComponent(BaseCompartmentManagerComponent)','0')
|
|
physical=physical.replace('BaseCompartmentManagerComponent manager = BaseCompartmentManagerComponent.Cast(0);','Manager* manager = aircraft->manager;')
|
|
physical=physical.replace('array<BaseCompartmentSlot> seats = {}','std::vector<Seat*> seats').replace('foreach (BaseCompartmentSlot seat : seats)','for (Seat* seat : seats)')
|
|
for var in ['caller','casualty','aircraft','manager','seat']:
|
|
physical=physical.replace(var+'.',var+'->')
|
|
physical=physical.replace('vector.Distance','Distance').replace('ERR_Actors.','ERR_Actors::').replace('CompartmentAccessComponent.','CompartmentAccessComponent::')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <vector>
|
|
using string=std::string;
|
|
struct Actor;using IEntity=Actor*;
|
|
struct Seat{bool transport=true,occupied=false,reserved=false,accessible=true,locked=false;bool GetOccupant(){return occupied;}bool IsReserved(){return reserved;}bool IsCompartmentAccessible(){return accessible;}bool IsGetInLockedFor(IEntity){return locked;}};
|
|
struct Manager{std::vector<Seat*> seats;void GetCompartments(std::vector<Seat*>& out){out=seats;}};
|
|
struct Actor{bool conscious=true,aboard=false,settled=true,ready=true;Manager* manager=nullptr;int GetOrigin(){return 0;}};
|
|
struct Mode{bool active=true;int phase=4;Actor* aircraft=nullptr;bool Active(){return active;}int Phase(){return phase;}Actor* CasualtyLoadingAircraft(){return aircraft;}};
|
|
namespace ERR_Phase{enum{EXTRACTION=4};}
|
|
namespace ERR_Actors{bool CanFight(IEntity a){return a&&a->conscious;}}
|
|
namespace CompartmentAccessComponent{bool GetVehicleIn(IEntity a){return a->aboard;}}
|
|
float Distance(int,int){return 0;}
|
|
namespace ERR_CasualtyLoadingRules{
|
|
bool ranges=true,nearby=true;
|
|
bool GroundSettled(IEntity a){return a&&a->settled;}
|
|
bool Ranges(float,float,float){return ranges;}
|
|
bool AircraftReady(IEntity a){return a&&a->ready;}
|
|
bool Nearby(Mode*,IEntity,IEntity){return nearby;}
|
|
bool TransportBench(Seat* s,IEntity){return s&&s->transport;}
|
|
}
|
|
''' + physical + r'''
|
|
int main(){
|
|
Actor caller,patient,aircraft;Mode mode;mode.aircraft=&aircraft;Manager manager;aircraft.manager=&manager;Seat seat;manager.seats={nullptr,&seat};
|
|
assert(PhysicalReason(&mode,&caller,&patient).empty());
|
|
for(int mask=1;mask<16;mask++){
|
|
seat.occupied=mask&1;seat.reserved=mask&2;seat.accessible=!(mask&4);seat.locked=mask&8;
|
|
assert(PhysicalReason(&mode,&caller,&patient).find("seat")!=string::npos);
|
|
}
|
|
seat=Seat{};seat.transport=false;assert(PhysicalReason(&mode,&caller,&patient).find("seat")!=string::npos);seat.transport=true;
|
|
aircraft.manager=nullptr;assert(PhysicalReason(&mode,&caller,&patient).find("seat")!=string::npos);aircraft.manager=&manager;
|
|
mode.active=false;assert(PhysicalReason(&mode,&caller,&patient).find("Wait")!=string::npos);mode.active=true;
|
|
mode.phase=3;assert(!PhysicalReason(&mode,&caller,&patient).empty());mode.phase=4;
|
|
assert(!PhysicalReason(nullptr,&caller,&patient).empty());assert(!PhysicalReason(&mode,nullptr,&patient).empty());assert(!PhysicalReason(&mode,&patient,&patient).empty());
|
|
caller.conscious=false;assert(PhysicalReason(&mode,&caller,&patient).find("conscious rescuer")!=string::npos);caller.conscious=true;
|
|
caller.aboard=true;assert(PhysicalReason(&mode,&caller,&patient).find("Leave")!=string::npos);caller.aboard=false;
|
|
patient.settled=false;assert(PhysicalReason(&mode,&caller,&patient).find("stable ground")!=string::npos);patient.settled=true;
|
|
ERR_CasualtyLoadingRules::ranges=false;assert(PhysicalReason(&mode,&caller,&patient).find("twelve metres")!=string::npos);ERR_CasualtyLoadingRules::ranges=true;
|
|
aircraft.ready=false;assert(PhysicalReason(&mode,&caller,&patient).find("stationary")!=string::npos);aircraft.ready=true;
|
|
ERR_CasualtyLoadingRules::nearby=false;assert(PhysicalReason(&mode,&caller,&patient).find("clear access")!=string::npos);ERR_CasualtyLoadingRules::nearby=true;
|
|
assert(PhysicalReason(&mode,&caller,&patient).empty());
|
|
}
|
|
''')
|
|
print('PASS: actual physical denial precedence and transport-seat occupancy/reservation/access/lock checks')
|