fix(movement): reject server teleports to corrupted near-origin positions
Some checks failed
Build / Build (arm64) (push) Has been cancelled
Build / Build (x86-64) (push) Has been cancelled
Build / Build (macOS arm64) (push) Has been cancelled
Build / Build (windows-arm64) (push) Has been cancelled
Build / Build (windows-x86-64) (push) Has been cancelled
Security / CodeQL (C/C++) (push) Has been cancelled
Security / Semgrep (push) Has been cancelled
Security / Sanitizer Build (ASan/UBSan) (push) Has been cancelled

The server can persist a corrupted near-origin position on map 0 (from a
faulty area-trigger destination) across sessions. On re-login it sends the
bad position via LOGIN_VERIFY_WORLD; if the player walks into the offending
trigger again the server re-teleports there, and our heartbeats reinforce
the bad save — creating a permanent teleport loop.

Defenses added:
- handleTeleportAck rejects MSG_MOVE_TELEPORT to near-origin on map 0
  (no position update, no ACK, no world reload)
- applyPlayerTransportState rejects player UPDATE_OBJECT MOVEMENT blocks
  pushing the same bad position
- sendMovement blocks heartbeats originating from near-origin so the
  server cannot persist the bad save
- 10-second area-trigger cooldown after teleport / world entry / login
  (replaces the one-shot suppress flag that re-fired on jitter)
- Immediate STOP+HEARTBEAT after teleport ACK / WORLDPORT ACK / login
  to sync the real position with the server promptly
- CMSG_AREATRIGGER firing now logged at WARNING level for diagnosis
This commit is contained in:
Kelsi 2026-04-24 17:48:49 -07:00
parent f9f02569d6
commit d138269a35
5 changed files with 97 additions and 20 deletions

View file

@ -2332,6 +2332,7 @@ public:
auto& areaTriggerMsgsRef() { return areaTriggerMsgs_; }
auto& areaTriggersRef() { return areaTriggers_; }
auto& areaTriggerSuppressFirstRef() { return areaTriggerSuppressFirst_; }
auto& areaTriggerCooldownRef() { return areaTriggerCooldown_; }
// ── Death & Resurrection ─────────────────────────────────────────
auto& playerDeadRef() { return playerDead_; }
@ -2944,6 +2945,7 @@ private:
std::unordered_set<uint32_t> activeAreaTriggers_; // triggers player is currently inside
float areaTriggerCheckTimer_ = 0.0f;
bool areaTriggerSuppressFirst_ = false; // suppress first check after map transfer
float areaTriggerCooldown_ = 0.0f; // seconds remaining — suppress ALL triggers
std::array<ActionBarSlot, ACTION_BAR_SLOTS> actionBar{};
std::unordered_map<uint32_t, std::string> macros_; // client-side macro text (persisted in char config)