[code] Stage 1 Complete: Surface Classification System

- Implemented deterministic surface classification (Walkable/SteepSlope/Wall/Ceiling)
- Added comprehensive test suite (10 automated tests)
- Performance optimized with cached angle thresholds in radians
- Full documentation and inline comments added
- All acceptance criteria met
main
Nikolay Petrov 2025-08-17 23:39:24 +05:00
commit 4e1e7be2df
42 changed files with 1208 additions and 0 deletions

24
.gitattributes vendored Normal file
View File

@ -0,0 +1,24 @@
# Unreal Engine file types.
*.uasset filter=lfs diff=lfs merge=lfs -text
*.umap filter=lfs diff=lfs merge=lfs -text
# Raw Content file types.
*.3ds filter=lfs diff=lfs merge=lfs -text
*.bmp filter=lfs diff=lfs merge=lfs -text
*.exr filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text
*.jpeg filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.mov filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.mp4 filter=lfs diff=lfs merge=lfs -text
*.obj filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.xcf filter=lfs diff=lfs merge=lfs -text
# Anything in `/RawContent` dir.
/RawContent/**/* filter=lfs diff=lfs merge=lfs -text

56
.gitignore vendored Normal file
View File

@ -0,0 +1,56 @@
# Ignore all files by default, but scan all directories.
*
!*/
# Do not ignore git files in the root of the repo.
!/.git*
# Do not ignore current project's `.uproject`.
!/*.uproject
# Do not ignore source, config, plugins and documentation dirs.
!/Source/**
!/Config/**
!/Plugins/**
!/Documentation/**
# Only allow .uasset, .umap and .ts files from /Content dir.
# .uasset and .umap tracked by git-lfs, don't forget to track
# other files if adding them here.
!/Content/**/*.uasset
!/Content/**/*.umap
!/Content/**/*.ts
# Allow any files from /RawContent dir.
# Any file in /RawContent dir will be managed by git lfs.
!/RawContent/**/*
# OS/platform generated files.
# Windows
ehthumbs.db
Thumbs.db
# Mac OS X
.DS_Store
.DS_Store?
.AppleDouble
.LSOverride
._*
# Linux
*~
.directory
# vim
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
# Visual Studio
.vs
# IDEA
.idea

0
Config/DefaultEditor.ini Normal file
View File

94
Config/DefaultEngine.ini Normal file
View File

@ -0,0 +1,94 @@
[/Script/EngineSettings.GameMapsSettings]
GameDefaultMap=/Engine/Maps/Templates/OpenWorld
[/Script/Engine.RendererSettings]
r.AllowStaticLighting=False
r.GenerateMeshDistanceFields=True
r.DynamicGlobalIlluminationMethod=1
r.ReflectionMethod=1
r.SkinCache.CompileShaders=True
r.RayTracing=True
r.RayTracing.RayTracingProxies.ProjectEnabled=True
r.Shadow.Virtual.Enable=1
r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True
r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8
r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8
[/Script/WindowsTargetPlatform.WindowsTargetSettings]
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
DefaultGraphicsRHI=DefaultGraphicsRHI_DX12
-D3D12TargetedShaderFormats=PCD3D_SM5
+D3D12TargetedShaderFormats=PCD3D_SM6
-D3D11TargetedShaderFormats=PCD3D_SM5
+D3D11TargetedShaderFormats=PCD3D_SM5
Compiler=Default
AudioSampleRate=48000
AudioCallbackBufferFrameSize=1024
AudioNumBuffersToEnqueue=1
AudioMaxChannels=0
AudioNumSourceWorkers=4
SpatializationPlugin=
SourceDataOverridePlugin=
ReverbPlugin=
OcclusionPlugin=
CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0)
CacheSizeKB=65536
MaxChunkSizeOverrideKB=0
bResampleForDevice=False
MaxSampleRate=48000.000000
HighSampleRate=32000.000000
MedSampleRate=24000.000000
LowSampleRate=12000.000000
MinSampleRate=8000.000000
CompressionQualityModifier=1.000000
AutoStreamingThreshold=0.000000
SoundCueCookQualityIndex=-1
[/Script/LinuxTargetPlatform.LinuxTargetSettings]
-TargetedRHIs=SF_VULKAN_SM5
+TargetedRHIs=SF_VULKAN_SM6
[/Script/HardwareTargeting.HardwareTargetingSettings]
TargetedHardwareClass=Desktop
AppliedTargetedHardwareClass=Desktop
DefaultGraphicsPerformance=Maximum
AppliedDefaultGraphicsPerformance=Maximum
[/Script/WorldPartitionEditor.WorldPartitionEditorSettings]
CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet'
[/Script/Engine.UserInterfaceSettings]
bAuthorizeAutomaticWidgetVariableCreation=False
FontDPIPreset=Standard
FontDPI=72
[/Script/Engine.Engine]
+ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/TengriPlatformer")
+ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/TengriPlatformer")
[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings]
bEnablePlugin=True
bAllowNetworkConnection=True
SecurityToken=E22FC6A74FF0CDC2EF4851A56AD442B8
bIncludeInShipping=False
bAllowExternalStartInShipping=False
bCompileAFSProject=False
bUseCompression=False
bLogFiles=False
bReportStats=False
ConnectionType=USBOnly
bUseManualIPAddress=False
ManualIPAddress=

7
Config/DefaultGame.ini Normal file
View File

@ -0,0 +1,7 @@
[/Script/CommonUI.CommonUISettings]
CommonButtonAcceptKeyHandling=TriggerClick
[/Script/EngineSettings.GeneralProjectSettings]
ProjectID=56CEA3524FAE49EC0DF6D8A5178FEC04

84
Config/DefaultInput.ini Normal file
View File

@ -0,0 +1,84 @@
[/Script/Engine.InputSettings]
-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f))
-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f))
-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f))
-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f))
+AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
+AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False))
bAltEnterTogglesFullscreen=True
bF11TogglesFullscreen=True
bUseMouseForTouch=False
bEnableMouseSmoothing=True
bEnableFOVScaling=True
bCaptureMouseOnLaunch=True
bEnableLegacyInputScales=True
bEnableMotionControls=True
bFilterInputByPlatformUser=False
bShouldFlushPressedKeysOnViewportFocusLost=True
bAlwaysShowTouchInterface=False
bShowConsoleOnFourFingerTap=True
bEnableGestureRecognizer=False
bUseAutocorrect=False
DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown
DefaultViewportMouseLockMode=LockOnCapture
FOVScale=0.011110
DoubleClickTime=0.200000
DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput
DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent
DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks
-ConsoleKeys=Tilde
+ConsoleKeys=Tilde

BIN
Content/LevelPrototyping/Materials/MF_ProcGrid.uasset (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Content/LevelPrototyping/Materials/MI_Solid_Blue.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/LevelPrototyping/Materials/M_PrototypeGrid.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/LevelPrototyping/Materials/M_Solid.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/LevelPrototyping/Meshes/SM_ChamferCube.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/LevelPrototyping/Meshes/SM_Cube.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/LevelPrototyping/Meshes/SM_Cylinder.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/LevelPrototyping/Meshes/SM_QuarterCylinder.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/LevelPrototyping/Meshes/SM_Ramp.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/LevelPrototyping/Textures/T_GridChecker_A.uasset (Stored with Git LFS) Normal file

Binary file not shown.

BIN
Content/Levels/TestLevel.umap (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,13 @@
// Content/Movement/Blueprints/BP_MainCharacter.ts
import {AC_Movement} from "../Components/AC_Movement.js";
export class BP_MainCharacter {
// Components
MovementComponent = new AC_Movement();
// EventGraph
EventBeginPlay() {
this.MovementComponent.InitializeMovementSystem();
}
}

BIN
Content/Movement/Blueprints/BP_MainCharacter.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,235 @@
// Content/Movement/Components/AC_Movement.ts
import type {S_MovementConstants} from "../Structs/S_MovementConstants.js";
import type {S_AngleThresholds} from "../Structs/S_AngleThresholds.js";
import type {Float, Vector} from "../../types.js";
import {acos, cos, D2R, Dot, Print, sin} from "../../functions.js";
import {E_SurfaceType} from "../Enums/E_SurfaceType.js";
import type {S_SurfaceTestCase} from "../Structs/S_SurfaceTestCase.js";
export class AC_Movement {
// === Movement configuration exposed to designers ===
// Category: "Movement Config"
// Instance editable: true
readonly MovementConstants: S_MovementConstants = {
MaxSpeed: 600.0,
Acceleration: 2000.0,
Friction: 8.0,
Gravity: 980.0,
}
// Category: "Movement Config"
// Instance editable: true
readonly AngleThresholdsDegrees: S_AngleThresholds = {
Walkable: 50.0, // degrees
SteepSlope: 85.0, // degrees
Wall: 95.0, // degrees
};
// =====================================================
// Category: "Debug"
IsInitialized: boolean = false;
// Category: "Debug"
// Instance editable: true
ShowDebugInfo: boolean = true;
// === Runtime cached values for performance (radians) ===
// Category: "InternalCache"
AngleThresholdsRads: S_AngleThresholds;
// =====================================================
// Category: "Math"
// Pure: true
/**
* Calculate angle between two normalized vectors
* @param Vector1 - First normalized vector
* @param Vector2 - Second normalized vector
* @returns Angle between vectors in radians (0 to π)
* @example
* // 90° angle between X and Z axes
* GetAngleBetweenVectors([1,0,0], [0,0,1]) // returns π/2
*/
GetAngleBetweenVectors(Vector1: Vector, Vector2: Vector) {
return acos(Dot(Vector1, Vector2));
}
// Category: "Math"
// Pure: true
/**
* Calculate angle between surface normal and up vector
* @param SurfaceNormal - Normalized surface normal vector
* @returns Angle from horizontal plane in radians (0 = flat, π/2 = vertical)
* @example
* // Flat surface
* GetSurfaceAngle([0,0,1]) // returns 0
* // Vertical wall
* GetSurfaceAngle([1,0,0]) // returns π/2
*/
GetSurfaceAngle(SurfaceNormal: Vector): Float {
return this.GetAngleBetweenVectors(SurfaceNormal, [0.0, 0.0, 1.0]);
}
// Category: "Math"
// Pure: true
/**
* Generate surface normal vector from angle in degrees
* @param AngleDegrees - Angle from horizontal in degrees (0-180)
* @returns Normalized surface normal vector
* @example
* // Flat surface (0°)
* GenerateNormalFromAngle(0) // returns [0,0,1]
* // Vertical wall (90°)
* GenerateNormalFromAngle(90) // returns [1,0,0]
*/
GenerateNormalFromAngle(AngleDegrees: Float): Vector {
const AngleRads = D2R(AngleDegrees);
return [sin(AngleRads), 0.0, cos(AngleRads)];
}
// Category: "Surface Detection"
// Pure: true
/**
* Classify surface type based on normal vector
* @param SurfaceNormal - Normalized surface normal vector
* @param AngleThresholds - Angle thresholds in radians
* @returns Surface type classification
* @example
* // Classify flat ground
* ClassifySurface([0,0,1], thresholds) // returns E_SurfaceType.Walkable
*/
ClassifySurface(SurfaceNormal: Vector, AngleThresholds: S_AngleThresholds): E_SurfaceType {
const SurfaceAngle = this.GetSurfaceAngle(SurfaceNormal);
if (SurfaceAngle <= AngleThresholds.Walkable) {
return E_SurfaceType.Walkable;
} else if (SurfaceAngle <= AngleThresholds.SteepSlope) {
return E_SurfaceType.SteepSlope;
} else if (SurfaceAngle <= AngleThresholds.Wall) {
return E_SurfaceType.Wall;
} else {
return E_SurfaceType.Ceiling;
}
}
// Category: "Surface Detection"
// Pure: true
/**
* Check if surface allows normal walking movement
* @param SurfaceType - Surface type to check
* @returns True if surface is walkable
*/
IsSurfaceWalkable(SurfaceType: E_SurfaceType): boolean {
return SurfaceType === E_SurfaceType.Walkable;
}
// Category: "Surface Detection"
// Pure: true
/**
* Check if surface causes sliding behavior
* @param SurfaceType - Surface type to check
* @returns True if surface is steep slope
*/
IsSurfaceSteep(SurfaceType: E_SurfaceType): boolean {
return SurfaceType === E_SurfaceType.SteepSlope;
}
// Category: "Surface Detection"
// Pure: true
/**
* Check if surface blocks movement (collision)
* @param SurfaceType - Surface type to check
* @returns True if surface is a wall
*/
IsSurfaceWall(SurfaceType: E_SurfaceType): boolean {
return SurfaceType === E_SurfaceType.Wall;
}
// Category: "Surface Detection"
// Pure: true
/**
* Check if surface is overhead (ceiling)
* @param SurfaceType - Surface type to check
* @returns True if surface is ceiling
*/
IsSurfaceCeiling(SurfaceType: E_SurfaceType): boolean {
return SurfaceType === E_SurfaceType.Ceiling;
}
// Category: "Surface Detection"
// Pure: true
/**
* Check if no surface detected (airborne state)
* @param SurfaceType - Surface type to check
* @returns True if no surface contact
*/
IsSurfaceNone(SurfaceType: E_SurfaceType): boolean {
return SurfaceType === E_SurfaceType.None;
}
// Category: "Debug"
PrintDebugInfo() {
Print('=== Movement System Initialized ===');
Print(`Walkable: ≤ ${this.AngleThresholdsDegrees.Walkable}°, Steep: ${this.AngleThresholdsDegrees.Walkable}°-${this.AngleThresholdsDegrees.SteepSlope}°, Wall: ${this.AngleThresholdsDegrees.SteepSlope}°-${this.AngleThresholdsDegrees.Wall}°, Ceiling: >${this.AngleThresholdsDegrees.Wall}°`);
Print(`Max Speed: ${this.MovementConstants.MaxSpeed}, Acceleration: ${this.MovementConstants.Acceleration}, Friction: ${this.MovementConstants.Friction}, Gravity: ${this.MovementConstants.Gravity}`);
Print('==================================');
}
// Category: "Debug"
/**
* Run comprehensive surface classification test suite
* @returns True if all tests pass
*/
TestSurfaceClassification() {
let AllTestsPassed: boolean = true;
const TestCases: S_SurfaceTestCase[] = [
{ AngleDegrees: 0.0, ExpectedType: E_SurfaceType.Walkable, Description: "Flat surface" },
{ AngleDegrees: 25.0, ExpectedType: E_SurfaceType.Walkable, Description: "Gentle slope" },
{ AngleDegrees: 49.0, ExpectedType: E_SurfaceType.Walkable, Description: "Max walkable" },
{ AngleDegrees: 51.0, ExpectedType: E_SurfaceType.SteepSlope, Description: "Steep slope" },
{ AngleDegrees: 70.0, ExpectedType: E_SurfaceType.SteepSlope, Description: "Very steep" },
{ AngleDegrees: 84.0, ExpectedType: E_SurfaceType.SteepSlope, Description: "Max steep" },
{ AngleDegrees: 90.0, ExpectedType: E_SurfaceType.Wall, Description: "Vertical wall" },
{ AngleDegrees: 94.0, ExpectedType: E_SurfaceType.Wall, Description: "Max wall" },
{ AngleDegrees: 120.0, ExpectedType: E_SurfaceType.Ceiling, Description: "Overhang" },
{ AngleDegrees: 180.0, ExpectedType: E_SurfaceType.Ceiling, Description: "Ceiling" }
];
for (let i = 0; i < TestCases.length; i++) {
const testCase = TestCases[i];
const normal = this.GenerateNormalFromAngle(testCase.AngleDegrees);
const result = this.ClassifySurface(normal, this.AngleThresholdsRads);
if (result === testCase.ExpectedType) {
Print(`✅ Test ${i+1} PASS: ${testCase.Description} (${testCase.AngleDegrees}°) = ${testCase.ExpectedType}`);
} else {
Print(`❌ Test ${i+1} FAIL: ${testCase.Description} (${testCase.AngleDegrees}°) expected ${testCase.ExpectedType}, got ${result}`);
AllTestsPassed = false;
}
}
return AllTestsPassed;
}
// Category: "System"
/**
* Initialize movement system with angle conversion and testing
* Converts degree thresholds to radians for runtime performance
* Runs automated tests if debug mode enabled
*/
InitializeMovementSystem() {
this.IsInitialized = true;
this.AngleThresholdsRads = {
Walkable: D2R(this.AngleThresholdsDegrees.Walkable),
SteepSlope: D2R(this.AngleThresholdsDegrees.SteepSlope),
Wall: D2R(this.AngleThresholdsDegrees.Wall),
};
if (this.ShowDebugInfo) {
this.PrintDebugInfo();
this.TestSurfaceClassification();
}
}
}

BIN
Content/Movement/Components/AC_Movement.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,9 @@
// Content/Movement/Enums/E_SurfaceType.ts
export enum E_SurfaceType {
None = "None",
Walkable = "Walkable",
SteepSlope = "SteepSlope",
Wall = "Wall",
Ceiling = "Ceiling"
}

BIN
Content/Movement/Enums/E_SurfaceType.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,9 @@
// Content/Movement/Structs/S_AngleThresholds.ts
import type {Float} from "../../types.js";
export interface S_AngleThresholds {
Walkable: Float,
SteepSlope: Float,
Wall: Float,
}

BIN
Content/Movement/Structs/S_AngleThresholds.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,10 @@
// Content/Movement/Structs/S_MovementConstants.ts
import type {Float} from "../../types.js";
export interface S_MovementConstants {
MaxSpeed: Float;
Acceleration: Float;
Friction: Float;
Gravity: Float;
}

BIN
Content/Movement/Structs/S_MovementConstants.uasset (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -0,0 +1,10 @@
// Content/Movement/Structs/S_SurfaceTestCase.ts
import type {Float} from "../../types.js";
import type {E_SurfaceType} from "../Enums/E_SurfaceType.js";
export interface S_SurfaceTestCase {
AngleDegrees: Float;
ExpectedType: E_SurfaceType;
Description: string;
}

BIN
Content/Movement/Structs/S_SurfaceTestCase.uasset (Stored with Git LFS) Normal file

Binary file not shown.

30
Content/functions.ts Normal file
View File

@ -0,0 +1,30 @@
// Content/functions.ts
import type {Float, Vector} from "./types.js";
// Math
export function sin(x: Float): Float {
return Math.sin(x);
}
export function cos(x: Float): Float {
return Math.cos(x);
}
export function acos(x: Float): Float {
return Math.acos(x);
}
export function Dot(v1: Vector, v2: Vector): Float {
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
}
export function D2R(degrees: Float): Float {
return degrees * (Math.PI / 180);
}
// Utilities
export function Print(message: string): void {
console.log(message);
}

4
Content/types.ts Normal file
View File

@ -0,0 +1,4 @@
// Content/types.ts
export type Float = number;
export type Vector = [Float, Float, Float];

View File

@ -0,0 +1,59 @@
[//]: # (Documentation/Algorithm_Of_Development.md)
# Алгоритм разработки фичи
## Этап 1: Планирование
### 1.1 Понимание требований
**Читаем техническое задание:**
- ✅ Что нужно реализовать
- ✅ Входные данные
- ✅ Выходные данные
- ✅ Критерии успеха
### 1.2 Анализ архитектуры
### 1.3 Планирование структуры файлов
## Этап 2: Настройка инфраструктуры
Создание файлов, переменных, функций, настройка компонентов, разработка документации — описание системы, лог проектных решений.
## Этап 3: Реализация
Реализаций функций, написание тестов
## Этап 4: Тестирование
Чек-лист ручного тестирования, интеграция тестов
## Этап 5: Документирование
Обновление TDD, инлайн документация
## Этап 6: Code Review и финализация
### 6.1 Самопроверка кода
**Чеклист качества:**
- [ ] Все переменные имеют понятные имена
- [ ] Функции разбиты по категориям логично
- [ ] Есть комментарии к сложным местам
- [ ] Нет hardcoded значений
- [ ] Тесты покрывают основную функциональность
### 6.2 Проверка производительности
**Запустить Profiler и проверить:**
- [ ] Инициализация занимает <1ms
- [ ] Нет утечек памяти
- [ ] FPS не проседает при запуске тестов
### 6.3 Финальный коммит
**Подготовка к коммиту:**
1. **Сохранить** все Blueprint'ы
2. **Обновить** документацию
3. **Запустить** все тесты финально
4. **Сделать** git commit
# Шаблон этапов
Этот алгоритм является шаблоном для всех последующих этапов:
1. **Планирование** (понимание → архитектура → структура)
2. **Инфраструктура** (файлы → базовая настройка → документация)
3. **Реализация** (код → функции → интеграция)
4. **Тестирование** (автотесты → ручные тесты → критерии)
5. **Документирование** (TDD → комментарии → решения)
6. **Финализация** (ревью → производительность → коммит)

View File

@ -0,0 +1,8 @@
[//]: # (Documentation/Movement/Architect_Principles.md)
# Архитектурные принципы
- **Детерминированность** - приоритет #1 для всех решений
- **Модульность** - каждая система независима и тестируема
- **Производительность** - 60 FPS на целевом железе
- **Расширяемость** - легкое добавление новых механик
- **Отлаживаемость** - comprehensive debug tools

View File

@ -0,0 +1,14 @@
[//]: # (Documentation/Movement/Basics.md)
# Детерминированное управление для 3D-платформера в Unreal Engine 5
Необходимо реализовать детерменированное управление для 3D-платформера с помощью Blueprint в UnrealEngine 5 в стиле Super Mario Odyssey.
## Условия
1. Не использовать стандартный CharacterController
2. Не использовать стандартный Sweep
3. Реализовать всю логику вплоть до коллизий с нуля
## Пояснения
От UE5 в моём случае требуется только отрисовка графики, возможно базовая встроенная физика, инструменты анимации. В общем всё, что касается механик должно быть написано с нуля.

View File

@ -0,0 +1,18 @@
[//]: # (Documentation/Movement/ProjectDecisions/DecisionLog_01.md)
# Лог Проектных Решений - Этап 1
## Решение 1: Углы классификации
- **Проблема:** Исходные углы (45°/90°/135°) не подходили для платформера
- **Решение:** Обновили на 50°/85°/95°
- **Дата:** 17.08.2025
## Решение 2: Floating point precision
- **Проблема:** Тесты падали на границах углов
- **Решение:** Используем радианы внутри, градусы снаружи
- **Дата:** 17.08.2025
## Решение 3: Архитектура тестирования
- **Проблема:** Хардкодные векторы сложно поддерживать
- **Решение:** S_SurfaceTestCase + GenerateNormalFromAngle
- **Дата:** 17.08.2025

View File

@ -0,0 +1,393 @@
[//]: # (Documentation/Movement/Roadmap.md)
# Этап 1: Базовая настройка и константы
**Цель:** Система классификации поверхностей по углам
**Результат:** Стабильная база для всех последующих расчетов
**Что реализуем:**
- Переменные движения (MaxSpeed, Acceleration, Friction, Gravity)
- Система углов поверхностей (Walkable ≤50°, SteepSlope ≤85°, Wall ≤95°, Ceiling >95°)
- Конвертация градусы ↔ радианы
- Функции инициализации и тестирования констант
- Enhanced Input System интеграция
**Критерии успеха:**
- ✅ Корректная конвертация углов (точность <0.001)
- ✅ Все константы инициализируются при старте
- ✅ Debug вывод показывает правильные значения
- ✅ Автоматические тесты проходят
---
# Этап 2: Debug HUD система
**Цель:** Статичный debug вывод для удобной отладки
**Результат:** Профессиональная debug система
**Что реализуем:**
- Цветовое кодирование разных типов информации
- Функции переключения debug режимов
- Контроль частоты обновления HUD
**Критерии успеха:**
- ✅ Информация отображается статично на экране
- ✅ Цветовая дифференциация работает
- ✅ Легкое включение/выключение debug режимов
- ✅ Нет влияния на производительность
---
# Этап 3: Детекция поверхностей
**Цель:** Надежное определение типа поверхности под персонажем
**Результат:** Стабильная классификация Walkable/SteepSlope/Wall/Ceiling
**Что реализуем:**
- Функции классификации поверхности по нормали
- Функции запросов состояния (IsSurfaceWalkable, IsSurfaceSteep, etc.)
**Критерии успеха:**
- ✅ Точная классификация поверхностей по углам
- ✅ Стабильное определение типа поверхности
- ✅ Корректная работа с нормалями поверхностей
- ✅ Детальная debug информация
---
# Этап 3: Базовое движение по земле
**Цель:** Плавное детерминированное движение по плоским поверхностям
**Результат:** Отзывчивое управление без рывков и заиканий
**Что реализуем:**
- VInterpTo для плавного ускорения и торможения
- Применение гравитации с правильным обнулением на земле
- Горизонтальное движение только на walkable поверхностях
- Ограничение максимальной скорости
**Критерии успеха:**
- ✅ Плавное ускорение при нажатии WASD и стика геймпада
- ✅ Плавное торможение при отпускании клавиш/стика геймпада
- ✅ Скорость не превышает MaxSpeed
- ✅ Диагональное движение не быстрее прямого
- ✅ Стабильное поведение на земле
---
# Этап 4: Поворот персонажа вслед за движением
**Цель:** Плавный поворот персонажа в сторону движения
**Результат:** Персонаж естественно реагирует на направление движения
**Что реализуем:**
- При использовании мыши или стика геймпада персонаж поворачивается в сторону движения
- Учет наклона камеры для корректного поворота
**Критерии успеха:**
- ✅ Персонаж плавно поворачивается в сторону движения
- ✅ Поворот учитывает наклон камеры
- ✅ Плавный переход между направлениями
- ✅ Нет рывков при повороте
- ✅ Персонаж не поворачивается, если не движется
- ✅ Поворот не влияет на скорость движения
---
# Этап 5: Детерминированный Sweep collision
**Цель:** Полное устранение tunneling через stepped collision detection
**Результат:** Bullet-proof система коллизий
**Что реализуем:**
- PerformDeterministicSweep с пошаговой проверкой
- HandleSweepCollision для обработки ударов
- Адаптивный размер шагов sweep
**Критерии успеха:**
- ✅ Полное отсутствие tunneling при любых скоростях
- ✅ Стабильная Z позиция (разброс <0.5 единиц)
- ✅ Детерминированность (100% воспроизводимость)
- ✅ Performance <25 collision checks за кадр
---
# Этап 6: Обработка стен и углов
**Цель:** Плавное скольжение вдоль стен без застреваний
**Результат:** Качественная навигация в сложной геометрии
**Что реализуем:**
- Wall sliding - скольжение вдоль стен
- Corner resolution - обработка внутренних углов
- Multi-directional sweep - несколько попыток движения
- Edge detection и step-up механика
**Критерии успеха:**
- ✅ Персонаж не застревает в углах
- ✅ Плавное скольжение вдоль стен любой геометрии
- ✅ Автоматический step-up на небольшие препятствия
- ✅ Работает в сложных лабиринтах
---
# Этап 7: Движение по склонам
**Цель:** Реалистичное поведение на наклонных поверхностях
**Результат:** Естественное движение по пандусам и скатывание
**Что реализуем:**
- Slope walking - движение вверх/вниз по склонам ≤45°
- Slope sliding - скатывание с крутых поверхностей >45°
- Ground snapping - прилипание к неровной поверхности
- Momentum preservation на склонах
**Критерии успеха:**
- ✅ Плавный подъем по пандусам ≤45°
- ✅ Реалистичное скатывание с крутых склонов >45°
- ✅ Отсутствие "прыжков" на неровностях
- ✅ Сохранение импульса при переходах между поверхностями
---
# Этап 8: Разделение физики и рендера
**Цель:** Детерминированная физика + плавная визуализация
**Результат:** AAA-качество визуального движения
**Что реализуем:**
- Dual position system (physics + render positions)
- Position interpolation для плавности
- Fixed timestep для физики (120Hz physics, variable render)
- Smooth transitions между состояниями
**Критерии успеха:**
- ✅ Физика остается детерминированной
- ✅ Визуально плавное движение без микрозаиканий
- ✅ Stable 60+ FPS без влияния на физику
- ✅ Smooth interpolation работает корректно
---
# Этап 9: Профессиональная камера система
**Цель:** Плавная камера уровня AAA-игр
**Результат:** Комфортная камера без рывков
**Что реализуем:**
- Camera lag и damping для плавного следования
- Look-ahead prediction (камера смотрит вперед при движении)
- Smooth rotation следования за поворотами
- Dead zone для микродвижений
- Collision avoidance для камеры
**Критерии успеха:**
- ✅ Камера не дергается при остановке/старте
- ✅ Плавные повороты и наклоны
- ✅ Предсказание направления движения
- ✅ Нет проваливания камеры в стены
---
# Этап 10: Adaptive stepping optimization
**Цель:** Оптимизация производительности sweep системы
**Результат:** Меньше collision checks без потери качества
**Что реализуем:**
- Variable step size в зависимости от скорости
- Субпиксельная точность для медленного движения
- Performance monitoring и auto-tuning
- Spatial optimization для collision queries
**Критерии успеха:**
- ✅ <10 collision checks при обычном движении
- ✅ Субпиксельная точность при медленном движении
- ✅ Автоматическая адаптация под нагрузку
- ✅ Stable performance в сложных сценах
---
# Этап 11: Enhanced ground snapping
**Цель:** Плавное прилипание к неровным поверхностям
**Результат:** Персонаж идет по неровной геометрии без отрыва
**Что реализуем:**
- Multi-point ground detection
- Intelligent surface normal blending
- Smooth height transitions
- Predictive ground snapping
**Критерии успеха:**
- ✅ Плавное движение по ступенькам
- ✅ Нет отрыва от неровной поверхности
- ✅ Smooth transitions на изменениях высоты
- ✅ Работает на любой сложности геометрии
---
# Этап 12: Система прыжков
**Цель:** Отзывчивое воздушное управление уровня лучших платформеров
**Результат:** Качественный платформинг с точным контролем
**Что реализуем:**
- Variable jump height (короткое/длинное нажатие)
- Air control с ограничениями и инерцией
- Coyote time (прыжок после покидания платформы)
- Jump buffering (ранние нажатия прыжка)
- Multi-jump система (опционально)
**Критерии успеха:**
- ✅ Точный контроль высоты прыжка
- ✅ Forgiving jump timing (coyote + buffer)
- ✅ Responsive но не overpowered air control
- ✅ Плавные transitions между ground/air состояниями
---
# Этап 13: Воздушная физика
**Цель:** Реалистичная но игровая воздушная физика
**Результат:** Естественное поведение в полете
**Что реализуем:**
- Air resistance и terminal velocity
- Wind/updraft systems
- Gliding механика
- Landing impact detection и анимации
- Air-to-ground transition smoothing
**Критерии успеха:**
- ✅ Реалистичная траектория полета
- ✅ Плавные приземления без "хлопков"
- ✅ Terminal velocity ограничивает падение
- ✅ Responsive air control без нарушения физики
---
# Этап 14: Продвинутые склоны и поверхности
**Цель:** Сложные взаимодействия с геометрией
**Результат:** Разнообразные типы поверхностей
**Что реализуем:**
- Ice surfaces (скользкие поверхности с инерцией)
- Conveyor belts (движущиеся платформы)
- Bouncy surfaces (отскакивающие поверхности)
- Sticky surfaces (замедляющие движение)
- Slope acceleration/deceleration physics
**Критерии успеха:**
- ✅ Каждый тип поверхности ощущается уникально
- ✅ Плавные переходы между типами поверхностей
- ✅ Детерминированное поведение всех типов
- ✅ Легкая настройка параметров поверхностей
---
# Этап 15: Wall interactions
**Цель:** Продвинутые взаимодействия со стенами
**Результат:** Wall jumping, wall sliding, wall climbing
**Что реализуем:**
- Wall jumping с momentum preservation
- Wall sliding с контролем скорости
- Wall climbing на специальных поверхностях
- Corner grabbing и edge detection
- Wall run система (опционально)
**Критерии успеха:**
- ✅ Responsive wall jumping с правильными углами
- ✅ Контролируемое wall sliding
- ✅ Smooth transitions wall ↔ ground ↔ air
- ✅ Интуитивное управление wall mechanics
---
# Этап 16: Специальные движения
**Цель:** Уникальные движения для богатого геймплея
**Результат:** Dash, ground pound, ledge grab и другие
**Что реализуем:**
- Dash/dodge с invincibility frames
- Ground pound с area impact
- Ledge grabbing и climbing
- Slide/crouch движения
- Special movement abilities
**Критерии успеха:**
- ✅ Каждое движение ощущается impact-ful
- ✅ Smooth combinations между движениями
- ✅ Balanced timing и cooldowns
- ✅ Clear visual и audio feedback
---
# Этап 17: Performance optimization
**Цель:** 60 FPS на целевом железе в любых сценариях
**Результат:** Оптимизированная система коллизий
**Что реализуем:**
- Spatial partitioning для collision objects
- LOD system для collision complexity
- Multi-threading collision checks
- Memory pool для collision queries
- Predictive collision culling
**Критерии успеха:**
- ✅ Stable 60+ FPS на целевом железе
- ✅ <5ms на collision detection в worst case
- ✅ Scalable performance до 100+ collision objects
- ✅ Minimal memory allocations в runtime
---
# Этап 18: Debug и профилирование tools
**Цель:** Профессиональные инструменты для тонкой настройки
**Результат:** Полный контроль над системой
**Что реализуем:**
- Visual collision debugging (rays, sweeps, contacts)
- Real-time performance metrics и profiling
- Tweakable parameters в runtime через UI
- Automated testing suite для regression testing
- Replay system для детерминированности
**Критерии успеха:**
- ✅ Visual debugging показывает все collision queries
- ✅ Real-time параметры настраиваются без restart
- ✅ Performance metrics точные и useful
- ✅ Automated tests покрывают все основные сценарии
---
# Этап 19: Edge cases и stress testing
**Цель:** Bullet-proof система для любых условий
**Результат:** Система работает в экстремальных сценариях
**Что реализуем:**
- Extreme velocity testing (10000+ units/sec)
- Complex geometry stress tests
- Memory leak detection и prevention
- Determinism verification tools
- Edge case handling (NaN, infinity, etc.)
**Критерии успеха:**
- ✅ Система не ломается при экстремальных значениях
- ✅ No memory leaks при длительной работе
- ✅ Determinism поддерживается в любых условиях
- ✅ Graceful degradation при перегрузке
---
# Этап 20: User experience polish
**Время:** 3-4 дня | **Сложность:** Средняя
**Цель:** Finalized user experience
**Результат:** Система ощущается как в коммерческой игре
**Что реализуем:**
- Input buffering и prediction
- Haptic feedback integration (геймпады)
- Audio feedback integration для movement
- Visual effects integration (dust, particles)
- Accessibility options
**Критерии успеха:**
- ✅ Controls ощущаются максимально responsive
- ✅ Rich feedback для всех действий
- ✅ Поддержка различных input методов
- ✅ Accessibility options работают корректно
---

View File

@ -0,0 +1,38 @@
[//]: # (Documentation/Movement/TechnicalDesign/TDD.md)
# Система Движения - Техническая Документация
## Этап 1: Базовая настройка и константы ✅
### Обзор
Детерминированная система движения для 3D-платформера в стиле Super Mario Odyssey.
### Система Классификации Поверхностей
- **Walkable:** 0° - 50° (обычное движение)
- **SteepSlope:** 50° - 85° (механика скольжения)
- **Wall:** 85° - 95° (блокировка коллизий)
- **Ceiling:** >95° (потолочные поверхности)
### Соображения Производительности
- Чистые функции для математических операций (готовы к миграции в C++)
- Кэшированные пороги углов в радианах
- Единое Math Expression для расчёта углов
### Структура Файлов
```
Content\
Movement\
Blueprints\
BP_MainCharacter
Components\
AC_Movement
Enums\
E_SurfaceType
Structs\
S_AngleThresholds
S_MovementConstants
S_SurfaceTestCase
```
### Покрытие тестами
10 автоматизированных тестовых случаев, покрывающих граничные условия и краевые случаи.

View File

@ -0,0 +1,18 @@
[//]: # (Documentation/Movement/Testing/TestResults_01.md)
# Результаты Тестирования - Этап 1
## Автоматические тесты: ✅ PASS
- Test 1-10: Все тесты классификации поверхностей прошли
## Ручные тесты: ✅ PASS
- [x] Персонаж спавнится без ошибок
- [x] Debug информация выводится корректно
- [x] Нет warning'ов в Output Log
- [x] FPS стабильный
## Критерии успеха: ✅ ВЫПОЛНЕНО
- [x] Корректная конвертация углов (точность <0.001)
- [x] Все константы инициализируются при старте
- [x] Debug вывод показывает правильные значения
- [x] Автоматические тесты проходят

15
TengriPlatformer.uproject Normal file
View File

@ -0,0 +1,15 @@
{
"FileVersion": 3,
"EngineAssociation": "5.6",
"Category": "",
"Description": "",
"Plugins": [
{
"Name": "ModelingToolsEditorMode",
"Enabled": true,
"TargetAllowList": [
"Editor"
]
}
]
}