Skip to content

Fix false-green in DeckLip arrival check (R16) + pathfix handoff#65

Open
lrhodes404 wants to merge 30 commits into
mainfrom
fix/decklip-arrival-false-green
Open

Fix false-green in DeckLip arrival check (R16) + pathfix handoff#65
lrhodes404 wants to merge 30 commits into
mainfrom
fix/decklip-arrival-false-green

Conversation

@lrhodes404
Copy link
Copy Markdown
Collaborator

Summary

DeckLipClimbFromGruntToLiteralFrezza was passing while the bot stood at the tower base (z=24.8, ~29y below Frezza) — a false green. The arrival predicate accepted a stale [TRAVEL_LEG] complete reason=walk_arrived message from the whole RecentChatMessages ring (no delta, no proximity). R16 screenshot confirmed the bot never climbed.

Fix

Arrival now requires observed position near Frezza (dist2D<=6 / |dz|<=4 primary); a walk_arrived only counts when NEW since the post-teleport baseline AND within dist2D<=12 / |dz|<=6. The test is [SkippableFact] + RequiresInfrastructure + gated on WWOW_DECKLIP_DIRECT_FREZZA_TEST=1, so it skips in CI — this makes it honest when run live.

Diagnosis (probe-driven + 6-agent verified + live-validated on vmangos)

  • The current PathfindingService returns the clean 128-corner route (blockedReason=none) for Grunt#1->Frezza — bake + runtime physics agree; the 2026-05-27 exterior/interior_projection:98 route no longer reproduces.
  • With the false-green removed, the honest RED localizes the real blocker to caller-side base->climb consumption (smooth-path start-jitter cluster; semantic blocked=1.00 normal=0 before the first StepUp) — NOT the off-mesh interior_projection (the bot never reaches it).

Turnkey recipe + revised target: docs/physics/PATHFIX_GRUNT_FREZZA_HANDOFF_2026-06-01.md. Cross-game lesson L72 + collision-contract spec landed in PCECore.

🤖 Generated with Claude Code

lrhodes404 and others added 30 commits June 1, 2026 18:28
… + add pathfix handoff

The arrival predicate accepted a `[TRAVEL_LEG] complete reason=walk_arrived` message
from the whole RecentChatMessages ring (no delta, no proximity), so a stale walk_arrived
declared arrival at the tower BASE (z=24.8, ~29y below Frezza) and the test PASSED while
the bot never climbed (R16: the screenshot shows the bot at the base).

Fix: arrival now requires observed position near Frezza (dist2D<=6 / |dz|<=4 primary); a
walk_arrived only counts when NEW since the post-teleport baseline AND within
dist2D<=12 / |dz|<=6. With the fix the (infra-gated, CI-skipped) test is honestly RED,
localizing the real blocker to caller-side base->climb consumption (smooth-path
start-jitter cluster; semantic blocked=1.00 normal=0 before the first StepUp) on the
now-clean 128-corner service route -- NOT the off-mesh interior_projection (the bot
never reaches it).

Probe-driven + 6-agent adversarially-verified diagnosis, live-validated on the vmangos
stack. Turnkey recipe + revised target:
docs/physics/PATHFIX_GRUNT_FREZZA_HANDOFF_2026-06-01.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…now climbs the OG zeppelin spiral

Root cause of the DeckLip base stall: TravelTask.CanCompleteWalkLeg returned true
unconditionally for a non-transport walk leg, so TryGetWalkLegArrival completed the leg on
2D distance <= 15y ALONE. Frezza (z=53.6) is ~14.5y due-south of the Grunt base (z=24), so
the base is ~14.8y 2D from Frezza yet ~30y below it -> the leg false-completed at the base,
exhausting the route and dumping the bot into the line-178 route-exhausted fallback
(TryNavigateToward with the default STANDARD policy, which cannot drive the long climb).
The bot then sat frozen at the base.

Fix (arrival-VERIFICATION per R2/R3, NOT a route relaxation): add
WalkLegVerticalArrivalTolerance = 6.0f (matches the transport vertical tolerance) and gate
the non-transport CanCompleteWalkLeg branch on it. A walk leg now arrives only when within
the 2D radius AND on roughly the same vertical layer.

Validated:
- Unit: new Update_TargetDirectlyAboveWithinHorizontalRadius_DoesNotCompleteWalkLegAtBase;
  full TravelTaskTests 15/15 green (transport-handoff completion tests unaffected).
- Live (vmangos): the bot now climbs the spiral ramp z24->z42 (idx0->~60 of 128, each
  afford=StepUp, leg stays 0/1), vs previously stalling at the base.

Next blocker (distinct, documented in the handoff UPDATE 2): a mid-climb client
disconnect/reset at ~z42 on the upper spiral (likely vmangos movement validation), to be
diagnosed via mmo-movement-diagnostics — NOT band-aided.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 80%); isolate deck-lip physics blocker

Continues the DeckLip diagnosis. Three more live-isolated fixes peel the onion to the
original deck-lip problem:

1. TravelTask top-level arrival was ALSO vertical-blind: Update() completed on 3D distance
   <= _arrivalRadius, so at z41 (~8y horizontal + ~12.5y BELOW Frezza = ~15y 3D) it popped
   travel_complete 12.5y under the deck. Now requires horizontal proximity AND
   verticalDelta <= WalkLegVerticalArrivalTolerance.

2. ActionDispatcher sameMapTravelArrivalTolerance 15f -> 5f (matches TravelTask.DefaultArrivalRadius).
   15y let TravelTo arrive ~14y short / on the ramp ~6y below a deck target. 5y = actually
   reach the target (NPC interaction range).
   ** BLAST RADIUS: affects EVERY same-map TravelTo (now must reach within 5y, not 15y).
      Validate against the full long-pathing/LiveValidation suite before merging to main. **

3. DeckLip is SAME-MAP, so stop disabling FG packet hooks (the cross-map-only
   DisableForegroundPacketHooksForCrossMapTransfers caused a mid-climb LoginScreen reset).

Result: the bot now climbs the spiral base (z24) -> deck-lip (z47.4, idx102/128 ~80%), every
segment afford=StepUp, no false completion, no disconnect. TravelTaskTests 15/15 green.

TRUE remaining blocker (isolated, deep): at z47 the bot stalled_near_waypoint and SLIDES BACK
to z41. Probing the final stretch from the live stall returns 30 corners ALL Walk/StepUp/Clear
(ZERO Blocked) up to Frezza on the deck -- so route + static physics say it is traversable, but
the LIVE continuous swept-capsule motion cannot sustain the steep deck-lip climb and slides back
(B3-class static-vs-swept divergence). That is a PhysicsEngine slope/step-up / movement-execution
issue -- NOT bake, NOT arrival, NOT route. Next: mmo-movement-diagnostics on the z47->53.6 climb.
Full recipe in docs/physics/PATHFIX_GRUNT_FREZZA_HANDOFF_2026-06-01.md UPDATE 3.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…thPath route, not physics

Verified the deck-lip waypoints rather than assuming a physics wall. Each segment is
Walk/StepUp/Clear, but the SMOOTH sequence oscillates: probe shows the smooth Grunt->Frezza
route has 16 DOWN segments / 5.7y backward climb (the deck-lip idx86-101 bounces
z47->46.5->47 in a ~2y cluster), while the RAW (findStraightPath) route has 1 DOWN seg and
is perfectly monotonic. So smoothPath densification introduces the jitter; the bake/raw path
is clean. The bot follows the smooth corners, is steered backward/down, makes no net progress
(stalled_near_waypoint), and slides back -> stuck-guard. Mechanism = native findSmoothPath
(PathFinder.cpp): moveAlongSurface + getPolyHeight hopping the spiral's overlapping Z-layers
(or 2y step overshooting the tight curve); the custom densification even densifies the -dz
hops. Recommended native fix on the smoothPath OUTPUT (instrument first, then monotonic/
forward-progress guard or curvature-adaptive step) -- NOT bake, NOT physics, NOT a managed
band-aid. Portable check added to the collision-contract: verify route VALIDITY (monotonicity),
not just per-segment walkability.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A self-contained iterate-until-GREEN prompt for a Claude (Opus) orchestrator session/loop that
marshals diverse agents to fix the OG zeppelin deck-lip route: Workflow fan-outs to propose +
adversarially refute fix hypotheses across model lenses, codex:rescue for native PathFinder.cpp /
MmapGen bake implementation + an independent GPT-5.x diagnosis, parallel sub-agents for reads,
with Claude owning the probe, the R16 screenshot reads, the validation loop, and commits.

Encodes the confirmed root cause (MmapGen polys merge across non-traversable spiral/step-up
geometry -> smoothPath hops Z-layers -> oscillating deck-lip waypoints), the probe/live oracles,
a vetted fix-candidate menu (walkableClimb-vs-step-up, deck-edge poly break, off-mesh, native
smoothPath guard), the R13/freeze rules, R16, the commit policy (PR #65), and a hard stop
condition. Supersedes the Codex-only draft.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OG Orgrimmar zeppelin tower is a vertical spiral; native findSmoothPath fell
into a period-2 limit cycle at the deck-lip (moveAlongSurface's fixed 2.0y step
bouncing iterPos between two mutually-reachable corridor anchors A(z47)/B(z46)
while fixupCorridor rewound the corridor), emitting a non-monotonic A<->B
waypoint ping-pong (16 DOWN segs / 5.66y backward) that stalled the live bot. RAW
findStraightPath over the IDENTICAL Detour corridor is monotonic (DOWN=1), so the
defect is in the smoothing densification alone, not the bake or physics.

Fix (Exports/Navigation/PathFinder.cpp::findSmoothPath): an emission guard with
retroactive cycle suppression. The WALK is byte-unchanged (the 2.0y step is
load-bearing for corridor drain + termination -- capping it starves consumption
and spins to a 4095-corner truncation). Each iteration computes the remaining
corridor-arc distance to target; a non-progress corner is buffered, then a SHORT
buffered run (a benign path bend) is FLUSHED so its chord stays walkable while a
LONG run (the limit cycle) is DISCARDED so the oscillation collapses. A hard
iteration cap truncates+replans a non-escaping cycle.

Validation: probe Grunt->Frezza smooth collapses 16 DOWN / 5.66y -> 4 DOWN /
1.05y, 0 Blocked, reaches Frezza; live DeckLip test now climbs MONOTONICALLY
z24->z47.9 with no oscillation (R16 screenshots confirm bot on the upper ramp).
Regression guard added: DeckLipRawPathContractTests.SmoothRouteDoesNotOscillate.
The live test stays RED on a now-DISTINCT blocker (slide-back after the walk-leg
completes at z47.9, currentSpeed=0); see handoff UPDATE 5.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… on the overlapping spiral

Iteration-B localization (codex-cross-checked, code-verified). The deck-lip
slide-back is a NATIVE swept-physics defect in Exports/Navigation/PhysicsEngine.cpp
(StepV2 / CollisionStepWoW), not managed and not the static classifier (which says
the ramp is fully climbable from z41 and z47.7 -- 0 Blocked). Root: drive-gap
support loss -- when the leg false-completes at z47.9 and drive drops, the grounded-
idle branch (PhysicsEngine.cpp:5988-5998) does a raw GetGroundZ that re-grounds the
bot to a LOWER wrap of the overlapping spiral (same XY stacks z24/28/37/47/53), and
CollisionStepWoW then re-enters from the corrupted lower-layer support and stalls
(blocked=1.00 / no wall / no normal = semantic zero-progress, the native default).
Fix surface = native ground-stick on walkable slopes (prefer prevGroundZ support,
guard the pre-sweep snap), NOT the managed arrival radius. Iteration C: confirm
idle-snap-down vs FALLINGFAR-fall with a live per-tick physics trace before editing
this load-bearing engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… for the slide-back physics fix

Scoped how to capture the confirming per-tick physics trace before editing the
load-bearing FG swept engine. Findings: native PHYS logging exists (VMAP_PHYS_LOG_*
env, std::cout sink) but the FG bot's native stdout is NOT captured live; the managed
MovementController already records a full per-tick PhysicsFrameRecord (pos/rawZ/groundZ/
prevGroundZ/falling/flags/blocked) but only when IsRecording and never dumps it, and it
lives in the FG-bot process (unreachable from the test). Recommended trace path = an
env-gated file-dump added to the FG bot (managed; no bitness). WoWSharpClient is AnyCPU
(loads Navigation.dll at FG-host bitness); build both x64+x86 for any native physics
change. Root + fix surface unchanged (UPDATE 6): native ground-stick in the grounded-idle
branch (prefer prevGroundZ support, don't snap to a lower spiral wrap), NOT the arrival
radius. Iteration-D recipe + owner status framing recorded: the loop's explicit smooth-
oscillation mission is fixed+committed (6ec1355); the slide-back is a deeper multi-cycle
FG-physics effort best run as a focused session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…vmesh-first)

The bot should follow accurate Detour paths, not gate/deflect movement on a
physics wall-contact signal. For the ForegroundBotRunner that signal was never
even real -- the FG ObjectManager never overrode PhysicsHitWall/BlockedFraction/
WallNormal2D, so the nav was reading the IObjectManager DEFAULTS (false / 1.0 /
(0,0)) every tick. Remove the whole run-time crutch chain (R18 full removal):
native swept output -> MovementController.LastHitWall/LastBlockedFraction/
LastWallNormal -> IObjectManager.Physics* props -> NavigationPath.GetNextWaypoint
wall-avoidance (geometric deflection + wall-stuck force-repath + avoidance
waypoint). Also delete PhysicsStateHelper, the [NAV_EXEC] physics-read diagnostic,
the PhysicsFrameRecord wall fields + DiagnosticsRecorder CSV columns, and the
wall-specific tests.

The CORE Detour path-following in GetNextWaypoint is untouched: next-waypoint
resolution, advance-on-reach, recalc-on-exhaustion, direct fallback, transport
hold, and the non-wall stalled-near-waypoint replan all preserved. The removed
block only ran under `if(physicsHitWall)` (now permanently absent), so it was dead
input, not lost capability.

KEPT (scaffolding, to be removed after the navmesh is made accurate): the
plan-time swept-physics corridor-walkability validation (PathfindingClient.
IsBlockingWallContact + PathfindingService Navigation.cs segment validation) and
the native PhysicsOutput fields. These guard against committing to a Detour
corridor that is walkable on the mesh but not in geometry -- i.e. they compensate
for navmesh inaccuracy, so they become redundant once Recast/mmaps are accurate.
The client-side strict Compatible=!hitWall (PathfindingClient.cs:400) is the
remnant to remove first at that point.

Verified: managed build green; NavigationPath/TravelTask/factory tests 164 pass /
0 fail; 3-lens adversarial review clean (path-follow + completeness), no orphans.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(proper LAYERS partitioning)

Owner direction reset: the FG bot drives the ORIGINAL WoW.exe client, so the run-
time physics-wall-feedback the nav was gating on (a dummy IObjectManager default
for FG) is a crutch over an inaccurate navmesh -- removed in 6fa84c4. The real
deck-lip fix is the Recast bake: the OG tower is a vertical spiral (same XY stacks
z24/28/37/47/53) and Recast's single-layer heightfield merges the stacked surfaces
(~64 polys/>=7 wraps survive at one XY), so Detour string-pulls/smooths across
non-traversable wraps. tools/MmapGen/config.json tile 4029 is a ~190-line graveyard
of per-coordinate cull band-aids + an over-lowered climb (0.2 vs harvested 1.8) that
never collapsed the stack. Verified config-only fix: per-tile partitionType:layers
(rcBuildLayerRegions, fully plumbed) -- Recast's purpose-built tool for stacked
surfaces -- retried CLEAN (the prior layers attempt failed only by coupling
simplification/maxVertsPerPoly churn), plus retire the cull band-aids + restore
harvested climb. Full turnkey recipe + probe/no-regression validation gate + the
4029 poly-ID-churn rebaseline + the post-accuracy plan-time-validation removal
recorded. Honesty: the UPDATE 6-7 slide-back is a downstream consumer of the bad
mesh; the layered bake removes that surface (complementary), and any PhysicsEngine
change must come from decompiled WoW.exe per the parity rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(layers) for the OG spiral

Per owner directive (navmesh-first, no band-aids): the OG zeppelin spiral was
fixed by ~600 occurrences of bespoke per-coordinate culls/anchor hacks in
TileWorker.cpp (~10,250 lines) + ~42 hardcoded-coordinate config entries + Recast
contour-simplify anchor overrides -- none of which collapsed the stacked-wrap mesh.
Remove ALL of it (R18 full removal): the entire anonymous-namespace cull subsystem
(postDetourCull*/preRegionCull*/preMedianCull*/prePoly*/preRasterizeCreate* /
anchor-coord + route-target lists / anchor-stage manifest+diagnostics), reverting
RecastContour.cpp to upstream (drop rcSetContourSimplifyAnchorOverrides + the seed
helpers) and Recast.h (drop rcAnchorContourSimplifyOverride). config.json: drop
every band-aid key from every tile; tile 4029 is now clean -- cs/tileSize +
partitionType:layers (rcBuildLayerRegions, Recast's purpose-built tool for
vertically-stacked surfaces) and the agentMaxClimb overrides deleted (restores
harvested 1.8/1.2).

TileWorker.cpp 12210 -> 1961 lines; cull/anchor refs 1846 -> 0; config band-aids
42 -> 0. MmapGen.exe rebuilt clean (966KB -> 584KB). Core Recast pipeline
(rasterize->filter->compact->regions->contours->polymesh->detail->Detour),
slope/area classification, climb derivation, off-mesh handling, and standard config
knobs preserved untouched.

Validated (rebaked tile 40,29 with layers + probe): Grunt#1->Frezza RAW 52 segs /
DOWN=3 / 0 Blocked / reaches Frezza, SMOOTH 96 segs / DOWN=4 / 0 Blocked / reaches
Frezza -- a routable deck-lip with zero band-aids. Next: full-map rebake +
og-zeppelin no-regression + revert the iter-A smoothPath emission guard (also a
band-aid) to confirm the bake alone yields clean smooth paths.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The deck-lip fix is the accurate Recast bake, not the consumer-side band-aids.
This lands the two remaining pieces of the UPDATE-8 navmesh-first pivot.

1. Full-world rebake was a silent no-op. bake-all-maps.ps1 ("regenerate ALL
   tiles") completed in 0.9s writing ZERO .mmtile, so the cull/anchor removal
   never reached the world. Root cause: MapBuilder::buildMap never set
   TileInfo.m_forceRebuild, so buildTile hit `!forceRebuild && shouldSkipTile()`
   and skipped all 786 existing tiles -- shouldSkipTile only rejects on
   magic/dtVersion/mmapVersion, so a generation-LOGIC change (cull removal,
   which does not bump the version) is silently swallowed by the incremental
   skip. Fix: explicit --rebuild flag (generator.cpp -> MapBuilder::SetRebuildAll
   -> buildMap propagates to TileInfo.m_forceRebuild); bake-all-maps.ps1 passes
   it. Incremental skip stays the default for quick `MmapGen.exe <map>` dev
   bakes. processQueuedTiles also now drains gracefully (queue.Cancel() +
   WaitCompletion) instead of m_cancel.store(true) so in-flight builds finish.
   Verified: map-1 rebake 786/786 tiles fresh, 335.7s, 866MB->671MB (-22%, the
   culls were inflating poly counts).

2. Removed the iter-A findSmoothPath emission guard (78 lines, R18 full
   removal). It suppressed a 16-DOWN/5.66y spiral limit cycle at the deck-lip;
   with the accurate LAYERS-partitioned bake that cycle never forms. Re-probed
   guard-free: RAW DOWN=3 / SMOOTH DOWN=6, 0 Blocked, reaches Frezza z53.63 --
   identical to with-guard. The guard was masking bake inaccuracy.

No-regression: deck-lip RAW/SMOOTH reach Frezza 0-Blocked; OG-zeppelin manifest
5/5 routes resolve (clean=0 step=5 blocked=0 error=0). DeckLipRawPathContract
thresholds (DOWN<=8, backwardZ<=3.0, finalZ>=50) still hold without the guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…calc loop pins bot at base wp1

Live DeckLipClimbFromGruntToLiteralFrezza on the rebaked world is RED, but the
diag + screenshots (R16) localize the failure AWAY from the navmesh: the live
service returns the same clean 96-corner 0-Blocked Grunt->Frezza route the probe
does. The bot stalls at base waypoint 1 (max idx reached this run = 1, player z
pinned 23.7-24.0) in a stall-recalc starvation loop: reach wp1 -> stalled_near_
waypoint force-recalc resets currentIndex -> re-target wp1 -> repeat (8x), never
attempting the climb to wp2+. AdvanceReachableWaypoints/stall machinery is intact
(the wall-feedback removal only deleted the physicsHitWall-gated branch FG never
entered), so the trigger is the new --rebuild path geometry tripping the stall
detector at the base. Next: isolate rebake-vs-stall-logic, fix recalc to preserve
corridor progress (not reset to base), parity-correct FG step-up if needed -- no
wall-feedback crutch, no arrival relaxation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l steeper

The generator marked 52-60deg slopes as walkable NAV_GROUND (AREA_STEEP_SLOPE,
10x cost), so the player pathfinder routed the bot over surfaces the WoW client
cannot walk -- e.g. the OG-tower route diving z61->z8 down ~55deg slopes into a
harbor dead-end (the bot would actually slide/fall those, not walk them). A
non-walkable slope is non-walkable in BOTH directions (the client slides; it
never "walks down" a steep face), so these polys must be absent from the player
mesh, not penalized-walkable.

Fix: set the walkable cutoff to the harvested WoW.exe limit -- cos(50deg)=0.642788
@ VA 0x0080DFFC ("slopes steeper than 50deg non-walkable"). playerClimbLimit
52->50deg; walkableSlopeAngle/VMaps 60->50deg. The two thresholds now coincide so
the AREA_STEEP_SLOPE walkable band collapses: >50deg terrain is nulled at raster
(stock rcMarkWalkableTriangles behavior), like vanilla vmangos' player mesh. No
runtime NAV_STEEP_SLOPES exclude (that path exploded Detour A* and was reverted) --
the slopes are simply gone from the mesh.

Validated (map 1 rebake): deck-lip Grunt->Frezza climb survives clean (RAW reaches
z53.63, biggest drop -0.4y; the legit spiral ramp is <=50deg). Mesh dropped
671MB->368MB (-45%): walls, cliffs, and steep slide-faces that were wrongly
walkable. og-zeppelin probe: deck-lip + boarding routes still resolve; the two
z61-start "descent" routes (ClimbOrgrimmarTowerToFrezza, FlightMasterDescentControl)
now correctly classify Blocked -- they were false slide-surfaces and need real
walkable routes or off-mesh links (zeppelin/flightpath), not >50deg ramps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ive base wedge

The deck-lip tile (4029) used walkableErosionRadius=0.2y -- a band-aid that
decoupled the navmesh erosion from the Tauren collision capsule (radius 1.0247y)
to preserve thin top-deck slabs. Side effect: the walkable surface hugged to
within 0.2y of the OG tower-base awning walls (46 vertical VMAP walls, walkable=0,
sit 0.5y off the route), so the bot's 1.02y capsule CLIPPED those walls and the
live FG bot wedged at the base -- pinned z23.8, max waypoint idx=1, never climbed.

Fix: erode by the full capsule radius (1.0247y) so the navmesh stays >1y off the
walls. The "thin slabs would break" fear that justified the band-aid does not hold
at 1.02y erosion (the old failure was at 0.0y, a different regime).

Live-confirmed 2026-06-03 on the rebaked tile: the bot now climbs z24 -> z47.7
(68 waypoints up the spiral; screenshot shows it on the tower looking down at the
harbor), where the remaining blocker is the original deck-lip final step onto
Frezza's deck (z47.7 -> z53.6). No route regression: Grunt->Frezza RAW reaches
z53.63 (59 segs) and the boarding/deck routes (OgApproach/OgFrezza/DeckLipStall)
all still resolve 0-blocked. Tile 5041KB -> 2749KB (near-wall mesh correctly gone).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…agnosis)

Screenshot-based diagnosis was unreliable (mislabeled a mid-climb ramp position
as "ramming the center pole" and invented a harbor view). Replace it with
machine-readable per-tick telemetry on the long-travel drive: position, current
facing vs bearing-to-waypoint (hdgErr), target waypoint + dist2D/wpDz, and actual
displacement since last tick (moved2D/movedZ). Gated to the same long-travel trace
flag as the other NAV_EXEC lines.

This classifies movement failures from data, no images:
  moved2D~0 + small hdgErr  -> blocked/colliding (x-ref offline navmesh wall/headroom)
  large hdgErr              -> facing/turn problem
  movedZ<0 while grounded   -> slide-back

First use proved its worth on the OG deck-lip: the trace shows the bot climbs the
spiral SMOOTHLY z24->z47.36 (moved2D~1.2/tick, movedZ~+0.5, small hdgErr -- the
navmesh is reliable after the 50deg + full-erosion fixes), then the [TICK#] z goes
z47.0 -> z41.7 -- a ~5y slide-back at the deck-lip. So the remaining blocker is the
native grounded-idle re-grounding onto a lower spiral wrap (UPDATE 6), NOT a navmesh
wedge and NOT the center pole.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ng footprint-trim fix

Tile 4029 runs cs=0.1 but inherited the global detailSampleDist=1.6/MaxError=0.5
(one detail sample per ~16 cells) -- a real cs/detail mismatch. Set 0.3/0.2 so
rcBuildPolyMeshDetail conforms to the fine ramp/lip surface.

This is a genuine accuracy fix but does NOT solve the deck-lip fall. Live-confirmed
via [NAV_TELEM]: bot still climbs z24->z47.36 then falls to z41. Workflow + probes
root-caused it: the navmesh lip poly cantilevers ~1.3y past the real floor edge
(y=-4652.0) over an 8y inter-wrap void; the failed waypoint sits over the void. The
detail mesh refines Z WITHIN the poly footprint -- it cannot trim the over-extended
FOOTPRINT, which is what the bot walks off.

Remaining fix (pinned in _4029_README_detail): trim the footprint by nulling the
overhanging lip cells in filterLedgeSpans. They survive because mixedAreaUsesTerrain
Climb=true holds the 1.8y terrain climb at the model-decorated deck edge, so the
~1.5y drop to the SEPARATE lower spiral wrap reads as a climbable downhill neighbor
rather than a ledge-over-void (TileWorker.cpp:918-927). Needs a surgical change that
distinguishes overhang-over-distinct-lower-layer from a continuous ramp/dock --
flipping the band-aids blindly risks both the legitimate 1.25y ramp->deck link and
the dock/boarding cells.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…osion (tile 4029)

The live FG test DeckLipClimbFromGruntToLiteralFrezza fell off the z47 spiral
deck-lip into the inter-wrap void (landing z~41.7) for weeks. Root-caused with
the per-stage compact-span CSV: the supported inner ramp floor at the lip dies
at the EROSION stage (X1345.6/Y-4651..-4652: 394->152 walkable spans), NOT at
filterLedgeSpans (rasterize==filterLedge there) and NOT at region/contour
(regions==median==erode). The full 1.0247y Tauren-capsule erosion bites ~1.1y
inward from the deck-edge riser/wall and wipes the narrow supported floor,
leaving only the wider UNSUPPORTED cantilever; Detour then hugs that cantilever
out over the void and the live capsule drops.

Base (z24, awning-wall clip) needs the full radius while the z47 lip needs less
-- same tile, so a single tile-wide radius cannot satisfy both. Add
erodeWalkableAreaZBanded(): byte-identical to rcErodeWalkableArea's distance
transform, but the final null pass picks a per-span erosion radius by world Z.
Per-tile JSON-gated (erosionBandRadius + erosionBandMinZ/MaxZ); absent => stock
rcErodeWalkableArea, so every other tile/map is byte-identical (no flag rot).

Tile 4029: band z45-51 at radius 0.4y. Recovers the supported lip floor up to
the real edge y~-4652 and widens the corridor ~0.5-1y -> ~2y. The z24 base and
z53 Frezza deck/dock cells stay outside the band at full 1.0247y erosion.

Live-verified 2/2: bot climbs z24->z47->~z51 on the deck, NO fall (fallTime=0,
no movedZ<0 collapse at z47), test GREEN. Probe gates hold: Grunt->Frezza
--detour-resolve + --smooth reach z>=53.6 with 0 Blocked; og-zeppelin dock/
boarding routes unchanged from baseline; tile ~3.3MB, no 16-bit vertex abort.
Residual ~7-8y to Frezza's literal spawn is the separate pre-existing deck-lip
1.75y step-up, not the fall.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…DISABLED)

NEGATIVE RESULT, not a validated fix. Bake is byte-identical to 545a782
(navCullBoxesWow empty); the live test still fails. Records this session's
diagnosis so the next iteration starts from it.

Findings (see docs/DECKLIP_ARRIVAL_DIAGNOSIS_HANDOFF.md):
- The under-deck ramp tongue at the stall (1337.56,-4650.75,50.47) is REAL,
  well-supported, gently-sloped WMO floor (inst 234682). Per-stage compact-span
  CSV proves it passes the slope test, filterLedgeSpans, low-height AND erosion;
  it is interior-connected (~6-7y wide). No standard Recast filter rejects it,
  so the thin-slab-that-should-be-filtered hypothesis is refuted.
- Added cullWalkableCompactSpansInBoxes (TileWorker.cpp): tile-scoped, JSON-gated
  (navCullBoxesWow) post-erosion+median walkable-span excision. With the box
  [1330,-4658,48.7,1339.3,-4645,52.0] it removes the tongue poly and ALL 4 probe
  gates pass (poly gone, Grunt to Frezza raw+smooth reach z53.63 0-blocked via
  the NE junction, dock routes baseline clean=0/step=3/blocked=2, tile 3.27MB).
- BUT it REGRESSES the live FG test 2/2: the tongue IS the live bot's ascent
  route (it climbs region-11 westward onto it to z50.5); removing it makes the
  live NavigationPath cliff-reroute walk the bot OFF the ramp at z46 (freefall to
  z41) instead of taking the clean NE route. Mechanism left INERT (empty box)
  pending a fix that first makes the NE-junction to deck transition
  live-traversable (the real blocker the live path avoids).

Also commits the honest tightened arrival gate (deck-Z required) in
LongPathingTests.cs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eaction

Doc-only. No bake/tile change (still 545a782-equivalent + 89f5744 inert cull).

Iter-2 diagnosis (docs/DECKLIP_ARRIVAL_DIAGNOSIS_HANDOFF.md):
- Probe --detour-resolve --smooth from the z47.5 hand-off point (1345.4,-4652.6,
  47.5) to Frezza = 28 segs, 0 Blocked, stays on the corridor (min X at z49-52 =
  1339.97, never the tongue), climbs the NE junction to the deck (z54.06) and
  walks to Frezza (z53.63). The navmesh path is correct from EVERY start point.
- Live trace: single leg; walk-nav follows the 95-corner path correctly to idx~68
  (z47.5-48.3, X1344-1348), then all NAV/TRAVEL logging stops ~4.2s while the bot
  drifts to the tongue (1337.6,-4650.8,50.5). No existing log captures the drift.
- Mechanism is the consumer (NavigationPath.GetNextWaypoint ResolveDirectFallback
  + TryReplanFromNearVerticalLayerMismatch promoting a waypoint toward Frezza/west)
  reacting to local geometry; at the stall the path recalcs to waypointCount=1.
- Bake<->consumer bridge: the destination-ward promotion is suppressed only if
  IsUphillLayerProgression && PreservesWalkableCorridor (= local-physics Z-delta<=5
  && collision-support && IsSegmentWideEnoughForCharacter && HasWalkableNavmeshSamples)
  -- all navmesh-determined, so a tile-scoped corridor refinement near z48-52 is the
  legit bake lever, without touching the consumer.
- Iter 3 = instrument the drift window (temp diagnostic), pin the failing check,
  then refine the bake.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Doc-only (temporary DECKLIP_DIAG instrumentation already reverted; tree clean).

Irrefutable live trace (TravelTask.ExecuteWalkLeg + legs-exhausted final approach):
- walk_arrived fires at pos=(1344.4,-4653.7,48.0): dist2D=13.96 (<=15) and
  vDelta=5.62 (<=6) -> arrived=True (prior tick z47.1/vDelta=6.50 was False).
  The walk leg arrives ~14y short of Frezza and 5.6y below the deck.
- Then 93 ticks of the legs-exhausted same-map branch TryNavigateToward(target)
  (Standard policy, no NAV_EXEC -> explains the log silence at z47.5) drift the
  bot west+down from (1344.3,-4653.7,48.0) to (1337.4,-4649.4,41.2).

Root cause: WalkLegArrivalRadius=15 + WalkLegVerticalArrivalTolerance=6 complete
the navmesh-following walk leg 5.6y BELOW the deck; the Standard final approach
then drives straight at Frezza's XY onto the under-deck tongue (stall) or off the
ramp (fall). This is pure distance, NOT navmesh-sampled -> no bake change can
prevent it. The navmesh path is correct (iter 2). The fix is necessarily a
CONSUMER change (deck-tier-aware arrival / multi-leg route / LongTravel final
approach), which the mission forbids -- needs the user to decide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lets an agent inspect a baked MmapGen --debug navmesh OBJ without RecastDemo:
flood-fills poly connectivity into disjoint walkable islands, reports whether
query points (e.g. lip + deck) share a component + nearest-approach gap, and
renders top-down (height + component colored) and elevation (X-Z) PNGs.

Built to give agent-side holistic navmesh inspection. It immediately corrected a
prior analysis error: it confirmed the OG zeppelin lip, deck, and NE-junction are
ONE connected component (lip-to-deck IS walkable via the NE-junction spiral), not
the two-disjoint-islands a region-label-based analysis had claimed. Requires
matplotlib+numpy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ull path

LongPathingTests.DeckLipClimbFromGruntToLiteralFrezza is now GREEN (live-verified
3/3: bot climbs the OG zeppelin spiral to Frezza's deck at z53.54, ~4.3-4.7y away,
fallTime=0; R16 screenshot confirms it standing on the deck).

Root cause (NOT the bake -- the navmesh connects lip->deck via the NE-junction
spiral, confirmed by tools/scripts/navmesh_view.py connectivity flood-fill + two
FindPath methods). TravelTask's plain walk-leg arrival fired on 2D-proximity to
the goal while the bot was still below the deck: at z48 the bot is within
WalkLegArrivalRadius=15y horizontally AND WalkLegVerticalArrivalTolerance=6y
vertically of Frezza, so the leg "arrived" ~14y short and 5.6y below the deck and
handed off to the Standard-policy close-range approach, which drove straight at
Frezza's XY (west) into the deck-overhang dead-end (stall z50.5 / fall z41). I.e.
the bot "got close and took a shortcut" instead of walking the full path up.

Fix (Exports/BotRunner/Tasks/Travel/TravelTask.cs):
- WalkLegArrivalRadius 15 -> 5: the navmesh-following leg drives all the way onto
  the deck to near Frezza before handing off, leaving only a short on-tier step
  (a bisect proved this is load-bearing: vert-alone passed the flag but the bot
  fell off the deck edge afterward).
- WalkLegVerticalArrivalTolerance 6 -> 2: a plain walk leg only "arrives" on the
  destination's tier, never on the lip/tongue ~5.6y below the deck (refines the
  2026-06-01 vertical-tier guard; cf. the 1.5y same-deck-tier boarding tolerance).

Consumer-side only: no bake/navmesh change, no off-mesh link, no physics
step-up-tolerance change. The committed Z-banded erosion fall-fix and the
tightened test gate are kept. NOTE: these constants are global to walk legs --
the change makes ALL walk legs follow the path closer before the close-range
handoff (the intended "walk all the way to the waypoint" behavior).

Resolution recorded in docs/DECKLIP_ARRIVAL_DIAGNOSIS_HANDOFF.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n only

Follow-up hardening of 710ab3e. That commit tightened the SHARED
WalkLegArrivalRadius 15->5, which unintentionally also tightened transport-handoff
detection (WalkLegHandsOffToTransport), flight-master approach
(ExecuteFlightPathLeg), and transport/elevator EXIT arrival (HasReachedTransportExit)
-- a latent regression risk for boarding/flight flows.

Split it: WalkLegArrivalRadius is restored to 15y (used by the transport/flight/exit
paths, behavior unchanged), and a new WalkLegFinalArrivalRadius=5y is used ONLY by
GetWalkLegArrivalRadius (plain non-transport walk-leg completion -- the OG zeppelin
deck-lip case). WalkLegVerticalArrivalTolerance stays 2y (already scoped to
non-transport completion via CanCompleteWalkLeg).

Re-verified the DeckLip live test stays GREEN with the scoped change (2/2 clean;
03-final z53.54, ~4.6y from Frezza, fallTime=0, R16 screenshot shows bot on the
deck) -- 6/6 total across the unscoped + scoped versions. Transport/flight/exit
arrival detection is now unaffected by the fix.

Resolution updated in docs/DECKLIP_ARRIVAL_DIAGNOSIS_HANDOFF.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add tile-scoped low-headroom preservation for the Orgrimmar zeppelin
deck-lip beam treads. The new JSON-gated boxes keep the beam spans from
being deleted by full standing-clearance filtering, restore compact links in
that same box, and apply a separate no-erosion box to the upper treads while
leaving the existing z[45,51] erosion fall-fix intact.

Validation:
- tools/MmapGen/build-mmapgen.ps1 (with DOTNET_ROOT=E:\dotnet8)
- debug bake map 1 tile 40,29 to D:\wwow-bot\test-data, tile size 3,284,612 bytes
- navmesh_view.py: final crop renders 511 faces; lip/deck/upper-beam samples share component c33
- PathPhysicsProbe Grunt->Frezza --detour-resolve: 59 segments, 0 Blocked, ends at z=53.63
- PathPhysicsProbe Grunt->Frezza --detour-resolve --smooth: 94 segments, 0 Blocked, ends at z=53.63
- probe-routes.ps1 og-zeppelin: baseline summary clean=0 step=3 blocked=2 error=0
- control tile 40,30 rebake byte-identical before/after: 00DC53B8B0ABE7979F42C5D2FC5AB0D5250FF563C043858E1AF0F120B7F81378
- LongPathingTests.DeckLipClimbFromGruntToLiteralFrezza: Passed; final snapshot (1334.34,-4646.44,53.54), fallTime=0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant