Files
Viano-Core/tests/clientless/test_flight_departure.py
T
Nico 649915e575
clang PCH / build (push) Waiting to run
GCC PCH / build (push) Waiting to run
Fix AdventureBot travel recovery, area eligibility and safe build logout
2026-10-30 12:43:22 +01:00

144 lines
8.9 KiB
Python

"""Exercise real departure admission/ground relocation against layered terrain."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
s=(root/'src/server/game/World/ClientlessFlightMgr.cpp').read_text()
a=s.index('uint32 existingMount=0;');b=s.index(' bool foundLanding',a)
admission=s[a:b]
a=s.index('bool ClientlessFlightMgr::ApproachDeparture(');b=s.index('\nvoid ClientlessFlightMgr::Fail(',a)
approach=s[a:b]
a=s.index(' bool OpenColumn(');b=s.index(' char const * StateName',a)
column=s[a:b]
a=s.index(' float support=',s.index('bool ClientlessFlightMgr::Begin('));b=s.index(' auto const * profile =',a)
preparation=s[a:b]
a=s.index(' if(!existingMount)\n');b=s.index(' record.Status.State = ClientlessFlightState::Mounting;',a)
dismount=s[a:b]
a=s.index(' auto floor = sample(current.X, current.Y);');b=s.index(' player->StopMoving();',a)
touchdown=s[a:b]
a=s.index(' FindReturnLanding(player,record);');b=s.index(' record.Status.MountSpell=existingMount;',a)
return_anchor=s[a:b]
a=s.index('bool ClientlessFlightMgr::FindReturnLanding(');b=s.index('bool ClientlessFlightMgr::ApproachDeparture(',a)
find_return=s[a:b]
code=r'''
#include "ClientlessFlightPath.h"
#include <cassert>
#include <string>
#include <unordered_map>
#include <cstdint>
using uint32=unsigned;using namespace ClientlessFlightPath;
#define TC_LOG_WARN(...) ((void)0)
#define TC_LOG_INFO(...) ((void)0)
constexpr float INVALID_HEIGHT=-100000;constexpr unsigned SPELL_AURA_MOUNTED=1;
struct Position{float x=0,y=0,z=0,o=0;
float GetPositionX()const{return x;}float GetPositionY()const{return y;}float GetPositionZ()const{return z;}float GetOrientation()const{return o;}
void Relocate(float a,float b,float c,float d){x=a;y=b;z=c;o=d;}};
struct Guid{unsigned GetCounter()const{return 84;}};
struct PositionFullTerrainStatus{bool outdoors=true;};
struct Map{bool outdoors=true;void GetFullTerrainStatusForPosition(int,float,float,float,PositionFullTerrainStatus&t){t.outdoors=outdoors;}float feet=0;float GetHeight(int,float,float,float,bool,float){return feet;}}map;
struct Player:Position{bool mounted=false,fly=false,aura=false;int removals=0;
Guid GetGUID(){return {};}unsigned GetMapId(){return 530;}int GetPhaseShift(){return 0;}Map* GetMap(){return &map;}
bool outdoor=true;bool IsOutdoors(){return outdoor;}bool IsMounted(){return mounted;}bool CanFly(){return fly;}bool HasAura(unsigned){return aura;}
void RemoveAurasByType(unsigned){++removals;mounted=fly=false;}};
Point PointOf(Position const&p){return {p.x,p.y,p.z};}
enum class ClientlessFlightState{Mounting,Flying,Arrived,Failed};
struct Record{struct{Position Target;ClientlessFlightState State=ClientlessFlightState::Failed;bool FollowingPlayer=false;std::string Detail;}Status;
bool ApproachingDeparture=false;Position Departure,Start;ClientlessFlightPath::Settings Settings;};
struct ClientlessPathDiagnostics{bool Calculated=true,Complete=true,Suspicious=false;float FinalDistance=0,PathLength=15,MaxSegmentLength=4,MaxZDelta=1;};
struct Travel{ClientlessPathDiagnostics nav;bool start=true;int starts=0,probes=0;
bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics& d){++probes;d=nav;return true;}
bool StartTravel(Player*,Position const&,bool){++starts;return start;}}travel;
auto sClientlessTravelMgr=&travel;
struct Areas{bool allowed=true;bool AllowsSegment(Player*,unsigned,Position const&,Position const&){return allowed;}}areas;
auto sAdventureAreaMgr=&areas;
struct Lua{int calls=0;void DismountGroundMount(Player*,char const*){++calls;}}lua;
auto sClientlessLuaEngine=&lua;
bool clear=true,narrow=false,columnBlocked=false;
bool ClearRay(Player*,Point const&a,Point const&b){
if(!clear||(narrow&&std::fabs(a.Y)>=0.01f))return false;
if(columnBlocked)for(int i=0;i<=80;++i){auto p=Mix(a,b,i/80.f);if(std::fabs(p.X)<1&&std::fabs(p.Y)<1&&p.Z>3&&p.Z<5)return false;}
return true;
}
'''+column+r'''
struct ClientlessFlightMgr{
std::unordered_map<unsigned,Record>_records;Surface surface{0,300,true};int approached=0;bool seek=false;
Surface Sample(Player*,Record&,float,float){return surface;}
bool ApproachDeparture(Player*,Record&,bool);bool FindReturnLanding(Player*,Record&);
bool Prepare(Player*player,Record&record,std::string&error){auto origin=surface;bool following=false;
'''+preparation+r'''
return false;}
bool Admit(Player* player,Record&record,std::string&error,bool following=false){
auto origin=surface;std::vector<std::pair<unsigned,unsigned>>candidates{{123,1}};
'''+admission+r'''
return true;
}
void ReturnAnchor(Player*player,Record&record){auto origin=surface;
'''+return_anchor+r'''
}
void Fail(Player*,Record&,std::string const&){}
bool Touchdown(Player*player){auto current=PointOf(*player);Record record;auto sample=[&](float,float){return surface;};
'''+touchdown+r'''
return true;
}
void KeepMount(Player*player,bool airborne){uint32 existingMount=airborne?123:0;
'''+dismount+r'''
}
};
'''+find_return+approach+r'''
int main(){
ClientlessFlightMgr mgr;Player p;Record r;std::string error;r.Status.Target={100,0,0};
// An indoor actor may walk to a verified outdoor spot before mounting.
p.outdoor=false;assert(mgr.Prepare(&p,r,error)&&mgr._records.at(84).ApproachingDeparture);
mgr._records.clear();travel.starts=0;map.outdoors=false;
assert(!mgr.Prepare(&p,r,error)&&travel.starts==0);map.outdoors=true;p.outdoor=true;
// Open ground beyond the old 40-yard ring remains usable, with a bounded MMAP leg.
travel.nav.PathLength=200;assert(mgr.ApproachDeparture(&p,r,false));
travel.nav.PathLength=15;travel.starts=0;mgr._records.clear();
// A normal supported open-air departure remains valid.
assert(mgr.Admit(&p,r,error));
// Brynli: a native flying mount already suspended ten yards above terrain.
p.z=10;p.mounted=p.fly=p.aura=true;map.feet=INVALID_HEIGHT;
assert(mgr.Admit(&p,r,error));assert(mgr._records.empty());
r.Start=p;mgr.ReturnAnchor(&p,r);assert(r.Start.z==0);
narrow=true;r.Start=p;mgr.ReturnAnchor(&p,r);assert(r.Start.z==10);narrow=false;
columnBlocked=true;r.Start=p;mgr.ReturnAnchor(&p,r);assert(r.Start.x>1&&r.Start.z==0&&p.x==0&&p.z==10);
areas.allowed=false;r.Start=p;mgr.ReturnAnchor(&p,r);assert(r.Start.z==10);areas.allowed=true;columnBlocked=false;
mgr.KeepMount(&p,true);assert(p.mounted&&p.fly&&p.removals==0&&lua.calls==0);
// Airborne recovery never applies to an unmounted/ground-only bot.
p.fly=false;assert(!mgr.Admit(&p,r,error));assert(error.find("departure-unavailable:")==0);
p.fly=true;p.aura=false;assert(!mgr.Admit(&p,r,error));p.aura=true;
clear=false;assert(!mgr.Admit(&p,r,error));clear=true;
mgr.surface.Ceiling=12;assert(!mgr.Admit(&p,r,error));mgr.surface.Ceiling=300;
// Tholgar: grounded on a lower layer, while the sky sample sees an overhead floor.
p.z=0;p.mounted=p.fly=p.aura=false;map.feet=0;mgr.surface.Ground=12;
assert(mgr.Admit(&p,r,error));auto saved=mgr._records.at(84);
assert(saved.ApproachingDeparture&&saved.Status.State==ClientlessFlightState::Mounting);
assert(saved.Status.Target.x==100&&p.x==0&&p.z==0&&travel.starts==1);
// A roof, forbidden ground corridor, partial MMAP path, cliff or failed movement
// must never manufacture a departure or move the player through the obstruction.
mgr._records.clear();travel.starts=0;clear=false;
assert(!mgr.Admit(&p,r,error)&&mgr._records.empty()&&travel.starts==0);clear=true;
narrow=true;assert(!mgr.Admit(&p,r,error));narrow=false;
areas.allowed=false;assert(!mgr.Admit(&p,r,error));areas.allowed=true;
travel.nav.Complete=false;assert(!mgr.Admit(&p,r,error));travel.nav.Complete=true;
travel.nav.MaxZDelta=20;assert(!mgr.Admit(&p,r,error));travel.nav.MaxZDelta=1;
travel.nav.PathLength=601;assert(!mgr.Admit(&p,r,error));travel.nav.PathLength=15;
travel.start=false;assert(!mgr.Admit(&p,r,error));travel.start=true;
map.feet=INVALID_HEIGHT;assert(!mgr.Admit(&p,r,error));
mgr.surface.Ground=0;map.feet=NAN;assert(!mgr.Admit(&p,r,error));map.feet=0;mgr.surface.Ground=12;
// A touchdown may use the actual lower floor, never a roof above it.
map.feet=0;p.z=0;mgr.surface.Ground=12;
assert(OpenColumn(&p,0,0,{0,300,true}));narrow=true;assert(!OpenColumn(&p,0,0,{0,300,true}));narrow=false;
assert(!OpenColumn(&p,0,0,{0,9,true}));assert(!OpenColumn(&p,0,0,{0,300,false}));
assert(mgr.Touchdown(&p));
p.z=1.2f;assert(!mgr.Touchdown(&p));p.z=0;
map.feet=INVALID_HEIGHT;assert(!mgr.Touchdown(&p));map.feet=0;
clear=false;assert(!mgr.Touchdown(&p));clear=true;
mgr.KeepMount(&p,false);assert(p.removals==1&&lua.calls==1);
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-flight-departure-') as d:
p=Path(d);(p/'test.cpp').write_text(code)
subprocess.run(['c++','-include',str(Path(__file__).with_name('telemetry_test_stub.h')),'-std=c++17','-I'+str(root/'src/server/game/World'),str(p/'test.cpp'),'-o',str(p/'test')],check=True)
subprocess.run([str(p/'test')],check=True)
print('Production flight admission: airborne mount retained, lower-floor MMAP departure, blocked/cliff/missing support rejected.')