Files
Viano-Core/tests/clientless/test_flight_handoff.py
T
Nico ba2a4e629c
clang PCH / build (push) Waiting to run
GCC PCH / build (push) Waiting to run
Recover stalled flights locally and fly rejected route approaches
2026-11-04 01:14:02 +01:00

152 lines
8.7 KiB
Python

"""Exercise production Update with a running native spline, not a string check."""
from pathlib import Path
import subprocess
import tempfile
root = Path(__file__).resolve().parents[2]
source = (root / 'src/server/game/World/ClientlessFlightMgr.cpp').read_text()
a = source.index('void ClientlessFlightMgr::Update(uint32 diff)')
b = source.index('bool ClientlessFlightMgr::Command(', a)
update = source[a:b]
harness = r'''
#include "ClientlessFlightPath.h"
#include "ClientlessFlightProgress.h"
#include <cassert>
#include <memory>
#include <cstdint>
#include <string>
#include <unordered_map>
using uint32 = uint32_t;
using namespace ClientlessFlightPath;
struct Position {
float x=0,y=0,z=60;
float GetPositionX() const{return x;} float GetPositionY() const{return y;} float GetPositionZ() const{return z;}
void Relocate(Position const* p){*this=*p;}
float GetExactDist(Position const&p){return std::hypot(std::hypot(x-p.x,y-p.y),z-p.z);}
};
Point PointOf(Position const& p){return {p.x,p.y,p.z};}
struct Spline {int remaining=1100; int timeRemaining()const{return remaining;}};
struct Player:Position {
Spline spline; Spline* movespline=&spline;
bool alive=true,fly=true,running=true;int stops=0;
bool IsInCombat(){return false;}bool IsInWorld(){return true;} uint32 GetMapId(){return 0;}
bool IsAlive(){return alive;} bool IsBeingTeleported(){return false;}
bool IsMounted(){return true;} bool CanFly(){return fly;}
bool IsSplineEnabled(){return running;}
void StopMoving(){++stops;running=false;}
} player;
struct Session {Player* GetPlayer(){return &player;}} session;
enum class ClientlessPlayerState {Online};
struct World {
Session* FindClientlessSession(uint32){return &session;}
ClientlessPlayerState GetClientlessPlayerState(uint32){return ClientlessPlayerState::Online;}
} world;
auto sWorld=&world;
enum class ClientlessFlightState {Mounting,Flying,Arrived,Failed};
struct Record {
struct {bool FollowingPlayer=false;ClientlessFlightState State=ClientlessFlightState::Flying;uint32 MapId=0;Position Landing{800,0,0},Target{800,0,0};float Travelled=0;uint32 Segments=0,Avoidances=0;std::string Detail;} Status;
ClientlessFlightProgress::Watch Progress;uint32 StallRecoveries=0;SharedJoin Join;
Position LastPosition,Start,Segment{25,0,60};Settings Settings;
std::shared_ptr<int> Following;uint32 FormationDelay=0,TargetUpdated=0,Elapsed=0,SegmentElapsed=0,CheckTimer=0;
bool Moving=true,Returning=false,ApproachingDeparture=false,ActionOwned=false,SegmentUnderOverhang=false;uint32 DetachedRecoveryCount=0;Position Departure;Memory Memory;std::shared_ptr<SharedTrail> Published=std::make_shared<SharedTrail>();std::vector<Position> Breadcrumbs;
};
enum class ClientlessTravelState {Moving,Arrived,Stuck};
struct Travel {struct{ClientlessTravelState State=ClientlessTravelState::Moving;}status;auto GetStatus(uint32){return &status;}}travel;
auto sClientlessTravelMgr=&travel;
#define TC_LOG_WARN(...) ((void)0)
struct Executor{bool OwnsActiveFlight(uint32){return true;}}executor;auto sClientlessActionExecutorMgr=&executor;
namespace ClientlessTravelTelemetry{void Note(Player*,char const*,std::string const&){} }
bool clear=true;
bool ClearRay(Player*,Point const&,Point const&){return clear;}
class ClientlessFlightMgr {
public:
std::unordered_map<uint32,Record> _records;
int advances=0,handoffs=0,failures=0,begins=0;
bool Begin(Player*,Position const&,std::string&,bool){++begins;return true;}
Surface Sample(Player*,Record&,float,float){return {0,300,true};}
void Fail(Player*,Record& r,std::string const&){++failures;r.Status.State=ClientlessFlightState::Failed;}
bool Advance(Player*,Record&,bool handoff=false){++advances;handoffs+=handoff;return true;}
bool LandFollowing(Player*){return true;}bool AdvanceFollowing(Player*,Record&){return true;}
void Update(uint32 diff);
};
'''.replace('Settings Settings;', 'ClientlessFlightPath::Settings Settings;').replace('Memory Memory;', 'ClientlessFlightPath::Memory Memory;')
harness += update + r'''
int main(){
// Small real movements accumulate; oscillating distance does not reset best progress.
ClientlessFlightProgress::Watch watch;watch.Begin(100,120000);
for(int i=0;i<59;++i)watch.Observe(100+(i%2?0.1f:-0.1f),1000);
assert(!watch.Stalled());watch.Observe(100,1000);assert(watch.Stalled());
watch.Observe(99.4f,200);assert(!watch.Stalled()&&watch.IdleMs==0);
// Vertical movement is measured with the same 3D distance as horizontal flight.
watch.Begin(Distance({0,0,0},{0,0,200}),20000);
watch.Observe(Distance({0,0,1},{0,0,200}),1000);assert(watch.IdleMs==0);
ClientlessFlightMgr mgr;mgr._records[26]={};
mgr._records[26].Progress.Begin(25,2000);
// Begin replacement while the existing spline is still active: no stop.
mgr.Update(50);assert(mgr.handoffs==1&&mgr.advances==1&&player.stops==0);
// The 200ms collision cadence must not launch a replacement each tick.
mgr.Update(50);assert(mgr.advances==1);
mgr._records[26].CheckTimer=0;player.spline.remaining=1900;
mgr.Update(50);assert(mgr.advances==1);
// Exact landing endpoint must finish normally, never repeatedly extend.
auto& r=mgr._records[26];r.CheckTimer=0;r.Status.Landing=r.Segment;
player.spline.remaining=400;mgr.Update(50);assert(mgr.advances==1);
// Reverse trail keeps its original checked corners.
r.Segment={25,0,60};r.Returning=true;r.CheckTimer=0;
mgr.Update(50);assert(mgr.advances==1);
// Distance means measured motion, not unflown overlapping destinations.
player.x=7;r.CheckTimer=0;mgr.Update(50);assert(std::fabs(r.Status.Travelled-7)<0.01);
// A newly blocked corridor still stops/replans immediately.
clear=false;r.CheckTimer=0;mgr.Update(50);
assert(player.stops==1&&mgr.advances==2&&mgr.handoffs==1);
// A completed spline continues via the normal non-handoff path.
clear=true;r.CheckTimer=0;mgr.Update(50);assert(mgr.advances==3);
// Long flights keep running when actual progress continues, even overdue.
r.Returning=false;r.Moving=true;player.running=true;player.spline.remaining=3000;
r.Progress.Begin(1000,20000);r.Segment={1007,0,60};
for(int i=0;i<90;++i){player.x+=1;r.CheckTimer=0;mgr.Update(1000);}
assert(mgr.failures==0&&r.Progress.Overdue()&&r.Progress.IdleMs==0);
// A genuinely stuck spline survives 59s, then replans locally at 60s.
r.Progress.Begin(Distance(PointOf(player),PointOf(r.Segment)),20000);r.StallRecoveries=0;
int before=mgr.advances;
for(int i=0;i<59;++i){r.CheckTimer=0;mgr.Update(1000);}
assert(mgr.advances==before&&mgr.failures==0);
r.CheckTimer=0;mgr.Update(1000);
assert(mgr.advances==before+1&&r.StallRecoveries==1&&mgr.failures==0);
player.fly=false;mgr.Update(50);assert(mgr.failures==1);
// A ground departure approach must wait for actual arrival before mounting.
r.Status.State=ClientlessFlightState::Mounting;r.ApproachingDeparture=true;
r.Elapsed=0;r.Departure=player;travel.status.State=ClientlessTravelState::Moving;
mgr.Update(50);assert(mgr.begins==0&&mgr.failures==1);
travel.status.State=ClientlessTravelState::Arrived;mgr.Update(50);assert(mgr.begins==1);
// A new airborne generation must publish its verified landing immediately
// and leave a replacement link for followers already holding the old trail.
r.ApproachingDeparture=false;r.Status.State=ClientlessFlightState::Flying;
r.Returning=false;r.Status.Segments=12;r.Status.Avoidances=12;r.DetachedRecoveryCount=0;
player.fly=true;auto old=r.Published;r.Status.Landing={800,0,0};
mgr.Update(50);assert(old->Aborted&&old->Replacement==r.Published);
assert(r.Published->LandingReady&&r.Published->Landing.X==800);
r.ApproachingDeparture=true;r.Status.State=ClientlessFlightState::Mounting;
// A stale Arrived flag cannot permit mounting away from the selected spot.
r.Departure.x=player.x+10;mgr.Update(50);assert(mgr.failures==2&&mgr.begins==1);
// Repeated genuine stalls exhaust local recovery, without dropping the leader trail.
ClientlessFlightMgr stuck;auto& sr=stuck._records[77];
sr.Following=std::make_shared<int>(1);auto leaderTrail=sr.Following;
sr.Segment={player.x+100,0,60};player.fly=true;player.spline.remaining=3000;
for(int attempt=0;attempt<3;++attempt){
sr.Moving=true;player.running=true;sr.Progress.Begin(100,4000);
for(int i=0;i<60;++i){sr.CheckTimer=0;stuck.Update(1000);}
assert(sr.Following==leaderTrail);
assert(stuck.failures==(attempt==2?1:0));
}
assert(stuck.advances==2&&sr.StallRecoveries==2);
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-flight-handoff-') as folder:
cpp = Path(folder) / 'test.cpp'
binary = Path(folder) / 'test'
cpp.write_text(harness)
subprocess.run(['c++', '-std=c++17', '-I', str(root/'src/server/game/World'), str(cpp), '-o', str(binary)], check=True)
subprocess.run([str(binary)], check=True)
print('Flight handoff, collision, 60s measured stall, jitter, vertical progress, long-flight overrun and local recovery passed.')