"""Execute production optional-exit methods with deterministic movement doubles.""" from pathlib import Path import subprocess import tempfile root = Path(__file__).resolve().parents[2] source = (root / 'src/server/game/World/ClientlessActionExecutorMgr.cpp').read_text() header = (root / 'src/server/game/World/ClientlessActionExecutorMgr.h').read_text() graph = (root / 'src/server/game/World/ClientlessTravelGraphMgr.h').read_text() def method(signature): start = source.index(signature) opening = source.index('{', start) depth, end = 1, opening + 1 while depth: depth += (source[end] == '{') - (source[end] == '}') end += 1 return source[start:end] preamble = r''' #include #include #include #include #include #include #include #include #include #include #include #include using uint8=uint8_t; using uint32=uint32_t; using uint64=uint64_t; using int32=int32_t; #define TC_GAME_API #define TC_LOG_INFO(...) ((void)0) #define TC_LOG_WARN(...) ((void)0) #define TC_LOG_DEBUG(...) ((void)0) struct Player; struct ClientlessAction; struct Position { float x=0,y=0,z=0,o=0; void Relocate(float a,float b,float c,float d=0){x=a;y=b;z=c;o=d;} void Relocate(Player const*); float GetExactDist(Position const&p)const{return std::hypot(x-p.x,y-p.y);} float GetPositionX()const{return x;} float GetPositionY()const{return y;} float GetPositionZ()const{return z;} float GetOrientation()const{return o;} }; struct Motion { int launches=0;std::vector path; void MoveSmoothPath(int,Position const* p,std::size_t count,bool){++launches;path.assign(p,p+count);} }; struct Player { float x=0; bool spline=true, flying=false;Motion motion; void StopMoving(){spline=false;}bool IsFlying()const{return flying;}bool IsSplineEnabled()const{return spline;} bool IsInWorld()const{return true;}uint32 GetMapId()const{return 0;} float GetPositionX()const{return x;}float GetPositionY()const{return 0;}float GetPositionZ()const{return 0;} float GetOrientation()const{return 0;} float GetExactDist(float a,float b,float c)const{return std::sqrt((x-a)*(x-a)+b*b+c*c);} std::string GetName()const{return "Arthoran";} Motion* GetMotionMaster(){return &motion;} }; void Position::Relocate(Player const* p){x=p->x;y=z=o=0;} enum class ClientlessTravelState {Moving,Arrived,PathRejected}; struct ClientlessPathDiagnostics {bool Complete=false;float FinalDistance=0,PathLength=0;}; struct TravelStatus {ClientlessTravelState State=ClientlessTravelState::Moving;}; struct Travel { bool reachable=false, starts=true,rejectOnStart=false;int probes=0, launches=0,cancels=0;float targetX=0;TravelStatus status; bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics& nav){++probes;nav.Complete=reachable;nav.PathLength=40;return true;} bool StartTravel(Player* p,Position const& pos,bool){++launches;targetX=pos.x;p->spline=false;status.State=starts&&!rejectOnStart?ClientlessTravelState::Moving:ClientlessTravelState::PathRejected;return starts;} void CancelTravel(uint32){++cancels;} TravelStatus const* GetStatus(uint32){return &status;} } travel; auto sClientlessTravelMgr=&travel; enum class ClientlessFlightState {Mounting,Flying,Arrived,Failed}; struct FlightStatus {ClientlessFlightState State=ClientlessFlightState::Mounting;}; struct Flight { bool enabled=false,starts=false,recovery=false;int launches=0;FlightStatus status; void BeginPlanning(){} float Estimate(Player*,uint32,Position const&,Position const&,bool fallback=false){recovery=fallback;return enabled?100:std::numeric_limits::infinity();} bool Start(Player*,Position const&,std::string&){++launches;return starts;} FlightStatus const* GetStatus(uint32){return &status;} } flight; auto sClientlessFlightMgr=&flight; struct Lua {void PrepareGroundMount(Player*,float,char const*){}} lua; auto sClientlessLuaEngine=&lua; ''' structs = graph[graph.index('struct TC_GAME_API ClientlessTravelNode'):graph.index('class TC_GAME_API ClientlessTravelGraphMgr')] cls = header[header.index('enum class ClientlessActionExecutionState'):header.index('#define sClientlessActionExecutorMgr')].replace('private:', 'public:') doubles = r''' struct Graph { ClientlessTravelNode node; std::vector points; auto GetEdgePoints(uint32){return &points;} ClientlessTravelNode const* FindNode(std::string const& key){return key==node.Key?&node:nullptr;} } graph; auto sClientlessTravelGraphMgr=&graph; static int recoveries=0; bool ClientlessActionExecutorMgr::RecoverTravel(Player*,uint32,Record&,uint32,std::string const&){++recoveries;return false;} ''' methods = '\n'.join(method(sig) for sig in ( 'bool StartGroundConnection(', 'bool ClientlessActionExecutorMgr::PrepareRecordedGroundEdge(', 'bool ClientlessActionExecutorMgr::GetTravelGoal(', 'bool ClientlessActionExecutorMgr::TryFallbackFlight(', 'void ClientlessActionExecutorMgr::CheckGroundExit(', 'bool ClientlessActionExecutorMgr::ResumeRecordedEdge(', 'void ClientlessActionExecutorMgr::UpdateGroundExit(', 'bool ClientlessActionExecutorMgr::StartRecordedEdgePath(', )) tests = r''' int main(){ using Record=ClientlessActionExecutorMgr::Record; ClientlessActionExecutorMgr mgr;Player p;Record r; r.Active=true;r.Status.ActionName="travel_to_location";r.FinalX=100;r.FinalLocationId="inn"; r.Route.resize(2);r.Route[0].NodeKey="entry";r.Route[1].NodeKey="gate"; r.Route[1].EdgeType="ground";r.Route[1].EdgeDatabaseId=42;r.RouteStep=1; for(float x:{0.f,20.f,40.f,60.f,80.f,100.f,120.f}){ ClientlessTravelEdgePoint point;point.X=x;r.EdgePath.push_back(point); } r.EdgeNextPoint=2;r.RecoveryCount=4;r.ReplanAtGroundEnd=true; // A virtual plan keeps the entire road after its proposed exit, to the real node. graph.node.Key="gate";graph.node.X=120;graph.points=r.EdgePath; Record virtualPlan=r;virtualPlan.Route[1].NodeKey="virtual_ground_exit"; virtualPlan.Route[1].GroundContinuationNode="gate"; virtualPlan.Route[1].GroundStartIndex=1;virtualPlan.Route[1].GroundEndIndex=3; virtualPlan.Route.push_back({});virtualPlan.Route.back().EdgeType="free_flight"; assert(mgr.PrepareRecordedGroundEdge(virtualPlan)); assert(virtualPlan.Route[1].NodeKey=="gate" && virtualPlan.Route[1].GroundEndIndex==-1); assert(virtualPlan.Route.size()==3 && virtualPlan.ReplanAtGroundEnd && virtualPlan.GroundFlightExit); assert(virtualPlan.EdgePath.front().X==20 && virtualPlan.EdgePath.back().X==120); // Reverse execution retains the same forward-only continuation in execution order. virtualPlan=r;virtualPlan.Route[1].NodeKey="virtual_ground_exit"; virtualPlan.Route[1].GroundContinuationNode="gate";virtualPlan.Route[1].EdgeReverse=true; virtualPlan.Route[1].GroundStartIndex=1;virtualPlan.Route[1].GroundEndIndex=3; assert(mgr.PrepareRecordedGroundEdge(virtualPlan)); assert(virtualPlan.EdgePath.front().X==100 && virtualPlan.EdgePath.back().X==0); // Twenty failed probes, not five: same route, edge, direction and active spline. for(int i=0;i<20;++i){ r.GroundExitCheckMs=0;mgr.CheckGroundExit(&p,26,r); assert(r.Active && p.spline && !r.GroundExitActive && r.EdgePath.size()==7); assert(r.RouteStep==1 && r.Route.size()==2 && r.Route[1].NodeKey=="gate"); assert(r.RecoveryCount==4 && recoveries==0 && travel.cancels==0 && travel.launches==0); assert(r.FailedBridgeKeys.empty() && r.FailedEdgeDatabaseIds.empty()); mgr.CheckGroundExit(&p,26,r); // clock has not elapsed: do not probe again } assert(travel.probes==20 && r.GroundExitCheckMs==6000); // A later valid probe selects the exit without pre-emptive route replacement. travel.reachable=true;r.GroundExitCheckMs=0;mgr.CheckGroundExit(&p,26,r); assert(r.GroundExitActive && !p.spline && travel.launches==1 && r.EdgePath.size()==7); // If the accepted exit subsequently fails, reconnect only to the NEXT road point. p.x=35;travel.status.State=ClientlessTravelState::PathRejected; mgr.UpdateGroundExit(&p,26,r); assert(r.GroundResumeActive && !r.GroundExitActive && travel.targetX==40 && recoveries==0); p.x=40;travel.status.State=ClientlessTravelState::Arrived;mgr.UpdateGroundExit(&p,26,r); assert(!r.GroundResumeActive && p.motion.launches==1 && p.motion.path.front().x==40); assert(r.EdgeNextPoint==3 && r.EdgeSplinePoints.front()==3); for(auto const& point:p.motion.path)assert(point.x>=40); // no replay towards entry // An execution-query rejection after a successful probe resumes road immediately. travel.starts=false;r.GroundExitCheckMs=0;p.spline=true; mgr.CheckGroundExit(&p,26,r);assert(!r.GroundExitActive && p.motion.launches==2 && recoveries==0); travel.starts=true;travel.rejectOnStart=true;r.GroundExitCheckMs=0;p.spline=true; mgr.CheckGroundExit(&p,26,r);assert(!r.GroundExitActive && p.motion.launches==3 && recoveries==0); travel.rejectOnStart=false; // A successful local exit completes the original destination. r.GroundExitCheckMs=0;mgr.CheckGroundExit(&p,26,r); p.x=100;travel.status.State=ClientlessTravelState::Arrived;mgr.UpdateGroundExit(&p,26,r); assert(!r.Active && r.Status.State==ClientlessActionExecutionState::Completed); assert(r.Status.TargetName=="location:inn" && r.EdgePath.empty() && recoveries==0); // A normal graph route with no speculative virtual end can also take a valid exit. r.Active=true;r.ReplanAtGroundEnd=false;r.EdgePath.resize(1);r.EdgeNextPoint=0;r.GroundExitCheckMs=0; mgr.CheckGroundExit(&p,26,r);assert(r.GroundExitActive); // Missing regular travel triggers the separate recovery flight policy. Record fallback;fallback.Status.ActionName="travel_to_location";fallback.FinalX=260; flight.enabled=true;flight.starts=true; assert(mgr.TryFallbackFlight(&p,26,fallback)); assert(flight.recovery && fallback.FlightActive && fallback.RouteStep==1); assert(fallback.Route.back().X==260 && fallback.FinalLocalLegPending); assert(!mgr.TryFallbackFlight(&p,26,fallback)); // no repeated accepted fallback // A failed flight departure from an optional exit also preserves forward road. Record air;air=r;air.GroundExitActive=false;air.GroundFlightExit=true;air.GroundExitCheckMs=0; air.EdgeNextPoint=0;air.EdgePath.resize(2);air.EdgePath[0].X=120;air.EdgePath[1].X=140; travel.reachable=false;flight.starts=false;p.spline=false; auto before=p.motion.launches;mgr.CheckGroundExit(&p,26,air); assert(!flight.recovery && p.motion.launches==before+1 && !air.GroundExitActive); // End-point rejoin does not launch an empty spline and fail the journey. air.GroundResumeActive=true;air.GroundExitActive=false;p.x=140;travel.status.State=ClientlessTravelState::Arrived; before=p.motion.launches;mgr.UpdateGroundExit(&p,26,air); assert(!air.GroundResumeActive && p.motion.launches==before && air.Active); // Owner adapters can reject, wait, or shorten a recorded road without // losing the original index mapping or launching an unsafe spline. Record adapted;adapted.Active=true;adapted.EdgePath.resize(5);for(unsigned i=0;i<5;++i)adapted.EdgePath[i].X=150+i*10;p.x=140; mgr._groundAdapters[26]=[](Player*,std::vector&){return false;};before=p.motion.launches; assert(!mgr.StartRecordedEdgePath(&p,26,adapted) && p.motion.launches==before); mgr._groundAdapters[26]=[](Player*,std::vector&path){path.resize(1);return true;}; assert(mgr.StartRecordedEdgePath(&p,26,adapted) && adapted.FormationWaiting && p.motion.launches==before); mgr._groundAdapters[26]=[](Player*,std::vector&path){path.resize(3);return true;}; assert(mgr.StartRecordedEdgePath(&p,26,adapted) && adapted.GroundChunk && !adapted.FormationWaiting && adapted.EdgeSplinePoints.size()==3); std::cout<<"optional exits: repeated probes, retained route, delayed success, forward resume, flight fallback passed\n"; } ''' with tempfile.TemporaryDirectory(prefix='vcore-ground-exit-') as folder: cpp = Path(folder) / 'test.cpp' binary = Path(folder) / 'test' cpp.write_text(preamble + structs + cls + doubles + methods + tests) subprocess.run(['c++', '-std=c++17', '-Wall', '-Wextra', '-Wno-unused-parameter', str(cpp), '-o', str(binary)], check=True) subprocess.run([str(binary)], check=True)