103 lines
5.9 KiB
Python
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
|