Files
Viano-Core/tests/clientless/test_free_flight.py
T

103 lines
5.9 KiB
Python

"""Run the production 3D flight geometry against deterministic terrain/collisions."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
harness=r'''
#include "ClientlessFlightPath.h"
#include <cassert>
#include <iostream>
using namespace ClientlessFlightPath;
struct Run {Point End;float Peak=0,Side=0;unsigned Steps=0,Avoidances=0;bool Arrived=false;};
Run simulate(Point start,Point target,Sample sample,Ray ray,int limit=500){
Settings cfg;Memory mem;mem.Departed=start.Z-sample(start.X,start.Y).Ground>cfg.Safety;Run r;Point p=start;
for(int i=0;i<limit;++i){
auto next=Next(p,target,cfg,mem,sample,ray);
if(!next.Valid)break;
assert(Corridor(p,next.Next,target,cfg,sample,ray,p.Z-sample(p.X,p.Y).Ground<cfg.Safety));
assert(next.Next.Z+5<=sample(next.Next.X,next.Next.Y).Ceiling);
p=next.Next;++r.Steps;r.Peak=std::max(r.Peak,p.Z);r.Side=std::max(r.Side,std::fabs(p.Y));r.Avoidances+=next.Avoiding;
if(Distance(p,target)<0.8f){r.Arrived=true;break;}
}
r.End=p;return r;
}
int main(){
auto flat=[](float,float){return Surface{0,300,true};};
auto clear=[](Point const&,Point const&){return true;};
Memory finalApproach;finalApproach.Departed=true;
auto touchdown=Next({6,0,10},{0,0,0},Settings{},finalApproach,flat,clear);
assert(touchdown.Valid&&touchdown.Landing&&Distance(touchdown.Next,{0,0,0})<0.01f);
auto solidWall=[](Point const&,Point const&){return false;};
assert(!Next({6,0,10},{0,0,0},Settings{},finalApproach,flat,solidWall).Valid);
// Diagonal movement is blocked but the local air column is clear: take off
// and touch down vertically instead of rejecting an otherwise safe flight.
auto verticalOnly=[](Point const&a,Point const&b){return Horizontal(a,b)<0.1f;};
Memory verticalTakeoff;
auto ascent=Next({0,0,0},{100,0,0},Settings{},verticalTakeoff,flat,verticalOnly);
assert(ascent.Valid&&Horizontal({0,0,0},ascent.Next)<0.1f&&ascent.Next.Z>10);
Memory verticalLanding;verticalLanding.Departed=true;
auto descent=Next({0,0,40},{0,0,0},Settings{},verticalLanding,flat,verticalOnly);
assert(descent.Valid&&descent.Landing&&Distance(descent.Next,{0,0,0})<0.01f);
auto normal=simulate({0,0,0},{800,0,0},flat,clear);
assert(normal.Arrived&&normal.Peak>30&&normal.Peak<=60.1f);
auto low=[](float,float){return Surface{0,42,true};};
auto ceiling=simulate({0,0,0},{800,0,0},low,clear);
assert(ceiling.Arrived&&ceiling.Peak<=36.1f);
auto peak=[](float x,float){return Surface{x>390&&x<400?40.0f:0.0f,300,true};};
auto smooth=simulate({0,0,0},{800,0,0},peak,clear);
assert(smooth.Arrived&&smooth.Peak<75); // no nominal clearance chase over short peak
auto wall=[](Point const&a,Point const&b){
for(int i=0;i<=80;++i){auto p=Mix(a,b,i/80.0f);if(p.X>=240&&p.X<=260&&std::fabs(p.Y)<16&&p.Z<110)return false;}return true;
};
auto side=simulate({0,0,0},{800,0,0},flat,wall);
assert(side.Arrived&&side.Avoidances>0&&side.Side>10);
auto wide=[](Point const&a,Point const&b){
for(int i=0;i<=80;++i){auto p=Mix(a,b,i/80.0f);if(p.X>=240&&p.X<=260&&std::fabs(p.Y)<500&&p.Z<95)return false;}return true;
};
auto climb=simulate({0,0,0},{800,0,0},flat,wide);
assert(climb.Arrived&&climb.Peak>95&&climb.Avoidances>0);
auto unknown=[](float x,float){return Surface{0,300,x<120};};
assert(!simulate({0,0,0},{800,0,0},unknown,clear).Arrived);
auto blocked=[](Point const&,Point const&){return false;};
assert(!simulate({0,0,0},{800,0,0},flat,blocked).Arrived);
// Missing/covered/wrong-floor landing is not an arrival.
auto invalidEnd=[](float x,float){return Surface{x>790?20.0f:0.0f,300,true};};
assert(!simulate({0,0,0},{800,0,0},invalidEnd,clear).Arrived);
auto shortFlight=simulate({0,0,0},{90,0,0},flat,clear);
assert(shortFlight.Arrived&&shortFlight.Peak>5);
// An avoidance may leave the bot high immediately above the landing site.
// Open a descending approach instead of oscillating over the target.
Memory landingMemory;landingMemory.Departed=true;
Point landingStart{660,0,60};
auto firstDescent=Next(landingStart,{800,0,0},Settings{},landingMemory,flat,clear);
assert(firstDescent.Valid&&firstDescent.Next.X>660&&firstDescent.Next.Z<60);
auto highApproach=simulate({0,0,70},{1,0,0},flat,clear);
assert(highApproach.Arrived&&highApproach.Steps<20&&highApproach.Peak<=70);
// Convoy members replay exactly the published segments, retaining their own index.
SharedTrail trail;trail.Points={{20,0,30},{40,0,40}};std::size_t index=0;Settings settings;
auto shared=FollowTrail({0,0,0},{800,0,0},settings,trail,index,flat,clear);
assert(shared.Step.Valid&&shared.Step.Next.X==20&&index==0);
shared=FollowTrail({20,0,30},{800,0,0},settings,trail,index,flat,clear);
assert(shared.Step.Valid&&shared.Step.Next.X==40&&index==1);
shared=FollowTrail({40,0,40},{800,0,0},settings,trail,index,flat,clear);
assert(shared.Waiting&&index==2);trail.Points.push_back({60,0,40});
shared=FollowTrail({40,0,40},{800,0,0},settings,trail,index,flat,clear);
assert(shared.Step.Valid&&shared.Step.Next.X==60);
assert(!FollowTrail({40,0,40},{800,0,0},settings,trail,index,flat,blocked).Step.Valid);
trail.Aborted=true;shared=FollowTrail({40,0,40},{800,0,0},settings,trail,index,flat,clear);
assert(!shared.Step.Valid&&!shared.Waiting);
std::cout<<"Flight geometry passed: takeoff/landing, smoothed peak, ceiling, lateral/climbing avoidance, missing terrain and blocked corridors.\n";
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-free-flight-') 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)
flight=(root/'src/server/game/World/ClientlessFlightMgr.cpp').read_text()
executor=(root/'src/server/game/World/ClientlessActionExecutorMgr.cpp').read_text()
assert 'FLIGHT_AIRBORNE_REPLAN' in flight
assert 'OwnsActiveFlight(guid)' in flight
assert 'found->second.ActionOwned=actionOwned' in flight
assert 'itr->second.Active && itr->second.FlightActive' in executor