Keep clientless flight moving across spline handoffs
Replace ordinary flight splines before they finish while retaining collision checks, terminal landing and conservative return behavior. Measure actual travelled distance, cover handoff timing with a production Update regression test, retire Arthoran's automatic ship test and document visual acceptance of the travel milestone.
This commit is contained in:
1 parent
8df9591c99
commit
ca656403bb
5 files changed
+149
-12
No files matched your search
@@ -0,0 +1,92 @@
|
||||
"""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 <cassert>
|
||||
#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;}
|
||||
};
|
||||
Point PointOf(Position const& p){return {p.x,p.y,p.z};}
|
||||
struct Spline {int remaining=500; int timeRemaining()const{return remaining;}};
|
||||
struct Player:Position {
|
||||
Spline spline; Spline* movespline=&spline;
|
||||
bool alive=true,fly=true,running=true;int stops=0;
|
||||
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 {ClientlessFlightState State=ClientlessFlightState::Flying;uint32 MapId=0;Position Landing{800,0,0};float Travelled=0;} Status;
|
||||
Position LastPosition,Start,Segment{25,0,60};Settings Settings;
|
||||
uint32 Elapsed=0,SegmentElapsed=0,CheckTimer=0;
|
||||
bool Moving=true,Returning=false;
|
||||
};
|
||||
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;
|
||||
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;}
|
||||
void Update(uint32 diff);
|
||||
};
|
||||
'''.replace('Settings Settings;', 'ClientlessFlightPath::Settings Settings;')
|
||||
harness += update + r'''
|
||||
int main(){
|
||||
ClientlessFlightMgr mgr;mgr._records[26]={};
|
||||
// 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=900;
|
||||
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);
|
||||
player.fly=false;mgr.Update(50);assert(mgr.failures==1);
|
||||
}
|
||||
'''
|
||||
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 before spline end, terminal landing, return corners, collision stop and actual distance passed.')
|
||||
Reference in new issue
Block a user