Skip to content

[Movement] Reject packed zero-length waypoint segments - #355

Merged
MadMaxMangos merged 4 commits into
masterfrom
fix/cata-packed-waypoint-degenerate
Sep 2, 2026
Merged

MadMaxMangos merged 4 commits into
masterfrom
fix/cata-packed-waypoint-degenerate

Conversation

@MadMaxMangos

@MadMaxMangos MadMaxMangos commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

  • Reject packed waypoint paths that collapse to a zero-length segment before emitting client movement.
  • Resolve the exact vehicle/live-spline/pending-stop launch position through the same selector used by packet launch.
  • Sanitize routed fallbacks by removing only packed duplicate vertices, retaining distinct navmesh corners and the destination.
  • Add focused regressions for launch-grid, source-index, tail-collapse, and fallback behavior.

Verification

  • Release build completed through mangosd.
  • Full repository CTest: 13/13 passed.
  • git diff --check passed.

Dependency

This is the movement prerequisite for the separate Cata Warden redesign PR and should be squash-merged first.


This change is Reviewable.

Preflight smoothed waypoint chunks against the exact 4.3.4 packed linear-path reconstruction, using the mover position that MoveSplineInit will install as the first control point. Unsafe chunks fall back to existing per-leg routing so packet and server spline state remain aligned.

This guard is intentionally scoped to the smoothed waypoint producer; generic routed single-leg paths remain outside it.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T15:16:05.696787Z af80d7a New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd0fc8a7a5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// PacketBuilder::WriteLinearPath sends the destination directly and every
// intermediate point as an offset from this midpoint. Compare the path the
// client reconstructs, including both uncompressed endpoint boundaries.
const Geometry::Vector3 midpoint = (launchPosition + points.back()) / 2.0f;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Derive the midpoint from the packet builder's first path point

When points[1] differs from the launch position, this validates against the wrong quantization grid. In the inspected PacketBuilder::WriteLinearPath implementation (src/game/movement/packet_builder.cpp:142-155), real_path starts at spline.getPoint(1) and the midpoint uses real_path[0], while linear spline initialization copies controls beginning at index 0 (src/game/movement/spline.cpp:301-319), so the actual midpoint is (points[1] + points.back()) / 2, not (launchPosition + points.back()) / 2. Consequently an unsafe path can pass this check and still reconstruct with adjacent identical points on the client, defeating the protection this commit adds.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rechecked against the actual spline data flow and kept launchPosition. MoveSplineInit::Launch replaces args.path[0] with real_position before init_spline; InitCatmullRom then copies args.path[0] into spline control point 1, which is exactly where PacketBuilder starts real_path. Thus getPoint(1) equals the launch position, not the pre-Launch points[1]. The projected-first-point regression covers this distinction, and an independent Claude trace reached the same result. Using points[1] here would model geometry that never reaches the wire.

Comment on lines +452 to 456
if (m_segment.size() <= 1 || m_legPoints.size() < 2 ||
!IsWaypointSmoothingWireSafe(m_legPoints, launchPosition))
{
ClearSegment();
m_legPoints.clear();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate the fallback waypoint leg before sending it

When the collapsing pair belongs to the routed geometry of the first waypoint leg rather than a seam between waypoint legs, clearing m_legPoints does not prevent the bad packet. PrepareMove then returns an intent without an explicit path, and MotionDriver::LayLeg reruns the route and passes query->Points() directly to MoveSplineInit (src/game/MotionGenerators/MotionDriver.cpp:166-200) without this wire-safety check or even the smoothing point filter. A route containing two corners that quantize identically therefore falls back to substantially the same unsafe packed spline and can still trigger the client failure this branch is intended to avoid.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c73f891. MotionDriver now applies the packed-wire guard to query->Points() with the exact mover start before laying the fallback route, and reports the leg blocked when client reconstruction would collapse a segment. The isolated Release build and all 13 CTest entries pass.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up in 2c831ff replaces the blocked-leg response from c73f891. Unsafe routed fallbacks are now sanitized at the exact launch packing grid: only client-side duplicate vertices are removed, distinct navmesh corners and the destination remain, and the route is launched rather than retried forever.

@AppVeyorBot

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c73f891812

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// back through this general router. Apply the same packed-wire guard to
// that routed geometry so the fallback cannot recreate the collapsed
// client-side segment that was just rejected.
if (!IsWaypointSmoothingWireSafe(query->Points(), start))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Validate against the spline's computed relay position

When a routed leg is relaid while the previous spline is still running, such as after target drift or a speed change, start is taken from owner.Where(), but MoveSplineInit::Launch replaces the first control point with move_spline.ComputePosition() instead (MoveSplineInit.cpp:130-153). Passing the stale stored position here means the first reconstructed packed point can coincide with the actual computed launch position while differing from start, so this check accepts the initial zero-length client segment it is intended to reject.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2c831ff. MoveSplineInit now exposes the launch position through the same resolver used by Launch itself, including live-spline, vehicle-seat and pending-stop cases. MotionDriver sanitizes the route against that exact packing grid and removes only packed duplicate vertices. The full Release build and all 13 CTest entries pass.

// client reconstructs, including both uncompressed endpoint boundaries.
const Geometry::Vector3 midpoint = (launchPosition + points.back()) / 2.0f;
Geometry::Vector3 previous = launchPosition;
for (size_t i = 1; i + 1 < points.size(); ++i)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Start validation at the first point actually packed

SplineBase::InitLinear copies the controls starting at spline point 0, while PacketBuilder::WriteLinearPath sets real_path to spline point 1 and begins packing at real_path[1] (packet_builder.cpp:142-155), making points[2] the first reconstructed vertex; points[1] only contributes to the midpoint and is not itself sent as a path vertex. Starting this loop at index 1 invents a client-side vertex, so when decoded points[2] coincides with the launch position but decoded points[1] does not, the function accepts the actual zero-length initial segment; it can also reject safe paths based solely on the omitted point.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rechecked against the actual index mapping and retained source index 1. InitCatmullRom copies source path[0] to spline point 1; PacketBuilder sets real_path to spline point 1, so its real_path[1] (the first packed vertex) is source path[1]. Starting at source index 2 would skip the first transmitted packed point. Commit 2c831ff adds a dedicated quarter-yard regression for this mapping; the full Release build and all 13 CTest entries pass.

@AppVeyorBot

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2c831ff811

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// MoveSplineInit replaces the path's first routed point with this exact mover
// position before PacketBuilder derives the packed-path midpoint. Preserve it
// while the loop advances `start` through later waypoint legs.
const Motion::Vector3 launchPosition = frame.MoverPosition(creature);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Resolve the smoothed launch position through MoveSplineInit

When a waypoint-controlled creature is seated on a vehicle, frame.MoverPosition(creature) returns Where(), but MoveSplineInit::Launch replaces path[0] with the vehicle seat position via ResolveLaunchLocation. This validates the explicit smoothed path against a different midpoint/quantization grid from the one sent, so an adjacent packed pair can still collapse and trigger the client corruption this guard is intended to prevent. Although the new ResolveLaunchPosition() call fixes routed fallback paths, the explicit smoothing branch still bypasses it; obtain the launch position using the same resolver before both routing and IsWaypointSmoothingWireSafe.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in af80d7a. ResolveLaunchPosition is now a side-effect-free static selector shared by MoveSplineInit::Launch, MotionDriver routed fallbacks, and WaypointMovementGenerator explicit smoothing. Vehicle-seat, live-spline and pending-stop starts therefore use the same first wire point and packing grid before routing and validation. The full Release build and all 13 CTest entries pass.

@AppVeyorBot

Copy link
Copy Markdown

@MadMaxMangos
MadMaxMangos merged commit 8610263 into master Sep 2, 2026
9 checks passed
@MadMaxMangos
MadMaxMangos deleted the fix/cata-packed-waypoint-degenerate branch September 2, 2026 16:33
Meltie2013 pushed a commit to Prodigy-Project/MangosCataclysm that referenced this pull request Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants