diff --git a/doc/clientless/SESSION-RESUME.md b/doc/clientless/SESSION-RESUME.md index 14f9bc2c44..d040fcfca2 100644 --- a/doc/clientless/SESSION-RESUME.md +++ b/doc/clientless/SESSION-RESUME.md @@ -1,3 +1,75 @@ +# Aktuell 02.11.2026, 10:00: Shared-Flight-Landepunkt/Flugpausen korrigiert + +Nutzer meldete Nachfolger kehren über Plattform um und fliegen stockend. +Rootcause belegt:159/161/166 abonnieren09:34:55 Leaderspur bevor Winienwi163 +seine Abflugvorbereitung/Landesuche beendet. Begin kopierte nochleere Landing +Position. Spur später korrekt bis837.77 gefolgt, letzterSinkflug falsch geprüft, +09:37:49..52 Rückflug. Leader landete09:37:33 und tratInstanz2 bei. +Fix: SharedTrail publiziert Landing+LandingReady, Advance wartet/bindet den +bestätigtenLandepunkt statt früher Kopie. Geprüfter Lookahead fürFollower und +1500msSplinehandoff; keine ungeprüften Eckabkürzungen. AchtTests PASS, +Build-j10/Install PASS. Checkpoint2026-11-02-shared-flight/README.md. +Loader0 brauchte explizitenPopulationReload (zunächst nachBuildstart korrigiert). +SafeLogout09:49:23;14Restbots nach5Min dokumentiert, saubererShutdown, +ProzessendevorInstall. Kein Einzellogout erzwungen. NeustartPID258518 ready, +Ports8085/8087. Loader1 und PopulationReload restauriert.14Restbots Login, +sechs74/77/91/93/102/121 nachungültigemSchwebezustand nativgeborgen; übrige +nativeTaxi/Flugbewegungen ungestört. Rohzustände/Recoverybelege aufbewahrt. + +FürLive-Nachprüfung alle10 offlineRose, nurderenalteInstanceVisit-/Handoff- +Zustände gesichertundentfernt.09Formation/10Raid6354_d0 wiederaktiviert. +Gruppe1 Leader163 undalle10 bestätigt. TempGM-Teleportgelöscht. +Beobachter vcore-adventure-raid-observer-20261102-1000.service weiteraktiv +bis11MEZ. Live-Retry10Uhr bleibt schoninSW anbekanntemGroundSafety-Fehler hängen: +MMAP taxi_2->stormwind_tradecenter komplett189.55yd, AusführungPathRejected, +wiederholteAbflugannäherungen unterbrochen.10:02 nochkeinneuerTurmanflug. +Keine erfolgreicheFollowerlandung/visuellflüssigeGesamtflügebehaupten. +Alle110Bots/Loader1 wiederaktiv, Gruppevollständig. Nächsteunabhängige +Baustelle ist lokalerGroundSafety/Recovery. Beobachterbis11weiteraktiv. +Flugfix separat lokal versionieren; kein Push. FrühereCommits2778cf9bdd/3056063f12. +AnderebenannteBaustellen und abgelehnteGruppennachbesetzung unverändert. + +--- + +# Neuer Zehnertest am 02.11.2026, Gruppenbildung09 / Bastion10:00 + +Nutzer wollte Wiederholung nur der bekannten10 Bots. Alle159..167/176 wurden +nativ zur GoldenenRose teleportiert, alte Gruppen verlassen, Aruendas162 für +Teststart wiederbelebt. Slots09 groupformation the_gilded_rose,10 raid +instance_visit_at_6354_d0 für alle10 per nativer editplan gesetzt und20DB-Zeilen +validiert. Temporärer GM-Teleport nach Benutzung gelöscht/Cache reloaded. +Vollständige native Gruppe1 nachgewiesen: Leader Winienwi163, Mitglieder +159,160,161,162,163,164,165,166,167,176;2Tank/2Heal/6Damage, Corric dabei. +Kein globaler erzwungener Managerlauf, keine weiteren Tagespläne verändert. +Bestehende Frühaufbruchlogik unverändert: volle Gruppe kann fünf Minuten nach +Vollständigkeit schon vor10 abziehen. Nicht als Fehler oder geänderten10Slot +missverstehen. Vorzustände zeigen alte Fehlflüge; nachTeleport allebodennahSW, +Gruppenbildungsintent aktiv. Beobachterservice: +`vcore-adventure-raid-observer-20261102-1000.service`, read-only bis11MEZ, +Snapshot alle2Minuten und inkrementelle Logs ohne den alten Tail-Replayfehler. +Daten/Skript unter checkpoints/2026-11-02-1000-raid-test/. +Kein Build/Serverrestart für Einrichtung; Loader normal aktiv. + +--- + +# Abschließende Testauswertung ohne Bastion-Cohort + +Nutzer verlangte endgültige Auswertung unter Ausschluss der bekannten10 Bots. +Erstellt: doc/clientless/2026-11-02-instance-test-final.md. Reproduzierbare +Offline-Analyse evaluate_without_raid.py und final-without-raid.json im06Uhr- +Testcheckpoint.82 Bots (38A/44H),67Dungeon/15Raid,38 vor07 am fixen Treffpunkt +belegt (18A/20H, gleichwertige Inn-Aliase anerkannt; exakt gleiche Schlüssel33), kein Instanzeintritt. Schluss62 gruppiert in22 Gruppen, +20solo, nur eine vollständige Fünfergruppe. Rollen7Tank/11Heal/64Damage; +Population erlaubt keine vollständige Aufteilung aller82 in strikte Gruppen. +35 Bots RecoveryFailed;60 WaitingGroup,16 nochGruppenbildung und1Leisure. +Fünfergruppe26/41/43/45/72 ZielVersunkenerTempel scheiterte anReise; drei +ab07:56 im Timeout-Rest, keinabsolvierterDungeon. Logsammlerduplikate: +Rohereignishäufigkeiten nicht als unabhängige Versuche zählen. Bericht erläutert +Zeitlücke bis06:13, künstlichenManagerlauf und dass Flugfix nach08 außerhalb +Test liegt. Keine weiteren Source-/Serveränderungen für diese Auswertung. + +--- + # Aktuell 02.11.2026, 08:33 MEZ: erhöhter Landeanflug installiert und live bestätigt Kostenmodell zuvor lokal committed: 2778cf9bdd, kein Push. Neuer Auftrag: diff --git a/doc/clientless/checkpoints/2026-11-02-1000-raid-test/observe.py b/doc/clientless/checkpoints/2026-11-02-1000-raid-test/observe.py new file mode 100644 index 0000000000..f61c929012 --- /dev/null +++ b/doc/clientless/checkpoints/2026-11-02-1000-raid-test/observe.py @@ -0,0 +1,42 @@ +"""Read-only observation of the ten-bot retry until 11:00 CET.""" +from pathlib import Path +import sys,os,re,json,time,datetime +sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'2026-11-02-0600-group-test')) +import test_driver as d +BASE=Path(__file__).resolve().parent +END=datetime.datetime.fromisoformat('2026-11-02T11:00:00+01:00') +IDS=set(d.RAID) +def append(name,v): + with (BASE/name).open('a') as f:f.write(json.dumps(v,ensure_ascii=False)+'\n') +class Tail: + def __init__(self,path): + self.path=path;self.f=path.open('rb');self.inode=os.fstat(self.f.fileno()).st_ino;self.f.seek(0,2);self.pending=b'' + def poll(self): + st=self.path.stat() + if st.st_ino==self.inode and st.st_size=next_snapshot:snapshot();next_snapshot=time.monotonic()+120 + except Exception as e:append('errors.jsonl',{'time':d.now().isoformat(),'error':str(e)}) + time.sleep(2) + for t in tails:t.poll() + append('lifecycle.jsonl',{'time':d.now().isoformat(),'event':'finished'}) diff --git a/doc/clientless/checkpoints/2026-11-02-shared-flight/README.md b/doc/clientless/checkpoints/2026-11-02-shared-flight/README.md new file mode 100644 index 0000000000..5215a56170 --- /dev/null +++ b/doc/clientless/checkpoints/2026-11-02-shared-flight/README.md @@ -0,0 +1,75 @@ +# Shared-flight landing and continuity fix — 02.11.2026 + +User observed followers turning away above the Bastion platform and repeated +pauses in flight. No group-refill policy changes are part of this fix. + +## Root cause + +09:34:53: leader Winienwi163 walks8yd to open takeoff column. His prepared +Flight Record already has Target but does not have a validated Landing yet. +09:34:55: followers166/159/161 attach to the leader trail BEFORE the leader's +landing-search result is published. Old Begin copied Status.Landing immediately, +which at this phase is the default zero Position. The shared pointer to the +trail survives preparation, but the copied landing never updated. +09:37:33: leader lands correctly at827.765625 and subsequently enters instance2. +09:37:49..52: followers reach the hover point837.765625. The zero-goal corridor +retains the8yd safety floor margin at the true touchdown and rejects descent. +Fail() therefore starts a checked breadcrumb return; this is not a successful +arrival or a deliberate new destination. Evidence root-cause-evidence.jsonl. + +## Implementation + +SharedTrail now publishes Landing and LandingReady. Begin does not copy the +leader's potentially uninitialised Record Position. Advance binds the validated +landing from the shared object, waiting for publication if preparation continues. +Followers retain a shared object even after the leader leaves for the instance. +Native attachment logs now include landingReady. + +For continuity, handoff begins while up to1500ms remain rather than600ms. +Followers may advance to the next published endpoint before stopping at the old +endpoint, but only if the complete shortcut corridor is clear. Escape-leg corners +are retained. Aborted trails still fail closed. Explicit shared-reject diagnostics +include index, point count, abort flag and effective landing. + +Eight targeted tests PASS. New regression reproduces the zero-goal terminal +failure, exercises the real Advance landing-binding prefix with delayed +publication, tests terminal descent, early running handoff, blocked corners, +escape legs and aborted trails. Existing ordinary/elevated flight, native handoff, +group follow, policy, landing search and terrain tests retained. + +## Build and recovery + +Build-j10 and install PASS. Safe logout requested09:49:23.822668; configuration +loader0 initially did not stop the cached loader. Explicit population reload then +confirmed enabled0 (loader-paused.log). This pause correction happened after the +build had begun and is documented rather than treating the earlier pause as +verified. Before installation, the full five-minute drain elapsed. Remaining14 +bots, positions, native available flags and diagnostics were preserved in +final-remaining.json / final-remaining-native.log / last-available-remaining-flags.json. +Five-minute exception allowed clean server shutdown; no individual forced logout +or flight interruption was used to install. Process/tmux absence verified before +install. Fresh startup ready PID258518, ports8085/8087 verified. + +Original ten-bot test evidence preserved. Only ten test bots' previous handoff and +instance-visit records were backed up and cleared to start a fresh run; all ten +natively teleported offline to the Golden Rose, reloaded, slots09formation and +10raid6354_d0 applied. Complete group1, leader163, all ten confirmed. Normal loader +restored (including explicit population reload); all14 recovery bots requested +login. Moving native taxi/flight actors left intact. Six unsupported stationary +actors74/77/91/93/102/121 received native capital recovery after login checks; +post-restart-native.log and recovery-capitals.log preserve findings/results. +Temporary GM teleport destination removed and cache reloaded. No production use. +Live result of the new10:00 run is recorded separately below when available. + +## Live follow-up limitation + +10:00 retry remains a complete ten-bot group led by163 with unchanged raid goal. +The initial route now stalls on the separately known ground safety issue: native +MMAP taxi_2 -> stormwind_tradecenter is complete (path189.55yd) but execution +returns PathRejected. Repeated departure approaches are interrupted. The group +has not reached the tower again as of10:02, so this run does NOT prove the final +follower descent or visually smooth flight. New landingReady=1 attachment and +short native flights were logged, but they are not a substitute for that proof. +All110 bots online, loader enabled1, original group still intact. Existing +observer remains active until11:00; no automatic chat report is promised. +The next independent blocker is the local ground safety/recovery behavior. diff --git a/src/server/game/World/ClientlessFlightMgr.cpp b/src/server/game/World/ClientlessFlightMgr.cpp index 67b90021bb..947f37f94e 100644 --- a/src/server/game/World/ClientlessFlightMgr.cpp +++ b/src/server/game/World/ClientlessFlightMgr.cpp @@ -499,17 +499,22 @@ bool ClientlessFlightMgr::Begin(Player * player, Position const & target, std::s record.Status.State = ClientlessFlightState::Mounting; record.Status.MountSpell = existingMount ? existingMount : spell; record.Status.Detail = "waiting for native flying mount"; + record.Published->Landing=PointOf(record.Status.Landing); + record.Published->LandingReady=true; if(!following)if(auto group=player->GetGroup())if(group->GetLeaderGUID()!=player->GetGUID()) { auto source=_records.find(group->GetLeaderGUID().GetCounter()); if(source!=_records.end() && source->second.Status.MapId==player->GetMapId() && source->second.Status.Target.GetExactDist(target)<1 && !source->second.Published->Aborted) { - record.Following=source->second.Published;record.Status.Landing=source->second.Status.Landing; + // The leader may still be walking to takeoff and have no landing + // yet. Its published trail survives that preparation. Bind the + // verified landing lazily rather than copying an unset Position. + record.Following=source->second.Published; std::vector members;for(auto const& member:group->GetMemberSlots())if(member.guid!=group->GetLeaderGUID())members.push_back(member.guid); std::sort(members.begin(),members.end());auto slot=std::find(members.begin(),members.end(),player->GetGUID())-members.begin(); record.FormationDelay=3500+uint32(slot)*600; // native shared corridor, staggered single-file departure - TC_LOG_INFO("server.adventurebot.navigation","FLIGHT_SHARED_TRAIL guid=%u leader=%u",player->GetGUID().GetCounter(),group->GetLeaderGUID().GetCounter()); + TC_LOG_INFO("server.adventurebot.navigation","FLIGHT_SHARED_TRAIL guid=%u leader=%u landingReady=%u",player->GetGUID().GetCounter(),group->GetLeaderGUID().GetCounter(),record.Following->LandingReady?1u:0u); } } if(airborne) @@ -625,6 +630,17 @@ void ClientlessFlightMgr::Fail(Player * player, Record & record, std::string con bool ClientlessFlightMgr::Advance(Player * player, Record & record, bool handoff) { + if(record.Following && !record.Returning) + { + Point landing; + if(!record.Following->Aborted && !record.Following->BindLanding(landing)) + {record.Status.Detail="waiting for leader validated landing";return true;} + if(record.Following->BindLanding(landing)) + { + record.Status.Landing.Relocate(landing.X,landing.Y,landing.Z); + record.Published->Landing=landing;record.Published->LandingReady=true; + } + } Point current = PointOf(* player), goal = PointOf(record.Returning ? record.Start : record.Status.Landing); auto sample =[&](float x, float y) { @@ -665,9 +681,13 @@ bool ClientlessFlightMgr::Advance(Player * player, Record & record, bool handoff } else if(record.Following) { - auto shared=FollowTrail(current,goal,record.Settings,*record.Following,record.SharedIndex,sample,ray); + auto shared=FollowTrail(current,goal,record.Settings,*record.Following,record.SharedIndex,sample,ray, + handoff?player->GetSpeed(MOVE_FLIGHT)*1.5f:0.f); if(shared.Waiting)return true; next=shared.Step; + if(!next.Valid) + TC_LOG_WARN("server.adventurebot.navigation","FLIGHT_SHARED_REJECT guid=%u index=%u size=%u aborted=%u landing=(%.3f %.3f %.3f)", + player->GetGUID().GetCounter(),uint32(record.SharedIndex),uint32(record.Following->Points.size()),record.Following->Aborted?1u:0u,goal.X,goal.Y,goal.Z); if(handoff && next.Valid && Distance(next.Next,PointOf(record.Segment))<1)return true; } else @@ -832,7 +852,7 @@ void ClientlessFlightMgr::Update(uint32 diff) record.Moving = false; Advance(player, record); } - else if (!record.Returning && player->movespline->timeRemaining() <= 600 && + else if (!record.Returning && player->movespline->timeRemaining() <= 1500 && Distance(PointOf(record.Segment), PointOf(record.Status.Landing)) > 0.1f) { // Replace a still-running spline from its native current position. diff --git a/src/server/game/World/ClientlessFlightPath.h b/src/server/game/World/ClientlessFlightPath.h index d3db1566ca..e7fe4bda0a 100644 --- a/src/server/game/World/ClientlessFlightPath.h +++ b/src/server/game/World/ClientlessFlightPath.h @@ -305,15 +305,32 @@ namespace ClientlessFlightPath { std::vector Points; std::vector UnderOverhang; + Point Landing; + bool LandingReady=false; bool Aborted=false; + bool BindLanding(Point& goal) const + { + if(!LandingReady || Aborted)return false; + goal=Landing;return true; + } }; struct SharedResult {Result Step;bool Waiting=false;}; inline SharedResult FollowTrail(Point const& current,Point const& goal,Settings const& settings, - SharedTrail const& trail,std::size_t& index,Sample const& sample,Ray const& ray) + SharedTrail const& trail,std::size_t& index,Sample const& sample,Ray const& ray,float lookaheadDistance=0) { if(trail.Aborted)return {}; + if(!trail.LandingReady)return {{},true}; while(index.1f;++steps){ auto n=Next(p,goal,cfg,m,terrain,rays);assert(n.Valid); assert(Corridor(p,n.Next,goal,cfg,terrain,rays,false,n.UnderOverhang)); diff --git a/tests/clientless/test_flight_handoff.py b/tests/clientless/test_flight_handoff.py index bcb7692263..cb691924a6 100644 --- a/tests/clientless/test_flight_handoff.py +++ b/tests/clientless/test_flight_handoff.py @@ -24,7 +24,7 @@ struct Position { float GetExactDist(Position const&p){return std::hypot(std::hypot(x-p.x,y-p.y),z-p.z);} }; Point PointOf(Position const& p){return {p.x,p.y,p.z};} -struct Spline {int remaining=500; int timeRemaining()const{return remaining;}}; +struct Spline {int remaining=1100; int timeRemaining()const{return remaining;}}; struct Player:Position { Spline spline; Spline* movespline=&spline; bool alive=true,fly=true,running=true;int stops=0; @@ -75,7 +75,7 @@ int main(){ 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._records[26].CheckTimer=0;player.spline.remaining=1900; 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; diff --git a/tests/clientless/test_free_flight.py b/tests/clientless/test_free_flight.py index e26f0eebe4..2995dd81ac 100644 --- a/tests/clientless/test_free_flight.py +++ b/tests/clientless/test_free_flight.py @@ -73,7 +73,7 @@ int main(){ 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; + SharedTrail trail;trail.LandingReady=true;trail.Landing={800,0,0};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); diff --git a/tests/clientless/test_shared_flight_landing.py b/tests/clientless/test_shared_flight_landing.py new file mode 100644 index 0000000000..f3808683f5 --- /dev/null +++ b/tests/clientless/test_shared_flight_landing.py @@ -0,0 +1,66 @@ +"""Regression: join while leader walks to takeoff; continuous safe trail handoff.""" +from pathlib import Path +import subprocess,tempfile +root=Path(__file__).resolve().parents[2] +source=(root/'src/server/game/World/ClientlessFlightMgr.cpp').read_text() +a=source.index(' if(record.Following && !record.Returning)',source.index('bool ClientlessFlightMgr::Advance(')) +b=source.index(' Point current =',a) +sync=source[a:b] +code=r''' +#include "ClientlessFlightPath.h" +#include +#include +#include +#include +using namespace ClientlessFlightPath; +struct Position{float x=0,y=0,z=0;void Relocate(float a,float b,float c){x=a;y=b;z=c;}}; +struct Record{std::shared_ptr Following;std::shared_ptr Published=std::make_shared();bool Returning=false;struct{Position Landing;std::string Detail;}Status;}; +bool Sync(Record& record){ +''' + sync + r''' + return false; +} +int main(){ + Record r;r.Following=std::make_shared();r.Status.Landing.z=777; + assert(Sync(r)&&r.Status.Landing.z==777); // still preparing; no zero landing copied + r.Following->Landing={12,34,827.765625f};r.Following->LandingReady=true; + assert(!Sync(r)&&r.Status.Landing.x==12&&r.Status.Landing.y==34&&r.Status.Landing.z==827.765625f); + assert(r.Published->LandingReady&&r.Published->Landing.Z==827.765625f); + + Settings cfg;auto sample=[](float,float){return Surface{827.765625f,1000,true,0};}; + auto clear=[](Point const&,Point const&){return true;}; + // The same shared object first exists while its leader is walking to takeoff. + SharedTrail trail;Point landing{0,0,0};std::size_t index=0; + assert(!trail.BindLanding(landing)); + auto pending=FollowTrail({0,0,0},landing,cfg,trail,index,sample,clear); + assert(pending.Waiting&&!pending.Step.Valid); + // Leader completes admission later. Follower must bind this published landing, + // including after the leader record is gone/has entered the instance. + trail.Landing={-4905.67f,-4232.75f,827.765625f};trail.LandingReady=true; + Point above{trail.Landing.X,trail.Landing.Y,trail.Landing.Z+10};trail.Points={above,trail.Landing}; + // Old bug: stale zero goal retains the 8-yard floor margin during touchdown. + assert(!Corridor(above,trail.Landing,landing,cfg,sample,clear,true)); + assert(trail.BindLanding(landing));index=1; + auto final=FollowTrail(above,landing,cfg,trail,index,sample,clear); + assert(final.Step.Valid&&final.Step.Landing&&Distance(final.Step.Next,landing)<.01f); + // In open air the next segment is ready before reaching the prior endpoint. + SharedTrail cruise;cruise.LandingReady=true;cruise.Landing={100,0,0}; + cruise.Points={{25,0,60},{50,0,60}}; + auto flat=[](float,float){return Surface{0,300,true};}; + index=0;auto moving=FollowTrail({15,0,60},cruise.Landing,cfg,cruise,index,flat,clear,15); + assert(moving.Step.Valid&&moving.Step.Next.X==50&&index==1); + // No shortcut through a blocked corner, and no early escape-leg shortcut. + auto obstacle=[](Point const&a,Point const&b){return !(a.X<20&&b.X>30);}; + index=0;auto corner=FollowTrail({15,0,60},cruise.Landing,cfg,cruise,index,flat,obstacle,15); + assert(corner.Step.Valid&&corner.Step.Next.X==25&&index==0); + cruise.UnderOverhang={true,false};index=0; + auto escape=FollowTrail({15,0,60},cruise.Landing,cfg,cruise,index,flat,clear,15); + assert(escape.Step.Valid&&index==0); + trail.Aborted=true;assert(!trail.BindLanding(landing)); + assert(!FollowTrail(above,landing,cfg,trail,index,sample,clear).Step.Valid); + std::cout<<"Shared flight: late landing publication, terminal descent, early handoff, blocked/escape corners and abort PASS\n"; +} +''' +with tempfile.TemporaryDirectory() as directory: + p=Path(directory);(p/'test.cpp').write_text(code) + subprocess.run(['c++','-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)