Allow validated vertical approaches to elevated flight landings
clang PCH / build (push) Canceled after 0s
GCC PCH / build (push) Canceled after 0s

This commit is contained in:
Nico committed 2026-11-02 08:32:21 +01:00
1 parent 2778cf9bdd
commit 3056063f12
9 files changed
+344 -11

No files matched your search

@@ -0,0 +1,72 @@
# Testauswertung: 02.11.2026, Gruppenbildung 06:00 / Instanzen 07:00
Erstellt: 02.11.2026 08:00:09 CET. Geplanter Auswertungszeitpunkt: 08:00 UTC+01:00.
- Geprüfte Testbots: 92. Unter Stufe 15 ausgenommen: 18.
- Feste Treffpunkte: Allianz Goldene Rose in Sturmwind; Horde Broken Tusk in Orgrimmar.
- Status-Snapshots: 22; ausgewertete eindeutige Logereignisse: 16592.
- Erste erfasste Statusstichprobe: 2026-11-02T06:13:38.615403+01:00; frühere Ereignisse sind nicht vollständig erfasst.
- Bots mit belegter Ankunft am festgelegten Formationstreffpunkt: 40.
- Bots mit protokolliertem tatsächlichem Instanzbeitritt: 0.
- Snapshots ohne laufenden Testserver: 0.
## Bekannte Zehnergruppe: Bastion des Zwielichts, normal
- Maximal gemeinsam in einer nativen Gruppe beobachtet: 9/10.
- Alle zehn gemeinsam nachgewiesen: nein; durch die Stichproben nicht nachgewiesen.
- Bots mit Instanzplanung: 9/10; mit tatsächlichem Beitritt zu Karte 671 im Modus 0: 0/10.
| GUID | Name | Rolle | Bastion betreten | Letzter nativer Status |
|---:|---|---|---|---|
| 159 | Renren | damage | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / instance visit waiting for safe native state |
| 160 | Marndra | damage | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / travelling to instance_visit_at_6354_d0 |
| 161 | Marnda | healer | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / travelling to instance_visit_at_6354_d0 |
| 162 | Aruendas | healer | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / travelling to instance_visit_at_6354_d0 |
| 163 | Winienwi | damage | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / travelling to instance_visit_at_6354_d0 |
| 164 | Taliaarnora | damage | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / travelling to instance_visit_at_6354_d0 |
| 165 | Brynda | damage | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / travelling to instance_visit_at_6354_d0 |
| 166 | Corric | damage | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / waiting for a suitable group / native browser queue |
| 167 | Ragnorli | tank | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / travelling to instance_visit_at_6354_d0 |
| 176 | Ashaelri | tank | nicht nachgewiesen | raid / instance_visit_at_6354_d0 / travelling to instance_visit_at_6354_d0 |
## Übernahme der Anführerroute
- Protokollierte Routenübernahmen mit Journey-Seed: 78.
- Gleicher zuletzt beobachteter Anführer-Seed: 72; abweichend: 4; Anführer-Seed im Beobachtungsfenster nicht bekannt: 2.
- Ein abweichender letzter Seed erfordert Prüfung der konkreten Reise und des Zeitpunkts; diese Zählung allein beweist keinen Fehler.
## Fehler und Reiseereignisse
- GROUP_MERGE: 28.
- GROUP_MANAGER_RUN: 9.
- GROUP_EARLY_DEPART: 15.
- PORTAL_USE_START: 36.
- RECOVERY_FAILED: 117.
- INSTANCE_TRAVEL_RETRY: 11.
- INSTANCE_ENTRY_DENIED: 0.
Häufigste protokollierte Fehler:
- RECOVERY_FAILED: route-local-PathRejected (26 Ereignisse).
- RECOVERY_FAILED: free-flight-unavailable: safely returned after abort: no safe bounded flight corridor; holding position mounted (15 Ereignisse).
- RECOVERY_FAILED: free-flight-unavailable: departure-unavailable: ground approach interrupted (12 Ereignisse).
- RECOVERY_FAILED: free-flight-unavailable: no free solid landing with a validated target approach (12 Ereignisse).
- INSTANCE_TRAVEL_RETRY: siehe Rohlog (11 Ereignisse).
- RECOVERY_FAILED: deferred-start-movement-failed (9 Ereignisse).
- RECOVERY_FAILED: deferred-start-no-progress (7 Ereignisse).
- RECOVERY_FAILED: final-local-PathRejected (6 Ereignisse).
- RECOVERY_FAILED: deferred-start-endpoint-replan (6 Ereignisse).
- RECOVERY_FAILED: wander-ended-Arrived (4 Ereignisse).
- RECOVERY_FAILED: deferred-start-attempts-exhausted (4 Ereignisse).
- RECOVERY_FAILED: recorded group road rejected by safety adapter (4 Ereignisse).
## Grenzen der Auswertung
- Ausgewertet werden technische Zustände und native Logereignisse; keine visuelle Gameplay-Abnahme und keine Bestätigung von Bosskämpfen.
- Ein Reiseplan ist kein Ankunftsnachweis. Fehlende Ereignisse werden als nicht nachgewiesen ausgewiesen.
- Die Gruppenmitgliedschaft und Zustände werden alle fünf Minuten stichprobenartig gelesen; kurzlebige Zwischenzustände können fehlen.
- Charakterpositionen aus DB-Snapshots sind zuletzt gespeicherte Positionen. Live-Reisepositionen stehen in den TEMP_TRAVEL-Ereignissen.
- Der Beobachter repariert, teleportiert oder startet den Server nicht. Ausfälle werden dokumentiert.
- Letzter Snapshot: 2026-11-02T07:59:25.206244+01:00.
Rohdaten: doc/clientless/checkpoints/2026-11-02-0600-group-test/ (manifest.json, snapshots.jsonl, events.jsonl, snapshot-*.log).
+35
View File
@@ -1,3 +1,38 @@
# Aktuell 02.11.2026, 08:33 MEZ: erhöhter Landeanflug installiert und live bestätigt
Kostenmodell zuvor lokal committed: 2778cf9bdd, kein Push. Neuer Auftrag:
Steigungsbudget statt fester30-Yards-Grenze; senkrechter Aufstieg im laufenden
Flug, sichere seitliche Flucht unter Plattform, Anflug von oben. Implementiert
in ClientlessFlightPath.h und FlightMgr; Details/Belege:
`checkpoints/2026-11-02-elevated-flight/README.md`.
Sieben gezielte Tests PASS, Build-j10 und Install PASS. Bestehender
Abflug-Extraktionstest scheitert bereits auf HEAD; Baselinefehler archiviert.
Frischer Server ready PID249564, Ports8085/8087 und Prozess geprüft.
Finaler Build mit Loader0 und erfolgreichem SafeLogout bis0 Sessions; keine
5-Minuten-Ausnahme/kein erzwungener Einzellogout. Erster Build traf bereits
beendeten Server an, Ursache dieses alten Endes nicht geklärt.
LIVE: Renren159 flog isoliert ab taxi_663 zur Bastion-Plattform. Senkrechter
Aufstieg bei ca. (-4817.58,-4266.23) bis837.77, oberhalb der Plattform angeflogen,
08:27:42 sicher auf827.77 gelandet; FLIGHT_LANDED error0.00. Keine Bestätigung
von Raidbeitritt. Seitliches Ausweichen unter Plattform synthetisch getestet.
Temporäres Profil exakt restauriert, LuaReload OK, Scheduleon, temporären
GM-Teleport gelöscht. Loader1 restauriert, SafeLogoutresume, ursprüngliche110
Bots wieder online. Acht alte schwebende Raidbots vor Login nachSW geborgen;
zusätzlich Braneven121 nach nativer Fehlzustandsprüfung nachORG versetzt,
Position16.047/floor16.088/flying0/falling0 bestätigt. Recoverylisten und
Rohdiagnosen im Checkpoint erhalten. Normale Tagespläne laufen wieder.
Nutzer hat Nachbesetzungsänderung ausdrücklich ABGELEHNT: Elanora wurde manuell
für Test entfernt, dadurch entstandene Neunergruppe wird akzeptiert. Gruppen-
manager/Frühaufbruch für diesen Auftrag unverändert lassen.
08:00-Bericht erfolgreich um08:00:09 erstellt: 40/92 Treffpunktankünfte,
kein Instanzeintritt, maximal9/10 Cohort. Bericht ist vor dem Flugfix entstanden.
`doc/clientless/2026-11-02-0800-test-report.md`. Andere bekannte Baustellen
(Ground-Safety, Treffpunktfallback, Kolonnenbildung) nicht als behoben darstellen.
---
# Aktueller Stand 02.11.2026, 06:21 MEZ
Das Entfernungskostenmodell ist gebaut und installiert. Acht gezielte Tests sind
@@ -0,0 +1,77 @@
# Elevated flight navigation — 2026-11-02
User correction: no fixed 30-yard height threshold. Near a raised destination,
compare required ascent with horizontal distance × configured slope. Preserve
ordinary slope-limited flight but explicitly permit a vertical aerial escape.
Do not modify group refill behavior; the missing raid member followed a manual
removal and is accepted for this performative test.
Implementation in ClientlessFlightPath.h:
- Within 100 horizontal yards, an insufficient slope budget or an obstructed
ascending direct approach selects an elevated approach.
- Validate full descent from landing+10, a full open climb column, and the full
above-platform crossing before committing. At most 65 column candidates at
radii 0/25/50/100/150 around the current position.
- Stable Escape/Climb/Cross/Descend phases prevent height oscillation. Vertical
climb is exempt from ordinary slope; normal cruising remains slope limited.
- A blocked column can be reached laterally in clear air below an overhang.
The exception still checks the bare terrain/water floor, ceiling and all
collision-volume rays. Unknown floor fails closed. It does not tunnel through
an ADT hill or bypass platform collision.
- Early normal climb reserves ascent distance for raised destinations.
- Published group trails carry per-segment overhang permissions; followers and
moving-segment rechecks retain the same collision checks.
Seven targeted tests pass (individual logs here): elevated geometry, ordinary flight,
spline handoff, moving group follow, flight policy, landing search and native terrain fallback. Elevated tests
cover a solid thin platform, starting directly beneath it, a 10-yard height change
with insufficient horizontal slope budget, a blocked direct approach with enough
slope budget, rejected closed/low-ceiling routes, terrain-floor rejection and
shared follower segment validation.
Existing test_flight_departure.py fails with unordered_map::at both on HEAD and
this change: the extraction harness omits the newer preparation phase when
asserting admission behavior. Baseline failure is preserved separately; not a
new production regression. test_flight_handoff.py doubles were updated to the
current production record and owner/replan dependencies.
Initial build/install passed. The server was already offline before the build;
no process/tmux session, so no safe-logout command could be delivered. Preserve
residual online list (110 bots) and prior raw test data. Server exit cause unknown.
First restart ready PID248139, ports8085/8087 checked. Loader kept paused.
Initial native flight attempt was taken over by a normal day plan before schedule
disable; safe return requested, then successful safe logout with zero sessions.
Final build/install is tracked separately. Temporary Renren profile isolation and
GM teleport destination must be restored/removed after the live experiment.
## Final result
Final -j10 build and install passed (build-final.log/install-final.log). Before
shutdown, native safe logout confirmed zero pending/zero sessions; no five-minute
exception or forced logout was needed. Final startup fresh ready PID249564,
ports8085/8087 and live process verified.
Live isolated flight: Renren159, native taxi_663 departure (-4170.55,-4748.93,
126.787), destination portal_at_6354_source. Profile temporarily disabled ordinary
behavior, backed up and restored byte-for-byte; no fake travel edge was added.
At 08:27:23, vertical climb from (-4817.58,-4266.23,~619) was selected and reached
837.77. Above-platform crossing ended at (-4905.67,-4232.75,837.77), then vertical
touchdown at 827.77. FLIGHT_LANDED at 08:27:42: segments=64 avoidances=3,
travelled=1279.85 error=0.00. This proves the real tower landing, not raid entry.
Under-platform lateral escape is covered by geometry tests, not separately proven
in-game. Raw live-flight-evidence.log and landing-confirmed.log retain proof.
The profile was restored byte-for-byte, native Lua reload succeeded, schedule on.
Temporary GM teleport destination deleted and native game_tele cache reloaded.
110 original bots are back online; loader restored to1, safe-logout block resumed.
Eight formerly hovering raid members were recovered natively to Stormwind before
login; Renren landed safely on the platform. All110 login/movement/flight states
were sampled. Additional unsupported Braneven121 at z93.962 (floor24.148), failed
flight, was natively teleported to Orgrimmar; final z16.047/floor16.088, flying=0,
falling=0 verified. 112/132 had failed flight records but were supported on ground
and were left in place. Recovery logs retained; no production access/push.
Original 06/07 test report was generated successfully at08:00:09: 40/92 arrivals
at required meeting points, zero recorded instance entries, max9/10 cohort.
It predates this fix. Group refill logic was explicitly rejected by user and
remains unchanged.
+11 -3
View File
@@ -208,9 +208,12 @@ Surface ClientlessFlightMgr::Sample(Player * player, Record & record, float x, f
float water = map->GetWaterLevel(player->GetPhaseShift(), x, y);
if (std::isfinite(water) && water > ground) ground = water;
float ceiling = map->GetTerrain()->GetMaxHeight(player->GetPhaseShift(), map->GetId(), x, y);
float terrainFloor=map->GetTerrain()->GetStaticHeight(player->GetPhaseShift(),map->GetId(),x,y,MAX_HEIGHT,false,MAX_HEIGHT);
if(std::isfinite(water) && water>terrainFloor)terrainFloor=water;
return
{
ground, ceiling, std::isfinite(ground) && std::isfinite(ceiling) && ground > INVALID_HEIGHT + 1 && ceiling > ground + 5
ground, ceiling, std::isfinite(ground) && std::isfinite(ceiling) && ground > INVALID_HEIGHT + 1 && ceiling > ground + 5,
std::isfinite(terrainFloor) && terrainFloor>INVALID_HEIGHT+1?terrainFloor:std::numeric_limits<float>::infinity()
};
}
@@ -713,13 +716,18 @@ bool ClientlessFlightMgr::Advance(Player * player, Record & record, bool handoff
return false;
}
record.Published->Points.push_back(next.Next);
record.Published->UnderOverhang.push_back(next.UnderOverhang);
record.SegmentUnderOverhang=next.UnderOverhang;
record.Moving = true;
record.SegmentElapsed = 0;
++ record.Status.Segments;
record.Status.Avoidances += next.Avoiding ? 1 : 0;
if (handoff) ++record.Status.Handoffs;
bool const vertical=Horizontal(current,next.Next)<1.0f && std::fabs(next.Next.Z-current.Z)>1.0f;
record.Status.Detail = next.Avoiding ? "obstacle avoidance" : next.Landing && vertical ? "vertical landing fallback" :
record.Status.Detail = next.UnderOverhang ? "elevated approach lateral escape" :
record.Memory.Elevated==Memory::ElevatedPhase::Climb ? "elevated approach vertical climb" :
record.Memory.Elevated==Memory::ElevatedPhase::Cross ? "elevated approach above platform" :
next.Avoiding ? "obstacle avoidance" : next.Landing && vertical ? "vertical landing fallback" :
next.Landing ? "landing approach" : vertical && next.Next.Z>current.Z ? "vertical takeoff fallback" :
vertical ? "vertical descent fallback" : next.Next.Z < current.Z - 0.25f ? "diagonal descent" :
next.Next.Z > current.Z + 0.25f ? "diagonal climb" : "terrain-smoothed cruise";
@@ -818,7 +826,7 @@ void ClientlessFlightMgr::Update(uint32 diff)
return Sample(player, record, x, y);
};
// Recheck the remaining corridor as moving GO geometry changes.
if (! Corridor(PointOf(* player), PointOf(record.Segment), PointOf(record.Returning ? record.Start : record.Status.Landing), record.Settings, sample, ray, true))
if (! Corridor(PointOf(* player), PointOf(record.Segment), PointOf(record.Returning ? record.Start : record.Status.Landing), record.Settings, sample, ray, true, record.SegmentUnderOverhang && !record.Returning))
{
player->StopMoving();
record.Moving = false;
@@ -48,6 +48,7 @@ class TC_GAME_API ClientlessFlightMgr
ClientlessFlightPath::Settings Settings;
ClientlessFlightPath::Memory Memory;
Position Start, Segment, LastPosition;
bool SegmentUnderOverhang = false;
uint32 Elapsed = 0, SegmentElapsed = 0, CheckTimer = 0;
bool Moving = false, Returning = false;
bool ApproachingDeparture = false;
+70 -3
View File
@@ -18,6 +18,9 @@ namespace ClientlessFlightPath
{
float Ground = 0, Ceiling = 0;
bool Valid = false;
// Bare terrain/water floor, without overhead VMAP/GO platforms. Unknown
// values keep the normal highest-surface restriction (fail closed).
float TerrainFloor = std::numeric_limits<float>::infinity();
};
struct Settings
{
@@ -29,11 +32,15 @@ namespace ClientlessFlightPath
float LowerTerrainDistance = 0;
float HeadingX = 1, HeadingY = 0;
bool Departed = false;
enum class ElevatedPhase { None, Escape, Climb, Cross, Descend };
ElevatedPhase Elevated = ElevatedPhase::None;
Point Column, Hover, ElevatedGoal;
};
struct Result
{
Point Next;
bool Valid = false, Avoiding = false, Landing = false;
bool UnderOverhang = false;
};
using Sample = std::function < Surface(float, float) >;
using Ray = std::function < bool(Point const &, Point const &) >;
@@ -52,7 +59,7 @@ namespace ClientlessFlightPath
a.X +(b.X - a.X) * t, a.Y +(b.Y - a.Y) * t, a.Z +(b.Z - a.Z) * t
};
}
inline bool Corridor(Point const & from, Point const & to, Point const & goal, Settings const & cfg, Sample const & sample, Ray const & ray, bool departure = false)
inline bool Corridor(Point const & from, Point const & to, Point const & goal, Settings const & cfg, Sample const & sample, Ray const & ray, bool departure = false, bool underOverhang = false)
{
int const count = std::max(1, int(std::ceil(Distance(from, to) / 5)));
Surface const origin = sample(from.X, from.Y);
@@ -66,7 +73,11 @@ namespace ClientlessFlightPath
if (departure) margin = std::min(margin, std::max(0.0f, from.Z - origin.Ground) + Horizontal(from, p) * std::min(0.3f, std::max(0.0f, to.Z - from.Z) / std::max(1.0f, Horizontal(from, to)) * 0.75f));
margin = std::min(margin, std::max(0.0f, Horizontal(p, goal) * 0.2f));
Surface s = sample(p.X, p.Y);
if (! s.Valid || ! std::isfinite(s.Ground) || ! std::isfinite(s.Ceiling) || p.Z + 0.5f < s.Ground + margin || p.Z + 5 > s.Ceiling) return false;
// MAX_HEIGHT reports the TOP of a platform, not the support floor
// beneath an airborne bot. Only a checked elevated escape may use
// air below that surface; all volume rays still apply.
if (! s.Valid || ! std::isfinite(s.Ground) || ! std::isfinite(s.Ceiling) ||
p.Z + 0.5f < (underOverhang?std::min(s.Ground,s.TerrainFloor):s.Ground) + margin || p.Z + 5 > s.Ceiling) return false;
}
// A volume, not one center ray. Keep the lower rays above foot level so
// a valid ground departure/touchdown does not intersect the ground plane.
@@ -113,6 +124,57 @@ namespace ClientlessFlightPath
if (! here.Valid || ! end.Valid || std::fabs(end.Ground - goal.Z) > 2.5f) return
{
};
using Phase = Memory::ElevatedPhase;
if (memory.Elevated != Phase::None && Distance(memory.ElevatedGoal,goal)>0.1f)
memory.Elevated=Phase::None;
Point hover{goal.X,goal.Y,goal.Z+10};
bool steep = goal.Z-from.Z > distance*cfg.Slope;
bool blockedAscent = memory.Departed && distance<=100 && !steep && goal.Z>from.Z &&
!Corridor(from,goal,goal,cfg,sample,ray);
if (memory.Departed && memory.Elevated==Phase::None && distance<=100 &&
(steep || blockedAscent))
{
// Validate the whole manoeuvre before committing: escape laterally,
// climb in open air, cross ABOVE the platform, then touch down.
// A diagonal shortcut through the platform is never substituted.
float best=std::numeric_limits<float>::infinity();
Point column;
if (!Corridor(hover,goal,goal,cfg,sample,ray)) return {};
for(float radius : {0.f,25.f,50.f,100.f,150.f})
for(unsigned i=0;i<(radius==0?1u:16u);++i)
{
float a=float(i)*6.283185307f/16;
Point low{from.X+radius*std::cos(a),from.Y+radius*std::sin(a),from.Z};
Point high{low.X,low.Y,hover.Z};
if(!Corridor(from,low,goal,cfg,sample,ray,false,true) ||
!Corridor(low,high,goal,cfg,sample,ray) ||
!Corridor(high,hover,goal,cfg,sample,ray)) continue;
float score=radius+Distance(low,high)+Distance(high,hover);
if(score<best){best=score;column=low;}
}
if(!std::isfinite(best))return {};
memory.Column=column;memory.Hover=hover;memory.ElevatedGoal=goal;
memory.Elevated=Distance(from,column)>0.1f?Phase::Escape:Phase::Climb;
}
if(memory.Elevated!=Phase::None)
{
Point waypoint;
for(unsigned transition=0;transition<4;++transition)
{
waypoint=memory.Elevated==Phase::Escape?memory.Column:
memory.Elevated==Phase::Climb?Point{memory.Column.X,memory.Column.Y,memory.Hover.Z}:
memory.Elevated==Phase::Cross?memory.Hover:goal;
if(Distance(from,waypoint)>0.1f)break;
if(memory.Elevated==Phase::Descend)return {goal,true,false,true};
memory.Elevated=memory.Elevated==Phase::Escape?Phase::Climb:
memory.Elevated==Phase::Climb?Phase::Cross:Phase::Descend;
}
bool escape=memory.Elevated==Phase::Escape;
// Check the full remaining leg, not just the next 25 yards.
if(!Corridor(from,waypoint,goal,cfg,sample,ray,false,escape))return {};
Point next=Mix(from,waypoint,std::min(1.f,cfg.Step/std::max(0.1f,Distance(from,waypoint))));
return {next,true,false,memory.Elevated==Phase::Descend,escape};
}
// A short, low final descent need not open another horizontal approach.
// Validate the entire landing volume, including obstacles and terrain.
if (memory.Departed && from.Z>=goal.Z && from.Z-goal.Z<=12 &&
@@ -150,6 +212,9 @@ namespace ClientlessFlightPath
}
std::sort(heights.begin(), heights.end());
float desired = heights[2] + cfg.Clearance;
// Reserve enough ascent distance for elevated destinations rather than
// following low terrain until the tower is immediately in front of us.
if(goal.Z>from.Z)desired=std::max(desired,goal.Z+10-distance*cfg.Slope*0.8f);
// ignore short radical peaks in nominal height
bool const approach = distance < std::max(150.0f,(from.Z - goal.Z) / cfg.Slope + cfg.Step);
if (approach) desired = goal.Z + distance * cfg.Slope * 0.65f;
@@ -239,6 +304,7 @@ namespace ClientlessFlightPath
struct SharedTrail
{
std::vector<Point> Points;
std::vector<bool> UnderOverhang;
bool Aborted=false;
};
struct SharedResult {Result Step;bool Waiting=false;};
@@ -249,7 +315,8 @@ namespace ClientlessFlightPath
while(index<trail.Points.size() && Distance(current,trail.Points[index])<0.7f)++index;
if(index==trail.Points.size())return {{},true};
auto next=trail.Points[index];
return {{next,Corridor(current,next,goal,settings,sample,ray,true),false,Distance(next,goal)<0.7f},false};
bool escape=index<trail.UnderOverhang.size() && trail.UnderOverhang[index];
return {{next,Corridor(current,next,goal,settings,sample,ray,true,escape),false,Distance(next,goal)<0.7f,escape},false};
}
}
+67
View File
@@ -0,0 +1,67 @@
"""Exercise elevated landings with production geometry and solid overhangs."""
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;
// Tower platform at z=200: collision surface is thin, open air beneath its lip.
auto terrain=[](float x,float y){return Surface{std::hypot(x,y)<20?200.f:0.f,500,true,0.f};};
auto rays=[](Point const&a,Point const&b){
for(int i=0;i<=200;++i){auto p=Mix(a,b,i/200.f);
if(std::hypot(p.X,p.Y)<20 && p.Z>=199.8f && p.Z<=200.2f)return false;
if(std::hypot(p.X,p.Y)<5 && p.Z<200)return false;
}return true;
};
void run(Point p,bool escapeExpected){
Settings cfg;Memory m;m.Departed=true;Point goal{12,0,200};
bool climbed=false,escaped=false,crossed=false,descended=false;unsigned steps=0;
SharedTrail trail;
for(;steps<100 && Distance(p,goal)>.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));
if(m.Elevated==Memory::ElevatedPhase::Climb){assert(Horizontal(p,n.Next)<.01f);climbed=true;}
if(n.UnderOverhang){assert(std::fabs(p.Z-n.Next.Z)<.01f);escaped=true;}
if(m.Elevated==Memory::ElevatedPhase::Cross){assert(n.Next.Z>=209.9f);crossed=true;}
if(m.Elevated==Memory::ElevatedPhase::Descend){assert(Horizontal(p,n.Next)<.01f);descended=true;}
trail.Points.push_back(n.Next);trail.UnderOverhang.push_back(n.UnderOverhang);
std::size_t index=trail.Points.size()-1;
auto shared=FollowTrail(p,goal,cfg,trail,index,terrain,rays);
assert(shared.Step.Valid && shared.Step.UnderOverhang==n.UnderOverhang);
p=n.Next;
}
assert(Distance(p,goal)<.1f && steps<100 && climbed && crossed && descended);
assert(!escapeExpected || escaped);
}
int main(){
run({90,0,80},false); // high target: climb before approaching the wall
run({12,0,100},true); // directly UNDER platform: move out before rising
run({25,0,190},false); // only 10 yards high, slope budget is 7.8: no fixed threshold
// Reachable diagonal approach intersects platform underside. The collision
// query, rather than a height threshold, must trigger the elevated manoeuvre.
Memory m;m.Departed=true;Settings cfg;
auto n=Next({40,0,190},{12,0,200},cfg,m,terrain,rays);
assert(n.Valid && m.Elevated!=Memory::ElevatedPhase::None);
// A blocked ascent/crossing must fail, never tunnel through a platform.
auto blocked=[](Point const&,Point const&){return false;};
Memory b;b.Departed=true;assert(!Next({12,0,100},{12,0,200},cfg,b,terrain,blocked).Valid);
// The escape exception must not tunnel through bare terrain or water.
auto hill=[](float,float){return Surface{200.f,500,true,200.f};};
assert(!Corridor({12,0,100},{80,0,100},{12,0,200},cfg,hill,rays,false,true));
// A low flight ceiling cannot be ignored by the vertical exception.
auto low=[](float x,float y){return Surface{std::hypot(x,y)<20?200.f:0.f,213,true,0.f};};
Memory c;c.Departed=true;assert(!Next({90,0,80},{12,0,200},cfg,c,low,rays).Valid);
// Ordinary small height changes still use the normal slope.
auto gentle=[](float x,float){return Surface{x>99?5.f:0.f,500,true};};
auto clear=[](Point const&,Point const&){return true;};
Memory d;d.Departed=true;auto normal=Next({0,0,10},{100,0,5},cfg,d,gentle,clear);
assert(normal.Valid && d.Elevated==Memory::ElevatedPhase::None);
std::cout<<"Elevated flight: slope budget, open column, solid overhang escape, above-platform crossing, touchdown, shared trail, blocked/ceiling checks PASS\n";
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-elevated-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)
+6 -3
View File
@@ -43,14 +43,17 @@ struct World {
auto sWorld=&world;
enum class ClientlessFlightState {Mounting,Flying,Arrived,Failed};
struct Record {
struct {bool FollowingPlayer=false;ClientlessFlightState State=ClientlessFlightState::Flying;uint32 MapId=0;Position Landing{800,0,0},Target{800,0,0};float Travelled=0;} Status;
struct {bool FollowingPlayer=false;ClientlessFlightState State=ClientlessFlightState::Flying;uint32 MapId=0;Position Landing{800,0,0},Target{800,0,0};float Travelled=0;uint32 Segments=0,Avoidances=0;std::string Detail;} Status;
Position LastPosition,Start,Segment{25,0,60};Settings Settings;
std::shared_ptr<int> Following;uint32 FormationDelay=0,TargetUpdated=0,Elapsed=0,SegmentElapsed=0,CheckTimer=0;
bool Moving=true,Returning=false,ApproachingDeparture=false;Position Departure;
bool Moving=true,Returning=false,ApproachingDeparture=false,ActionOwned=false,SegmentUnderOverhang=false;uint32 DetachedRecoveryCount=0;Position Departure;Memory Memory;std::shared_ptr<SharedTrail> Published;std::vector<Position> Breadcrumbs;
};
enum class ClientlessTravelState {Moving,Arrived,Stuck};
struct Travel {struct{ClientlessTravelState State=ClientlessTravelState::Moving;}status;auto GetStatus(uint32){return &status;}}travel;
auto sClientlessTravelMgr=&travel;
#define TC_LOG_WARN(...) ((void)0)
struct Executor{bool OwnsActiveFlight(uint32){return true;}}executor;auto sClientlessActionExecutorMgr=&executor;
namespace ClientlessTravelTelemetry{void Note(Player*,char const*,std::string const&){} }
bool clear=true;
bool ClearRay(Player*,Point const&,Point const&){return clear;}
class ClientlessFlightMgr {
@@ -64,7 +67,7 @@ public:
bool LandFollowing(Player*){return true;}bool AdvanceFollowing(Player*,Record&){return true;}
void Update(uint32 diff);
};
'''.replace('Settings Settings;', 'ClientlessFlightPath::Settings Settings;')
'''.replace('Settings Settings;', 'ClientlessFlightPath::Settings Settings;').replace('Memory Memory;', 'ClientlessFlightPath::Memory Memory;')
harness += update + r'''
int main(){
ClientlessFlightMgr mgr;mgr._records[26]={};
@@ -7,13 +7,14 @@ a=s.index('Surface ClientlessFlightMgr::Sample(');b=s.index('\nbool ClientlessFl
code=r'''
#include <cassert>
#include <cmath>
#include <limits>
#include <string>
#include <unordered_map>
#include <set>
using uint32=unsigned;using int32=int;
constexpr int MAX_NUMBER_OF_GRIDS=64;constexpr float MAX_HEIGHT=100000,INVALID_HEIGHT=-100000;
struct Grid{int x_coord=35,y_coord=23;};namespace Trinity{Grid ComputeGridCoord(float,float){return {};}}
struct Terrain{bool child=false;uint32 GetId(){return 1;}
struct Terrain{float floor=5;bool child=false;float GetStaticHeight(int,unsigned,float,float,float,bool,float){return floor;}uint32 GetId(){return 1;}
bool HasChildTerrainGridFile(unsigned,int,int){return child;}float GetMaxHeight(int,unsigned,float,float){return 1000;}}terrain;
struct Map{unsigned GetId(){return 1;}Terrain*GetTerrain(){return &terrain;}void LoadGrid(float,float){}
float GetHeight(int,float,float,float,bool,float){return 20;}float GetWaterLevel(int,float,float){return -100000;}}map;
@@ -21,12 +22,14 @@ struct Player{Map*GetMap(){return &map;}int GetPhaseShift(){return 0;}};
namespace PhasingHandler{unsigned swap=719;unsigned GetTerrainMapId(int,unsigned,Terrain*,float,float){return swap;}}
struct TerrainMgr{static inline std::set<unsigned>files{1};static inline unsigned checked=0;
static bool ExistMapAndVMap(unsigned id,float,float){checked=id;return files.count(id);}};
struct Surface{float Ground=0,Ceiling=0;bool Valid=false;};
struct Surface{float Ground=0,Ceiling=0;bool Valid=false;float TerrainFloor=0;};
struct Record{std::unordered_map<std::string,bool>Tiles;};
struct ClientlessFlightMgr{Surface Sample(Player*,Record&,float,float)const;};
'''+s[a:b]+r'''
int main(){ClientlessFlightMgr mgr;Player p;Record a;
auto surface=mgr.Sample(&p,a,1751,-4389);assert(surface.Valid&&surface.Ground==20&&TerrainMgr::checked==1);
assert(surface.TerrainFloor==5);
terrain.floor=INVALID_HEIGHT;Record missingFloor;assert(!std::isfinite(mgr.Sample(&p,missingFloor,1751,-4389).TerrainFloor));terrain.floor=5;
terrain.child=true;Record b;assert(!mgr.Sample(&p,b,1751,-4389).Valid&&TerrainMgr::checked==719);
TerrainMgr::files.insert(719);Record c;assert(mgr.Sample(&p,c,1751,-4389).Valid&&TerrainMgr::checked==719);
TerrainMgr::files.clear();Record d;assert(!mgr.Sample(&p,d,1751,-4389).Valid);