tengri/Content/Movement/TDD.md

41 KiB
Raw Blame History

Movement System - Technical Documentation (Stage 9 Refactored)

Обзор

Детерминированная система движения для 3D-платформера, построенная на принципе Functional Core, Imperative Shell. Система обеспечивает математически предсказуемое поведение через композицию чистых функциональных модулей, с точной классификацией поверхностей, swept collision detection и ground snapping.

Версия: Stage 9 (Post-Refactoring)
Архитектурный паттерн: Pipeline Processor + Pure Function Libraries
Статус: Production Ready


Архитектурные принципы

Core Design Philosophy

  1. Functional Core, Imperative Shell

    • Pure business logic в BFL_* модулях
    • Imperative framework integration в AC_Movement
  2. Immutable State Transformation

    • S_MovementState → ProcessMovement() → S_MovementState
    • Никогда не мутируем входящее состояние
  3. Pipeline Processing

    • Четкие последовательные фазы обработки
    • Каждая фаза читает результаты предыдущей
  4. Separation of Concerns

    • AC_Movement: координация и framework integration
    • BFL_MovementProcessor: бизнес-логика движения
    • BFL_*: специализированные подсистемы
  5. Data-Oriented Design

    • Явные структуры данных (S_MovementState, S_MovementInput)
    • Функции оперируют данными, не прячут их

Компоненты системы

1. AC_Movement (Component - Imperative Shell)

Роль: Thin orchestration layer между UE Actor system и функциональной логикой

Ответственности:

  • Lifecycle management (инициализация, cleanup)
  • Component references (CapsuleComponent, DebugHUD)
  • State storage (CurrentMovementState)
  • Framework integration (SetActorLocation, SetActorRotation)
  • Debug visualization coordination

Ключевые методы:

// Инициализация системы
InitializeMovementSystem(
  CapsuleComponentRef: CapsuleComponent | null,
  DebugHUDComponentRef: AC_DebugHUD | null
): void

// Обработка движения (главная точка входа)
ProcessMovementInput(
  InputVector: Vector,
  DeltaTime: Float
): void

// Debug
UpdateDebugPage(): void

Приватные поля:

CurrentMovementState: S_MovementState      // Текущее состояние
Config: DA_MovementConfig                  // Конфигурация
AngleThresholdsRads: S_AngleThresholds     // Кэшированные пороги в радианах
CapsuleComponent: CapsuleComponent | null  // Ссылка на капсулу
DebugHUDComponent: AC_DebugHUD | null      // Ссылка на debug HUD
IsInitialized: boolean                     // Флаг инициализации

Размер: ~230 LOC (↓ 62% от original)


2. BFL_MovementProcessor (Core - Functional Heart)

Роль: Unified movement processing pipeline - центральная точка всей логики движения

Ответственности:

  • Orchestration всех подсистем в правильном порядке
  • State transformation (CurrentState + Input → NextState)
  • Phase sequencing и data flow
  • Integration между subsystems

Главный метод:

ProcessMovement(
  CurrentState: S_MovementState,
  Input: S_MovementInput,
  IsShowVisualDebug: boolean = false
): S_MovementState

Processing Pipeline (6 фаз):

// PHASE 1: INPUT & ROTATION
├─ Calculate input magnitude
└─ Update character rotation (BFL_RotationController)

// PHASE 2: GROUND DETECTION  
├─ Check ground with trace (BFL_GroundProbe)
├─ Determine IsGrounded
└─ Classify surface type (BFL_SurfaceClassifier)

// PHASE 3: PHYSICS CALCULATION
├─ Calculate ground velocity (BFL_Kinematics) [if grounded]
  OR Apply air friction (BFL_Kinematics) [if airborne]
├─ Apply gravity (BFL_Kinematics)
└─ Calculate horizontal speed

// PHASE 4: MOVEMENT APPLICATION (Sweep)
├─ Convert velocity to delta
├─ Perform swept collision (BFL_CollisionResolver)
└─ Calculate slide vector if blocked

// PHASE 5: GROUND SNAPPING
└─ Snap to ground if conditions met (BFL_GroundProbe)

// PHASE 6: STATE DETERMINATION
└─ Determine movement state (BFL_MovementStateMachine)

// RETURN: Complete new S_MovementState

Вспомогательные методы:

CreateInitialState(
  Location: Vector,
  Rotation: Rotator
): S_MovementState

Purity: Impure (из-за collision traces), но deterministic
Размер: ~260 LOC


3. BFL_Kinematics (Physics Library)

Роль: Pure physics calculations для движения

Ключевые методы:

// Ground movement с acceleration
CalculateGroundVelocity(
  CurrentVelocity: Vector,
  InputVector: Vector,
  DeltaTime: Float,
  Config: DA_MovementConfig
): Vector

// Friction (deceleration)
CalculateFriction(
  CurrentVelocity: Vector,
  DeltaTime: Float,
  Config: DA_MovementConfig
): Vector

// Gravity application
CalculateGravity(
  CurrentVelocity: Vector,
  IsGrounded: boolean,
  Config: DA_MovementConfig
): Vector

// Horizontal speed query
GetHorizontalSpeed(Velocity: Vector): Float

Характеристики:

  • Pure functions
  • VInterpTo для smooth movement
  • Frame-rate independent (uses DeltaTime)

4. BFL_CollisionResolver (Collision Library)

Роль: Swept collision detection и surface sliding

Ключевые методы:

// Главный swept trace
PerformSweep(
  StartLocation: Vector,
  DesiredDelta: Vector,
  CapsuleComponent: CapsuleComponent | null,
  Config: DA_MovementConfig,
  DeltaTime: Float,
  IsShowVisualDebug: boolean
): S_SweepResult

// Surface sliding projection
ProjectOntoSurface(
  MovementDelta: Vector,
  SurfaceNormal: Vector
): Vector

// Slide vector calculation
CalculateSlideVector(
  SweepResult: S_SweepResult,
  OriginalDelta: Vector,
  StartLocation: Vector
): Vector

// Adaptive step size для swept trace
CalculateStepSize(
  Velocity: Vector,
  DeltaTime: Float,
  Config: DA_MovementConfig
): Float

Характеристики:

  • ⚠️ Impure (world traces)
  • Deterministic stepping
  • Tunneling protection
  • Adaptive precision

5. BFL_GroundProbe (Ground Detection Library)

Роль: Ground detection, snapping, surface queries

Ключевые методы:

// Ground detection trace
CheckGround(
  CharacterLocation: Vector,
  CapsuleComponent: CapsuleComponent | null,
  AngleThresholdsRads: S_AngleThresholds,
  Config: DA_MovementConfig,
  IsShowVisualDebug: boolean
): HitResult

// Ground snapping calculation
CalculateSnapLocation(
  CurrentLocation: Vector,
  GroundHit: HitResult,
  CapsuleComponent: CapsuleComponent | null,
  SnapThreshold: Float
): Vector

// Snapping condition check
ShouldSnapToGround(
  CurrentVelocityZ: Float,
  GroundHit: HitResult,
  IsGrounded: boolean
): boolean

// Surface type query
GetSurfaceType(
  GroundHit: HitResult,
  AngleThresholdsRads: S_AngleThresholds
): E_SurfaceType

Характеристики:

  • ⚠️ Impure (LineTraceByChannel)
  • Separate snapping logic
  • Clear condition checking

6. BFL_RotationController (Rotation Library)

Роль: Character rotation toward movement direction

Ключевые методы:

// Calculate target yaw from direction
CalculateTargetYaw(MovementDirection: Vector): Float

// Calculate full target rotation
CalculateTargetRotation(MovementDirection: Vector): Rotator

// Smooth rotation interpolation
InterpolateRotation(
  CurrentRotation: Rotator,
  TargetRotation: Rotator,
  RotationSpeed: Float,
  DeltaTime: Float,
  MinSpeedForRotation: Float,
  CurrentSpeed: Float
): S_RotationResult

// Convenience method
UpdateRotation(
  CurrentRotation: Rotator,
  MovementDirection: Vector,
  Config: DA_MovementConfig,
  DeltaTime: Float,
  CurrentSpeed: Float
): S_RotationResult

Характеристики:

  • Pure functions
  • Wraparound handling (180°/-180°)
  • Min speed threshold

7. BFL_MovementStateMachine (State Machine)

Роль: Determine movement state from context

Ключевые методы:

// Main state determination
DetermineState(Context: S_MovementContext): E_MovementState

// Internal helpers
private DetermineAirborneState(Context: S_MovementContext): E_MovementState
private DetermineGroundedState(Context: S_MovementContext): E_MovementState

State Priority Logic:

1. IsGrounded?
   ├─ Yes: Check surface type
   │   ├─ SteepSlope → Sliding
   │   ├─ Wall/Ceiling → Blocked
   │   └─ Walkable → Check input
   │       ├─ Has input & speed > 1.0 → Walking
   │       └─ Else → Idle
   └─ No → Airborne

Характеристики:

  • Pure FSM logic
  • Priority-based transitions
  • Clear state rules

8. BFL_SurfaceClassifier (Surface Classification)

Роль: Classify surface based on normal angle

Ключевые методы:

// Main classification
Classify(
  SurfaceNormal: Vector,
  AngleThresholdsRads: S_AngleThresholds
): E_SurfaceType

// Type checking helpers
IsWalkable(surfaceType: E_SurfaceType): boolean
IsSteep(surfaceType: E_SurfaceType): boolean
IsWall(surfaceType: E_SurfaceType): boolean
IsCeiling(surfaceType: E_SurfaceType): boolean
IsNone(surfaceType: E_SurfaceType): boolean

Classification Rules:

Surface Angle → Type
─────────────────────
≤ Walkable      → Walkable    (0°-50°)
≤ SteepSlope    → SteepSlope  (50°-85°)
≤ Wall          → Wall        (85°-95°)
> Wall          → Ceiling     (95°-180°)

Характеристики:

  • Pure functions
  • Angle-based classification
  • Type-safe queries

Структуры данных

S_MovementState (Complete State Snapshot)

Роль: Immutable snapshot всего состояния движения

interface S_MovementState {
  // ═══════════════════════════════════════════════════════
  // TRANSFORM
  // ═══════════════════════════════════════════════════════
  Location: Vector          // World location
  Rotation: Rotator         // Yaw rotation

  // ═══════════════════════════════════════════════════════
  // VELOCITY & PHYSICS
  // ═══════════════════════════════════════════════════════
  Velocity: Vector          // Current velocity (cm/s)
  Speed: Float              // Horizontal speed (cm/s)

  // ═══════════════════════════════════════════════════════
  // GROUND STATE
  // ═══════════════════════════════════════════════════════
  IsGrounded: boolean       // On walkable ground?
  GroundHit: HitResult      // Ground trace result
  SurfaceType: E_SurfaceType // Current surface classification

  // ═══════════════════════════════════════════════════════
  // COLLISION STATE
  // ═══════════════════════════════════════════════════════
  IsBlocked: boolean        // Blocked by collision?
  CollisionCount: number    // Collision checks this frame

  // ═══════════════════════════════════════════════════════
  // ROTATION STATE
  // ═══════════════════════════════════════════════════════
  IsRotating: boolean       // Currently rotating?
  RotationDelta: Float      // Remaining angular distance (degrees)

  // ═══════════════════════════════════════════════════════
  // MOVEMENT STATE
  // ═══════════════════════════════════════════════════════
  MovementState: E_MovementState  // Current FSM state
  InputMagnitude: Float           // Input magnitude (0-1)
}

Usage Pattern:

// Immutable transformation
const newState = BFL_MovementProcessor.ProcessMovement(
  currentState,  // Never modified
  input,
  debugFlag
);

// Apply to actor
this.GetOwner().SetActorLocation(newState.Location);
this.GetOwner().SetActorRotation(newState.Rotation);

// Store for next frame
this.CurrentMovementState = newState;

S_MovementInput (Input Encapsulation)

Роль: All data needed для movement processing

interface S_MovementInput {
  InputVector: Vector                  // Player input (normalized XY)
  DeltaTime: Float                     // Frame delta time (seconds)
  CapsuleComponent: CapsuleComponent | null  // Collision capsule
  Config: DA_MovementConfig           // Movement config
  AngleThresholdsRads: S_AngleThresholds    // Surface thresholds (radians)
}

Usage:

const input: S_MovementInput = {
  InputVector: playerInput,
  DeltaTime: deltaTime,
  CapsuleComponent: this.CapsuleComponent,
  Config: this.Config,
  AngleThresholdsRads: this.AngleThresholdsRads
};

const newState = BFL_MovementProcessor.ProcessMovement(
  this.CurrentMovementState,
  input,
  this.DebugHUDComponent?.ShowVisualDebug ?? false
);

S_MovementContext (State Machine Input)

Роль: Context для state determination

interface S_MovementContext {
  IsGrounded: boolean         // On walkable ground?
  SurfaceType: E_SurfaceType  // Surface classification
  InputMagnitude: Float       // Input strength (0-1)
  CurrentSpeed: Float         // Horizontal speed (cm/s)
  VerticalVelocity: Float     // Z velocity (cm/s)
  IsBlocked: boolean          // Blocked by collision?
}

DA_MovementConfig (Configuration Asset)

Роль: Centralized movement constants

class DA_MovementConfig extends PrimaryDataAsset {
  // ═══════════════════════════════════════════════════════
  // MOVEMENT PHYSICS
  // ═══════════════════════════════════════════════════════
  readonly MaxSpeed: Float = 800.0           // Max horizontal speed (cm/s)
  readonly Acceleration: Float = 10.0        // VInterpTo acceleration rate
  readonly Friction: Float = 8.0             // VInterpTo friction rate
  readonly Gravity: Float = 980.0            // Gravity (cm/s²)

  // ═══════════════════════════════════════════════════════
  // SURFACE DETECTION
  // ═══════════════════════════════════════════════════════
  readonly AngleThresholdsDegrees: S_AngleThresholds = {
    Walkable: 50.0,      // ≤50° = walkable
    SteepSlope: 85.0,    // ≤85° = steep slope
    Wall: 95.0           // ≤95° = wall
  }

  // ═══════════════════════════════════════════════════════
  // COLLISION SETTINGS
  // ═══════════════════════════════════════════════════════
  readonly GroundTraceDistance: Float = 50.0  // Ground detection distance
  readonly MinStepSize: Float = 1.0           // Min sweep step size
  readonly MaxStepSize: Float = 50.0          // Max sweep step size
  readonly MaxCollisionChecks: Float = 25     // Max checks per frame

  // ═══════════════════════════════════════════════════════
  // CHARACTER ROTATION
  // ═══════════════════════════════════════════════════════
  RotationSpeed: Float = 360.0               // Rotation speed (deg/s)
  MinSpeedForRotation: Float = 50.0          // Min speed to rotate
  ShouldRotateToMovement: boolean = true     // Enable rotation
}

Enums

// Movement FSM states
enum E_MovementState {
  Idle = 'Idle',               // Stationary on ground
  Walking = 'Walking',         // Moving on ground
  Airborne = 'Airborne',       // In the air
  Sliding = 'Sliding',         // Sliding on steep slope
  Blocked = 'Blocked'          // Blocked by collision
}

// Surface classification
enum E_SurfaceType {
  None = 'None',               // No ground contact
  Walkable = 'Walkable',       // Normal walking ≤50°
  SteepSlope = 'SteepSlope',   // Sliding 50°-85°
  Wall = 'Wall',               // Collision 85°-95°
  Ceiling = 'Ceiling'          // Overhead >95°
}

Data Flow Diagram

┌──────────────────────────────────────────────────────────────┐
│                      AC_Movement                             │
│                 (Imperative Shell)                           │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│  ProcessMovementInput(InputVector, DeltaTime)                │
│                           │                                  │
│                           ▼                                  │
│              ┌────────────────────────┐                      │
│              │ Prepare S_MovementInput│                      │
│              └────────────────────────┘                      │
│                           │                                  │
│                           ▼                                  │
│         ┌─────────────────────────────────────┐              │
│         │   BFL_MovementProcessor             │              │
│         │   .ProcessMovement()                │              │
│         │                                     │              │
│         │   ┌─────────────────────────────┐   │              │
│         │   │ PHASE 1: Input & Rotation   │   │              │
│         │   │ • Calculate input magnitude │   │              │
│         │   │ • BFL_RotationController    │   │              │
│         │   └─────────────────────────────┘   │              │
│         │             │                       │              │
│         │   ┌─────────▼───────────────────┐   │              │
│         │   │ PHASE 2: Ground Detection   │   │              │
│         │   │ • BFL_GroundProbe           │   │              │
│         │   │ • BFL_SurfaceClassifier     │   │              │
│         │   └─────────────────────────────┘   │              │
│         │             │                       │              │
│         │   ┌─────────▼───────────────────┐   │              │
│         │   │ PHASE 3: Physics            │   │              │
│         │   │ • BFL_Kinematics            │   │              │
│         │   │   - Ground velocity / Friction│  │              │
│         │   │   - Gravity                  │  │              │
│         │   └─────────────────────────────┘   │              │
│         │             │                       │              │
│         │   ┌─────────▼───────────────────┐   │              │
│         │   │ PHASE 4: Movement (Sweep)   │   │              │
│         │   │ • BFL_CollisionResolver     │   │              │
│         │   │   - Perform sweep           │   │              │
│         │   │   - Calculate slide         │   │              │
│         │   └─────────────────────────────┘   │              │
│         │             │                       │              │
│         │   ┌─────────▼───────────────────┐   │              │
│         │   │ PHASE 5: Ground Snapping    │   │              │
│         │   │ • BFL_GroundProbe           │   │              │
│         │   └─────────────────────────────┘   │              │
│         │             │                       │              │
│         │   ┌─────────▼───────────────────┐   │              │
│         │   │ PHASE 6: State Determination│   │              │
│         │   │ • BFL_MovementStateMachine  │   │              │
│         │   └─────────────────────────────┘   │              │
│         │             │                       │              │
│         │   ┌─────────▼───────────────────┐   │              │
│         │   │ Return S_MovementState      │   │              │
│         │   └─────────────────────────────┘   │              │
│         └─────────────────────────────────────┘              │
│                           │                                  │
│                           ▼                                  │
│              ┌────────────────────────┐                      │
│              │ Apply to Actor         │                      │
│              │ • SetActorLocation()   │                      │
│              │ • SetActorRotation()   │                      │
│              └────────────────────────┘                      │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Processing Pipeline Details

Phase 1: Input & Rotation

// Calculate input strength
const inputMagnitude = VectorLength(InputVector);

// Update rotation
const rotationResult = BFL_RotationController.UpdateRotation(
  CurrentState.Rotation,
  InputVector,
  Config,
  DeltaTime,
  CurrentState.Speed
);

Output: rotationResult.Rotation, rotationResult.IsRotating


Phase 2: Ground Detection

// Perform ground trace
const groundHit = BFL_GroundProbe.CheckGround(
  CurrentState.Location,
  CapsuleComponent,
  AngleThresholdsRads,
  Config
);

// Determine ground state
const isGrounded = groundHit.BlockingHit;

// Classify surface
const surfaceType = BFL_GroundProbe.GetSurfaceType(
  groundHit,
  AngleThresholdsRads
);

Output: groundHit, isGrounded, surfaceType


Phase 3: Physics Calculation

let newVelocity = CurrentState.Velocity;

// Ground movement OR air friction
if (IsWalkable(surfaceType) && isGrounded) {
  newVelocity = BFL_Kinematics.CalculateGroundVelocity(
    newVelocity,
    InputVector,
    DeltaTime,
    Config
  );
} else {
  newVelocity = BFL_Kinematics.CalculateFriction(
    newVelocity,
    DeltaTime,
    Config
  );
}

// Apply gravity
newVelocity = BFL_Kinematics.CalculateGravity(
  newVelocity,
  isGrounded,
  Config
);

// Calculate speed
const newSpeed = BFL_Kinematics.GetHorizontalSpeed(newVelocity);

Output: newVelocity, newSpeed


Phase 4: Movement Application (Sweep)

// Convert velocity to displacement
const desiredDelta = newVelocity * DeltaTime;

// Perform swept collision
const sweepResult = BFL_CollisionResolver.PerformSweep(
  CurrentState.Location,
  desiredDelta,
  CapsuleComponent,
  Config,
  DeltaTime,
  IsShowVisualDebug
);

let finalLocation = sweepResult.Location;

// Handle collision sliding
if (sweepResult.Blocked) {
  const slideVector = BFL_CollisionResolver.CalculateSlideVector(
    sweepResult,
    desiredDelta,
    CurrentState.Location
  );

  // Apply slide if valid
  if (VectorLength(slideVector) > 0.5 &&
      Dot(Normal(slideVector), sweepResult.Hit.ImpactNormal) >= -0.1) {
    finalLocation = sweepResult.Location + slideVector;
  }
}

Output: finalLocation, sweepResult


Phase 5: Ground Snapping

if (BFL_GroundProbe.ShouldSnapToGround(
  newVelocity.Z,
  groundHit,
  isGrounded
)) {
  finalLocation = BFL_GroundProbe.CalculateSnapLocation(
    finalLocation,
    groundHit,
    CapsuleComponent,
    Config.GroundTraceDistance
  );
}

Output: finalLocation (potentially snapped)


Phase 6: State Determination

const movementState = BFL_MovementStateMachine.DetermineState({
  IsGrounded: isGrounded,
  SurfaceType: surfaceType,
  InputMagnitude: inputMagnitude,
  CurrentSpeed: newSpeed,
  VerticalVelocity: newVelocity.Z,
  IsBlocked: sweepResult.Blocked
});

Output: movementState (E_MovementState)


Final State Construction

return {
  Location: finalLocation,
  Rotation: rotationResult.Rotation,
  Velocity: newVelocity,
  Speed: newSpeed,
  IsGrounded: isGrounded,
  GroundHit: groundHit,
  SurfaceType: surfaceType,
  IsBlocked: sweepResult.Blocked,
  CollisionCount: sweepResult.CollisionCount,
  IsRotating: rotationResult.IsRotating,
  RotationDelta: rotationResult.RemainingDelta,
  MovementState: movementState,
  InputMagnitude: inputMagnitude
};

API Reference

AC_Movement

InitializeMovementSystem()

InitializeMovementSystem(
  CapsuleComponentRef: CapsuleComponent | null = null,
  DebugHUDComponentRef: AC_DebugHUD | null = null
): void

Описание: Инициализирует систему движения
Эффекты:

  • Sets IsInitialized = true
  • Converts angle thresholds degrees → radians
  • Creates initial movement state
  • Registers debug page if HUD provided

Пример:

this.MovementComponent.InitializeMovementSystem(
  this.CharacterCapsule,
  this.DebugHUDComponent
);

ProcessMovementInput()

ProcessMovementInput(
  InputVector: Vector,
  DeltaTime: Float
): void

Описание: Main movement processing entry point
Параметры:

  • InputVector - Camera-relative movement input (normalized)
  • DeltaTime - Frame delta time (seconds)

Flow:

  1. Constructs S_MovementInput
  2. Calls BFL_MovementProcessor.ProcessMovement()
  3. Applies resulting Location and Rotation to actor

Пример:

// In EventTick
this.MovementComponent.ProcessMovementInput(
  this.CalculateMovementInput(),
  DeltaSeconds
);

BFL_MovementProcessor

ProcessMovement()

ProcessMovement(
  CurrentState: S_MovementState,
  Input: S_MovementInput,
  IsShowVisualDebug: boolean = false
): S_MovementState

Описание: Unified movement processing - executes all 6 phases
Параметры:

  • CurrentState - Current movement state (immutable)
  • Input - Movement input data
  • IsShowVisualDebug - Show debug traces in world

Возвращает: New complete movement state

Purity: Impure (collision traces), but deterministic

Пример:

const newState = BFL_MovementProcessor.ProcessMovement(
  this.CurrentMovementState,
  {
    InputVector: inputVector,
    DeltaTime: deltaTime,
    CapsuleComponent: this.CapsuleComponent,
    Config: this.Config,
    AngleThresholdsRads: this.AngleThresholdsRads
  },
  this.ShowDebug
);

// Apply results
this.GetOwner().SetActorLocation(newState.Location);
this.GetOwner().SetActorRotation(newState.Rotation);
this.CurrentMovementState = newState;

CreateInitialState()

CreateInitialState(
  Location: Vector,
  Rotation: Rotator
): S_MovementState

Описание: Creates initial movement state with defaults
Purity: Pure

Пример:

this.CurrentMovementState = BFL_MovementProcessor.CreateInitialState(
  this.GetOwner().GetActorLocation(),
  this.GetOwner().GetActorRotation()
);

Best Practices

Initialization

// ✅ Good - proper initialization order
class BP_MainCharacter extends Character {
  private MovementComponent: AC_Movement;

  ReceiveBeginPlay(): void {
    this.MovementComponent.InitializeMovementSystem(
      this.GetCapsuleComponent(),
      this.DebugHUDComponent
    );
  }
}

// ❌ Bad - using before initialization
ReceiveBeginPlay(): void {
  this.MovementComponent.ProcessMovementInput(input, dt); // Will early-return!
}

State Access

// ✅ Good - direct state access
const currentSpeed = this.MovementComponent.CurrentMovementState.Speed;
const isGrounded = this.MovementComponent.CurrentMovementState.IsGrounded;

// 🔄 Alternative - expose via getter
class AC_Movement {
  public GetMovementState(): S_MovementState {
    return this.CurrentMovementState;
  }
}

const state = this.MovementComponent.GetMovementState();
const speed = state.Speed;

Testing

// ✅ Good - test processor directly
describe('BFL_MovementProcessor', () => {
  it('should accelerate on ground', () => {
    const initialState = BFL_MovementProcessor.CreateInitialState(
      new Vector(0, 0, 100),
      new Rotator(0, 0, 0)
    );

    const input: S_MovementInput = {
      InputVector: new Vector(1, 0, 0),
      DeltaTime: 0.016,
      CapsuleComponent: mockCapsule,
      Config: testConfig,
      AngleThresholdsRads: testThresholds
    };

    const newState = BFL_MovementProcessor.ProcessMovement(
      initialState,
      input,
      false
    );

    expect(newState.Velocity.X).toBeGreaterThan(0);
    expect(newState.MovementState).toBe(E_MovementState.Walking);
  });
});

Performance

// ✅ Good - check initialization once
if (this.MovementComponent.IsInitialized) {
  // Movement processing
}

// ✅ Good - cache config access
const maxSpeed = this.Config.MaxSpeed;
for (let i = 0; i < 100; i++) {
  // Use maxSpeed
}

// ❌ Bad - repeated property access
for (let i = 0; i < 100; i++) {
  if (speed > this.Config.MaxSpeed) { ... }
}

Configuration Guidelines

Movement Physics

Parameter Default Description Tuning Guide
MaxSpeed 800.0 Max horizontal speed (cm/s) Higher = faster character
Acceleration 10.0 VInterpTo acceleration rate Higher = more responsive
Friction 8.0 VInterpTo friction rate Higher = faster stopping
Gravity 980.0 Gravity force (cm/s²) Standard Earth gravity

Surface Detection

Parameter Default Description
Walkable 50° Max walkable angle
SteepSlope 85° Max steep slope angle
Wall 95° Max wall angle

Collision

Parameter Default Description Performance Impact
MinStepSize 1.0 Min sweep step (cm) Smaller = more precise, slower
MaxStepSize 50.0 Max sweep step (cm) Larger = faster, less precise
MaxCollisionChecks 25 Max checks per frame Higher = safer, more expensive
GroundTraceDistance 50.0 Ground detection distance (cm) Balance precision vs cost

Rotation

Parameter Default Description
RotationSpeed 360.0 Rotation speed (deg/s)
MinSpeedForRotation 50.0 Min speed to rotate (cm/s)
ShouldRotateToMovement true Enable rotation

Performance Characteristics

Frame Budget

  • Target: <1ms per frame (60 FPS)
  • Typical: 0.3-0.7ms
  • Max: ~1.5ms (complex collision scenarios)

Bottlenecks

  1. Swept Collision (most expensive)

    • Multiple CapsuleTraceByChannel calls
    • Adaptive stepping helps
    • Monitor CollisionCount in debug HUD
  2. Ground Detection (moderate)

    • Single LineTraceByChannel per frame
    • Always runs (even airborne)
  3. Physics Calculations (cheap)

    • Pure math operations
    • VInterpTo is optimized

Optimization Tips

  1. Increase MinStepSize if collision checks too high
  2. Decrease GroundTraceDistance to minimum needed
  3. Disable ShouldRotateToMovement for static NPCs
  4. Use IsShowVisualDebug = false in production

Testing Strategy

Unit Testing (BFL_* modules)

describe('BFL_Kinematics', () => {
  it('should apply friction correctly', () => {
    const velocity = new Vector(800, 0, 0);
    const result = BFL_Kinematics.CalculateFriction(
      velocity,
      0.016,
      testConfig
    );
    
    expect(result.X).toBeLessThan(velocity.X);
    expect(result.Z).toBe(0);
  });
});

Integration Testing (BFL_MovementProcessor)

describe('Movement Pipeline', () => {
  it('should process complete frame', () => {
    const state = processFullFrame(initialState, input);
    
    expect(state.Location).not.toEqual(initialState.Location);
    expect(state.Velocity).toBeDefined();
    expect(state.MovementState).toBeDefined();
  });
});

Manual Testing (see ManualTestingChecklist.md)

  • Ground detection
  • Surface classification
  • Collision response
  • Rotation behavior
  • Debug visualization

Known Limitations

Current Constraints

  1. Binary Ground State: No partial contact detection
  2. Single Collision Shape: Capsule only
  3. Frame-Dependent Stepping: Sweep precision varies with framerate
  4. No Material Physics: Surface material not considered
  5. Simple Sliding: Basic projection, no advanced friction

By Design

  1. Capsule Component Required: System assumes capsule collision
  2. Deterministic Traces: Relies on UE physics determinism
  3. Horizontal Focus: Optimized for ground-based movement
  4. No Network Code: Not yet optimized for multiplayer

Future Extensions (Stage 10+)

Planned Features

  • Jump System: Vertical velocity application, coyote time, jump buffering
  • Steep Slope Sliding: Physics-based sliding on non-walkable surfaces
  • Moving Platforms: Platform attachment and relative movement
  • Wall Running: Advanced surface interaction
  • Ledge Detection: Edge detection for platforming
  • Material-Based Physics: Surface material awareness

File Structure

Content/
├── Movement/
│   ├── Components/
│   │   └── AC_Movement.ts                    # Imperative shell (~230 LOC)
│   ├── Core/
│   │   ├── BFL_MovementProcessor.ts          # Functional core (~260 LOC)
│   │   ├── DA_MovementConfig.ts              # Configuration asset
│   │   ├── DA_MovementConfigDefault.ts       # Default config
│   │   ├── E_MovementState.ts                # Movement states enum
│   │   ├── S_MovementInput.ts                # Input structure
│   │   └── S_MovementState.ts                # State structure
│   ├── Collision/
│   │   ├── BFL_CollisionResolver.ts          # Swept collision
│   │   ├── BFL_GroundProbe.ts                # Ground detection
│   │   └── S_SweepResult.ts                  # Sweep result structure
│   ├── Physics/
│   │   └── BFL_Kinematics.ts                 # Movement physics
│   ├── Rotation/
│   │   ├── BFL_RotationController.ts         # Rotation logic
│   │   └── S_RotationResult.ts               # Rotation result structure
│   ├── State/
│   │   ├── BFL_MovementStateMachine.ts       # FSM logic
│   │   └── S_MovementContext.ts              # State context structure
│   ├── Surface/
│   │   ├── BFL_SurfaceClassifier.ts          # Surface classification
│   │   ├── E_SurfaceType.ts                  # Surface types enum
│   │   └── S_AngleThresholds.ts              # Angle thresholds structure
│   └── Documentation/
│       ├── TDD.md                            # This document
│       └── ManualTestingChecklist.md         # Testing procedures
└── Math/
    └── Libraries/
        └── BFL_Vectors.ts                    # Vector math utilities

Troubleshooting

Character Falling Through Ground

Symptoms: Character drops through walkable surface
Checks:

  1. GroundTraceDistance > 0
  2. Ground has Visibility collision channel
  3. CapsuleComponent initialized correctly
  4. IsInitialized == true

Debug:

// Enable visual debug
this.DebugHUDComponent.ShowVisualDebug = true;
// Check ground hit in debug HUD
// Look for green line trace downward

Excessive Collision Checks

Symptoms: CollisionCount consistently hitting MaxCollisionChecks
Fixes:

  1. Increase MaxStepSize (careful: less precision)
  2. Decrease MaxSpeed
  3. Increase MaxCollisionChecks (careful: performance cost)
  4. Check for collision geometry issues

Debug:

// Monitor in debug HUD
CollisionCount / MaxCollisionChecks
// Should be <50% most frames

Jittery Z Position

Symptoms: Character bouncing up/down slightly
Fixes:

  1. Verify ground detection working (IsGrounded in debug HUD)
  2. Check ground snapping active
  3. Slightly increase GroundTraceDistance
  4. Verify ground has consistent collision

Character Not Rotating

Symptoms: Character faces wrong direction
Checks:

  1. Config.ShouldRotateToMovement == true
  2. CurrentSpeed > Config.MinSpeedForRotation
  3. SetActorRotation() called each frame
  4. Input vector not zero

Debug:

// Check in debug HUD
Is Rotating: true/false
Rotation Delta: [degrees remaining]
Current Yaw: [current angle]

Conclusion

Movement System представляет собой production-ready систему с следующими характеристиками:

Архитектурные достижения

  • Functional Core, Imperative Shell pattern реализован полностью
  • Clear separation of concerns между subsystems
  • Pipeline processing с явными фазами
  • Immutable state transformations везде где возможно
  • Testable design благодаря pure function libraries

Production Readiness

  • Deterministic physics с VInterpTo
  • Tunneling protection через swept collision
  • Frame-rate independence через DeltaTime
  • Performance optimized (<1ms typical frame time)
  • Debug visualization comprehensive
  • Extensible architecture для future features

Code Quality

  • LOC reduced 62% в AC_Movement (600 → 230)
  • Modularity high - 8 focused modules
  • Documentation comprehensive
  • Type safety strong через TypeScript structs

Production Status: Ready for Stage 10

Архитектура готова для расширения Jump System и последующих features без major refactoring.