Files
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

241 lines
10 KiB
Python

"""Execute Cessna boarding policy; engine transitions/components are explicit doubles."""
from policy_harness import ROOT, body, run
SOURCE = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_PlaneFlight.c'
def cpp(method):
# Translate syntax only; the three production decision bodies stay intact.
method = method.replace('protected ', '')
method = method.replace(
'AIControlComponent.Cast(entry.Actor.FindComponent(AIControlComponent))',
'entry.Actor.Control',
).replace(
'CompartmentAccessComponent.Cast(entry.Actor.FindComponent(CompartmentAccessComponent))',
'entry.Actor.Access',
)
for before, after in (
('ERR_PlaneCrewSeat entry', 'Entry* entry'),
('AIControlComponent control', 'Control* control'),
('CompartmentAccessComponent access', 'Access* access'),
('CompartmentAccessComponent.', 'Access::'),
('SCR_ChimeraAIAgent.Cast(', 'CharacterAgent::Cast('),
('SCR_ChimeraAIAgent characterAgent', 'Agent* characterAgent'),
('SCR_FactionManager.', 'Factions::'),
('ERR_Actors.', 'Actors::'),
('ECloseDoorAfterActions.', 'DoorAction::'),
('string action', 'std::string action'),
):
method = method.replace(before, after)
for name in ('entry', 'Actor', 'Seat', 'Agent', 'access', 'control', 'm_Mode'):
method = method.replace(name + '.', name + '->')
return method
methods = '\n'.join(cpp(body(SOURCE, signature)) for signature in (
'protected bool OwnsCrewSeat(',
'protected bool CrewSeated(',
'protected bool PrepareCrewSeat(',
))
run(r'''
#include <cassert>
#include <functional>
#include <string>
#include <vector>
struct Entity;
struct Seat;
struct Access;
struct Agent {
int lod=2, activations=0, lodWrites=0;
bool active=false,playerPending=false,character=true;
std::vector<std::string> calls;
void SetPermanentLOD(int value) {lod=value;lodWrites++;calls.push_back("LOD");}
void ActivateAI() {active=true;activations++;calls.push_back("activate");}
bool IsPlayerPending_S(){return playerPending;}
};
struct CharacterAgent {static Agent* Cast(Agent* agent){return agent->character?agent:nullptr;}};
struct Control {Agent* agent=nullptr;Agent* GetControlAIAgent(){return agent;}};
struct Entity {
bool player=false,fit=true;
int faction=1;
Entity* group=nullptr;
struct Control* Control=nullptr;
struct Access* Access=nullptr;
};
struct Seat {
Entity* owner=nullptr;
Entity* occupant=nullptr;
Entity* reservation=nullptr;
bool occupied=false,accessible=true,locked=false,reserved=false;
Entity* GetOwner(){return owner;}
Entity* GetOccupant(){return occupant;}
bool IsOccupied(){return occupied||occupant;}
bool IsCompartmentAccessible(){return accessible;}
bool IsGetInLockedFor(Entity*){return locked;}
bool IsReserved(){return reserved;}
bool IsReservedBy(Entity* actor){return reservation==actor;}
};
struct DoorAction {enum {INVALID=-1};};
struct Access {
Entity* actor=nullptr;
Entity* vehicle=nullptr;
Seat* compartment=nullptr;
Seat* expectedSeat=nullptr;
bool entering=false,exiting=false,inside=false,accepted=true,synchronousSeat=false;
int requests=0;
bool IsGettingIn(){return entering;}
bool IsGettingOut(){return exiting;}
bool IsInCompartment(){return inside;}
Seat* GetCompartment(){return compartment;}
static Entity* GetVehicleIn(Entity* actor){return actor&&actor->Access?actor->Access->vehicle:nullptr;}
bool GetInVehicle(Entity* owner,Seat* seat,bool teleport,int door,int close,bool unknown) {
assert(seat==expectedSeat&&owner==expectedSeat->owner);
assert(teleport&&door==-1&&close==DoorAction::INVALID&&!unknown);
Agent* agent=actor->Control->agent;
assert(agent->active&&agent->lod==0);
assert(agent->calls.size()>=2&&agent->calls[agent->calls.size()-2]=="LOD"&&agent->calls.back()=="activate");
agent->calls.push_back("board");requests++;
if(synchronousSeat) {inside=true;compartment=seat;vehicle=owner;seat->occupant=actor;}
return accepted;
}
};
struct Actors {
static bool Player(Entity* actor){return actor->player;}
static Entity* Group(Entity* actor){return actor->group;}
static bool CanFight(Entity* actor){return actor&&actor->fit;}
};
struct Factions {static int SGetFaction(Entity* actor){return actor->faction;}};
struct Mode {int EnemyFaction(){return 1;}};
struct Entry {
Entity* Actor=nullptr;
struct Seat* Seat=nullptr;
struct Agent* Agent=nullptr;
int Attempts=0;
float NextAttempt=0;
};
struct Flight {
Mode* m_Mode=nullptr;
Entity* Crew=nullptr;
Entity* Aircraft=nullptr;
std::vector<std::string> actions;
void LogCrewSeat(Entry*,int,float,std::string action="",bool force=false) {
if(force)actions.push_back(action);
}
''' + methods + r'''
};
struct Setup {
Mode mode;
Entity aircraft,group,actor,other;
Agent agent,otherAgent;
Control control;
Access access;
Seat seat,otherSeat;
Entry entry;
Flight flight;
Setup() {
flight.m_Mode=&mode;flight.Aircraft=&aircraft;flight.Crew=&group;
actor.group=&group;actor.Control=&control;actor.Access=&access;control.agent=&agent;
access.actor=&actor;access.expectedSeat=&seat;seat.owner=&aircraft;
entry.Actor=&actor;entry.Seat=&seat;entry.Agent=&agent;
}
bool Tick(float now){return flight.PrepareCrewSeat(&entry,0,now);}
void Seated(){access.inside=true;access.compartment=&seat;access.vehicle=&aircraft;seat.occupant=&actor;}
};
int main() {
// Native acceptance can leave the actor on foot. All requests, including
// refused requests, consume the same finite budget with 5s between requests.
for(bool accepted:{false,true}) {
Setup s;s.access.accepted=accepted;
assert(!s.Tick(1000)&&s.entry.Attempts==1&&s.access.requests==1);
assert(s.entry.NextAttempt==6000&&s.agent.active&&s.agent.lod==0);
assert(!s.Tick(1001)&&!s.Tick(5999)&&s.access.requests==1);
assert(!s.Tick(6000)&&s.entry.Attempts==2&&s.entry.NextAttempt==11000);
assert(!s.Tick(10999)&&s.access.requests==2);
assert(!s.Tick(11000)&&s.entry.Attempts==3&&s.entry.NextAttempt==16000);
for(float now:{16000.f,45000.f,1000000.f})assert(!s.Tick(now));
assert(s.access.requests==3&&s.agent.activations==3&&s.agent.lodWrites==3);
assert(s.flight.actions==std::vector<std::string>(3,accepted?"boarding accepted":"boarding refused"));
// Delayed native completion is still accepted after the retry cap.
s.Seated();assert(s.Tick(1000001)&&s.access.requests==3);
}
// Even if a fake native request completes synchronously, that request is not
// the readiness proof. The next observation must confirm the exact seat.
{Setup s;s.access.synchronousSeat=true;assert(!s.Tick(1000));assert(s.Tick(1001)&&s.access.requests==1);}
{Setup s;s.Seated();assert(s.Tick(1000)&&s.access.requests==0&&s.agent.activations==0);}
// Each independent native reading must agree before readiness is reported;
// disagreement must not eject/reboard someone already transitioning/inside.
for(const auto& fault:std::vector<std::function<void(Setup&)>>{
[](Setup& s){s.seat.occupant=nullptr;},
[](Setup& s){s.seat.occupant=&s.other;},
[](Setup& s){s.access.inside=false;},
[](Setup& s){s.access.compartment=&s.otherSeat;},
[](Setup& s){s.access.vehicle=&s.other;},
[](Setup& s){s.access.vehicle=nullptr;},
[](Setup& s){s.access.entering=true;},
[](Setup& s){s.access.exiting=true;},
[](Setup& s){s.actor.Access=nullptr;},
}) {
Setup s;s.Seated();fault(s);
assert(!s.flight.CrewSeated(&s.entry)&&!s.Tick(10000));
assert(s.access.requests==0&&s.entry.Attempts==0&&s.agent.activations==0);
}
// Unfit, player-controlled, foreign, replaced or missing actors/components
// cannot receive another request, regardless of the elapsed retry interval.
for(const auto& fault:std::vector<std::function<void(Setup&)>>{
[](Setup& s){s.entry.Actor=nullptr;},
[](Setup& s){s.actor.player=true;},
[](Setup& s){s.actor.group=nullptr;},
[](Setup& s){s.actor.group=&s.other;},
[](Setup& s){s.actor.faction=2;},
[](Setup& s){s.actor.Control=nullptr;},
[](Setup& s){s.control.agent=&s.otherAgent;},
[](Setup& s){s.control.agent=nullptr;},
[](Setup& s){s.entry.Agent=nullptr;},
[](Setup& s){s.agent.playerPending=true;},
[](Setup& s){s.actor.fit=false;},
[](Setup& s){s.entry.Seat=nullptr;},
[](Setup& s){s.actor.Access=nullptr;},
[](Setup& s){s.access.entering=true;},
[](Setup& s){s.access.exiting=true;},
[](Setup& s){s.access.inside=true;s.access.compartment=&s.otherSeat;},
[](Setup& s){s.access.vehicle=&s.other;},
[](Setup& s){s.seat.occupied=true;},
[](Setup& s){s.seat.occupant=&s.other;},
[](Setup& s){s.seat.accessible=false;},
[](Setup& s){s.seat.locked=true;},
[](Setup& s){s.seat.reserved=true;s.seat.reservation=&s.other;},
}) {
Setup s;assert(!s.Tick(1000));fault(s);
assert(!s.Tick(10000)&&!s.Tick(20000));
assert(s.access.requests==1&&s.entry.Attempts==1&&s.entry.NextAttempt==6000);
assert(s.agent.activations==1&&s.agent.lodWrites==1&&s.otherAgent.activations==0);
}
// Capture/group/control/health changes also prevent already seated readiness.
for(const auto& fault:std::vector<std::function<void(Setup&)>>{
[](Setup& s){s.actor.player=true;},
[](Setup& s){s.actor.group=&s.other;},
[](Setup& s){s.actor.faction=2;},
[](Setup& s){s.control.agent=&s.otherAgent;},
[](Setup& s){s.agent.playerPending=true;},
[](Setup& s){s.actor.fit=false;},
}) {
Setup s;s.Seated();assert(s.Tick(1000));fault(s);assert(!s.Tick(10000)&&s.access.requests==0);
}
// Our own reservation is permitted. Native transitions can finish/fail on
// their own schedule; skipped ticks do not burn attempts or push the deadline.
{Setup s;s.seat.reserved=true;s.seat.reservation=&s.actor;assert(!s.Tick(1000)&&s.access.requests==1);}
{Setup s;assert(!s.Tick(1000));s.access.entering=true;assert(!s.Tick(6000)&&!s.Tick(10000));
assert(s.entry.Attempts==1&&s.entry.NextAttempt==6000);s.access.entering=false;
assert(!s.Tick(10250)&&s.entry.Attempts==2&&s.entry.NextAttempt==15250);}
// Pilot and observer have independent request budgets.
{Setup pilot,observer;for(float now:{1000.f,6000.f,11000.f})assert(!pilot.Tick(now));
assert(!pilot.Tick(16000)&&!observer.Tick(16000));assert(pilot.access.requests==3&&observer.access.requests==1);}
}
''')
print('PASS: production Cessna boarding requires exact observed occupancy, activates before requesting, caps retries at three with 5s spacing, and preserves transition, ownership, health and seat restrictions; engine behavior is faked')