Files
benilla-octo/crates/benilla/src/net/motion.rs
T
SamandClaude Fable 5 fd54ea9197 walkers keep walking, and dungeon entrances stop dropping you
- A creature that was already walking when it came into view stood frozen at
  its spawn point, then teleported — up to a full wander leg, 30 yards in the
  worst measured case — when its next movement packet arrived. The create
  packet carries the live path (where the unit is along it, how much is left)
  and we parsed all of it into the void. The unit now joins its walk exactly
  where the server has it: measured before/after, 13% of movement starts were
  over-a-yard teleports, now effectively zero.
- Walking into Zul'Gurub or Stratholme through the portal dropped you straight
  through the world. The landing check asks "is there a floor under me?" — but
  on the arrival frame the loaded world is still the map you just left, and an
  instance entrance is a copy of its outdoor ground at the same coordinates,
  0.23 yards under your feet. The old floor said yes, then despawned. The hold
  now waits for the destination's own ground to arrive, and a world that never
  arrives keeps the loading screen instead of dropping you into the void.
- Same fix covers a decoder hazard found on the way: a plain two-point path
  announces two points but ships only the destination, and trusting the count
  over-ran the packet — it never fires on our server's pathfinder, but now it
  can't.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 19:38:01 +07:00

75 lines
3.6 KiB
Rust

//! Per-frame motion model for streamed units: server splines + remote-player dead-reckoning.
//!
//! Two motion sources drive an entity's [`Transform`] between the sparse server packets (decisions 0052
//! + 0053):
//! - **[`Spline`]** — a server-authored path, sampled at constant speed by
//! [`spline::sample_splines`]. Creatures ride these. Two sources, one component: a fresh
//! `SMSG_MONSTER_MOVE` ([`spline::monster_move_spline`]), and the walk a unit was **already** on when
//! it streamed into view, joined mid-path from its create block ([`spline::create_spline`], 0708).
//! - **[`RemoteMotion`]** — another player's flag-driven locomotion, integrated by
//! [`remote::extrapolate_remote_units`]: the client's own dead-reckoning in miniature (extrapolate
//! from the last reported state, snap to the truth on the next packet). A jump is a **ballistic
//! event** — the launch played out locally under gravity — not flag-driven walking (decision 0053).
//!
//! The parent [`super`] module owns the wire→ECS bridge that seeds + corrects these from the packet
//! stream; this module owns the integration, split by concern: [`spline`] (the path walk + its
//! terrain re-ground), [`remote`] (dead-reckoning + jump ballistics), [`relay`] (***when*** a relayed
//! move replays — the per-unit fire-time chain), [`facing`] (wire facing
//! resolution + the client-local idle re-face and its [`FacingStep`] shuffle latch). This face keeps
//! the shared pose glue. Poses live canonically in raw WoW space on the components; the `Transform`
//! is derived (translation + facing only — scale, baked by the renderer, is preserved).
use benilla_assets::coords::wow_to_bevy;
use bevy::prelude::*;
mod facing;
mod relay;
mod remote;
mod spline;
#[cfg(test)]
mod tests;
pub(crate) use facing::FacingStep;
pub(super) use facing::{face_target, resolve_facing};
pub(crate) use relay::{PendingMove, RelayMove};
pub(crate) use remote::RemoteMotion;
pub(in crate::net) use remote::{apply_move, arrival_snap, trace_relay, RelayOutcome};
pub(super) use remote::{drain_pending_moves, extrapolate_remote_units};
pub(super) use spline::{
create_spline, ground_clamp_creatures, mark_swimming_creatures, monster_move_spline,
sample_splines, trace_create_spline, trace_move_snap,
};
pub(crate) use spline::{CreatureSwimming, Spline};
/// A full [`Transform`] for a freshly-spawned entity from a raw-WoW pose. WoW orientation (yaw about
/// +Z) maps straight to a Bevy +Y yaw under `wow_to_bevy` — no 180° offset (unlike doodad/WMO
/// *placement* rotation).
pub(super) fn pose_transform(position: [f32; 3], orientation: f32) -> Transform {
Transform {
translation: wow_to_bevy(position),
rotation: Quat::from_rotation_y(orientation),
..default()
}
}
/// Update an existing entity's translation + rotation from a raw-WoW pose, **preserving its scale**
/// (the renderer bakes per-display model scale into the transform; a move must not reset it). The
/// entity may not be query-visible the very frame it spawned — then the move is simply skipped (the
/// spawn pose already placed it, and the next move lands once commands flush).
pub(super) fn write_pose(
transforms: &mut Query<&mut Transform>,
e: Entity,
position: [f32; 3],
orientation: f32,
) {
if let Ok(mut t) = transforms.get_mut(e) {
t.translation = wow_to_bevy(position);
t.rotation = Quat::from_rotation_y(orientation);
}
}
/// Recover the WoW yaw from a unit [`Transform`] rotation (always built as [`Quat::from_rotation_y`]).
fn yaw_of(rotation: Quat) -> f32 {
rotation.to_euler(EulerRot::YXZ).0
}