// Content/Debug/Components/AC_DebugHUD.ts import type {AC_Movement} from "../../Movement/Components/AC_Movement.js"; import type {Float, Integer} from "../../types.js"; import type {S_DebugPage} from "../Structs/S_DebugPage.js"; import {AddToArray, CreateWidget, GetFromArray, IsValid, Print, SetArrayElem} from "../../functions.js"; import {E_DebugPageID} from "../Enums/E_DebugPageID.js"; import {E_DebugUpdateFunction} from "../Enums/E_DebugUpdateFunction.js"; import {WBP_DebugHUD} from "../UI/WBP_DebugHUD.js"; import type {S_DebugSettings} from "../Structs/S_DebugSettings.js"; import {E_DebugMode} from "../Enums/E_DebugMode.js"; import {ESlateVisibility} from "../../enums.js"; /** * Debug HUD Controller Component * Manages debug information display system for deterministic movement * Provides real-time performance monitoring and parameter visualization * Part of Stage 2: Debug HUD system implementation */ export class AC_DebugHUD { // Category: "DebugConfig" // Instance Editable: true /** * Debug system configuration settings * Controls visibility, update frequency, and current page * Instance editable for designer customization */ public DebugSettings: S_DebugSettings = { CurrentMode: E_DebugMode.Visible, CurrentPageIndex: 0, ShowVisualDebug: false, UpdateFrequency: 0.0, } // Category: "DebugState" /** * System initialization state flag * Set to true after successful InitializeDebugHUD call */ private IsInitialized: boolean = false; // Category: "DebugState" /** * Timestamp of last HUD update (seconds) * Used for update frequency control */ private LastUpdateTime: Float = 0; // Category: "DebugState" /** * Current frame counter for performance tracking * Incremented each update cycle */ private FrameCounter: Float = 0; // Category: "DebugState" /** * Current frames per second calculation * Calculated as 1.0 / DeltaTime */ private FPS: Float = 0; // Category: "DebugState" /** * Reference to movement component being debugged * Set during initialization, used for accessing movement data */ private MovementComponent: AC_Movement | null = null; // Category: "Widget Control" /** * Debug HUD widget instance * Created during initialization, manages UI display */ private DebugWidget: WBP_DebugHUD | null = null; // Category: "Page System" /** * Array of registered debug pages * Contains all available pages with their data and update functions */ private DebugPages: S_DebugPage[] = []; // Category: "HUD Control" // Pure: true /** * Check if debug HUD should be visible * @returns True if system is initialized and mode is visible * @pure Function has no side effects */ private ShouldShowDebugHUD() { return this.IsInitialized && this.DebugSettings.CurrentMode === E_DebugMode.Visible; } // Category: "HUD Control" // Pure: true /** * Check if debug HUD should update this frame * @param CurrentTime - Current game time in seconds * @returns True if enough time has passed since last update * @pure Function has no side effects * @example * // Update every frame (UpdateFrequency = 0) * ShouldUpdateDebugHUD(gameTime) // returns true * // Update at 30Hz (UpdateFrequency = 30) * ShouldUpdateDebugHUD(gameTime) // returns true every 1/30 seconds */ private ShouldUpdateDebugHUD(CurrentTime: Float): boolean { if (this.DebugSettings.UpdateFrequency <= 0) { return true; // Update every frame } else { return (CurrentTime - this.LastUpdateTime) >= (1.0 / this.DebugSettings.UpdateFrequency); } } // Category: "Page Management" /** * Register or update a debug page in the system * @param PageData - Page configuration and content data * @private Internal page management method * @example * // Register movement constants page * RegisterDebugPage({ * PageID: E_DebugPageID.MovementInfo, * Title: "Movement Constants", * Content: "", * IsVisible: true, * UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage * }) */ private RegisterDebugPage(PageData: S_DebugPage): void { let existingIndex: Integer = -1; this.DebugPages.forEach((page, index) => { if (page.PageID === PageData.PageID) { this.DebugPages[index] = PageData; return; } }) if (existingIndex >= 0) { SetArrayElem(this.DebugPages, existingIndex, PageData); } else{ AddToArray(this.DebugPages, PageData); } } // Category: "Page Management" // Pure: true /** * Get all currently visible debug pages * @returns Array of visible pages only * @pure Function has no side effects */ private GetVisiblePages(): S_DebugPage[] { const filteredPages: S_DebugPage[] = []; this.DebugPages.forEach((page) => { if (page.IsVisible) { AddToArray(filteredPages, page); } }) return filteredPages; } // Category: "Page Management" /** * Get currently selected debug page * @returns Object with page data and validity flag * @pure Function has no side effects */ private GetCurrentPage(): {Page: S_DebugPage | null, IsFound: boolean} { const length = this.GetVisiblePages().length; if (!((length === 0) || (this.DebugSettings.CurrentPageIndex >= length))) { return {Page: GetFromArray(this.DebugPages, this.DebugSettings.CurrentPageIndex), IsFound: true}; } else { return {Page: null, IsFound: false}; } } // Category: "Navigation" /** * Toggle debug HUD visibility between visible and hidden * @public User input handler for F1 key or similar */ public ToggleDebugHUD() { this.DebugSettings.CurrentMode = this.DebugSettings.CurrentMode === E_DebugMode.Visible ? E_DebugMode.Hidden : E_DebugMode.Visible; } // Category: "Navigation" /** * Navigate to previous debug page * Wraps around to last page if at beginning * @public User input handler for PageUp key */ public PreviousPage() { const length = this.GetVisiblePages().length; if (length > 1) { const currentPage = this.DebugSettings.CurrentPageIndex; this.DebugSettings.CurrentPageIndex = currentPage - 1 < 0 ? length - 1 : currentPage - 1; } } // Category: "Navigation" /** * Navigate to next debug page * Wraps around to first page if at end * @public User input handler for PageDown key */ public NextPage() { const length = this.GetVisiblePages().length; if (length > 1) { this.DebugSettings.CurrentPageIndex = (this.DebugSettings.CurrentPageIndex + 1) % length; } } // Category: "Visual Debug" /** * Toggle visual debug rendering (collision shapes, rays, etc.) * @public User input handler for visual debug toggle */ public ToggleVisualDebug() { this.DebugSettings.ShowVisualDebug = !this.DebugSettings.ShowVisualDebug; } // Category: "Page Updates" /** * Update content of currently selected page * Calls appropriate update function based on page type * @private Internal update system method */ private UpdateCurrentPage() { const {Page, IsFound}: {Page: S_DebugPage, IsFound: boolean} = this.GetCurrentPage(); let CurrentPage = Page; if (IsFound) { switch (CurrentPage.UpdateFunction) { case E_DebugUpdateFunction.UpdateMovementPage: CurrentPage = this.UpdateMovementPage(CurrentPage); break; case E_DebugUpdateFunction.UpdateSurfacePage: CurrentPage = this.UpdateSurfacePage(CurrentPage); break; case E_DebugUpdateFunction.UpdatePerformancePage: CurrentPage = this.UpdatePerformancePage(CurrentPage); break; } SetArrayElem(this.DebugPages, this.DebugSettings.CurrentPageIndex, CurrentPage); this.UpdateWidgetPage(); } } // Category: "Page Updates" /** * Update movement constants page content * @param Page - Page structure to update * @returns Updated page with current movement data * @private Page-specific update method */ private UpdateMovementPage(Page: S_DebugPage): S_DebugPage { if (IsValid(this.MovementComponent)) { return { PageID: Page.PageID, Title: Page.Title, Content: `Max Speed: ${this.MovementComponent.MovementConstants.MaxSpeed}\n` + `Acceleration: ${this.MovementComponent.MovementConstants.Acceleration}\n` + `Friction: ${this.MovementComponent.MovementConstants.Friction}\n` + `Gravity: ${this.MovementComponent.MovementConstants.Gravity}`, IsVisible: Page.IsVisible, UpdateFunction: Page.UpdateFunction } } else { return { PageID: Page.PageID, Title: Page.Title, Content: 'Movement Component Not Found', IsVisible: Page.IsVisible, UpdateFunction: Page.UpdateFunction } } } // Category: "Page Updates" /** * Update surface classification page content * @param Page - Page structure to update * @returns Updated page with current surface angle thresholds * @private Page-specific update method */ private UpdateSurfacePage(Page: S_DebugPage): S_DebugPage { if (IsValid(this.MovementComponent)) { return { PageID: Page.PageID, Title: Page.Title, Content: `Walkable: ≤${this.MovementComponent.AngleThresholdsDegrees.Walkable}°\n` + `Steep Slope: ${this.MovementComponent.AngleThresholdsDegrees.Walkable}°-${this.MovementComponent.AngleThresholdsDegrees.SteepSlope}°\n` + `Wall: ${this.MovementComponent.AngleThresholdsDegrees.SteepSlope}°-${this.MovementComponent.AngleThresholdsDegrees.Wall}°\n` + `Ceiling: >${this.MovementComponent.AngleThresholdsDegrees.Wall}°`, IsVisible: Page.IsVisible, UpdateFunction: Page.UpdateFunction } } else { return { PageID: Page.PageID, Title: Page.Title, Content: 'Movement Component Not Found', IsVisible: Page.IsVisible, UpdateFunction: Page.UpdateFunction } } } // Category: "Page Updates" /** * Update performance metrics page content * @param Page - Page structure to update * @returns Updated page with current performance data * @private Page-specific update method */ private UpdatePerformancePage(Page: S_DebugPage): S_DebugPage { if (IsValid(this.MovementComponent)) { return { PageID: Page.PageID, Title: Page.Title, Content: `Frame: ${this.FrameCounter}\n` + `FPS: ${this.FPS}\n` + `Update Rate: ${this.DebugSettings.UpdateFrequency <= 0 ? 'Every Frame' : (this.DebugSettings.UpdateFrequency + ' Hz')}\n` + `ActivePages: ${this.GetVisiblePages().length}`, IsVisible: Page.IsVisible, UpdateFunction: Page.UpdateFunction } } else { return { PageID: Page.PageID, Title: Page.Title, Content: 'Movement Component Not Found', IsVisible: Page.IsVisible, UpdateFunction: Page.UpdateFunction } } } // Category: "Widget Control" /** * Create debug widget instance and add to viewport * @private Internal widget management method */ private CreateDebugWidget() { this.DebugWidget = CreateWidget(new WBP_DebugHUD); this.DebugWidget.MovementComponent = this.MovementComponent; if (IsValid(this.DebugWidget)) { this.DebugWidget.AddToViewport(); } else { Print('Failed to create debug widget'); } } // Category: "Widget Control" /** * Update widget visibility based on current debug mode * @private Internal widget management method */ private UpdateWidgetVisibility() { if (IsValid(this.DebugWidget)) { this.DebugWidget.SetVisibility(this.ShouldShowDebugHUD() ? ESlateVisibility.Visible : ESlateVisibility.Hidden); } } // Category: "Widget Communication" /** * Send current page data to debug widget for display * @private Internal widget communication method */ private UpdateWidgetPage() { const {Page, IsFound} = this.GetCurrentPage(); if (IsFound) { this.DebugWidget.SetHeaderText(Page.Title); this.DebugWidget.SetContentText(Page.Content); this.DebugWidget.SetNavigationText(`Page ${this.DebugSettings.CurrentPageIndex + 1}/${this.GetVisiblePages().length} | PageUp/PageDown - Navigate`); } } // Category: "HUD Rendering" /** * Main update loop for debug HUD system * Called every frame from game loop * @param CurrentTime - Current game time in seconds * @param DeltaTime - Time since last frame in seconds * @public Called by BP_MainCharacter or game framework */ public UpdateHUD(CurrentTime: Float, DeltaTime: Float) { if (this.IsInitialized && this.ShouldUpdateDebugHUD(CurrentTime)) { this.FrameCounter++; this.FPS = DeltaTime > 0.0 ? 1.0 / DeltaTime : 0.0; this.LastUpdateTime = CurrentTime; if (this.ShouldShowDebugHUD()) { this.UpdateCurrentPage(); this.UpdateWidgetPage(); } } } // Category: "System Setup" /** * Register default debug pages (Movement, Surface, Performance) * @private Internal system setup method */ private RegisterDefaultPages() { this.RegisterDebugPage({ PageID: E_DebugPageID.MovementInfo, Title: "Movement Constants", Content: '', IsVisible: true, UpdateFunction: E_DebugUpdateFunction.UpdateMovementPage }); this.RegisterDebugPage({ PageID: E_DebugPageID.SurfaceInfo, Title: "Surface Classification", Content: '', IsVisible: true, UpdateFunction: E_DebugUpdateFunction.UpdateSurfacePage }); this.RegisterDebugPage({ PageID: E_DebugPageID.PerformanceInfo, Title: "Performance Metrics", Content: '', IsVisible: true, UpdateFunction: E_DebugUpdateFunction.UpdatePerformancePage }); } // Category: "Testing" /** * Test basic debug system functionality * @returns True if all basic tests pass * @private Internal testing method */ private TestDebugSystem() { Print('=== DEBUG SYSTEM TESTS ==='); if (this.IsInitialized) { Print('✅ System initialized'); if (this.DebugPages.length === 3) { Print('✅ All pages registered'); if (IsValid(this.MovementComponent)) { Print('✅ Movement component valid'); if (IsValid(this.DebugWidget)) { Print('✅ Debug widget created'); Print('✅ ALL TESTS PASSED'); return true; } else { Print('❌ Debug widget not created'); return false; } } else { Print('❌ Movement component not valid'); return false; } } else { Print(`❌ Expected 3 pages, got ${this.DebugPages.length}`); return false; } } else { Print('❌ System not initialized'); return false; } } // Category: "Testing" /** * Test page content generation for all registered pages * @returns True if all pages generate non-empty content * @private Internal testing method */ private TestPageContentGeneration() { let allTestsPassed = true; this.DebugPages.forEach((page, index) => { let updatedPage: S_DebugPage = page; switch (updatedPage.UpdateFunction) { case E_DebugUpdateFunction.UpdateMovementPage: updatedPage = this.UpdateMovementPage(updatedPage); break; case E_DebugUpdateFunction.UpdateSurfacePage: updatedPage = this.UpdateSurfacePage(updatedPage); break; case E_DebugUpdateFunction.UpdatePerformancePage: updatedPage = this.UpdatePerformancePage(updatedPage); break; } if (updatedPage.Content.length > 0) { Print(`✅ Page ${index + 1} content generated`); } else { Print(`❌ Page ${index + 1} content empty`); allTestsPassed = false; return; } }) return allTestsPassed; } // Category: "Testing" // Pure: true /** * Validate current navigation state * @returns True if current page index is valid * @pure Function has no side effects */ private IsValidNavigationState() { if (this.DebugSettings.CurrentPageIndex >= 0) { const visiblePagesLength = this.GetVisiblePages().length; return !((visiblePagesLength > 0) && (this.DebugSettings.CurrentPageIndex >= visiblePagesLength)); } else { return false; } } // Category: "Testing" /** * Test page navigation functionality * @param StartCurrentPage - Initial page index to restore after test * @returns True if navigation works correctly * @private Internal testing method */ private TestNavigation(StartCurrentPage: Integer) { if (this.IsValidNavigationState()) { if (this.IsValidNavigationState()) { this.NextPage(); if (this.IsValidNavigationState()) { if (this.IsValidNavigationState()) { this.PreviousPage(); if (this.IsValidNavigationState()) { this.DebugSettings.CurrentPageIndex = StartCurrentPage; if (this.IsValidNavigationState()) { return true; } else { Print('❌ Navigation: Failed to restore valid state'); return false; } } else { Print('❌ Navigation: PrevPage failed — State became invalid after PreviousPage'); return false; } } else { Print('❌ Navigation: PreviousPage failed — Invalid state before PreviousPage'); return false; } } else { Print('❌ Navigation: NextPage failed — State became invalid after NextPage'); return false; } } else { Print('❌ Navigation: NextPage failed — Invalid state before NextPage'); return false; } } else { Print('❌ Navigation: Invalid initial state'); return false; } } // Category: "Testing" /** * Run comprehensive test suite for debug system * Tests initialization, page content generation, and navigation * @private Called during initialization for validation */ private RunAllTests() { Print('=== COMPREHENSIVE DEBUG SYSTEM TESTING ==='); if (this.TestDebugSystem()) { if (this.TestPageContentGeneration()) { if (this.TestNavigation(this.DebugSettings.CurrentPageIndex)) { Print('✅ ALL TESTS PASSED'); } else { Print('❌ Navigation Tests: Failed'); } } else { Print('❌ Page Content Generation Tests: Failed'); } } else { Print('❌ System Tests: Failed'); } } // Category: "System Setup" /** * Initialize debug HUD system with movement component reference * Sets up pages, creates widget, runs tests, and starts display * @param MovementComponentRef - Reference to movement component to debug * @public Called by BP_MainCharacter during initialization * @example * // Initialize debug HUD in main character * this.DebugHUDComponent.InitializeDebugHUD(this.MovementComponent); */ public InitializeDebugHUD(MovementComponentRef: AC_Movement) { this.MovementComponent = MovementComponentRef; this.IsInitialized = true; this.FrameCounter = 0; this.LastUpdateTime = 0; this.FPS = 0; this.RegisterDefaultPages(); this.CreateDebugWidget(); this.RunAllTests(); this.DebugSettings.CurrentPageIndex = 0; this.UpdateWidgetVisibility(); Print('=== DEBUG HUD INITIALIZED ==='); this.UpdateCurrentPage(); } }