# Movement Owns the character, the movement component, input, the look model, the two camera modes, the gym level and the hooks other systems use to change how a body moves. It does **not** own abilities that move a body (dodge, blink, charge: [Combat.md](Combat.md), built on the root-motion hooks here) or picking things up ([Interaction.md](Interaction.md), which supplies the carry-weight penalty this document consumes). Read [Architecture.md](Architecture.md) first. This is the first system built and the one everything else stands on, which is why it gets three steps of its own in [`../Steps.md`](../Steps.md) before a single enemy exists. ## Why the controller comes first A crafting game with bad movement is a menu with a walk between screens. A combat game with bad movement is unfair before the first swing. Both earlier projects put movement in a corner of a combat step and tuned it by feel once, against one enemy; neither ever wrote down what "good" meant. This time the controller is the first thing built, it is built against a level made of nothing but movement problems, and it is not called done until a written checklist passes with a person at the keyboard. ## Decisions ``` [DECIDED] UCharacterMovementComponent, extended. Not a custom controller, not the Mover plugin. The engine's character movement is server-authoritative with client prediction and server correction built in, which is exactly the posture Architecture.md demands and the thing the earlier Unity project never had (its movement was client-authoritative, which does not survive contact with a real server). It integrates with root motion sources, which is how abilities move a body without a second movement system. It is mature, documented and what the engine's own animation tooling assumes. The Mover plugin is the engine's future answer and is still marked experimental. Q2 keeps it in view; the extension points used here (a saved-move flag, a tuning asset, root motion sources) are the ones Mover also exposes, so a later move is a port, not a rewrite. ``` ``` [DECIDED] One rig, two camera modes. The camera is presentation and never enters the authority path. First person is the engine's mannequin seen from a socket on its own head; third person is the same mannequin seen over its shoulder. There is no first-person arms rig, no second animation set. Every activity, prop and fight must be completable in both modes, and nothing about the camera is ever replicated, saved or used by the server to decide anything. Which mode is the default is Q1; both exist from step 3 so the answer can be played rather than argued. ``` ``` [DECIDED] Units are the engine's: centimetres, Z up, 1 uu = 1 cm. Every figure in this doc is in those. ``` ## Layout ``` Source/Core/Movement/ ├── LookModel.h / .cpp // pure: the deadzone-then-body-follows look model └── MovementTuning.h // the tuning asset class and its validation Source//Movement/ ├── BaseCharacter.h // ACharacter subclass shared by players and enemies: mesh, team, ASC access ├── PlayerCharacter.h // adds camera, input, interaction and carry components ├── ExtendedCharacterMovement.h // the CMC subclass: sprint flag, coyote time, jump buffer, speed multipliers ├── CameraModeDefinition.h // UPrimaryDataAsset per mode └── CameraModeComponent.h // owns the camera and spring arm, blends between modes, applies the look model Content/Movement/ ├── Definitions/DA_Tuning_Player, DA_CameraMode_FirstPerson, DA_CameraMode_ThirdPerson ├── Input/IMC_Gameplay, IA_* // one mapping context for play; a second (IMC_Menu) arrives with the menu ├── BP_PlayerCharacter // sets the mesh, animation blueprint, tuning and camera assets. No logic. ├── Surfaces/PM_Mud, PM_Ice // UPhysicalMaterialWithTags for the gym's surfaces (Stats.md), step 5 └── Maps/L_Gym // the movement test level ``` ## Types at a glance | Type | Module | Lifetime | Notes | | --- | --- | --- | --- | | `FLookModelParams`, `FLookModelState`, `LookModel::Tick` | Core | value | pure, tested without a world | | `UMovementTuning` | Core | asset | numbers, with `IsDataValid` checks | | `ABaseCharacter` | Gameplay | per body | `IAbilitySystemInterface`, `IGenericTeamAgentInterface` | | `APlayerCharacter` | Gameplay | per body | camera, input binding, interaction, carry | | `UExtendedCharacterMovement` | Gameplay | per body | the CMC subclass, predicted | | `UCameraModeDefinition` | Gameplay | asset | one per mode | | `UCameraModeComponent` | Gameplay | per player body | local only, never replicated | ## Input Enhanced Input. One mapping context, `IMC_Gameplay`, added at priority 0 in `APlayerCharacter::SetupPlayerInputComponent` through the local player's `UEnhancedInputLocalPlayerSubsystem`. Actions are assets under `Content/Movement/Input/` and are bound by the action asset, never by key, so a rebind is a settings change and not a code change. Rebinding itself uses the engine's `UEnhancedInputUserSettings` (player-mappable key settings on each action), which persists to the player's save folder; no custom rebinding code is written. The default layout. Keyboard bindings are the ones the earlier project settled on, with the one conflict resolved in Q4's favour: sprint takes Shift, the fourth ability slot moves. | Action | Keyboard and mouse | Gamepad | Value | Notes | | --- | --- | --- | --- | --- | | `IA_Move` | W A S D | Left stick | Axis2D | Relative to the body in first person, to the camera in third | | `IA_Look` | Mouse | Right stick | Axis2D | Mouse deltas are **not** scaled by delta time; stick rates are | | `IA_Jump` | Space | A | Bool | Buffered and coyote-timed, see below | | `IA_Sprint` | Left Shift (hold) | Left stick click | Bool | A predicted movement flag, not an ability | | `IA_Crouch` | Left Ctrl (toggle) | B (hold) | Bool | Engine crouch, capsule shrinks | | `IA_Dodge` | Left Alt | B (tap) | Bool | Activates the dodge ability once it exists, step 5 | | `IA_Attack` | Left mouse | Right trigger | Bool | Owned by Combat; bound here so the map is in one place | | `IA_Interact` | F | X | Bool | Owned by Interaction | | `IA_Drop` | G (tap drops, hold throws) | Y | Bool | Owned by Interaction | | `IA_Ability1..4` | Q, E, R, C | LB, RB, Y, LT | Bool | Owned by Combat, routed through input tags | | `IA_CameraToggle` | V | D-pad down | Bool | Swaps camera mode | | `IA_Menu` | Escape | Start | Bool | Owned by UI | Every action carries an `Input.*` gameplay tag in its player-mappable key settings so the ability system, the prompt and the action bar can ask "which key is `Input.Interact` on the device this player touched last" and get the live binding back. No view ever prints a literal key. ## The character and the movement component ```cpp // Source/Core/Movement/MovementTuning.h UCLASS(BlueprintType) class UMovementTuning : public UPrimaryDataAsset { GENERATED_BODY() public: // Speeds, cm/s. All guesses until the gym says otherwise. Walk is the old project's 4.5 m/s. UPROPERTY(EditDefaultsOnly, Category = "Speed") float WalkSpeed = 450.f; UPROPERTY(EditDefaultsOnly, Category = "Speed") float SprintSpeed = 650.f; UPROPERTY(EditDefaultsOnly, Category = "Speed") float CrouchSpeed = 250.f; UPROPERTY(EditDefaultsOnly, Category = "Speed") float MaxAcceleration = 2048.f; UPROPERTY(EditDefaultsOnly, Category = "Speed") float BrakingDeceleration = 2048.f; UPROPERTY(EditDefaultsOnly, Category = "Speed") float AirControl = 0.35f; // Jump. Apex ≈ JumpZ² / (2 · 980 · GravityScale): 560 at 1.5 gravity is about 106 cm, the old 1.1 m jump. UPROPERTY(EditDefaultsOnly, Category = "Jump") float JumpZVelocity = 560.f; UPROPERTY(EditDefaultsOnly, Category = "Jump") float GravityScale = 1.5f; UPROPERTY(EditDefaultsOnly, Category = "Jump") float CoyoteTime = 0.10f; // seconds after leaving a ledge a jump still counts UPROPERTY(EditDefaultsOnly, Category = "Jump") float JumpBufferTime = 0.12f; // seconds before landing a press is remembered // Ground. The engine defaults are right for stairs up to 45 cm and slopes to 45 degrees; listed so they are tuned here. UPROPERTY(EditDefaultsOnly, Category = "Ground") float MaxStepHeight = 45.f; UPROPERTY(EditDefaultsOnly, Category = "Ground") float WalkableFloorAngle = 45.f; // Landing. A drop taller than this costs a brief recovery; taller than the second, fall damage (Combat.md). UPROPERTY(EditDefaultsOnly, Category = "Landing") float HardLandingHeight = 300.f; UPROPERTY(EditDefaultsOnly, Category = "Landing") float HardLandingRecovery = 0.2f; #if WITH_EDITOR virtual EDataValidationResult IsDataValid(FDataValidationContext& Context) const override; // SprintSpeed > WalkSpeed > CrouchSpeed > 0; CoyoteTime and JumpBufferTime under 0.3 s; angles in (0, 90). #endif }; ``` ```cpp // Source//Movement/ExtendedCharacterMovement.h /** * The engine's character movement plus the four things every feel pass ends up adding: a sprint flag that * predicts correctly, coyote time, a jump buffer, and the ground surface trace that turns mud into an effect. * * Sprint is a compressed flag in the saved move, which is the engine's mechanism for predicted input state. * It is deliberately NOT a gameplay ability: an ability round-trips through the ability system for something the * movement component already replicates for free. Dodge and blink ARE abilities, because they apply root motion. */ UCLASS() class UExtendedCharacterMovement : public UCharacterMovementComponent { GENERATED_BODY() public: void ApplyTuning(const UMovementTuning& Tuning); // called by the character on BeginPlay and on tuning change // Input state, set by the owning character, carried in the saved move void SetWantsToSprint(bool bWants); void PressJumpBuffered(); // remembers a press for JumpBufferTime // Speed is the state's tuning value times the body's MoveSpeed attribute (Stats.md), and nothing else. Carrying, // being downed, mud, a haste and gear all change that one attribute through effects; this component never // holds a multiplier of its own. Before the stat block exists (steps 3 and 4) the attribute reads as one. float GetMoveSpeedAttribute() const; // 1.0 when the owner has no ability system component yet void UpdateGroundSurface(); // server, ~5 Hz: the floor's UPhysicalMaterialWithTags -> its surface effect // UCharacterMovementComponent virtual float GetMaxSpeed() const override; // walk/sprint/crouch by state, times MoveSpeed virtual bool CanAttemptJump() const override; // grounded, OR within CoyoteTime of leaving the ground virtual void UpdateFromCompressedFlags(uint8 Flags) override; virtual FNetworkPredictionData_Client* GetPredictionData_Client() const override; virtual void OnMovementModeChanged(EMovementMode PrevMode, uint8 PrevCustomMode) override; // starts the coyote clock protected: bool bWantsToSprint = false; float TimeLeftGround = -1.f; float JumpBufferedAt = -1.f; TWeakObjectPtr CurrentSurface; FActiveGameplayEffectHandle SurfaceEffect; // FSavedMove_Character subclass carrying bWantsToSprint in FLAG_Custom_0; FNetworkPredictionData_Client_Character // subclass allocating it. Standard engine pattern; see the engine's own ACharacter crouch flag for the shape. }; ``` ```cpp // Source//Movement/PlayerCharacter.h /** * The player's body. Owner-only concerns (camera, input, interaction, carrying) live here as components; * everything shared with enemies is on ABaseCharacter. The character decides nothing: it reads input, hands it to * the movement component and the ability system, and lets the camera component look. */ UCLASS() class APlayerCharacter : public ABaseCharacter { GENERATED_BODY() public: APlayerCharacter(const FObjectInitializer& OI); protected: UPROPERTY(EditDefaultsOnly, Category = "Movement") TObjectPtr Tuning; UPROPERTY(EditDefaultsOnly, Category = "Input") TObjectPtr GameplayContext; UPROPERTY(VisibleAnywhere) TObjectPtr CameraMode; UPROPERTY(VisibleAnywhere) TObjectPtr Interaction; // Interaction.md UPROPERTY(VisibleAnywhere) TObjectPtr Carry; // Interaction.md // Replicated so remote players can see where this one is looking. RemoteViewPitch is the engine's; yaw is ours. UPROPERTY(Replicated) uint8 RemoteHeadYaw; // HeadYaw compressed to a byte, written by the owner every tick virtual void SetupPlayerInputComponent(UInputComponent* Input) override; // binds IA_* to the handlers below void OnMove(const FInputActionValue& V); // AddMovementInput relative to body (FP) or camera (TP) void OnLook(const FInputActionValue& V); // feeds CameraMode->AddLookInput void OnJumpPressed(); void OnJumpReleased(); // Jump() plus the buffer void OnSprint(const FInputActionValue& V); // Movement->SetWantsToSprint void OnCameraToggle(); // CameraMode->CycleMode() // Attack, Interact, Drop and Ability1..4 forward to Combat and Interaction; they decide nothing here. virtual void Landed(const FHitResult& Hit) override; // hard-landing recovery, fall damage event, telemetry }; ``` `ABaseCharacter` sets `UExtendedCharacterMovement` as the movement class (so an enemy is slowed by the same mud a player is), sets the capsule to the mannequin's 42 cm radius and 96 cm half-height, implements `IAbilitySystemInterface` (returning the player state's component for players, its own for enemies) and `IGenericTeamAgentInterface`, and carries the `USkeletalMeshComponent` and animation blueprint reference. It has no input. ## The look model ``` [SALVAGED] The head turns freely inside a yaw deadzone; past it, the body eases around to follow. Inside the deadzone the body does not move at all, and that "nothing" is the whole feel: you can glance at a teammate or the thing on the bench beside you without stepping out of position. The deadzone widens while carrying something big, so you can peek round your own load. ``` ```cpp // Source/Core/Movement/LookModel.h // pure, no UObject, tested struct FLookModelParams { float YawDeadzone = 45.f; // degrees either side before the body turns float YawDeadzoneCarry = 70.f; // while carrying a two-handed object float MaxTurnRate = 540.f; // degrees per second the body may turn to catch up float MovingDeadzoneScale = 0.35f; // smaller while walking: you face where you go float ReCenterRate = 90.f; // degrees per second the head drifts forward while moving float PitchMin = -80.f, PitchMax = 70.f; float SpinePitchShare = 0.5f; // how much of the pitch bends the spine, for the visible body }; struct FLookModelState { float HeadYaw = 0.f; float BodyYaw = 0.f; float Pitch = 0.f; }; struct FLookModelInput { FVector2D LookDelta; bool bMoving; bool bStrafing; bool bCarryingTwoHanded; float DeltaSeconds; }; namespace LookModel { // pure. Returns the new state; the caller writes BodyYaw to the actor and BodyYaw + HeadYaw to the camera. FLookModelState Tick(const FLookModelState& State, const FLookModelParams& Params, const FLookModelInput& In); // HeadYaw += LookDelta.X // deadzone = (carrying ? YawDeadzoneCarry : YawDeadzone) * (moving ? MovingDeadzoneScale : 1) // if |HeadYaw| > deadzone: turn = min(|HeadYaw| - deadzone, MaxTurnRate * dt) * sign; BodyYaw += turn; HeadYaw -= turn // if moving && !strafing: HeadYaw = MoveTowards(HeadYaw, 0, ReCenterRate * dt) // Pitch = clamp(Pitch + LookDelta.Y, PitchMin, PitchMax) } ``` How it meets the engine: `bUseControllerRotationYaw` is off. In **first person** the character's actor yaw is `BodyYaw`, the camera yaw is `BodyYaw + HeadYaw`, and movement input is relative to the body, so you walk where you face and look elsewhere. In **third person** the camera is free (spring arm on control rotation), `bOrientRotationToMovement` is on so the body faces where it walks, and the model drives only the head and spine aim in the animation blueprint so teammates can see where you are looking. One implementation, both modes; `Pitch` reaches the rig through the engine's replicated `RemoteViewPitch`, `HeadYaw` through `RemoteHeadYaw`. ## Camera modes ```cpp UCLASS(BlueprintType) class UCameraModeDefinition : public UPrimaryDataAsset { GENERATED_BODY() public: UPROPERTY(EditDefaultsOnly) FGameplayTag ModeTag; // Camera.Mode.FirstPerson / .ThirdPerson UPROPERTY(EditDefaultsOnly) float FieldOfView = 90.f; UPROPERTY(EditDefaultsOnly) bool bFirstPerson = false; // third person UPROPERTY(EditDefaultsOnly) float ArmLength = 300.f; UPROPERTY(EditDefaultsOnly) FVector SocketOffset = FVector(0, 60, 40); // over the right shoulder // first person UPROPERTY(EditDefaultsOnly) FName HeadSocket = TEXT("head"); UPROPERTY(EditDefaultsOnly) FVector EyeOffset = FVector(10, 0, 0); UPROPERTY(EditDefaultsOnly) float HeadStabilization = 20.f; // damping stiffness; 0 = rigidly on the bone (do not ship that) UPROPERTY(EditDefaultsOnly) bool bHideLocalHead = true; UPROPERTY(EditDefaultsOnly) FLookModelParams Look; UPROPERTY(EditDefaultsOnly) float BlendSeconds = 0.25f; }; /** Owns the camera and spring arm, blends between definitions, runs the look model. Local player only. */ UCLASS() class UCameraModeComponent : public UActorComponent { GENERATED_BODY() public: void SetMode(FGameplayTag ModeTag); void CycleMode(); void AddLookInput(FVector2D Delta); FRotator GetCameraRotation() const; // BodyYaw + HeadYaw, Pitch // TickComponent: run LookModel::Tick, write BodyYaw to the owner, position the camera: // FP: camera location = damped follow of Mesh->GetSocketLocation(HeadSocket) + EyeOffset, in LateUpdate order // (tick group PostUpdateWork) so it runs after animation; rotation from the look model, NEVER from the bone. // Mesh->HideBoneByName(head) on the locally controlled pawn only; other clients see the whole body. // TP: spring arm with collision test on, length and socket offset from the definition, camera on control rotation. protected: UPROPERTY(EditDefaultsOnly) TArray> Modes; UPROPERTY(VisibleAnywhere) TObjectPtr SpringArm; UPROPERTY(VisibleAnywhere) TObjectPtr Camera; FLookModelState Look; }; ``` The stabilised head socket is the make-or-break piece. Rigid parenting to the bone turns every walk-cycle bob into camera shake, which the earlier project found out the hard way and fixed twice. The camera follows the socket's position through a damped spring and takes its rotation from the look model only. An optional procedural bob (off by default, scaled by the motion accessibility setting) is the only bob there is. Comfort settings are launch requirements for a first-person mode, not options: stabilisation strength, FOV per mode, bob toggle, and the motion scale that also governs camera kicks in combat. ## Networking | State | Authority | Mechanism | | --- | --- | --- | | Position, velocity, movement mode | Server, client predicts | Character movement's own prediction and correction | | Sprint, crouch | Server, client predicts | Compressed flags in the saved move | | Jump | Server, client predicts | Engine jump plus the buffered press, resolved in the predicted move | | Look pitch | Owner writes | `RemoteViewPitch`, engine built-in, for the rig only | | Head yaw | Owner writes | `RemoteHeadYaw` byte, for the rig only | | Camera mode, FOV, stabilisation | Local only | Never replicated | | `MoveSpeed`, `JumpPower` | Server | Attributes on the stat block, replicated; a server-applied change costs one small correction on the owner, see [Stats.md](Stats.md) | Remote bodies interpolate through the movement component's network smoothing (exponential). Test every step of this document with the editor's network emulation profile set to 100 ms and 5 % loss, and `p.NetShowCorrections 1` on; a correction you can see at those settings is a bug in the saved move. ## The gym `L_Gym` is a level made only of movement problems, greyboxed from engine primitives with a material per problem kind. It is the level every movement step is proved in, and it stays in the project forever as the regression test for the controller. | Section | What it holds | | --- | --- | | Stairs | Risers of 15, 20, 30 and 45 cm, straight and spiral | | Slopes | 15, 30, 45 and 60 degrees, up and down; the last must not be walkable | | Gaps | 150, 200, 250 and 300 cm, flat; 250 makeable at sprint only | | Ledges | Drops of 100, 200, 300 and 500 cm onto flat ground | | Doorways | 110 by 220 cm, and a 90 by 200 cm one that a crouch fits | | Beams | 30 cm wide walkways over a drop | | Corridor | A 40 m straight for speed and stop-distance measurement, marked every 5 m | | Surfaces | Patches of mud and ice on the corridor's second half, on tagged physical materials; inert until step 5 | | Arena | An open 30 by 30 m circle for the combat steps later | ## The feel checklist Step 4 closes when a person at the keyboard ticks every line, in both camera modes, and the numbers that made it pass are committed in `DA_Tuning_Player`. - Stairs of every riser at walk and sprint: no camera stutter, no snag, no bounce at the top. - Slopes: walkable to 45 degrees, slides off 60; speed on a 30 degree climb reads as effort, not a wall. - Gaps: 150 and 200 at walk, 250 at sprint, 300 never. The 250 is the one that teaches sprint. - Coyote time: stepping off a ledge and pressing jump within a tenth of a second still jumps. Buffer: pressing jump just before landing jumps on landing. - Landing from 100 and 200 cm: nothing. From 300: a visible knee-bend and a fifth of a second of no input. From 500: fall damage (once Combat exists) and the same recovery. - Stop distance from sprint under 150 cm; from walk under 60 cm. Turning at sprint has a radius, not a pivot. - Air control is enough to correct a jump onto the beam, not enough to reverse mid-air. - Doorways: the wide one at sprint without touching, the narrow one only crouched. - First person: a five-minute walk of the whole gym without discomfort with stabilisation at its default. Looking down shows your own feet. The deadzone lets you glance at a wall sign without turning. - Third person: the spring arm never clips through a wall; the body faces where it walks; the head turns to follow the look. - Two clients and a dedicated server in editor, emulation at 100 ms and 5 %: the remote body is smooth on stairs, slopes and jumps, and no correction snap is visible on the local one. ## Hooks for other systems - **`MoveSpeed` and `JumpPower`** on the stat block ([Stats.md](Stats.md)) are the only way anything slows or speeds a body: carrying, being downed, mud, gear and every buff or debuff are effects on those two attributes, and the movement component multiplies. It never holds a multiplier of its own. - **Root motion sources** are how abilities move a body. Dodge is a `FRootMotionSource_ConstantForce` over its duration with i-frames granted as a `State.Invulnerable` tag; blink is a `MoveToForce` along the aim, flattened; the shoulder charge is a constant force with a hit window. All three predict through the movement component's existing root-motion prediction and are specified in [Combat.md](Combat.md). - **Downed** sets the crouch capsule, overrides `MoveSpeed` to zero through `GE_Downed` and disables jump; the camera drops to the downed eye height through the camera component, which reads the `State.Downed` tag. - **Impulses** (a shove, a heal-launch) call `LaunchCharacter`, which the movement component already replicates. ## Telemetry | Event | When | Payload | | --- | --- | --- | | `movement_sample` | Every 5 s while moving | `speed`, `mode`, `camera_mode`, `sprinting` | | `jump` | A jump begins | `coyote` (bool), `buffered` (bool) | | `land` | `Landed` | `fall_height`, `hard` (bool) | | `camera_mode_changed` | Mode swap | `from`, `to` | | `settings_changed` | A comfort or binding setting is committed | `setting_id`, `value` | `movement_sample` is what later says which camera mode people actually live in and how much of the gym's speed range is used, which is what decides Q1 and the sprint tuning with data instead of taste. ## Tests - `LookModel` automation: inside the deadzone the body yaw does not change; an overshoot turns the body by the overshoot and never faster than `MaxTurnRate`; moving without strafing recenters the head; carrying widens the deadzone; pitch clamps. Five tests, no world. - `UMovementTuning::IsDataValid` refuses sprint slower than walk, negative times, and angles outside (0, 90). - Functional: `FT_Gym_Stairs` drives an `APlayerCharacter` up each riser with `AddMovementInput` and asserts it reaches the top; `FT_Gym_Slopes` asserts the 60 degree slope is not climbable; `FT_Gym_Gaps` asserts the 300 cm gap is not crossable at sprint. ## Open questions - **Q1. Which camera mode is the default?** Both earlier projects chose first person: it is where close manual work reads best and it is cheapest to make feel good with one rig. The case for third person is seeing your own crafted gear and a better read of a crowded fight. Build both in step 3, play the gym and the first fight in each, and let `camera_mode_changed` and `movement_sample` settle it by step 8. - **Q2. The Mover plugin.** Revisit when it leaves experimental. The port cost is bounded by keeping the custom surface to the saved-move flag, the tuning asset and root-motion sources. - **Q3. Does sprint cost stamina?** Not in step 3. If combat wants a stamina attribute, sprint may draw from it through the ability system's attribute, but the flag itself stays in the movement component. - **Q4. The Shift conflict.** The earlier layout put the third ability slot on Shift because that game had no sprint. This one does. Decided for now: Shift sprints, the slots are Q, E, R, C. Revisit if the fourth slot is unreachable in a fight. - **Q5. Head bob.** Off by default, one amplitude setting, scaled by the motion setting. Whether it earns a place in first person is a playtest question after step 4. - **Q6. Mantling and vaulting.** Not now. The gym has no ledge you are meant to climb. If the world later wants it, it is a movement ability on root motion, not a change to the component.