diff --git a/Plugins/UEBridge/Source/UEBridgeRuntime/Private/UEBridgeSubsystem.cpp b/Plugins/UEBridge/Source/UEBridgeRuntime/Private/UEBridgeSubsystem.cpp index 4250ef6..f94f3c0 100644 --- a/Plugins/UEBridge/Source/UEBridgeRuntime/Private/UEBridgeSubsystem.cpp +++ b/Plugins/UEBridge/Source/UEBridgeRuntime/Private/UEBridgeSubsystem.cpp @@ -27,7 +27,7 @@ void UUEBridgeSubsystem::Initialize(FSubsystemCollectionBase& Collection) void UUEBridgeSubsystem::Deinitialize() { - StopGame(); + bIsActive = false; UE_LOG(LogUEBridge, Log, TEXT("UEBridgeSubsystem deinitialized")); Super::Deinitialize(); } @@ -134,124 +134,6 @@ void UUEBridgeSubsystem::Tick(float DeltaTime) // === GAME FLOW === -void UUEBridgeSubsystem::StartGame() -{ - if (bIsActive) - { - BridgeLog(TEXT("Bridge already active")); - return; - } - - BridgeLog(TEXT("========================================")); - BridgeLog(TEXT("TRANSLATORS BRIDGE SUBSYSTEM v2.1.0")); - BridgeLog(TEXT("USD-native communication with JSON fallback")); - BridgeLog(FString::Printf(TEXT("Bridge Path: %s"), *BridgePath)); - BridgeLog(TEXT("========================================")); - - // Ensure bridge directory exists - IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); - if (!PlatformFile.DirectoryExists(*BridgePath)) - { - PlatformFile.CreateDirectory(*BridgePath); - BridgeLog(FString::Printf(TEXT("Created bridge directory: %s"), *BridgePath)); - } - - bIsActive = true; - SetState(EUEBridgeState::WaitingForBridge); - - // Check for existing state files (Python may have started first) - FString UsdFilePath = GetBridgeFilePath(TEXT("bridge_state.usda")); - FString JsonFilePath = GetBridgeFilePath(TEXT("state.json")); - - if (PlatformFile.FileExists(*UsdFilePath)) - { - BridgeLog(TEXT("Found existing bridge_state.usda - processing...")); - ProcessStateFile(); - } - else if (PlatformFile.FileExists(*JsonFilePath)) - { - BridgeLog(TEXT("Found existing state.json - processing...")); - ProcessStateFile(); - } -} - -void UUEBridgeSubsystem::StopGame() -{ - if (!bIsActive) - { - return; - } - - bIsActive = false; - bStateChangePending = false; - bUsdChangePending = false; - SetState(EUEBridgeState::Idle); - - BridgeLog(TEXT("Bridge stopped")); -} - - -void UUEBridgeSubsystem::SubmitAnswer(const FString& QuestionId, int32 OptionIndex, float ResponseTimeMs) -{ - // Try USD mode first - FString FilePath = GetBridgeFilePath(TEXT("bridge_state.usda")); - FString Content; - - if (bUsingUsdMode && FFileHelper::LoadFileToString(Content, *FilePath)) - { - FString Timestamp = FDateTime::UtcNow().ToIso8601(); - FString SelectedLabel = (OptionIndex >= 0 && OptionIndex < CurrentQuestion.OptionLabels.Num()) - ? CurrentQuestion.OptionLabels[OptionIndex] : TEXT(""); - FString SelectedDirection = (OptionIndex >= 0 && OptionIndex < CurrentQuestion.OptionDirections.Num()) - ? CurrentQuestion.OptionDirections[OptionIndex] : TEXT(""); - - Content = UpdateUsdaVariant(Content, TEXT("sync_status"), TEXT("answer_received")); - Content = UpdateUsdaVariant(Content, TEXT("message_type"), TEXT("answer")); - - Content = UpdateUsdaAttribute(Content, TEXT("Answer"), TEXT("question_id"), QuestionId, true); - Content = UpdateUsdaAttribute(Content, TEXT("Answer"), TEXT("option_index"), FString::FromInt(OptionIndex), false); - Content = UpdateUsdaAttribute(Content, TEXT("Answer"), TEXT("response_time_ms"), FString::SanitizeFloat(ResponseTimeMs), false); - Content = UpdateUsdaAttribute(Content, TEXT("Answer"), TEXT("selected_label"), SelectedLabel, true); - Content = UpdateUsdaAttribute(Content, TEXT("Answer"), TEXT("selected_direction"), SelectedDirection, true); - Content = UpdateUsdaAttribute(Content, TEXT("Answer"), TEXT("timestamp"), Timestamp, true); - - UpdateBehavioralSignals(Content, ResponseTimeMs); - - int32 MaxRetries = 3; - for (int32 Retry = 0; Retry < MaxRetries; ++Retry) - { - if (FFileHelper::SaveStringToFile(Content, *FilePath)) - { - BridgeLog(FString::Printf(TEXT("USD answer sent: %s = option %d (%.0fms)"), - *QuestionId, OptionIndex, ResponseTimeMs)); - SetState(EUEBridgeState::AnswerPending); - return; - } - FPlatformProcess::Sleep(0.1f); - } - - BridgeLog(TEXT("USD answer write failed, falling back to JSON")); - } - - // JSON fallback - TSharedPtr JsonObj = MakeShared(); - JsonObj->SetStringField(TEXT("$schema"), TEXT("translators-answer-v1")); - JsonObj->SetStringField(TEXT("type"), TEXT("answer")); - JsonObj->SetStringField(TEXT("timestamp"), FDateTime::UtcNow().ToIso8601()); - - TSharedPtr AnswerObj = MakeShared(); - AnswerObj->SetStringField(TEXT("question_id"), QuestionId); - AnswerObj->SetNumberField(TEXT("option_index"), OptionIndex); - AnswerObj->SetNumberField(TEXT("response_time_ms"), ResponseTimeMs); - JsonObj->SetObjectField(TEXT("answer"), AnswerObj); - - WriteJsonToFile(TEXT("answer.json"), JsonObj); - SetState(EUEBridgeState::AnswerPending); - - BridgeLog(FString::Printf(TEXT("JSON answer sent: %s = option %d (%.0fms)"), - *QuestionId, OptionIndex, ResponseTimeMs)); -} - void UUEBridgeSubsystem::SendAcknowledge() { diff --git a/Plugins/UEBridge/Source/UEBridgeRuntime/Public/BridgeTypes.h b/Plugins/UEBridge/Source/UEBridgeRuntime/Public/BridgeTypes.h index ff5744d..0481fa4 100644 --- a/Plugins/UEBridge/Source/UEBridgeRuntime/Public/BridgeTypes.h +++ b/Plugins/UEBridge/Source/UEBridgeRuntime/Public/BridgeTypes.h @@ -15,7 +15,7 @@ UENUM(BlueprintType, meta = (ToolTip = "Bridge state machine states")) enum class EUEBridgeState : uint8 { - /** No file watcher running. StartGame() not called yet. */ + /** No file watcher running; bridge inactive. */ Idle UMETA(DisplayName = "Idle"), /** File watcher running, waiting for Python bridge_orchestrator "ready" message. */ diff --git a/Plugins/UEBridge/Source/UEBridgeRuntime/Public/UEBridgeSubsystem.h b/Plugins/UEBridge/Source/UEBridgeRuntime/Public/UEBridgeSubsystem.h index 03c4f05..cb1e45a 100644 --- a/Plugins/UEBridge/Source/UEBridgeRuntime/Public/UEBridgeSubsystem.h +++ b/Plugins/UEBridge/Source/UEBridgeRuntime/Public/UEBridgeSubsystem.h @@ -34,18 +34,6 @@ class UEBRIDGERUNTIME_API UUEBridgeSubsystem // === GAME FLOW === - /** Start the bridge: resolve path, create directory, begin polling */ - UFUNCTION(BlueprintCallable, Category = "UE Bridge", meta = (ToolTip = "Start the bridge and begin watching for state files")) - void StartGame(); - - /** Stop the bridge and reset state */ - UFUNCTION(BlueprintCallable, Category = "UE Bridge", meta = (ToolTip = "Stop the bridge and clean up")) - void StopGame(); - - /** Submit a player answer (prefers USD, falls back to JSON) */ - UFUNCTION(BlueprintCallable, Category = "UE Bridge", meta = (ToolTip = "Submit the player's answer for the current question")) - void SubmitAnswer(const FString& QuestionId, int32 OptionIndex, float ResponseTimeMs); - /** Send acknowledgment that UE5 is ready (prefers USD, falls back to JSON) */ UFUNCTION(BlueprintCallable, Category = "UE Bridge", meta = (ToolTip = "Acknowledge readiness to the Python bridge")) void SendAcknowledge(); diff --git a/Source/UnrealEngineBridge/BridgeComponent.cpp b/Source/UnrealEngineBridge/BridgeComponent.cpp deleted file mode 100644 index 44b725a..0000000 --- a/Source/UnrealEngineBridge/BridgeComponent.cpp +++ /dev/null @@ -1,148 +0,0 @@ -// BridgeComponent.cpp -// Phase 4: Thin relay — all game flow logic lives in UUEBridgeSubsystem. -// This component binds subsystem delegates to legacy component delegates -// so existing Blueprints continue to work without modification. - -#include "BridgeComponent.h" -#include "UEBridgeRuntime.h" -#include "UEBridgeSubsystem.h" -#include "BridgeTypes.h" -#include "Engine/GameInstance.h" - - -UBridgeComponent::UBridgeComponent() -{ - PrimaryComponentTick.bCanEverTick = false; // No longer needs tick — subsystem handles polling -} - - -void UBridgeComponent::BeginPlay() -{ - Super::BeginPlay(); - - // Get the subsystem - UGameInstance* GI = GetWorld() ? GetWorld()->GetGameInstance() : nullptr; - if (GI) - { - BridgeSubsystem = GI->GetSubsystem(); - } - - if (!BridgeSubsystem) - { - UE_LOG(LogUEBridge, Error, TEXT("[BridgeComponent] Could not find UUEBridgeSubsystem — is the UEBridge plugin enabled?")); - return; - } - - // Forward config overrides to subsystem - BridgeSubsystem->bVerboseLogging = bVerboseLogging; - - // Bind subsystem delegates to our legacy delegates - BridgeSubsystem->OnBridgeReady.AddDynamic(this, &UBridgeComponent::OnSubsystemBridgeReady); - BridgeSubsystem->OnQuestionReady.AddDynamic(this, &UBridgeComponent::OnSubsystemQuestionReady); - BridgeSubsystem->OnTransitionReady.AddDynamic(this, &UBridgeComponent::OnSubsystemTransitionReady); - BridgeSubsystem->OnProfileComplete.AddDynamic(this, &UBridgeComponent::OnSubsystemProfileComplete); - BridgeSubsystem->OnUsdProfileUpdated.AddDynamic(this, &UBridgeComponent::OnSubsystemUsdProfileUpdated); - - // Start the bridge - BridgeSubsystem->StartGame(); -} - - -void UBridgeComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) -{ - if (BridgeSubsystem) - { - // Unbind our delegates - BridgeSubsystem->OnBridgeReady.RemoveDynamic(this, &UBridgeComponent::OnSubsystemBridgeReady); - BridgeSubsystem->OnQuestionReady.RemoveDynamic(this, &UBridgeComponent::OnSubsystemQuestionReady); - BridgeSubsystem->OnTransitionReady.RemoveDynamic(this, &UBridgeComponent::OnSubsystemTransitionReady); - BridgeSubsystem->OnProfileComplete.RemoveDynamic(this, &UBridgeComponent::OnSubsystemProfileComplete); - BridgeSubsystem->OnUsdProfileUpdated.RemoveDynamic(this, &UBridgeComponent::OnSubsystemUsdProfileUpdated); - - BridgeSubsystem->StopGame(); - BridgeSubsystem = nullptr; - } - - Super::EndPlay(EndPlayReason); -} - - -// === BLUEPRINT CALLABLE (forwarded to subsystem) === - -void UBridgeComponent::SendAcknowledge() -{ - if (BridgeSubsystem) - { - BridgeSubsystem->SendAcknowledge(); - } -} - - -void UBridgeComponent::SendAnswer(const FString& QuestionId, int32 OptionIndex, float ResponseTimeMs) -{ - if (BridgeSubsystem) - { - BridgeSubsystem->SubmitAnswer(QuestionId, OptionIndex, ResponseTimeMs); - } -} - - -FUEBridgeQuestion UBridgeComponent::GetCurrentQuestion() const -{ - return BridgeSubsystem ? BridgeSubsystem->GetCurrentQuestion() : FUEBridgeQuestion(); -} - - -FUEBridgeProfile UBridgeComponent::ParseCognitiveProfile(const FString& UsdPath) -{ - return BridgeSubsystem ? BridgeSubsystem->ParseCognitiveProfile(UsdPath) : FUEBridgeProfile(); -} - - -bool UBridgeComponent::IsBridgeConnected() const -{ - return BridgeSubsystem ? BridgeSubsystem->IsBridgeConnected() : false; -} - - -bool UBridgeComponent::IsUsingUsdMode() const -{ - return BridgeSubsystem ? BridgeSubsystem->IsUsingUsdMode() : false; -} - - -// === SUBSYSTEM DELEGATE HANDLERS === - -void UBridgeComponent::OnSubsystemBridgeReady(int32 TotalQuestions) -{ - OnBridgeReady.Broadcast(TotalQuestions); -} - - -void UBridgeComponent::OnSubsystemQuestionReady(const FUEBridgeQuestion& Question) -{ - // Build legacy JSON string for backward-compatible delegate - FString QuestionJson = FString::Printf( - TEXT("{\"type\":\"question\",\"index\":%d,\"total\":%d,\"id\":\"%s\",\"text\":\"%s\",\"scene\":\"%s\"}"), - Question.Index, Question.Total, *Question.QuestionId, *Question.Text, *Question.Scene); - - OnQuestionReceived.Broadcast(QuestionJson); -} - - -void UBridgeComponent::OnSubsystemTransitionReady(const FString& Direction, const FString& NextScene, float Progress) -{ - OnTransitionReceived.Broadcast(Direction, NextScene); -} - - -void UBridgeComponent::OnSubsystemProfileComplete(const FUEBridgeProfile& Profile, const FString& UsdPath) -{ - OnFinaleReceived.Broadcast(UsdPath); -} - - -void UBridgeComponent::OnSubsystemUsdProfileUpdated(const FString& UpdatedFilePath) -{ - OnUsdUpdated.Broadcast(); -} diff --git a/Source/UnrealEngineBridge/BridgeComponent.h b/Source/UnrealEngineBridge/BridgeComponent.h deleted file mode 100644 index 27ff0ef..0000000 --- a/Source/UnrealEngineBridge/BridgeComponent.h +++ /dev/null @@ -1,123 +0,0 @@ -// BridgeComponent.h -// Actor component relay for the UE Bridge. -// -// Phase 4: Game flow logic migrated to UUEBridgeSubsystem. -// This component is now a thin Blueprint-bindable relay that: -// - Gets the subsystem on BeginPlay and calls StartGame() -// - Forwards subsystem delegates to legacy component delegates -// - Provides deprecated wrapper functions for backward compatibility -// -// New Blueprints should bind directly to UUEBridgeSubsystem. - -#pragma once - -#include "CoreMinimal.h" -#include "Components/ActorComponent.h" -#include "BridgeTypes.h" -#include "BridgeComponent.generated.h" - -// Forward declarations -class UUEBridgeSubsystem; - -// ============================================================================ -// Legacy delegates (v1 Blueprint compatibility) -// New code should use the typed delegates on UUEBridgeSubsystem. -// ============================================================================ - -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnQuestionReceived, const FString&, QuestionJson); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnTransitionReceived, const FString&, Direction, const FString&, NextScene); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnFinaleReceived, const FString&, UsdPath); -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnUsdUpdated); -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnLegacyBridgeReady, int32, TotalQuestions); - - -UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent, DisplayName="UE Bridge", ToolTip="Relay component — delegates to UUEBridgeSubsystem for game flow")) -class UNREALENGINEBRIDGE_API UBridgeComponent : public UActorComponent -{ - GENERATED_BODY() - -public: - UBridgeComponent(); - - virtual void BeginPlay() override; - virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; - - // === LEGACY DELEGATES (Bind in Blueprint) === - - /** Fired when a new question arrives (raw JSON for backward compat) */ - UPROPERTY(BlueprintAssignable, Category = "UE Bridge", meta = (ToolTip = "Fires when a new question arrives (legacy JSON)")) - FOnQuestionReceived OnQuestionReceived; - - /** Fired when Python sends a transition command */ - UPROPERTY(BlueprintAssignable, Category = "UE Bridge", meta = (ToolTip = "Fires on scene transitions")) - FOnTransitionReceived OnTransitionReceived; - - /** Fired when questionnaire completes */ - UPROPERTY(BlueprintAssignable, Category = "UE Bridge", meta = (ToolTip = "Fires when profile is complete")) - FOnFinaleReceived OnFinaleReceived; - - /** Fired when cognitive_substrate.usda updates */ - UPROPERTY(BlueprintAssignable, Category = "UE Bridge", meta = (ToolTip = "Fires when USD profile file changes")) - FOnUsdUpdated OnUsdUpdated; - - /** Fired when Python bridge is ready */ - UPROPERTY(BlueprintAssignable, Category = "UE Bridge", meta = (ToolTip = "Fires when Python bridge connects")) - FOnLegacyBridgeReady OnBridgeReady; - - // === BLUEPRINT CALLABLE (forwarded to subsystem) === - - /** Send acknowledgment (delegates to subsystem) */ - UFUNCTION(BlueprintCallable, Category = "UE Bridge", meta = (ToolTip = "Send acknowledgment to Python bridge", DeprecatedFunction, DeprecationMessage = "Use UUEBridgeSubsystem::SendAcknowledge instead")) - void SendAcknowledge(); - - /** Send answer (delegates to subsystem) */ - UFUNCTION(BlueprintCallable, Category = "UE Bridge", meta = (ToolTip = "Send answer to Python bridge", DeprecatedFunction, DeprecationMessage = "Use UUEBridgeSubsystem::SubmitAnswer instead")) - void SendAnswer(const FString& QuestionId, int32 OptionIndex, float ResponseTimeMs); - - /** Get the current question from subsystem */ - UFUNCTION(BlueprintCallable, Category = "UE Bridge", meta = (ToolTip = "Get the currently active question")) - FUEBridgeQuestion GetCurrentQuestion() const; - - /** Parse cognitive profile (delegates to subsystem) */ - UFUNCTION(BlueprintCallable, Category = "UE Bridge", meta = (ToolTip = "Parse a cognitive profile from a .usda file")) - FUEBridgeProfile ParseCognitiveProfile(const FString& UsdPath); - - /** Check if bridge is connected */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "UE Bridge", meta = (ToolTip = "True if Python bridge is connected")) - bool IsBridgeConnected() const; - - /** Check if using USD mode */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "UE Bridge", meta = (ToolTip = "True if using USD-native transport")) - bool IsUsingUsdMode() const; - - // === CONFIGURATION === - - /** Bridge directory path override. Leave empty to use default (~/.translators) */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UE Bridge", meta = (ToolTip = "Path to the bridge exchange directory (empty = default)")) - FString BridgePath; - - /** Enable verbose logging */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UE Bridge", meta = (ToolTip = "Show detailed bridge logs on screen")) - bool bVerboseLogging = false; - -private: - /** Cached subsystem pointer (valid between BeginPlay and EndPlay) */ - UPROPERTY() - TObjectPtr BridgeSubsystem; - - // Subsystem delegate handlers - UFUNCTION() - void OnSubsystemBridgeReady(int32 TotalQuestions); - - UFUNCTION() - void OnSubsystemQuestionReady(const FUEBridgeQuestion& Question); - - UFUNCTION() - void OnSubsystemTransitionReady(const FString& Direction, const FString& NextScene, float Progress); - - UFUNCTION() - void OnSubsystemProfileComplete(const FUEBridgeProfile& Profile, const FString& UsdPath); - - UFUNCTION() - void OnSubsystemUsdProfileUpdated(const FString& UpdatedFilePath); -}; diff --git a/Source/UnrealEngineBridge/UI/UEBridgeGameMode.cpp b/Source/UnrealEngineBridge/UI/UEBridgeGameMode.cpp deleted file mode 100644 index 71dd022..0000000 --- a/Source/UnrealEngineBridge/UI/UEBridgeGameMode.cpp +++ /dev/null @@ -1,52 +0,0 @@ -// UEBridgeGameMode.cpp -// Implementation of game mode - -#include "UEBridgeGameMode.h" -#include "UEBridgeRuntime.h" -#include "UEBridgeHUD.h" -#include "../BridgeComponent.h" -#include "GameFramework/PlayerController.h" -#include "Engine/World.h" - - -AUEBridgeGameMode::AUEBridgeGameMode() -{ - // Set default HUD class - HUDClass = AUEBridgeHUD::StaticClass(); - - // No pawn needed for questionnaire game - DefaultPawnClass = nullptr; - - // Use default player controller - PlayerControllerClass = APlayerController::StaticClass(); - - BridgeActor = nullptr; - - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeGameMode] Constructed with UEBridgeHUD")); -} - - -void AUEBridgeGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) -{ - Super::InitGame(MapName, Options, ErrorMessage); - - // Auto-spawn BridgeActor so HUD can find BridgeComponent without manual placement - UWorld* World = GetWorld(); - if (World) - { - FActorSpawnParameters SpawnParams; - SpawnParams.Name = FName(TEXT("UEBridgeActor")); - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - - BridgeActor = World->SpawnActor(AActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams); - if (BridgeActor) - { - UBridgeComponent* Bridge = NewObject(BridgeActor, TEXT("BridgeComponent")); - Bridge->bVerboseLogging = true; - Bridge->RegisterComponent(); - BridgeActor->AddInstanceComponent(Bridge); - - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeGameMode] Auto-spawned BridgeActor with BridgeComponent")); - } - } -} diff --git a/Source/UnrealEngineBridge/UI/UEBridgeGameMode.h b/Source/UnrealEngineBridge/UI/UEBridgeGameMode.h deleted file mode 100644 index 6a3b97a..0000000 --- a/Source/UnrealEngineBridge/UI/UEBridgeGameMode.h +++ /dev/null @@ -1,33 +0,0 @@ -// UEBridgeGameMode.h -// Game mode for The UnrealEngine Bridge -// Part of The UnrealEngine Bridge - Claude Code → UE5.7 Bridge - -#pragma once - -#include "CoreMinimal.h" -#include "GameFramework/GameModeBase.h" -#include "UEBridgeGameMode.generated.h" - -/** - * AUEBridgeGameMode - Default game mode for UEBridge - * - * Sets up: - * - UEBridgeHUD as default HUD class - * - Default pawn (none needed for questionnaire) - * - Auto-spawns BridgeActor with BridgeComponent (no manual placement needed) - */ -UCLASS() -class UNREALENGINEBRIDGE_API AUEBridgeGameMode : public AGameModeBase -{ - GENERATED_BODY() - -public: - AUEBridgeGameMode(); - - virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; - -private: - /** The auto-spawned bridge actor */ - UPROPERTY() - AActor* BridgeActor; -}; diff --git a/Source/UnrealEngineBridge/UI/UEBridgeHUD.cpp b/Source/UnrealEngineBridge/UI/UEBridgeHUD.cpp deleted file mode 100644 index 6ddff07..0000000 --- a/Source/UnrealEngineBridge/UI/UEBridgeHUD.cpp +++ /dev/null @@ -1,698 +0,0 @@ -// UEBridgeHUD.cpp -// Implementation of main game HUD with title screen and profile display -// Programmatic UI - no Blueprint required - -#include "UEBridgeHUD.h" -#include "UEBridgeRuntime.h" -#include "W_QuestionDisplay.h" -#include "W_ProgressIndicator.h" -#include "W_ConnectingScreen.h" -#include "W_FinaleScreen.h" -#include "W_TitleScreen.h" -#include "../BridgeComponent.h" -#include "Blueprint/UserWidget.h" -#include "Kismet/GameplayStatics.h" -#include "Engine/World.h" -#include "GameFramework/Actor.h" -#include "GameFramework/PlayerController.h" -#include "InputCoreTypes.h" -#include "Engine/Canvas.h" - - -AUEBridgeHUD::AUEBridgeHUD() -{ - bIsBridgeConnected = false; - bIsComplete = false; - TotalQuestions = 8; - CurrentHUDState = EHUDState::Title; -} - - -void AUEBridgeHUD::BeginPlay() -{ - Super::BeginPlay(); - - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] BeginPlay - Initializing...")); - - // Find BridgeComponent - BridgeComponent = FindBridgeComponent(); - - if (BridgeComponent) - { - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Found BridgeComponent - binding events")); - - BridgeComponent->OnBridgeReady.AddDynamic(this, &AUEBridgeHUD::OnBridgeReady); - BridgeComponent->OnQuestionReceived.AddDynamic(this, &AUEBridgeHUD::OnQuestionReceived); - BridgeComponent->OnTransitionReceived.AddDynamic(this, &AUEBridgeHUD::OnTransitionReceived); - BridgeComponent->OnFinaleReceived.AddDynamic(this, &AUEBridgeHUD::OnFinaleReceived); - } - else - { - UE_LOG(LogUEBridge, Warning, TEXT("[UEBridgeHUD] BridgeComponent not found in level!")); - } - - // Create UI widgets - CreateWidgets(); - - // Start on title screen - SetHUDState(EHUDState::Title); -} - - -void AUEBridgeHUD::EndPlay(const EEndPlayReason::Type EndPlayReason) -{ - if (BridgeComponent) - { - BridgeComponent->OnBridgeReady.RemoveAll(this); - BridgeComponent->OnQuestionReceived.RemoveAll(this); - BridgeComponent->OnTransitionReceived.RemoveAll(this); - BridgeComponent->OnFinaleReceived.RemoveAll(this); - } - - if (QuestionWidget) - { - QuestionWidget->OnAnswerSelected.RemoveAll(this); - QuestionWidget->RemoveFromParent(); - } - if (ConnectingWidget) - { - ConnectingWidget->RemoveFromParent(); - } - if (FinaleWidget) - { - FinaleWidget->RemoveFromParent(); - } - if (TitleWidget) - { - TitleWidget->OnStartRequested.RemoveAll(this); - TitleWidget->RemoveFromParent(); - } - - Super::EndPlay(EndPlayReason); -} - - -UBridgeComponent* AUEBridgeHUD::FindBridgeComponent() -{ - TArray AllActors; - UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor::StaticClass(), AllActors); - - for (AActor* Actor : AllActors) - { - UBridgeComponent* Bridge = Actor->FindComponentByClass(); - if (Bridge) - { - return Bridge; - } - } - - // Fallback: spawn a dedicated actor with BridgeComponent - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] No BridgeComponent found - spawning BridgeActor")); - UWorld* World = GetWorld(); - if (World) - { - FActorSpawnParameters SpawnParams; - SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; - AActor* BridgeActor = World->SpawnActor(AActor::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator, SpawnParams); - if (BridgeActor) - { - UBridgeComponent* Bridge = NewObject(BridgeActor, TEXT("BridgeComponent")); - Bridge->bVerboseLogging = true; - BridgeActor->AddInstanceComponent(Bridge); - Bridge->RegisterComponent(); - return Bridge; - } - } - - return nullptr; -} - - -void AUEBridgeHUD::CreateWidgets() -{ - APlayerController* PC = GetOwningPlayerController(); - if (!PC) - { - UE_LOG(LogUEBridge, Warning, TEXT("[UEBridgeHUD] No PlayerController - cannot create widgets")); - return; - } - - // Create title screen widget (z-order 40 - on top of everything) - if (TitleWidgetClass) - { - TitleWidget = CreateWidget(PC, TitleWidgetClass); - } - else - { - TitleWidget = CreateWidget(PC, UW_TitleScreen::StaticClass()); - } - if (TitleWidget) - { - TitleWidget->AddToViewport(40); - TitleWidget->OnStartRequested.AddDynamic(this, &AUEBridgeHUD::OnTitleStartRequested); - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Created TitleWidget")); - } - - // Create question display widget - if (QuestionDisplayClass) - { - QuestionWidget = CreateWidget(PC, QuestionDisplayClass); - } - else - { - QuestionWidget = CreateWidget(PC, UW_QuestionDisplay::StaticClass()); - } - if (QuestionWidget) - { - QuestionWidget->AddToViewport(10); - QuestionWidget->SetVisibility(ESlateVisibility::Hidden); - QuestionWidget->SetRenderOpacity(0.0f); - QuestionWidget->OnAnswerSelected.AddDynamic(this, &AUEBridgeHUD::OnAnswerSelected); - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Created QuestionWidget")); - } - - // Create connecting widget - if (ConnectingWidgetClass) - { - ConnectingWidget = CreateWidget(PC, ConnectingWidgetClass); - } - else - { - ConnectingWidget = CreateWidget(PC, UW_ConnectingScreen::StaticClass()); - } - if (ConnectingWidget) - { - ConnectingWidget->AddToViewport(20); - ConnectingWidget->SetVisibility(ESlateVisibility::Hidden); - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Created ConnectingWidget")); - } - - // Create finale widget - if (FinaleWidgetClass) - { - FinaleWidget = CreateWidget(PC, FinaleWidgetClass); - } - else - { - FinaleWidget = CreateWidget(PC, UW_FinaleScreen::StaticClass()); - } - if (FinaleWidget) - { - FinaleWidget->AddToViewport(30); - FinaleWidget->SetVisibility(ESlateVisibility::Hidden); - FinaleWidget->SetRenderOpacity(0.0f); - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Created FinaleWidget")); - } -} - - -// === STATE MANAGEMENT === - -void AUEBridgeHUD::SetHUDState(EHUDState NewState) -{ - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] State transition: %d -> %d"), (uint8)CurrentHUDState, (uint8)NewState); - CurrentHUDState = NewState; - - switch (NewState) - { - case EHUDState::Title: - ShowTitleScreen(); - break; - case EHUDState::Connecting: - ShowConnectingScreen(); - break; - case EHUDState::Questions: - ShowQuestionScreen(); - break; - case EHUDState::Finale: - // ShowFinaleScreen is called directly with message - break; - } -} - - -void AUEBridgeHUD::ShowTitleScreen() -{ - if (TitleWidget) - { - TitleWidget->SetVisibility(ESlateVisibility::Visible); - TitleWidget->SetRenderOpacity(1.0f); - } - if (ConnectingWidget) - { - ConnectingWidget->SetVisibility(ESlateVisibility::Hidden); - } - if (QuestionWidget) - { - QuestionWidget->SetVisibility(ESlateVisibility::Hidden); - } - if (FinaleWidget) - { - FinaleWidget->SetVisibility(ESlateVisibility::Hidden); - } - - // Use GameAndUI input mode so PC->WasInputKeyJustPressed works for Enter detection - APlayerController* PC = GetOwningPlayerController(); - if (PC) - { - FInputModeGameAndUI InputMode; - InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); - PC->SetInputMode(InputMode); - PC->bShowMouseCursor = false; - } -} - - -void AUEBridgeHUD::ShowConnectingScreen() -{ - if (TitleWidget) - { - TitleWidget->SetVisibility(ESlateVisibility::Hidden); - } - if (ConnectingWidget) - { - ConnectingWidget->SetVisibility(ESlateVisibility::Visible); - ConnectingWidget->SetRenderOpacity(1.0f); - } - if (QuestionWidget) - { - QuestionWidget->SetVisibility(ESlateVisibility::Hidden); - } - if (FinaleWidget) - { - FinaleWidget->SetVisibility(ESlateVisibility::Hidden); - } -} - - -void AUEBridgeHUD::ShowQuestionScreen() -{ - if (TitleWidget) - { - TitleWidget->SetVisibility(ESlateVisibility::Hidden); - } - if (ConnectingWidget) - { - ConnectingWidget->SetVisibility(ESlateVisibility::Hidden); - } - if (QuestionWidget) - { - QuestionWidget->SetVisibility(ESlateVisibility::Visible); - } - if (FinaleWidget) - { - FinaleWidget->SetVisibility(ESlateVisibility::Hidden); - } - - // Enable mouse cursor for UI interaction - APlayerController* PC = GetOwningPlayerController(); - if (PC) - { - PC->bShowMouseCursor = true; - FInputModeGameAndUI InputMode; - InputMode.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); - PC->SetInputMode(InputMode); - } -} - - -void AUEBridgeHUD::ShowFinaleScreen(const FString& Message) -{ - TransitionState = EHUDTransition::None; - CurrentHUDState = EHUDState::Finale; - - if (TitleWidget) - { - TitleWidget->SetVisibility(ESlateVisibility::Hidden); - } - if (ConnectingWidget) - { - ConnectingWidget->SetVisibility(ESlateVisibility::Hidden); - } - if (QuestionWidget) - { - QuestionWidget->SetVisibility(ESlateVisibility::Hidden); - } - if (FinaleWidget) - { - FinaleWidget->SetVisibility(ESlateVisibility::Visible); - FinaleWidget->SetRenderOpacity(1.0f); - } -} - - -void AUEBridgeHUD::SendAcknowledgment() -{ - if (BridgeComponent) - { - BridgeComponent->SendAcknowledge(); - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Sent acknowledgment")); - } -} - - -// === EVENT HANDLERS === - -void AUEBridgeHUD::OnTitleStartRequested() -{ - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Title -> Connecting")); - SetHUDState(EHUDState::Connecting); - - // If bridge is already connected (orchestrator started before Enter was pressed) - if (BridgeComponent && BridgeComponent->IsBridgeConnected()) - { - OnBridgeReady(TotalQuestions); - } -} - - -void AUEBridgeHUD::OnBridgeReady(int32 InTotalQuestions) -{ - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Bridge ready! Total questions: %d"), InTotalQuestions); - - bIsBridgeConnected = true; - TotalQuestions = InTotalQuestions; - - SendAcknowledgment(); - - // If still on title, don't auto-transition (wait for Enter) - // If on connecting, check for existing question - if (CurrentHUDState == EHUDState::Connecting) - { - FUEBridgeQuestion Q = BridgeComponent->GetCurrentQuestion(); - if (!Q.QuestionId.IsEmpty()) - { - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Catching up - bridge already has question: %s"), *Q.QuestionId); - CurrentQuestion = Q; - QuestionStartTime = GetWorld()->GetTimeSeconds(); - if (QuestionWidget) - { - QuestionWidget->ShowQuestion(CurrentQuestion); - QuestionWidget->SetRenderOpacity(1.0f); - } - SetHUDState(EHUDState::Questions); - } - } -} - - -void AUEBridgeHUD::OnQuestionReceived(const FString& QuestionJson) -{ - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Question received")); - - if (!BridgeComponent) - { - return; - } - - CurrentQuestion = BridgeComponent->GetCurrentQuestion(); - QuestionStartTime = GetWorld()->GetTimeSeconds(); - - // Load question content into widget - if (QuestionWidget) - { - QuestionWidget->ShowQuestion(CurrentQuestion); - } - - // If we're still on connecting/title, transition to questions - if (CurrentHUDState == EHUDState::Connecting || CurrentHUDState == EHUDState::Title) - { - if (QuestionWidget) - { - QuestionWidget->SetRenderOpacity(1.0f); - } - SetHUDState(EHUDState::Questions); - } - // If we're waiting for next question (mid-transition), start fade-in - else if (TransitionState == EHUDTransition::WaitForNext) - { - TransitionState = EHUDTransition::FadeIn; - TransitionTimer = 0.0f; - ShowQuestionScreen(); - if (QuestionWidget) - { - QuestionWidget->SetRenderOpacity(0.0f); - } - } - else - { - // Already in questions state, show immediately - ShowQuestionScreen(); - if (QuestionWidget) - { - QuestionWidget->SetRenderOpacity(1.0f); - } - } - - // Debug overlay - if (GEngine) - { - GEngine->AddOnScreenDebugMessage(1, 15.0f, FColor::Green, - FString::Printf(TEXT("Q%d/%d [%s]: %s"), - CurrentQuestion.Index + 1, CurrentQuestion.Total, - *CurrentQuestion.DepthLabel, *CurrentQuestion.QuestionId)); - } - - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Displaying question %d/%d [%s]: %s"), - CurrentQuestion.Index + 1, CurrentQuestion.Total, - *CurrentQuestion.DepthLabel, *CurrentQuestion.QuestionId); -} - - -void AUEBridgeHUD::OnTransitionReceived(const FString& Direction, const FString& NextScene) -{ - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Transition: %s -> %s"), *Direction, *NextScene); - - if (GEngine) - { - GEngine->AddOnScreenDebugMessage(2, 3.0f, FColor::Cyan, - FString::Printf(TEXT("-> %s"), *NextScene)); - } -} - - -void AUEBridgeHUD::OnFinaleReceived(const FString& UsdPath) -{ - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Finale! USD path: %s"), *UsdPath); - - bIsComplete = true; - - UW_FinaleScreen* FinaleScreen = Cast(FinaleWidget); - if (FinaleScreen) - { - FinaleScreen->SetUsdPath(UsdPath); - - // Parse and display the cognitive profile - if (BridgeComponent) - { - FUEBridgeProfile Profile = BridgeComponent->ParseCognitiveProfile(UsdPath); - if (Profile.IsValid()) - { - FinaleScreen->DisplayProfile(Profile); - } - } - } - - ShowFinaleScreen(TEXT("Your cognitive profile is complete.")); -} - - -// === HUD CANVAS DRAWING (bypasses UMG - fallback overlay) === - -void AUEBridgeHUD::DrawHUD() -{ - Super::DrawHUD(); - - // The UMG widgets handle the primary UI. This DrawHUD provides - // a minimal fallback overlay for debugging. - - if (!Canvas) - { - return; - } - - // Only draw debug info if in question state - if (CurrentHUDState == EHUDState::Questions && !CurrentQuestion.QuestionId.IsEmpty()) - { - // Small debug text in top-left - FString DebugStr = FString::Printf(TEXT("Q%d/%d [%s]"), - CurrentQuestion.Index + 1, CurrentQuestion.Total, *CurrentQuestion.DepthLabel); - DrawText(DebugStr, FColor(80, 80, 100), 10, 10); - } -} - - -// === TICK & TRANSITIONS === - -void AUEBridgeHUD::Tick(float DeltaSeconds) -{ - Super::Tick(DeltaSeconds); - UpdateTransition(DeltaSeconds); - HandleKeyInput(); -} - - -void AUEBridgeHUD::UpdateTransition(float DeltaSeconds) -{ - if (TransitionState == EHUDTransition::None) - { - return; - } - - TransitionTimer += DeltaSeconds; - - switch (TransitionState) - { - case EHUDTransition::AnswerHold: - { - if (TransitionTimer >= ANSWER_HOLD_TIME) - { - // Now send the answer to bridge - if (BridgeComponent && PendingAnswerIndex >= 0) - { - float ResponseTimeMs = (GetWorld()->GetTimeSeconds() - QuestionStartTime) * 1000.0f; - BridgeComponent->SendAnswer(CurrentQuestion.QuestionId, PendingAnswerIndex, ResponseTimeMs); - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Sent deferred answer: option %d (%.0fms)"), PendingAnswerIndex, ResponseTimeMs); - PendingAnswerIndex = -1; - } - - TransitionState = EHUDTransition::FadeOut; - TransitionTimer = 0.0f; - } - break; - } - - case EHUDTransition::FadeOut: - { - float Alpha = FMath::Clamp(1.0f - (TransitionTimer / FADE_DURATION), 0.0f, 1.0f); - if (QuestionWidget) - { - QuestionWidget->SetRenderOpacity(Alpha); - } - - if (TransitionTimer >= FADE_DURATION) - { - if (QuestionWidget) - { - QuestionWidget->SetRenderOpacity(0.0f); - } - TransitionState = EHUDTransition::WaitForNext; - TransitionTimer = 0.0f; - } - break; - } - - case EHUDTransition::WaitForNext: - { - // Safety timeout - if (TransitionTimer > 10.0f) - { - UE_LOG(LogUEBridge, Warning, TEXT("[UEBridgeHUD] Transition timeout - returning to visible")); - if (QuestionWidget) - { - QuestionWidget->SetRenderOpacity(1.0f); - } - TransitionState = EHUDTransition::None; - } - break; - } - - case EHUDTransition::FadeIn: - { - float Alpha = FMath::Clamp(TransitionTimer / FADE_DURATION, 0.0f, 1.0f); - if (QuestionWidget) - { - QuestionWidget->SetRenderOpacity(Alpha); - } - - if (TransitionTimer >= FADE_DURATION) - { - if (QuestionWidget) - { - QuestionWidget->SetRenderOpacity(1.0f); - } - TransitionState = EHUDTransition::None; - QuestionStartTime = GetWorld()->GetTimeSeconds(); - } - break; - } - - default: - break; - } -} - - -void AUEBridgeHUD::HandleKeyInput() -{ - APlayerController* PC = GetOwningPlayerController(); - if (!PC) - { - return; - } - - // Title state: Enter or Space to start - if (CurrentHUDState == EHUDState::Title) - { - if (PC->WasInputKeyJustPressed(EKeys::Enter) || PC->WasInputKeyJustPressed(EKeys::SpaceBar)) - { - OnTitleStartRequested(); - } - return; - } - - // Only accept number key input during question state with no transition - if (CurrentHUDState != EHUDState::Questions) - { - return; - } - - if (TransitionState != EHUDTransition::None) - { - return; - } - - if (!QuestionWidget || QuestionWidget->GetVisibility() != ESlateVisibility::Visible) - { - return; - } - if (QuestionWidget->GetSelectedOptionIndex() != -1) - { - return; - } - - // Guard: ignore input for 0.5s after question appears - float TimeSinceQuestion = GetWorld()->GetTimeSeconds() - QuestionStartTime; - if (TimeSinceQuestion < 0.5f) - { - return; - } - - if (PC->WasInputKeyJustPressed(EKeys::One) || PC->WasInputKeyJustPressed(EKeys::NumPadOne)) - { - OnAnswerSelected(0); - } - else if (PC->WasInputKeyJustPressed(EKeys::Two) || PC->WasInputKeyJustPressed(EKeys::NumPadTwo)) - { - OnAnswerSelected(1); - } - else if (PC->WasInputKeyJustPressed(EKeys::Three) || PC->WasInputKeyJustPressed(EKeys::NumPadThree)) - { - OnAnswerSelected(2); - } -} - - -void AUEBridgeHUD::OnAnswerSelected(int32 OptionIndex) -{ - if (TransitionState != EHUDTransition::None) - { - return; - } - - UE_LOG(LogUEBridge, Log, TEXT("[UEBridgeHUD] Answer selected: option %d"), OptionIndex); - - PendingAnswerIndex = OptionIndex; - TransitionState = EHUDTransition::AnswerHold; - TransitionTimer = 0.0f; -} diff --git a/Source/UnrealEngineBridge/UI/UEBridgeHUD.h b/Source/UnrealEngineBridge/UI/UEBridgeHUD.h deleted file mode 100644 index 5d603a6..0000000 --- a/Source/UnrealEngineBridge/UI/UEBridgeHUD.h +++ /dev/null @@ -1,191 +0,0 @@ -// UEBridgeHUD.h -// Main HUD class connecting BridgeComponent to UI widgets -// Part of The UnrealEngine Bridge - Claude Code -> UE5.7 Bridge - -#pragma once - -#include "CoreMinimal.h" -#include "GameFramework/HUD.h" -#include "../BridgeComponent.h" -#include "UEBridgeHUD.generated.h" - -// Forward declarations -class UW_QuestionDisplay; -class UW_TitleScreen; -class UW_FinaleScreen; -class UW_ProgressIndicator; -class UUserWidget; - -/** - * AUEBridgeHUD - Main game HUD - * - * Manages the full game flow: Title -> Connecting -> Questions -> Finale - * - * Responsibilities: - * 1. Find and connect to BridgeComponent - * 2. Create and manage UI widgets - * 3. Handle BridgeComponent events - * 4. Track response timing - * 5. Send answers back to bridge - * 6. Parse and display cognitive profile on finale - */ -UCLASS() -class UNREALENGINEBRIDGE_API AUEBridgeHUD : public AHUD -{ - GENERATED_BODY() - -public: - AUEBridgeHUD(); - - // === CONFIGURATION === - - /** Widget class for question display (set in Blueprint child) */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UEBridge|Config") - TSubclassOf QuestionDisplayClass; - - /** Widget class for connecting message */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UEBridge|Config") - TSubclassOf ConnectingWidgetClass; - - /** Widget class for finale screen */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UEBridge|Config") - TSubclassOf FinaleWidgetClass; - - /** Widget class for title screen */ - UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UEBridge|Config") - TSubclassOf TitleWidgetClass; - - // === STATE === - - /** Is the bridge connected? */ - UPROPERTY(BlueprintReadOnly, Category = "UEBridge|State") - bool bIsBridgeConnected = false; - - /** Is the questionnaire complete? */ - UPROPERTY(BlueprintReadOnly, Category = "UEBridge|State") - bool bIsComplete = false; - - // === FUNCTIONS === - - /** Manually trigger acknowledgment (call from Blueprint if needed) */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Bridge") - void SendAcknowledgment(); - - virtual void Tick(float DeltaSeconds) override; - virtual void DrawHUD() override; - -protected: - virtual void BeginPlay() override; - virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; - - // === EVENT HANDLERS === - - /** Called when bridge is ready */ - UFUNCTION() - void OnBridgeReady(int32 TotalQuestions); - - /** Called when a question is received */ - UFUNCTION() - void OnQuestionReceived(const FString& QuestionJson); - - /** Called when transition is received */ - UFUNCTION() - void OnTransitionReceived(const FString& Direction, const FString& NextScene); - - /** Called when finale is received */ - UFUNCTION() - void OnFinaleReceived(const FString& UsdPath); - - /** Called when user selects an answer */ - UFUNCTION() - void OnAnswerSelected(int32 OptionIndex); - - /** Called when user presses Enter on title screen */ - UFUNCTION() - void OnTitleStartRequested(); - - // === WIDGETS === - - UPROPERTY() - UW_QuestionDisplay* QuestionWidget; - - UPROPERTY() - UUserWidget* ConnectingWidget; - - UPROPERTY() - UUserWidget* FinaleWidget; - - UPROPERTY() - UW_TitleScreen* TitleWidget; - -private: - /** High-level game screen states */ - enum class EHUDState : uint8 - { - Title, // Title screen - waiting for Enter - Connecting, // Waiting for bridge connection - Questions, // Answering questions - Finale // Profile results - }; - - /** Find BridgeComponent in the world */ - UBridgeComponent* FindBridgeComponent(); - - /** Create UI widgets */ - void CreateWidgets(); - - /** Transition to a new HUD state */ - void SetHUDState(EHUDState NewState); - - /** Show title screen */ - void ShowTitleScreen(); - - /** Show connecting message */ - void ShowConnectingScreen(); - - /** Hide connecting, show question */ - void ShowQuestionScreen(); - - /** Show finale screen */ - void ShowFinaleScreen(const FString& Message); - - /** Update transition animation in Tick */ - void UpdateTransition(float DeltaSeconds); - - /** Reference to bridge component */ - UPROPERTY() - UBridgeComponent* BridgeComponent; - - /** Handle keyboard input for option selection */ - void HandleKeyInput(); - - /** Current HUD state */ - EHUDState CurrentHUDState = EHUDState::Title; - - /** Time when current question was shown */ - float QuestionStartTime = 0.0f; - - /** Current question data */ - FUEBridgeQuestion CurrentQuestion; - - /** Total questions for progress tracking */ - int32 TotalQuestions = 8; - - // === TRANSITION STATE === - - enum class EHUDTransition : uint8 - { - None, // Idle - accepting input - AnswerHold, // Brief hold showing selected answer (0.7s) - FadeOut, // Fading out question widget (0.3s) - WaitForNext, // Waiting for next question from bridge - FadeIn // Fading in new question (0.3s) - }; - - EHUDTransition TransitionState = EHUDTransition::None; - float TransitionTimer = 0.0f; - int32 PendingAnswerIndex = -1; - - static constexpr float ANSWER_HOLD_TIME = 0.7f; - static constexpr float FADE_DURATION = 0.3f; -}; diff --git a/Source/UnrealEngineBridge/UI/W_ConnectingScreen.cpp b/Source/UnrealEngineBridge/UI/W_ConnectingScreen.cpp deleted file mode 100644 index 8b01909..0000000 --- a/Source/UnrealEngineBridge/UI/W_ConnectingScreen.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// W_ConnectingScreen.cpp -// Implementation of connecting screen widget -// Programmatic UI - no Blueprint required - -#include "W_ConnectingScreen.h" -#include "UEBridgeRuntime.h" -#include "Components/TextBlock.h" -#include "Components/Border.h" -#include "Blueprint/WidgetTree.h" -#include "UEBridgeStyle.h" - - -UW_ConnectingScreen::UW_ConnectingScreen(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - // 8-bit color scheme - BackgroundColor = FUEBridgeStyle::GetColor("Color.Background"); - TextColor = FUEBridgeStyle::GetColor("Color.TextDim"); -} - - -TSharedRef UW_ConnectingScreen::RebuildWidget() -{ - if (!StatusText) - { - BuildWidgetTree(); - } - return Super::RebuildWidget(); -} - - -void UW_ConnectingScreen::NativeConstruct() -{ - Super::NativeConstruct(); - - // Apply colors - if (BackgroundBorder) - { - BackgroundBorder->SetBrushColor(BackgroundColor); - } - if (StatusText) - { - StatusText->SetColorAndOpacity(FSlateColor(TextColor)); - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_ConnectingScreen] Constructed (Programmatic UI)")); -} - - -void UW_ConnectingScreen::SetStatusText(const FString& Status) -{ - if (StatusText) - { - StatusText->SetText(FText::FromString(Status)); - } -} - - -void UW_ConnectingScreen::BuildWidgetTree() -{ - // Border root (fills viewport, centers content) - BackgroundBorder = WidgetTree->ConstructWidget(UBorder::StaticClass(), TEXT("BackgroundBorder")); - BackgroundBorder->SetBrushColor(BackgroundColor); - BackgroundBorder->SetHorizontalAlignment(HAlign_Center); - BackgroundBorder->SetVerticalAlignment(VAlign_Center); - WidgetTree->RootWidget = BackgroundBorder; - - // Centered text - StatusText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("StatusText")); - StatusText->SetText(NSLOCTEXT("UEBridge", "ConnectingScreen.Status", "Connecting to Claude Code...")); - StatusText->SetColorAndOpacity(FSlateColor(TextColor)); - StatusText->SetJustification(ETextJustify::Center); - StatusText->SetFont(FUEBridgeStyle::GetFont("Font.Question")); - BackgroundBorder->AddChild(StatusText); - - UE_LOG(LogUEBridge, Log, TEXT("[W_ConnectingScreen] Built programmatic widget tree (Border root)")); -} diff --git a/Source/UnrealEngineBridge/UI/W_ConnectingScreen.h b/Source/UnrealEngineBridge/UI/W_ConnectingScreen.h deleted file mode 100644 index bd554ce..0000000 --- a/Source/UnrealEngineBridge/UI/W_ConnectingScreen.h +++ /dev/null @@ -1,55 +0,0 @@ -// W_ConnectingScreen.h -// Simple "Connecting..." screen widget -// Part of The UnrealEngine Bridge - Claude Code -> UE5.7 Bridge - -#pragma once - -#include "CoreMinimal.h" -#include "Blueprint/UserWidget.h" -#include "W_ConnectingScreen.generated.h" - -class UTextBlock; -class UBorder; - -/** - * W_ConnectingScreen - Displayed while waiting for bridge connection - * - * Shows a simple "Connecting to Claude Code..." message - * Programmatic UI - no Blueprint required - */ -UCLASS(Blueprintable, BlueprintType) -class UNREALENGINEBRIDGE_API UW_ConnectingScreen : public UUserWidget -{ - GENERATED_BODY() - -public: - UW_ConnectingScreen(const FObjectInitializer& ObjectInitializer); - - // === STYLE === - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor BackgroundColor = FLinearColor(0.02f, 0.02f, 0.05f, 0.98f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor TextColor = FLinearColor(0.5f, 0.5f, 0.6f, 1.0f); - - // === FUNCTIONS === - - /** Update the status message */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Display") - void SetStatusText(const FString& Status); - - virtual TSharedRef RebuildWidget() override; - -protected: - virtual void NativeConstruct() override; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* StatusText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UBorder* BackgroundBorder; - -private: - void BuildWidgetTree(); -}; diff --git a/Source/UnrealEngineBridge/UI/W_FinaleScreen.cpp b/Source/UnrealEngineBridge/UI/W_FinaleScreen.cpp deleted file mode 100644 index 0a3f41a..0000000 --- a/Source/UnrealEngineBridge/UI/W_FinaleScreen.cpp +++ /dev/null @@ -1,362 +0,0 @@ -// W_FinaleScreen.cpp -// Implementation of finale screen widget with cognitive profile display -// Programmatic UI - no Blueprint required - -#include "W_FinaleScreen.h" -#include "UEBridgeRuntime.h" -#include "Components/TextBlock.h" -#include "Components/Border.h" -#include "Components/CanvasPanel.h" -#include "Components/CanvasPanelSlot.h" -#include "Components/VerticalBox.h" -#include "Components/VerticalBoxSlot.h" -#include "Components/ScrollBox.h" -#include "Components/ScrollBoxSlot.h" -#include "Components/HorizontalBox.h" -#include "Components/HorizontalBoxSlot.h" -#include "Components/Spacer.h" -#include "Blueprint/WidgetTree.h" -#include "UEBridgeStyle.h" - - -UW_FinaleScreen::UW_FinaleScreen(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - BackgroundColor = FUEBridgeStyle::GetColor("Color.Background"); - TitleColor = FUEBridgeStyle::GetColor("Color.Cyan"); - SubtitleColor = FUEBridgeStyle::GetColor("Color.TextSecondary"); - TraitLabelColor = FUEBridgeStyle::GetColor("Color.CyanFaint"); - TraitValueColor = FUEBridgeStyle::GetColor("Color.TextPrimary"); - InsightColor = FUEBridgeStyle::GetColor("Color.Insight"); - DimColor = FUEBridgeStyle::GetColor("Color.TextMuted"); -} - - -TSharedRef UW_FinaleScreen::RebuildWidget() -{ - if (!TitleText) - { - BuildWidgetTree(); - } - return Super::RebuildWidget(); -} - - -void UW_FinaleScreen::NativeConstruct() -{ - Super::NativeConstruct(); - - // Apply colors - if (BackgroundBorder) - { - BackgroundBorder->SetBrushColor(BackgroundColor); - } - if (TitleText) - { - TitleText->SetColorAndOpacity(FSlateColor(TitleColor)); - } - if (SubtitleText) - { - SubtitleText->SetColorAndOpacity(FSlateColor(SubtitleColor)); - } - if (PathText) - { - PathText->SetColorAndOpacity(FSlateColor(DimColor)); - } - if (ChecksumText) - { - ChecksumText->SetColorAndOpacity(FSlateColor(DimColor)); - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_FinaleScreen] Constructed (Programmatic UI with Profile Display)")); -} - - -void UW_FinaleScreen::SetCompletionMessage(const FString& Message) -{ - if (SubtitleText) - { - SubtitleText->SetText(FText::FromString(Message)); - } -} - - -void UW_FinaleScreen::SetUsdPath(const FString& Path) -{ - if (PathText) - { - FString DisplayPath = FString::Printf(TEXT("Exported to %s"), *Path); - PathText->SetText(FText::FromString(DisplayPath)); - PathText->SetVisibility(ESlateVisibility::Visible); - } -} - - -void UW_FinaleScreen::DisplayProfile(const FUEBridgeProfile& Profile) -{ - if (!Profile.IsValid()) - { - UE_LOG(LogUEBridge, Warning, TEXT("[W_FinaleScreen] Empty profile - nothing to display")); - return; - } - - // Populate traits - if (TraitsContainer) - { - TraitsContainer->ClearChildren(); - - for (const FTranslatorsTrait& Trait : Profile.Traits) - { - // Create a horizontal row: dimension | label | score bar | behavior - UHorizontalBox* Row = WidgetTree->ConstructWidget(UHorizontalBox::StaticClass()); - - // Dimension name (dim color, left-aligned) - UTextBlock* DimText = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); - FString CleanDim = Trait.Dimension.Replace(TEXT("_"), TEXT(" ")); - DimText->SetText(FText::FromString(CleanDim)); - DimText->SetColorAndOpacity(FSlateColor(DimColor)); - DimText->SetFont(FUEBridgeStyle::GetFont("Font.Caption")); - - UHorizontalBoxSlot* DimSlot = Row->AddChildToHorizontalBox(DimText); - if (DimSlot) - { - DimSlot->SetPadding(FMargin(0.0f, 0.0f, 16.0f, 0.0f)); - DimSlot->SetSize(FSlateChildSize(ESlateSizeRule::Fill)); - } - - // Label (cyan, bold-ish) - UTextBlock* LabelText = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); - LabelText->SetText(FText::FromString(Trait.Label)); - LabelText->SetColorAndOpacity(FSlateColor(TraitLabelColor)); - LabelText->SetFont(FUEBridgeStyle::GetFont("Font.Progress")); - - UHorizontalBoxSlot* LabelSlot = Row->AddChildToHorizontalBox(LabelText); - if (LabelSlot) - { - LabelSlot->SetPadding(FMargin(0.0f, 0.0f, 16.0f, 0.0f)); - LabelSlot->SetSize(FSlateChildSize(ESlateSizeRule::Automatic)); - } - - // Score (numeric) - UTextBlock* ScoreText = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); - FString ScoreStr = FString::Printf(TEXT("%.0f%%"), Trait.Score * 100.0f); - ScoreText->SetText(FText::FromString(ScoreStr)); - ScoreText->SetColorAndOpacity(FSlateColor(TraitValueColor)); - ScoreText->SetFont(FUEBridgeStyle::GetFont("Font.Caption")); - - UHorizontalBoxSlot* ScoreSlot = Row->AddChildToHorizontalBox(ScoreText); - if (ScoreSlot) - { - ScoreSlot->SetSize(FSlateChildSize(ESlateSizeRule::Automatic)); - } - - UVerticalBoxSlot* RowSlot = TraitsContainer->AddChildToVerticalBox(Row); - if (RowSlot) - { - RowSlot->SetPadding(FMargin(0.0f, 4.0f, 0.0f, 4.0f)); - } - - // Behavior description (below the row, if present) - if (!Trait.Behavior.IsEmpty()) - { - UTextBlock* BehaviorText = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); - BehaviorText->SetText(FText::FromString(Trait.Behavior)); - BehaviorText->SetColorAndOpacity(FSlateColor(SubtitleColor)); - BehaviorText->SetAutoWrapText(true); - BehaviorText->SetFont(FUEBridgeStyle::GetFont("Font.Small")); - - UVerticalBoxSlot* BehaviorSlot = TraitsContainer->AddChildToVerticalBox(BehaviorText); - if (BehaviorSlot) - { - BehaviorSlot->SetPadding(FMargin(20.0f, 0.0f, 0.0f, 8.0f)); - } - } - } - } - - // Populate insights - if (InsightsContainer) - { - InsightsContainer->ClearChildren(); - - for (const FString& Insight : Profile.Insights) - { - UTextBlock* InsightText = WidgetTree->ConstructWidget(UTextBlock::StaticClass()); - FString BulletInsight = FString::Printf(TEXT(" %s"), *Insight); - InsightText->SetText(FText::FromString(BulletInsight)); - InsightText->SetColorAndOpacity(FSlateColor(InsightColor)); - InsightText->SetAutoWrapText(true); - InsightText->SetFont(FUEBridgeStyle::GetFont("Font.Insight")); - - UVerticalBoxSlot* InsightSlot = InsightsContainer->AddChildToVerticalBox(InsightText); - if (InsightSlot) - { - InsightSlot->SetPadding(FMargin(0.0f, 3.0f, 0.0f, 3.0f)); - } - } - } - - // Checksum/anchor - if (ChecksumText) - { - FString ChecksumDisplay; - if (!Profile.Anchor.IsEmpty()) - { - ChecksumDisplay = Profile.Anchor; - } - else if (!Profile.Checksum.IsEmpty()) - { - ChecksumDisplay = FString::Printf(TEXT("[TRANSLATORS:%s]"), *Profile.Checksum); - } - - if (!ChecksumDisplay.IsEmpty()) - { - ChecksumText->SetText(FText::FromString(ChecksumDisplay)); - ChecksumText->SetVisibility(ESlateVisibility::Visible); - } - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_FinaleScreen] Displayed profile: %d traits, %d insights"), - Profile.Traits.Num(), Profile.Insights.Num()); -} - - -void UW_FinaleScreen::BuildWidgetTree() -{ - // Create full-screen canvas - UCanvasPanel* RootCanvas = WidgetTree->ConstructWidget(UCanvasPanel::StaticClass(), TEXT("RootCanvas")); - WidgetTree->RootWidget = RootCanvas; - - // Full-screen background - BackgroundBorder = WidgetTree->ConstructWidget(UBorder::StaticClass(), TEXT("BackgroundBorder")); - BackgroundBorder->SetBrushColor(BackgroundColor); - - UCanvasPanelSlot* BorderSlot = RootCanvas->AddChildToCanvas(BackgroundBorder); - if (BorderSlot) - { - BorderSlot->SetAnchors(FAnchors(0.0f, 0.0f, 1.0f, 1.0f)); - BorderSlot->SetOffsets(FMargin(0.0f)); - } - - // Scrollable content area (centered, constrained width) - UScrollBox* ScrollArea = WidgetTree->ConstructWidget(UScrollBox::StaticClass(), TEXT("ScrollArea")); - - UCanvasPanelSlot* ScrollSlot = RootCanvas->AddChildToCanvas(ScrollArea); - if (ScrollSlot) - { - // Center with padding - ScrollSlot->SetAnchors(FAnchors(0.15f, 0.05f, 0.85f, 0.95f)); - ScrollSlot->SetOffsets(FMargin(0.0f)); - } - - // Main content vertical box - UVerticalBox* ContentBox = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("ContentBox")); - ScrollArea->AddChild(ContentBox); - - // === Title: "Your Cognitive Profile" === - TitleText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("TitleText")); - TitleText->SetText(NSLOCTEXT("UEBridge", "FinaleScreen.Title", "Your Cognitive Profile")); - TitleText->SetColorAndOpacity(FSlateColor(TitleColor)); - TitleText->SetJustification(ETextJustify::Center); - - TitleText->SetFont(FUEBridgeStyle::GetFont("Font.Heading")); - - UVerticalBoxSlot* TitleSlot = ContentBox->AddChildToVerticalBox(TitleText); - if (TitleSlot) - { - TitleSlot->SetPadding(FMargin(0.0f, 20.0f, 0.0f, 8.0f)); - TitleSlot->SetHorizontalAlignment(HAlign_Center); - } - - // === Subtitle === - SubtitleText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("SubtitleText")); - SubtitleText->SetText(NSLOCTEXT("UEBridge", "FinaleScreen.Subtitle", "Your cognitive profile has been generated.")); - SubtitleText->SetColorAndOpacity(FSlateColor(SubtitleColor)); - SubtitleText->SetJustification(ETextJustify::Center); - - SubtitleText->SetFont(FUEBridgeStyle::GetFont("Font.Body")); - - UVerticalBoxSlot* SubtitleSlot = ContentBox->AddChildToVerticalBox(SubtitleText); - if (SubtitleSlot) - { - SubtitleSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 30.0f)); - SubtitleSlot->SetHorizontalAlignment(HAlign_Center); - } - - // === Traits section header === - UTextBlock* TraitsHeader = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("TraitsHeader")); - TraitsHeader->SetText(NSLOCTEXT("UEBridge", "FinaleScreen.DimensionsHeader", "DIMENSIONS")); - TraitsHeader->SetColorAndOpacity(FSlateColor(DimColor)); - - TraitsHeader->SetFont(FUEBridgeStyle::GetFont("Font.Caption")); - - UVerticalBoxSlot* TraitsHeaderSlot = ContentBox->AddChildToVerticalBox(TraitsHeader); - if (TraitsHeaderSlot) - { - TraitsHeaderSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 10.0f)); - } - - // === Traits container (populated dynamically) === - TraitsContainer = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("TraitsContainer")); - - UVerticalBoxSlot* TraitsSlot = ContentBox->AddChildToVerticalBox(TraitsContainer); - if (TraitsSlot) - { - TraitsSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 30.0f)); - } - - // === Insights section header === - UTextBlock* InsightsHeader = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("InsightsHeader")); - InsightsHeader->SetText(NSLOCTEXT("UEBridge", "FinaleScreen.InsightsHeader", "INSIGHTS")); - InsightsHeader->SetColorAndOpacity(FSlateColor(DimColor)); - - InsightsHeader->SetFont(FUEBridgeStyle::GetFont("Font.Caption")); - - UVerticalBoxSlot* InsightsHeaderSlot = ContentBox->AddChildToVerticalBox(InsightsHeader); - if (InsightsHeaderSlot) - { - InsightsHeaderSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 10.0f)); - } - - // === Insights container (populated dynamically) === - InsightsContainer = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("InsightsContainer")); - - UVerticalBoxSlot* InsightsSlot = ContentBox->AddChildToVerticalBox(InsightsContainer); - if (InsightsSlot) - { - InsightsSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 30.0f)); - } - - // === Checksum/anchor (hidden until profile loaded) === - ChecksumText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("ChecksumText")); - ChecksumText->SetText(FText::FromString(TEXT(""))); - ChecksumText->SetColorAndOpacity(FSlateColor(DimColor)); - ChecksumText->SetJustification(ETextJustify::Center); - ChecksumText->SetVisibility(ESlateVisibility::Collapsed); - - ChecksumText->SetFont(FUEBridgeStyle::GetFont("Font.Small")); - - UVerticalBoxSlot* ChecksumSlot = ContentBox->AddChildToVerticalBox(ChecksumText); - if (ChecksumSlot) - { - ChecksumSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 16.0f)); - ChecksumSlot->SetHorizontalAlignment(HAlign_Center); - } - - // === Export path (hidden until set) === - PathText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("PathText")); - PathText->SetText(FText::FromString(TEXT(""))); - PathText->SetColorAndOpacity(FSlateColor(DimColor)); - PathText->SetJustification(ETextJustify::Center); - PathText->SetVisibility(ESlateVisibility::Collapsed); - - PathText->SetFont(FUEBridgeStyle::GetFont("Font.Small")); - - UVerticalBoxSlot* PathSlot = ContentBox->AddChildToVerticalBox(PathText); - if (PathSlot) - { - PathSlot->SetHorizontalAlignment(HAlign_Center); - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_FinaleScreen] Built programmatic widget tree with profile display")); -} diff --git a/Source/UnrealEngineBridge/UI/W_FinaleScreen.h b/Source/UnrealEngineBridge/UI/W_FinaleScreen.h deleted file mode 100644 index f3f2548..0000000 --- a/Source/UnrealEngineBridge/UI/W_FinaleScreen.h +++ /dev/null @@ -1,102 +0,0 @@ -// W_FinaleScreen.h -// Final completion screen widget with cognitive profile display -// Part of The UnrealEngine Bridge - Claude Code -> UE5.7 Bridge - -#pragma once - -#include "CoreMinimal.h" -#include "Blueprint/UserWidget.h" -#include "BridgeTypes.h" -#include "W_FinaleScreen.generated.h" - -class UTextBlock; -class UBorder; -class UVerticalBox; -class UScrollBox; - -/** - * W_FinaleScreen - Displayed when questionnaire is complete - * - * Shows: - * - "Your Cognitive Profile" title - * - Trait list with dimension, label, score, behavior - * - Insights section - * - Checksum/anchor - * - Export path - * - * Programmatic UI - no Blueprint required - */ -UCLASS(Blueprintable, BlueprintType) -class UNREALENGINEBRIDGE_API UW_FinaleScreen : public UUserWidget -{ - GENERATED_BODY() - -public: - UW_FinaleScreen(const FObjectInitializer& ObjectInitializer); - - // === STYLE === - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor BackgroundColor = FLinearColor(0.02f, 0.02f, 0.05f, 0.98f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor TitleColor = FLinearColor(0.36f, 1.0f, 0.86f, 1.0f); // Cyan - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor SubtitleColor = FLinearColor(0.7f, 0.7f, 0.8f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor TraitLabelColor = FLinearColor(0.36f, 1.0f, 0.86f, 0.9f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor TraitValueColor = FLinearColor(0.9f, 0.9f, 0.9f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor InsightColor = FLinearColor(0.7f, 0.8f, 0.7f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor DimColor = FLinearColor(0.4f, 0.4f, 0.5f, 1.0f); - - // === FUNCTIONS === - - /** Set the completion message */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Display") - void SetCompletionMessage(const FString& Message); - - /** Set the USD path display */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Display") - void SetUsdPath(const FString& Path); - - /** Display full cognitive profile */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Display") - void DisplayProfile(const FUEBridgeProfile& Profile); - - virtual TSharedRef RebuildWidget() override; - -protected: - virtual void NativeConstruct() override; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* TitleText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* SubtitleText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* PathText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UBorder* BackgroundBorder; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UVerticalBox* TraitsContainer; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UVerticalBox* InsightsContainer; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* ChecksumText; - -private: - void BuildWidgetTree(); -}; diff --git a/Source/UnrealEngineBridge/UI/W_OptionButton.cpp b/Source/UnrealEngineBridge/UI/W_OptionButton.cpp deleted file mode 100644 index 54fab33..0000000 --- a/Source/UnrealEngineBridge/UI/W_OptionButton.cpp +++ /dev/null @@ -1,197 +0,0 @@ -// W_OptionButton.cpp -// Implementation of clickable answer option button -// Programmatic UI - no Blueprint required - -#include "W_OptionButton.h" -#include "UEBridgeRuntime.h" -#include "Components/Button.h" -#include "Components/TextBlock.h" -#include "Components/Border.h" -#include "Components/SizeBox.h" -#include "Blueprint/WidgetTree.h" -#include "UEBridgeStyle.h" - - -UW_OptionButton::UW_OptionButton(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - // Default 8-bit color scheme - NormalColor = FUEBridgeStyle::GetColor("Color.ButtonNormal"); - HoveredColor = FUEBridgeStyle::GetColor("Color.ButtonHovered"); - PressedColor = FUEBridgeStyle::GetColor("Color.Cyan"); - TextColor = FUEBridgeStyle::GetColor("Color.TextPrimary"); -} - - -TSharedRef UW_OptionButton::RebuildWidget() -{ - if (!OptionButton || !OptionLabel) - { - BuildWidgetTree(); - } - return Super::RebuildWidget(); -} - - -void UW_OptionButton::NativeConstruct() -{ - Super::NativeConstruct(); - - // Bind button events if button exists - if (OptionButton) - { - OptionButton->OnClicked.AddDynamic(this, &UW_OptionButton::HandleButtonClicked); - OptionButton->OnHovered.AddDynamic(this, &UW_OptionButton::HandleButtonHovered); - OptionButton->OnUnhovered.AddDynamic(this, &UW_OptionButton::HandleButtonUnhovered); - } - - // Set initial visual state - UpdateVisualState(NormalColor); - - // Set label text if available - if (OptionLabel && !LabelText.IsEmpty()) - { - OptionLabel->SetText(LabelText); - OptionLabel->SetColorAndOpacity(FSlateColor(TextColor)); - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_OptionButton] Constructed (Programmatic UI)")); -} - - -void UW_OptionButton::NativeDestruct() -{ - // Unbind events - if (OptionButton) - { - OptionButton->OnClicked.RemoveAll(this); - OptionButton->OnHovered.RemoveAll(this); - OptionButton->OnUnhovered.RemoveAll(this); - } - - Super::NativeDestruct(); -} - - -void UW_OptionButton::SetupOption(int32 Index, const FText& Label, const FString& Dir) -{ - OptionIndex = Index; - LabelText = Label; - Direction = Dir; - - // Update label if widget exists - if (OptionLabel) - { - OptionLabel->SetText(LabelText); - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_OptionButton] Setup option %d: %s (dir: %s)"), - Index, *Label.ToString(), *Dir); -} - - -void UW_OptionButton::SetHighlighted(bool bHighlighted) -{ - if (bHighlighted) - { - UpdateVisualState(HoveredColor); - } - else - { - UpdateVisualState(NormalColor); - } -} - - -void UW_OptionButton::SimulateClick() -{ - HandleButtonClicked(); -} - - -void UW_OptionButton::HandleButtonClicked() -{ - UE_LOG(LogUEBridge, Log, TEXT("[W_OptionButton] Option %d clicked"), OptionIndex); - - // Visual feedback - UpdateVisualState(PressedColor); - - // Broadcast the click event - OnOptionClicked.Broadcast(OptionIndex); - - // Return to normal after brief delay (handled by caller typically) -} - - -void UW_OptionButton::HandleButtonHovered() -{ - UpdateVisualState(HoveredColor); - - // Optional: Play hover sound - // UGameplayStatics::PlaySound2D(this, HoverSound); -} - - -void UW_OptionButton::HandleButtonUnhovered() -{ - UpdateVisualState(NormalColor); -} - - -void UW_OptionButton::UpdateVisualState(const FLinearColor& BackgroundColor) -{ - if (ButtonBorder) - { - ButtonBorder->SetBrushColor(BackgroundColor); - } - else if (OptionButton) - { - // Fallback: tint the button itself - FButtonStyle Style = OptionButton->GetStyle(); - Style.Normal.TintColor = FSlateColor(BackgroundColor); - Style.Hovered.TintColor = FSlateColor(HoveredColor); - Style.Pressed.TintColor = FSlateColor(PressedColor); - OptionButton->SetStyle(Style); - } -} - - -void UW_OptionButton::BuildWidgetTree() -{ - // Create border as root (for background color) - ButtonBorder = WidgetTree->ConstructWidget(UBorder::StaticClass(), TEXT("ButtonBorder")); - ButtonBorder->SetBrushColor(NormalColor); - ButtonBorder->SetPadding(FMargin(20.0f, 12.0f)); - WidgetTree->RootWidget = ButtonBorder; - - // Create size box to ensure consistent button size - USizeBox* ButtonSizeBox = WidgetTree->ConstructWidget(USizeBox::StaticClass(), TEXT("ButtonSizeBox")); - ButtonSizeBox->SetMinDesiredWidth(400.0f); - ButtonSizeBox->SetMinDesiredHeight(50.0f); - ButtonBorder->AddChild(ButtonSizeBox); - - // Create the actual button (invisible, just for interaction) - OptionButton = WidgetTree->ConstructWidget(UButton::StaticClass(), TEXT("OptionButton")); - - // Make button transparent - border handles visuals - FButtonStyle TransparentStyle; - TransparentStyle.Normal.TintColor = FSlateColor(FLinearColor::Transparent); - TransparentStyle.Hovered.TintColor = FSlateColor(FLinearColor::Transparent); - TransparentStyle.Pressed.TintColor = FSlateColor(FLinearColor::Transparent); - OptionButton->SetStyle(TransparentStyle); - - ButtonSizeBox->AddChild(OptionButton); - - // Create label text - OptionLabel = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("OptionLabel")); - OptionLabel->SetText(LabelText.IsEmpty() ? NSLOCTEXT("UEBridge", "OptionButton.Default", "Option") : LabelText); - OptionLabel->SetColorAndOpacity(FSlateColor(TextColor)); - OptionLabel->SetJustification(ETextJustify::Center); - OptionLabel->SetAutoWrapText(true); - - OptionLabel->SetFont(FUEBridgeStyle::GetFont("Font.Option")); - - OptionButton->AddChild(OptionLabel); - - UE_LOG(LogUEBridge, Log, TEXT("[W_OptionButton] Built programmatic widget tree")); -} diff --git a/Source/UnrealEngineBridge/UI/W_OptionButton.h b/Source/UnrealEngineBridge/UI/W_OptionButton.h deleted file mode 100644 index 8e8edf8..0000000 --- a/Source/UnrealEngineBridge/UI/W_OptionButton.h +++ /dev/null @@ -1,113 +0,0 @@ -// W_OptionButton.h -// Clickable button widget for answer options -// Part of The UnrealEngine Bridge - Claude Code → UE5.7 Bridge - -#pragma once - -#include "CoreMinimal.h" -#include "Blueprint/UserWidget.h" -#include "Components/Button.h" -#include "Components/TextBlock.h" -#include "Components/Border.h" -#include "W_OptionButton.generated.h" - -// Delegate fired when option is selected -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnOptionClicked, int32, OptionIndex); - -/** - * W_OptionButton - Individual answer option button - * - * Features: - * - 8-bit styled button with hover effects - * - Keyboard focusable (accessibility) - * - Fires delegate with option index when clicked - * - * Deterministic: fixed visual states and click-to-delegate flow. - */ -UCLASS(Blueprintable, BlueprintType) -class UNREALENGINEBRIDGE_API UW_OptionButton : public UUserWidget -{ - GENERATED_BODY() - -public: - UW_OptionButton(const FObjectInitializer& ObjectInitializer); - - // === DELEGATES === - - /** Fired when this option is clicked */ - UPROPERTY(BlueprintAssignable, Category = "UEBridge|Events") - FOnOptionClicked OnOptionClicked; - - // === PROPERTIES === - - /** Index of this option (0, 1, or 2) */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Option") - int32 OptionIndex = 0; - - /** Display text for this option */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Option", meta = (MultiLine = true)) - FText LabelText; - - /** Direction associated with this option (for visual cues) */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Option") - FString Direction; - - // === COLORS (8-bit aesthetic) === - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor NormalColor = FLinearColor(0.1f, 0.1f, 0.15f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor HoveredColor = FLinearColor(0.2f, 0.4f, 0.5f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor PressedColor = FLinearColor(0.36f, 1.0f, 0.86f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor TextColor = FLinearColor(0.9f, 0.9f, 0.9f, 1.0f); - - // === FUNCTIONS === - - /** Set up the button with option data */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Option") - void SetupOption(int32 Index, const FText& Label, const FString& Dir); - - /** Update the visual state */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Option") - void SetHighlighted(bool bHighlighted); - - /** Simulate a click (for keyboard input) */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Option") - void SimulateClick(); - - virtual TSharedRef RebuildWidget() override; - -protected: - virtual void NativeConstruct() override; - virtual void NativeDestruct() override; - - // Widget components (bind in Blueprint or create in C++) - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UButton* OptionButton; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* OptionLabel; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UBorder* ButtonBorder; - -private: - UFUNCTION() - void HandleButtonClicked(); - - UFUNCTION() - void HandleButtonHovered(); - - UFUNCTION() - void HandleButtonUnhovered(); - - void UpdateVisualState(const FLinearColor& BackgroundColor); - - /** Build widget tree programmatically (no Blueprint required) */ - void BuildWidgetTree(); -}; diff --git a/Source/UnrealEngineBridge/UI/W_ProgressIndicator.cpp b/Source/UnrealEngineBridge/UI/W_ProgressIndicator.cpp deleted file mode 100644 index 172a0cc..0000000 --- a/Source/UnrealEngineBridge/UI/W_ProgressIndicator.cpp +++ /dev/null @@ -1,180 +0,0 @@ -// W_ProgressIndicator.cpp -// Implementation of progress indicator widget -// Programmatic UI - no Blueprint required - -#include "W_ProgressIndicator.h" -#include "UEBridgeRuntime.h" -#include "Components/HorizontalBox.h" -#include "Components/HorizontalBoxSlot.h" -#include "Components/Image.h" -#include "Components/TextBlock.h" -#include "Components/CanvasPanel.h" -#include "Components/CanvasPanelSlot.h" -#include "Blueprint/WidgetTree.h" -#include "UEBridgeStyle.h" - - -UW_ProgressIndicator::UW_ProgressIndicator(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - TotalQuestions = 8; - CurrentQuestion = 0; - - // 8-bit color scheme - CompletedColor = FUEBridgeStyle::GetColor("Color.Cyan"); - IncompleteColor = FUEBridgeStyle::GetColor("Color.IncompleteGray"); - CurrentColor = FUEBridgeStyle::GetColor("Color.Gold"); -} - - -void UW_ProgressIndicator::NativeConstruct() -{ - Super::NativeConstruct(); - - // Build widget tree programmatically if not bound from Blueprint - if (!IndicatorContainer) - { - BuildWidgetTree(); - } - - // Create indicator images if we have a container - if (IndicatorContainer) - { - IndicatorContainer->ClearChildren(); - IndicatorImages.Empty(); - - for (int32 i = 0; i < TotalQuestions; i++) - { - UImage* Indicator = WidgetTree->ConstructWidget(UImage::StaticClass()); - if (Indicator) - { - // Set size (small square) - Indicator->SetDesiredSizeOverride(FVector2D(12.0f, 12.0f)); - - // Add to container - UHorizontalBoxSlot* IndicatorSlot = IndicatorContainer->AddChildToHorizontalBox(Indicator); - if (IndicatorSlot) - { - IndicatorSlot->SetPadding(FMargin(4.0f, 0.0f, 4.0f, 0.0f)); - IndicatorSlot->SetVerticalAlignment(VAlign_Center); - } - - IndicatorImages.Add(Indicator); - } - } - } - - // Initial state - RefreshIndicators(); - - UE_LOG(LogUEBridge, Log, TEXT("[W_ProgressIndicator] Constructed with %d slots"), TotalQuestions); -} - - -void UW_ProgressIndicator::UpdateProgress(int32 QuestionsCompleted) -{ - CurrentQuestion = FMath::Clamp(QuestionsCompleted, 0, TotalQuestions); - RefreshIndicators(); - - UE_LOG(LogUEBridge, Log, TEXT("[W_ProgressIndicator] Progress: %d/%d (%.0f%%)"), - CurrentQuestion, TotalQuestions, GetCompletionPercent() * 100.0f); -} - - -void UW_ProgressIndicator::SetTotalQuestions(int32 Total) -{ - TotalQuestions = FMath::Max(1, Total); - - // Rebuild indicators if needed - if (IndicatorImages.Num() != TotalQuestions && IndicatorContainer) - { - // Clear and rebuild - IndicatorContainer->ClearChildren(); - IndicatorImages.Empty(); - - for (int32 i = 0; i < TotalQuestions; i++) - { - UImage* Indicator = WidgetTree->ConstructWidget(UImage::StaticClass()); - if (Indicator) - { - Indicator->SetDesiredSizeOverride(FVector2D(12.0f, 12.0f)); - UHorizontalBoxSlot* IndicatorSlot = IndicatorContainer->AddChildToHorizontalBox(Indicator); - if (IndicatorSlot) - { - IndicatorSlot->SetPadding(FMargin(4.0f, 0.0f, 4.0f, 0.0f)); - } - IndicatorImages.Add(Indicator); - } - } - } - - RefreshIndicators(); -} - - -float UW_ProgressIndicator::GetCompletionPercent() const -{ - if (TotalQuestions <= 0) - { - return 0.0f; - } - return static_cast(CurrentQuestion) / static_cast(TotalQuestions); -} - - -void UW_ProgressIndicator::RefreshIndicators() -{ - // Update indicator colors - for (int32 i = 0; i < IndicatorImages.Num(); i++) - { - UImage* Indicator = IndicatorImages[i]; - if (!Indicator) continue; - - FLinearColor Color; - if (i < CurrentQuestion) - { - // Completed - Color = CompletedColor; - } - else if (i == CurrentQuestion) - { - // Current (in progress) - Color = CurrentColor; - } - else - { - // Not yet reached - Color = IncompleteColor; - } - - Indicator->SetColorAndOpacity(Color); - } - - // Update text label - if (ProgressLabel) - { - FString LabelText = FString::Printf(TEXT("%d / %d"), CurrentQuestion, TotalQuestions); - ProgressLabel->SetText(FText::FromString(LabelText)); - } -} - - -void UW_ProgressIndicator::BuildWidgetTree() -{ - // Create root canvas - UCanvasPanel* RootCanvas = WidgetTree->ConstructWidget(UCanvasPanel::StaticClass(), TEXT("RootCanvas")); - WidgetTree->RootWidget = RootCanvas; - - // Create horizontal container for indicators - IndicatorContainer = WidgetTree->ConstructWidget(UHorizontalBox::StaticClass(), TEXT("IndicatorContainer")); - - UCanvasPanelSlot* ContainerSlot = RootCanvas->AddChildToCanvas(IndicatorContainer); - if (ContainerSlot) - { - ContainerSlot->SetAnchors(FAnchors(0.5f, 0.5f, 0.5f, 0.5f)); - ContainerSlot->SetAlignment(FVector2D(0.5f, 0.5f)); - ContainerSlot->SetAutoSize(true); - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_ProgressIndicator] Built programmatic widget tree")); -} diff --git a/Source/UnrealEngineBridge/UI/W_ProgressIndicator.h b/Source/UnrealEngineBridge/UI/W_ProgressIndicator.h deleted file mode 100644 index def2a2f..0000000 --- a/Source/UnrealEngineBridge/UI/W_ProgressIndicator.h +++ /dev/null @@ -1,87 +0,0 @@ -// W_ProgressIndicator.h -// Progress indicator showing questionnaire completion -// Part of The UnrealEngine Bridge - Claude Code → UE5.7 Bridge - -#pragma once - -#include "CoreMinimal.h" -#include "Blueprint/UserWidget.h" -#include "Components/HorizontalBox.h" -#include "Components/Image.h" -#include "Components/TextBlock.h" -#include "W_ProgressIndicator.generated.h" - -/** - * W_ProgressIndicator - Visual progress through 8 questions - * - * Shows: - * - 8 dots/boxes (filled for completed, empty for remaining) - * - Text like "3/8 COMPLETE" - * - * Deterministic: fixed 8 indicator slots, predictable visual state. - */ -UCLASS(Blueprintable, BlueprintType) -class UNREALENGINEBRIDGE_API UW_ProgressIndicator : public UUserWidget -{ - GENERATED_BODY() - -public: - UW_ProgressIndicator(const FObjectInitializer& ObjectInitializer); - - // === PROPERTIES === - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Progress") - int32 TotalQuestions = 8; - - UPROPERTY(BlueprintReadOnly, Category = "UEBridge|Progress") - int32 CurrentQuestion = 0; - - // === STYLE === - - /** Color for completed question indicators */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor CompletedColor = FLinearColor(0.36f, 1.0f, 0.86f, 1.0f); // Cyan - - /** Color for incomplete question indicators */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor IncompleteColor = FLinearColor(0.3f, 0.3f, 0.3f, 0.5f); // Gray - - /** Color for current question indicator */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor CurrentColor = FLinearColor(1.0f, 0.8f, 0.2f, 1.0f); // Gold - - // === FUNCTIONS === - - /** Update progress to show N questions completed */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Progress") - void UpdateProgress(int32 QuestionsCompleted); - - /** Set total number of questions */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Progress") - void SetTotalQuestions(int32 Total); - - /** Get completion percentage (0.0 - 1.0) */ - UFUNCTION(BlueprintCallable, BlueprintPure, Category = "UEBridge|Progress") - float GetCompletionPercent() const; - -protected: - virtual void NativeConstruct() override; - - // Widget components (bind in Blueprint) - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UHorizontalBox* IndicatorContainer; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* ProgressLabel; - -private: - /** Update all indicator visuals */ - void RefreshIndicators(); - - /** Build widget tree programmatically (no Blueprint required) */ - void BuildWidgetTree(); - - /** Created indicator images */ - UPROPERTY() - TArray IndicatorImages; -}; diff --git a/Source/UnrealEngineBridge/UI/W_QuestionDisplay.cpp b/Source/UnrealEngineBridge/UI/W_QuestionDisplay.cpp deleted file mode 100644 index 9a96e67..0000000 --- a/Source/UnrealEngineBridge/UI/W_QuestionDisplay.cpp +++ /dev/null @@ -1,298 +0,0 @@ -// W_QuestionDisplay.cpp -// Implementation of main question display widget -// Programmatic UI - no Blueprint required - -#include "W_QuestionDisplay.h" -#include "W_OptionButton.h" -#include "UEBridgeRuntime.h" -#include "Components/CanvasPanel.h" -#include "Components/CanvasPanelSlot.h" -#include "Components/VerticalBox.h" -#include "Components/VerticalBoxSlot.h" -#include "Components/TextBlock.h" -#include "Components/Border.h" -#include "Components/SizeBox.h" -#include "Blueprint/WidgetTree.h" -#include "UEBridgeStyle.h" - - -UW_QuestionDisplay::UW_QuestionDisplay(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - // 8-bit color scheme - BackgroundColor = FUEBridgeStyle::GetColor("Color.Background"); - QuestionTextColor = FUEBridgeStyle::GetColor("Color.Cyan"); - ProgressTextColor = FUEBridgeStyle::GetColor("Color.TextDim"); -} - - -TSharedRef UW_QuestionDisplay::RebuildWidget() -{ - if (!QuestionText || !OptionsContainer) - { - BuildWidgetTree(); - } - return Super::RebuildWidget(); -} - - -void UW_QuestionDisplay::NativeConstruct() -{ - Super::NativeConstruct(); - - // Apply colors - if (BackgroundBorder) - { - BackgroundBorder->SetBrushColor(BackgroundColor); - } - if (QuestionText) - { - QuestionText->SetColorAndOpacity(FSlateColor(QuestionTextColor)); - } - if (ProgressText) - { - ProgressText->SetColorAndOpacity(FSlateColor(ProgressTextColor)); - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_QuestionDisplay] Constructed (Programmatic UI)")); -} - - -void UW_QuestionDisplay::BuildWidgetTree() -{ - // Create root canvas - UCanvasPanel* RootCanvas = WidgetTree->ConstructWidget(UCanvasPanel::StaticClass(), TEXT("RootCanvas")); - WidgetTree->RootWidget = RootCanvas; - - // Create background border - centered panel - BackgroundBorder = WidgetTree->ConstructWidget(UBorder::StaticClass(), TEXT("BackgroundBorder")); - BackgroundBorder->SetBrushColor(BackgroundColor); - BackgroundBorder->SetPadding(FMargin(40.0f, 30.0f)); - - UCanvasPanelSlot* BorderSlot = RootCanvas->AddChildToCanvas(BackgroundBorder); - if (BorderSlot) - { - // Center the panel - BorderSlot->SetAnchors(FAnchors(0.5f, 0.5f, 0.5f, 0.5f)); - BorderSlot->SetAlignment(FVector2D(0.5f, 0.5f)); - BorderSlot->SetAutoSize(true); - } - - // Create main vertical layout - UVerticalBox* MainLayout = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("MainLayout")); - BackgroundBorder->AddChild(MainLayout); - - // Create size box to constrain width - USizeBox* ContentSizeBox = WidgetTree->ConstructWidget(USizeBox::StaticClass(), TEXT("ContentSizeBox")); - ContentSizeBox->SetWidthOverride(600.0f); - UVerticalBoxSlot* SizeBoxSlot = MainLayout->AddChildToVerticalBox(ContentSizeBox); - if (SizeBoxSlot) - { - SizeBoxSlot->SetHorizontalAlignment(HAlign_Center); - } - - // Inner vertical box for content - UVerticalBox* ContentBox = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("ContentBox")); - ContentSizeBox->AddChild(ContentBox); - - // Depth label (top - e.g. "SURFACE", "PATTERNS", etc.) - DepthText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("DepthText")); - DepthText->SetText(NSLOCTEXT("UEBridge", "QuestionDisplay.DefaultDepth", "SURFACE")); - DepthText->SetColorAndOpacity(FSlateColor(DepthLabelColor)); - DepthText->SetJustification(ETextJustify::Center); - - DepthText->SetFont(FUEBridgeStyle::GetFont("Font.Caption")); - - UVerticalBoxSlot* DepthSlot = ContentBox->AddChildToVerticalBox(DepthText); - if (DepthSlot) - { - DepthSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 8.0f)); - DepthSlot->SetHorizontalAlignment(HAlign_Center); - } - - // Progress text - ProgressText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("ProgressText")); - ProgressText->SetText(NSLOCTEXT("UEBridge", "QuestionDisplay.DefaultProgress", "1 / 8")); - ProgressText->SetColorAndOpacity(FSlateColor(ProgressTextColor)); - ProgressText->SetJustification(ETextJustify::Center); - - ProgressText->SetFont(FUEBridgeStyle::GetFont("Font.Progress")); - - UVerticalBoxSlot* ProgressSlot = ContentBox->AddChildToVerticalBox(ProgressText); - if (ProgressSlot) - { - ProgressSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 20.0f)); - ProgressSlot->SetHorizontalAlignment(HAlign_Center); - } - - // Question text (center) - QuestionText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("QuestionText")); - QuestionText->SetText(NSLOCTEXT("UEBridge", "QuestionDisplay.Loading", "Loading question...")); - QuestionText->SetColorAndOpacity(FSlateColor(QuestionTextColor)); - QuestionText->SetJustification(ETextJustify::Center); - QuestionText->SetAutoWrapText(true); - - QuestionText->SetFont(FUEBridgeStyle::GetFont("Font.Question")); - - UVerticalBoxSlot* QuestionSlot = ContentBox->AddChildToVerticalBox(QuestionText); - if (QuestionSlot) - { - QuestionSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 30.0f)); - QuestionSlot->SetHorizontalAlignment(HAlign_Fill); - } - - // Options container - OptionsContainer = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("OptionsContainer")); - UVerticalBoxSlot* OptionsSlot = ContentBox->AddChildToVerticalBox(OptionsContainer); - if (OptionsSlot) - { - OptionsSlot->SetHorizontalAlignment(HAlign_Fill); - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_QuestionDisplay] Built programmatic widget tree")); -} - - -void UW_QuestionDisplay::ShowQuestion(const FUEBridgeQuestion& Question) -{ - CurrentQuestion = Question; - SelectedOptionIndex = -1; - - // Update depth label with tier-specific color - if (DepthText) - { - DepthText->SetText(FText::FromString(Question.DepthLabel)); - - // Color by tier - FLinearColor TierColor; - if (Question.DepthLabel == TEXT("SURFACE")) - { - TierColor = FUEBridgeStyle::GetColor("Color.DepthSurface"); - } - else if (Question.DepthLabel == TEXT("PATTERNS")) - { - TierColor = FUEBridgeStyle::GetColor("Color.DepthPatterns"); - } - else if (Question.DepthLabel == TEXT("FEELINGS")) - { - TierColor = FUEBridgeStyle::GetColor("Color.DepthFeelings"); - } - else // CORE - { - TierColor = FUEBridgeStyle::GetColor("Color.DepthCore"); - } - DepthText->SetColorAndOpacity(FSlateColor(TierColor)); - } - - // Update question text - if (QuestionText) - { - FString FormattedText = Question.Text.Replace(TEXT("\\n"), TEXT("\n")); - QuestionText->SetText(FText::FromString(FormattedText)); - } - - // Update progress - UpdateProgress(Question.Index + 1, Question.Total); - - // Create option buttons - CreateOptionButtons(); - - UE_LOG(LogUEBridge, Log, TEXT("[W_QuestionDisplay] Showing question %d/%d: %s"), - Question.Index + 1, Question.Total, *Question.QuestionId); -} - - -void UW_QuestionDisplay::UpdateProgress(int32 Current, int32 Total) -{ - if (ProgressText) - { - FString ProgressString = FString::Printf(TEXT("%d / %d"), Current, Total); - ProgressText->SetText(FText::FromString(ProgressString)); - } -} - - -void UW_QuestionDisplay::ClearOptions() -{ - for (UW_OptionButton* Button : OptionButtons) - { - if (Button) - { - Button->OnOptionClicked.RemoveAll(this); - Button->RemoveFromParent(); - } - } - OptionButtons.Empty(); - SelectedOptionIndex = -1; -} - - -void UW_QuestionDisplay::SetDisplayVisible(bool bVisible) -{ - SetVisibility(bVisible ? ESlateVisibility::Visible : ESlateVisibility::Hidden); -} - - -void UW_QuestionDisplay::CreateOptionButtons() -{ - ClearOptions(); - - if (!OptionsContainer) - { - UE_LOG(LogUEBridge, Warning, TEXT("[W_QuestionDisplay] No OptionsContainer")); - return; - } - - TSubclassOf ButtonClass = OptionButtonClass; - if (!ButtonClass) - { - ButtonClass = UW_OptionButton::StaticClass(); - } - - const int32 NumOptions = CurrentQuestion.OptionLabels.Num(); - for (int32 i = 0; i < NumOptions; i++) - { - UW_OptionButton* NewButton = CreateWidget(this, ButtonClass); - if (NewButton) - { - FText Label = FText::FromString(CurrentQuestion.OptionLabels[i]); - FString Dir = CurrentQuestion.OptionDirections.IsValidIndex(i) - ? CurrentQuestion.OptionDirections[i] - : TEXT("forward"); - - NewButton->SetupOption(i, Label, Dir); - NewButton->OnOptionClicked.AddDynamic(this, &UW_QuestionDisplay::HandleOptionClicked); - - UVerticalBoxSlot* ButtonSlot = OptionsContainer->AddChildToVerticalBox(NewButton); - if (ButtonSlot) - { - ButtonSlot->SetPadding(FMargin(0.0f, 8.0f, 0.0f, 8.0f)); - ButtonSlot->SetHorizontalAlignment(HAlign_Fill); - } - - OptionButtons.Add(NewButton); - } - } -} - - -void UW_QuestionDisplay::HandleOptionClicked(int32 OptionIndex) -{ - if (SelectedOptionIndex != -1) - { - return; - } - - SelectedOptionIndex = OptionIndex; - - for (int32 i = 0; i < OptionButtons.Num(); i++) - { - if (OptionButtons[i]) - { - OptionButtons[i]->SetHighlighted(i == OptionIndex); - } - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_QuestionDisplay] Answer: option %d"), OptionIndex); - OnAnswerSelected.Broadcast(OptionIndex); -} diff --git a/Source/UnrealEngineBridge/UI/W_QuestionDisplay.h b/Source/UnrealEngineBridge/UI/W_QuestionDisplay.h deleted file mode 100644 index 862426e..0000000 --- a/Source/UnrealEngineBridge/UI/W_QuestionDisplay.h +++ /dev/null @@ -1,129 +0,0 @@ -// W_QuestionDisplay.h -// Main widget displaying question text and answer options -// Part of The UnrealEngine Bridge - Claude Code → UE5.7 Bridge - -#pragma once - -#include "CoreMinimal.h" -#include "Blueprint/UserWidget.h" -#include "Components/VerticalBox.h" -#include "Components/TextBlock.h" -#include "Components/Border.h" -#include "BridgeTypes.h" -#include "W_QuestionDisplay.generated.h" - -// Forward declaration -class UW_OptionButton; - -// Delegate for option selection (bubbles up from buttons) -DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAnswerSelected, int32, OptionIndex); - -/** - * W_QuestionDisplay - Main question display widget - * - * Shows: - * - Question text (multi-line) - * - Progress indicator (1/8) - * - Option buttons (dynamically created) - * - * Deterministic: fixed widget structure, same input produces same visual output. - */ -UCLASS(Blueprintable, BlueprintType) -class UNREALENGINEBRIDGE_API UW_QuestionDisplay : public UUserWidget -{ - GENERATED_BODY() - -public: - UW_QuestionDisplay(const FObjectInitializer& ObjectInitializer); - - // === DELEGATES === - - /** Fired when user selects an answer */ - UPROPERTY(BlueprintAssignable, Category = "UEBridge|Events") - FOnAnswerSelected OnAnswerSelected; - - // === PROPERTIES === - - /** Class to use for option buttons (set in Blueprint) */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Config") - TSubclassOf OptionButtonClass; - - /** Current question data */ - UPROPERTY(BlueprintReadOnly, Category = "UEBridge|State") - FUEBridgeQuestion CurrentQuestion; - - // === STYLE === - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor BackgroundColor = FLinearColor(0.05f, 0.05f, 0.08f, 0.95f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor QuestionTextColor = FLinearColor(1.0f, 1.0f, 1.0f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor ProgressTextColor = FLinearColor(0.5f, 0.5f, 0.5f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor DepthLabelColor = FLinearColor(0.5f, 0.8f, 0.5f, 1.0f); // Default sage green - - // === FUNCTIONS === - - /** Display a new question */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Display") - void ShowQuestion(const FUEBridgeQuestion& Question); - - /** Update progress text */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Display") - void UpdateProgress(int32 Current, int32 Total); - - /** Clear all options and reset */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Display") - void ClearOptions(); - - /** Show/hide the entire widget with animation */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|Display") - void SetDisplayVisible(bool bVisible); - - /** Get the currently selected option index (-1 if none) */ - UFUNCTION(BlueprintCallable, Category = "UEBridge|State") - int32 GetSelectedOptionIndex() const { return SelectedOptionIndex; } - - virtual TSharedRef RebuildWidget() override; - -protected: - virtual void NativeConstruct() override; - - // Widget components (bind in Blueprint) - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* QuestionText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* ProgressText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* DepthText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UVerticalBox* OptionsContainer; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UBorder* BackgroundBorder; - -private: - /** Handle option button click */ - UFUNCTION() - void HandleOptionClicked(int32 OptionIndex); - - /** Create option buttons for current question */ - void CreateOptionButtons(); - - /** Build widget tree programmatically (no Blueprint required) */ - void BuildWidgetTree(); - - /** Currently selected option (-1 = none) */ - int32 SelectedOptionIndex = -1; - - /** Created option button widgets */ - UPROPERTY() - TArray OptionButtons; -}; diff --git a/Source/UnrealEngineBridge/UI/W_TitleScreen.cpp b/Source/UnrealEngineBridge/UI/W_TitleScreen.cpp deleted file mode 100644 index 3574f6c..0000000 --- a/Source/UnrealEngineBridge/UI/W_TitleScreen.cpp +++ /dev/null @@ -1,188 +0,0 @@ -// W_TitleScreen.cpp -// Implementation of title screen widget -// Programmatic UI - no Blueprint required - -#include "W_TitleScreen.h" -#include "UEBridgeRuntime.h" -#include "Components/TextBlock.h" -#include "Components/Border.h" -#include "Components/VerticalBox.h" -#include "Components/VerticalBoxSlot.h" -#include "Components/Spacer.h" -#include "Blueprint/WidgetTree.h" -#include "Misc/Paths.h" -#include "EnhancedInputComponent.h" -#include "UEBridgeStyle.h" -#include "EnhancedInputSubsystems.h" -#include "InputAction.h" -#include "InputActionValue.h" - - -UW_TitleScreen::UW_TitleScreen(const FObjectInitializer& ObjectInitializer) - : Super(ObjectInitializer) -{ - // Dark background matching the game aesthetic - BackgroundColor = FUEBridgeStyle::GetColor("Color.BackgroundSolid"); - TitleColor = FUEBridgeStyle::GetColor("Color.Cyan"); - SubtitleColor = FUEBridgeStyle::GetColor("Color.TextDim"); - PromptColor = FUEBridgeStyle::GetColor("Color.CyanDim"); - - // Widget must be focusable to receive key events - SetIsFocusable(true); -} - - -TSharedRef UW_TitleScreen::RebuildWidget() -{ - if (!TitleText) - { - BuildWidgetTree(); - } - return Super::RebuildWidget(); -} - - -void UW_TitleScreen::NativeConstruct() -{ - Super::NativeConstruct(); - - // Apply colors - if (BackgroundBorder) - { - BackgroundBorder->SetBrushColor(BackgroundColor); - } - if (TitleText) - { - TitleText->SetColorAndOpacity(FSlateColor(TitleColor)); - } - if (SubtitleText) - { - SubtitleText->SetColorAndOpacity(FSlateColor(SubtitleColor)); - } - if (PromptText) - { - PromptText->SetColorAndOpacity(FSlateColor(PromptColor)); - } - - // Request keyboard focus so we can receive Enter key - SetKeyboardFocus(); - - // Optionally bind Enhanced Input action (if configured by designer) - if (StartInputAction) - { - if (APlayerController* PC = GetOwningPlayer()) - { - if (UEnhancedInputComponent* EIC = Cast(PC->InputComponent)) - { - EIC->BindAction(StartInputAction, ETriggerEvent::Started, this, &UW_TitleScreen::HandleStartAction); - UE_LOG(LogUEBridge, Log, TEXT("[W_TitleScreen] Bound StartInputAction via Enhanced Input")); - } - } - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_TitleScreen] Constructed (Programmatic UI)")); -} - - -void UW_TitleScreen::NativeTick(const FGeometry& MyGeometry, float InDeltaTime) -{ - Super::NativeTick(MyGeometry, InDeltaTime); - - // Pulse the "Press ENTER to begin" text opacity - PulseTimer += InDeltaTime; - if (PromptText) - { - // Sine wave oscillation between 0.3 and 1.0 - float Alpha = 0.3f + 0.7f * (0.5f + 0.5f * FMath::Sin(PulseTimer * 2.5f)); - PromptText->SetRenderOpacity(Alpha); - } -} - - -FReply UW_TitleScreen::NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) -{ - if (!bStartRequested) - { - FKey Key = InKeyEvent.GetKey(); - if (Key == EKeys::Enter || Key == EKeys::SpaceBar) - { - bStartRequested = true; - UE_LOG(LogUEBridge, Log, TEXT("[W_TitleScreen] Start requested!")); - OnStartRequested.Broadcast(); - return FReply::Handled(); - } - } - - return Super::NativeOnKeyDown(InGeometry, InKeyEvent); -} - - -void UW_TitleScreen::HandleStartAction(const FInputActionValue& Value) -{ - if (!bStartRequested) - { - bStartRequested = true; - UE_LOG(LogUEBridge, Log, TEXT("[W_TitleScreen] Start requested via Enhanced Input!")); - OnStartRequested.Broadcast(); - } -} - - -void UW_TitleScreen::BuildWidgetTree() -{ - // Simple structure: Border (root, fills viewport) -> centered VerticalBox -> TextBlocks - // Avoids CanvasPanel AutoSize issues that can collapse to zero - BackgroundBorder = WidgetTree->ConstructWidget(UBorder::StaticClass(), TEXT("BackgroundBorder")); - BackgroundBorder->SetBrushColor(BackgroundColor); - BackgroundBorder->SetHorizontalAlignment(HAlign_Center); - BackgroundBorder->SetVerticalAlignment(VAlign_Center); - BackgroundBorder->SetPadding(FMargin(40.0f)); - WidgetTree->RootWidget = BackgroundBorder; - - // Content box (centered by border alignment) - UVerticalBox* ContentBox = WidgetTree->ConstructWidget(UVerticalBox::StaticClass(), TEXT("ContentBox")); - BackgroundBorder->AddChild(ContentBox); - - // === Title: "UE Bridge" === - TitleText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("TitleText")); - TitleText->SetText(NSLOCTEXT("UEBridge", "TitleScreen.Title", "UE Bridge")); - TitleText->SetColorAndOpacity(FSlateColor(TitleColor)); - TitleText->SetJustification(ETextJustify::Center); - TitleText->SetFont(FUEBridgeStyle::GetFont("Font.Title")); - - UVerticalBoxSlot* TitleSlot = ContentBox->AddChildToVerticalBox(TitleText); - if (TitleSlot) - { - TitleSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 12.0f)); - TitleSlot->SetHorizontalAlignment(HAlign_Center); - } - - // === Subtitle: "A cognitive profiling experience" === - SubtitleText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("SubtitleText")); - SubtitleText->SetText(NSLOCTEXT("UEBridge", "TitleScreen.Subtitle", "A cognitive profiling experience")); - SubtitleText->SetColorAndOpacity(FSlateColor(SubtitleColor)); - SubtitleText->SetJustification(ETextJustify::Center); - SubtitleText->SetFont(FUEBridgeStyle::GetFont("Font.Subtitle")); - - UVerticalBoxSlot* SubtitleSlot = ContentBox->AddChildToVerticalBox(SubtitleText); - if (SubtitleSlot) - { - SubtitleSlot->SetPadding(FMargin(0.0f, 0.0f, 0.0f, 60.0f)); - SubtitleSlot->SetHorizontalAlignment(HAlign_Center); - } - - // === Prompt: "Press ENTER to begin" === - PromptText = WidgetTree->ConstructWidget(UTextBlock::StaticClass(), TEXT("PromptText")); - PromptText->SetText(NSLOCTEXT("UEBridge", "TitleScreen.Prompt", "Press ENTER to begin")); - PromptText->SetColorAndOpacity(FSlateColor(PromptColor)); - PromptText->SetJustification(ETextJustify::Center); - PromptText->SetFont(FUEBridgeStyle::GetFont("Font.Body")); - - UVerticalBoxSlot* PromptSlot = ContentBox->AddChildToVerticalBox(PromptText); - if (PromptSlot) - { - PromptSlot->SetHorizontalAlignment(HAlign_Center); - } - - UE_LOG(LogUEBridge, Log, TEXT("[W_TitleScreen] Built programmatic widget tree (Border root)")); -} diff --git a/Source/UnrealEngineBridge/UI/W_TitleScreen.h b/Source/UnrealEngineBridge/UI/W_TitleScreen.h deleted file mode 100644 index eb7c3ba..0000000 --- a/Source/UnrealEngineBridge/UI/W_TitleScreen.h +++ /dev/null @@ -1,95 +0,0 @@ -// W_TitleScreen.h -// Title screen for The Translators cognitive profiling game -// Part of The UnrealEngine Bridge - Claude Code -> UE5.7 Bridge - -#pragma once - -#include "CoreMinimal.h" -#include "Blueprint/UserWidget.h" -#include "W_TitleScreen.generated.h" - -class UTextBlock; -class UBorder; -class UVerticalBox; -class UInputAction; -struct FInputActionValue; - -// Delegate fired when user presses Enter to start -DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnStartRequested); - -/** - * W_TitleScreen - Opening title screen - * - * Displays: - * - "UE Bridge" (large, cyan) - * - "A cognitive profiling experience" (dim subtitle) - * - "Press ENTER to begin" (pulsing opacity) - * - * Programmatic UI - no Blueprint required - */ -UCLASS(Blueprintable, BlueprintType) -class UNREALENGINEBRIDGE_API UW_TitleScreen : public UUserWidget -{ - GENERATED_BODY() - -public: - UW_TitleScreen(const FObjectInitializer& ObjectInitializer); - - // === DELEGATES === - - /** Fired when user presses Enter to start the game */ - UPROPERTY(BlueprintAssignable, Category = "UEBridge|Events") - FOnStartRequested OnStartRequested; - - // === STYLE === - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor BackgroundColor = FLinearColor(0.02f, 0.02f, 0.05f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor TitleColor = FLinearColor(0.36f, 1.0f, 0.86f, 1.0f); // Cyan #5cffdb - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor SubtitleColor = FLinearColor(0.5f, 0.5f, 0.6f, 1.0f); - - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Style") - FLinearColor PromptColor = FLinearColor(0.36f, 1.0f, 0.86f, 0.8f); - - // === INPUT === - - /** Optional Enhanced Input action for starting the game. If set, binds to Enhanced Input system. */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "UEBridge|Input") - TObjectPtr StartInputAction; - - // Build widget tree BEFORE Slate hierarchy is constructed - virtual TSharedRef RebuildWidget() override; - -protected: - virtual void NativeConstruct() override; - virtual void NativeTick(const FGeometry& MyGeometry, float InDeltaTime) override; - virtual FReply NativeOnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* TitleText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* SubtitleText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UTextBlock* PromptText; - - UPROPERTY(BlueprintReadOnly, meta = (BindWidget, OptionalWidget = true)) - UBorder* BackgroundBorder; - -private: - void BuildWidgetTree(); - - /** Handle Enhanced Input start action */ - void HandleStartAction(const FInputActionValue& Value); - - /** Elapsed time for pulsing animation */ - float PulseTimer = 0.0f; - - /** Whether start has already been requested (prevent double-fire) */ - bool bStartRequested = false; -}; diff --git a/bridge_orchestrator.py b/bridge_orchestrator.py deleted file mode 100644 index 08b58a1..0000000 --- a/bridge_orchestrator.py +++ /dev/null @@ -1,892 +0,0 @@ -#!/usr/bin/env python3 -""" -Translators Bridge Orchestrator v2.0.0 -Drives the cognitive profiling questionnaire through USD-native or JSON file-based bridge. - -USD-Native Communication (v2.0.0): -- Uses bridge_state.usda with VariantSets as state machine -- Behavioral signals enable ADHD_MoE expert routing -- Full cognitive substrate integration via USD composition - -Legacy JSON Communication (v1.0.0): -- Uses state.json / answer.json for backward compatibility -- Falls back to JSON if USD module unavailable - -Usage: - python bridge_orchestrator.py # Run with USD (fallback to JSON) - python bridge_orchestrator.py --json # Force JSON mode - python bridge_orchestrator.py --test # Write test question and exit -""" - -import argparse -import json -import os -import subprocess -import sys -import tempfile -import threading -import time -from datetime import datetime -from pathlib import Path -from typing import Optional, Dict, Any, List - -# Try to import USD bridge module -try: - from usd_bridge import ( - write_question_usda, - read_answer_usda, - write_transition_usda, - write_finale_usda, - write_ready_usda, - read_ack_usda, - read_behavioral_signals, - set_variant, - validate_bridge_state, - get_bridge_file_path, - ensure_bridge_directory, - compute_checksum as _usd_compute_checksum, - ) - HAS_USD_BRIDGE = True -except ImportError: - HAS_USD_BRIDGE = False - print("[Bridge] USD bridge module not available, using JSON mode") - -# ============================================ -# Configuration -# ============================================ - -BRIDGE_DIR = Path.home() / ".translators" -STATE_FILE = BRIDGE_DIR / "state.json" -ANSWER_FILE = BRIDGE_DIR / "answer.json" -ACK_FILE = BRIDGE_DIR / "ack.json" -PROFILE_FILE = BRIDGE_DIR / "cognitive_profile.usda" - -POLL_INTERVAL_MIN = 0.05 # 50ms — fast start -POLL_INTERVAL_MAX = 0.5 # 500ms — ceiling -POLL_BACKOFF_FACTOR = 1.5 # multiply interval each miss -BRIDGE_VERSION = "2.0.0" -HEARTBEAT_INTERVAL = 5.0 # seconds between heartbeat writes -HEARTBEAT_FILE = BRIDGE_DIR / "heartbeat.json" - -# USD mode flag (set by CLI args or auto-detect) -USE_USD_MODE = HAS_USD_BRIDGE - -# ============================================ -# The 8 Calibration Questions -# Updated for USD Cognitive Substrate v4.3.0 -# Each question maps to a cognitive dimension (0.0-1.0) -# ============================================ - -QUESTIONS = [ - { - "id": "load", - "text": "How much can you hold at once\nbefore it starts to blur?", - "dimension": "cognitive_density", # USD dimension mapping - "scene": "forest_edge", - "options": [ - {"label": "Not much. One thing at a time.", "direction": "low", "trait": "focused", "value": 0.2}, - {"label": "Quite a lot. I can hold complexity.", "direction": "high", "trait": "parallel", "value": 0.8}, - {"label": "It varies. Some days more than others.", "direction": "mid", "trait": "adaptive", "value": 0.5} - ] - }, - { - "id": "pace", - "text": "When you're working on something\nthat matters to you...", - "dimension": "processing_pace", - "scene": "forest_path", - "options": [ - {"label": "I go deep. Hours disappear.", "direction": "low", "trait": "hyperfocus", "value": 0.2}, - {"label": "I take breaks. Steady rhythm.", "direction": "high", "trait": "sustainable", "value": 0.8}, - {"label": "Bursts of intensity, then rest.", "direction": "mid", "trait": "cyclical", "value": 0.5} - ] - }, - { - "id": "uncertainty", - "text": "When facing the unknown...", - "dimension": "uncertainty_tolerance", - "scene": "misty_clearing", - "options": [ - {"label": "I need a plan before I move.", "direction": "low", "trait": "structured", "value": 0.2}, - {"label": "I explore. The path reveals itself.", "direction": "high", "trait": "emergent", "value": 0.8}, - {"label": "I sketch a direction, then adapt.", "direction": "mid", "trait": "iterative", "value": 0.5} - ] - }, - { - "id": "feedback", - "text": "How do you know\nyou're on the right track?", - "dimension": "guidance_frequency", - "scene": "ancient_tree", - "options": [ - {"label": "External validation. Others confirm.", "direction": "high", "trait": "external", "value": 0.8}, - {"label": "Internal sense. I just know.", "direction": "low", "trait": "internal", "value": 0.2}, - {"label": "Results. The work speaks.", "direction": "mid", "trait": "empirical", "value": 0.5} - ] - }, - { - "id": "recovery", - "text": "After intense effort,\nwhat restores you?", - "dimension": "home_altitude", # Grounding vs elevated perspective - "scene": "quiet_stream", - "options": [ - {"label": "Solitude. Silence. Nothing.", "direction": "low", "trait": "solitary", "value": 0.2}, - {"label": "Connection. People. Talk.", "direction": "high", "trait": "social", "value": 0.8}, - {"label": "Movement. Change of scene.", "direction": "mid", "trait": "kinetic", "value": 0.5} - ] - }, - { - "id": "starting", - "text": "Beginning something new...", - "dimension": "default_paradigm", - "scene": "dawn_ridge", - "options": [ - {"label": "Is hard. I circle before landing.", "direction": "low", "trait": "cautious", "value": 0.2}, - {"label": "Is exciting. I dive in.", "direction": "high", "trait": "eager", "value": 0.8}, - {"label": "Depends on whether I chose it.", "direction": "mid", "trait": "autonomous", "value": 0.5} - ] - }, - { - "id": "completion", - "text": "When something is 'done'...", - "dimension": "feedback_style", - "scene": "summit_view", - "options": [ - {"label": "I know exactly when. Clean edges.", "direction": "low", "trait": "definitive", "value": 0.2}, - {"label": "It's never quite done. Always more.", "direction": "high", "trait": "perfectionist", "value": 0.8}, - {"label": "Done enough to ship. Move on.", "direction": "mid", "trait": "pragmatic", "value": 0.5} - ] - }, - { - "id": "essence", - "text": "At your core,\nyou are someone who...", - "dimension": "tangent_tolerance", - "scene": "mirror_pool", - "options": [ - {"label": "Builds. Makes things exist.", "direction": "low", "trait": "builder", "value": 0.2}, - {"label": "Connects. Sees relationships.", "direction": "mid", "trait": "connector", "value": 0.5}, - {"label": "Discovers. Follows curiosity.", "direction": "high", "trait": "explorer", "value": 0.8} - ] - } -] - -# ============================================ -# Display -# ============================================ - -class Colors: - CYAN = '\033[96m' - GREEN = '\033[92m' - YELLOW = '\033[93m' - RED = '\033[91m' - DIM = '\033[90m' - RESET = '\033[0m' - BOLD = '\033[1m' - -class HeartbeatWriter: - """Background thread that writes heartbeat.json every HEARTBEAT_INTERVAL seconds.""" - - def __init__(self): - self._stop_event = threading.Event() - self._thread = None - - def start(self): - self._stop_event.clear() - self._thread = threading.Thread(target=self._run, daemon=True) - self._thread.start() - - def stop(self): - self._stop_event.set() - if self._thread: - self._thread.join(timeout=2) - - def _run(self): - while not self._stop_event.is_set(): - try: - heartbeat = { - "timestamp": datetime.now().isoformat(), - "pid": __import__("os").getpid(), - "bridge_version": BRIDGE_VERSION, - "alive": True, - } - # Write atomically via tmp + replace - import tempfile, os - fd, tmp = tempfile.mkstemp(dir=str(BRIDGE_DIR), suffix=".tmp") - with os.fdopen(fd, "w", encoding="utf-8") as f: - json.dump(heartbeat, f) - os.replace(tmp, str(HEARTBEAT_FILE)) - except Exception: - pass # Best-effort — don't crash the bridge - self._stop_event.wait(HEARTBEAT_INTERVAL) - - -def _adaptive_sleep(poll_interval: float) -> float: - """Sleep and return the next (backed-off) interval.""" - time.sleep(poll_interval) - return min(poll_interval * POLL_BACKOFF_FACTOR, POLL_INTERVAL_MAX) - - -def clear_screen(): - try: - import os - if sys.stdout.isatty(): - os.system('cls' if os.name == 'nt' else 'clear') - else: - print("\n" + "=" * 60 + "\n") - except Exception: - pass - -def print_banner(): - clear_screen() - print(f""" -{Colors.CYAN} +-----------------------------------------------------------+ - | | - | {Colors.BOLD}THE TRANSLATORS{Colors.RESET}{Colors.CYAN} -- Cognitive Profile Orchestrator | - | | - | Waiting for UE5 connection... | - | | - +-----------------------------------------------------------+{Colors.RESET} -""") - -def print_progress(current: int, total: int): - filled = int((current / total) * 30) - bar = "#" * filled + "." * (30 - filled) - print(f"\n {Colors.CYAN}Progress: [{bar}] {current}/{total}{Colors.RESET}") - -def print_question(q: dict, index: int, total: int): - clear_screen() - print_progress(index, total) - print(f""" -{Colors.BOLD} Question {index + 1} of {total}{Colors.RESET} -{Colors.DIM} Scene: {q['scene']}{Colors.RESET} - -{Colors.CYAN} {q['text'].replace(chr(10), chr(10) + ' ')}{Colors.RESET} - - Waiting for answer in UE5... -""") - -# ============================================ -# Bridge Communication -# ============================================ - -def _atomic_write_json(file_path: Path, data: dict) -> None: - """Write JSON atomically via tmp + os.replace.""" - fd, tmp = tempfile.mkstemp(dir=str(file_path.parent), suffix=".tmp") - try: - with os.fdopen(fd, "w", encoding="utf-8") as f: - json.dump(data, f, indent=2) - os.replace(tmp, str(file_path)) - except BaseException: - try: - os.unlink(tmp) - except OSError: - pass - raise - - -def ensure_bridge_dir(): - """Create bridge directory if it doesn't exist.""" - BRIDGE_DIR.mkdir(parents=True, exist_ok=True) - return BRIDGE_DIR.exists() - -def clear_bridge_files(): - """Remove stale communication files.""" - for f in [STATE_FILE, ANSWER_FILE, ACK_FILE, HEARTBEAT_FILE]: - if f.exists(): - f.unlink() - -def write_question(question: dict, index: int, total: int): - """Write question to bridge_state.usda (USD) or state.json (JSON fallback).""" - global USE_USD_MODE - - if USE_USD_MODE and HAS_USD_BRIDGE: - # USD-native mode - try: - write_question_usda( - question_id=question["id"], - text=question["text"], - options=[ - { - "label": opt["label"], - "direction": opt["direction"], - "semantic_tag": opt.get("trait", "") - } - for opt in question["options"] - ], - index=index, - total=total, - scene=question.get("scene", ""), - bridge_path=BRIDGE_DIR - ) - return - except Exception as e: - print(f"{Colors.YELLOW} USD write failed, falling back to JSON: {e}{Colors.RESET}") - USE_USD_MODE = False - - # JSON fallback - state = { - "type": "question", - "index": index, - "total": total, - "id": question["id"], - "text": question["text"], - "scene": question["scene"], - "dimension": question.get("dimension", ""), # Include dimension for profile - "options": [ - { - "index": i, - "label": opt["label"], - "direction": opt["direction"], - "value": opt.get("value", 0.5) # Include numeric value - } - for i, opt in enumerate(question["options"]) - ], - "timestamp": datetime.now().isoformat(), - "bridge_version": BRIDGE_VERSION - } - - _atomic_write_json(STATE_FILE, state) - -def wait_for_answer(question: dict = None, timeout: float = 300.0) -> Optional[dict]: - """Wait for answer from UE5 (USD or JSON mode) with adaptive backoff polling.""" - global USE_USD_MODE - start = time.time() - poll_interval = POLL_INTERVAL_MIN # Start fast, back off on misses - - while time.time() - start < timeout: - # Try USD mode first - if USE_USD_MODE and HAS_USD_BRIDGE: - try: - answer_data = read_answer_usda(BRIDGE_DIR) - if answer_data and answer_data.get("option_index", -1) >= 0: - # Match question_id if provided - if question and answer_data.get("question_id") != question.get("id"): - poll_interval = _adaptive_sleep(poll_interval) - continue - - # Clear answer state - set_variant("sync_status", "idle", BRIDGE_DIR) - - # Read behavioral signals for ADHD_MoE routing - signals = read_behavioral_signals(BRIDGE_DIR) - if signals: - answer_data["behavioral_signals"] = signals - - # Map to legacy format for compatibility - return { - "type": "answer", - "question_id": answer_data.get("question_id", ""), - "option_index": answer_data.get("option_index", 0), - "response_time_ms": answer_data.get("response_time_ms", 0), - "answer": { - "question_id": answer_data.get("question_id", ""), - "option_index": answer_data.get("option_index", 0), - "response_time_ms": answer_data.get("response_time_ms", 0), - }, - "behavioral_signals": answer_data.get("behavioral_signals", {}) - } - except Exception as e: - if time.time() - start < 5: # Only log once at start - print(f"{Colors.DIM} USD read: {e}{Colors.RESET}") - - # JSON fallback - if ANSWER_FILE.exists(): - try: - with open(ANSWER_FILE, 'r', encoding='utf-8') as f: - answer = json.load(f) - # Clear the answer file after reading - ANSWER_FILE.unlink() - return answer - except (json.JSONDecodeError, IOError): - pass - - poll_interval = _adaptive_sleep(poll_interval) - - return None - -def write_transition(direction: str, next_scene: str, progress: float = 0.0, from_question_id: str = ""): - """Write transition state for UE5 (USD or JSON mode).""" - global USE_USD_MODE - - if USE_USD_MODE and HAS_USD_BRIDGE: - try: - write_transition_usda( - direction=direction, - next_scene=next_scene, - progress=progress, - from_question_id=from_question_id, - bridge_path=BRIDGE_DIR - ) - return - except Exception as e: - print(f"{Colors.DIM} USD transition failed: {e}{Colors.RESET}") - - # JSON fallback - state = { - "type": "transition", - "direction": direction, - "next_scene": next_scene, - "progress": progress, - "from_question_id": from_question_id, - "timestamp": datetime.now().isoformat(), - "bridge_version": BRIDGE_VERSION - } - - _atomic_write_json(STATE_FILE, state) - - -def write_finale(profile_path: str, checksum: str = "", total_answered: int = 8): - """Write finale state for UE5 (USD or JSON mode).""" - global USE_USD_MODE - - if USE_USD_MODE and HAS_USD_BRIDGE: - try: - write_finale_usda( - usd_path=profile_path, - checksum=checksum, - message="Cognitive profile complete! Your profile is ready for AI consumption.", - total_questions=len(QUESTIONS), - questions_answered=total_answered, - bridge_path=BRIDGE_DIR - ) - return - except Exception as e: - print(f"{Colors.DIM} USD finale failed: {e}{Colors.RESET}") - - # JSON fallback - state = { - "type": "finale", - "usd_path": profile_path, - "checksum": checksum, - "total_questions": len(QUESTIONS), - "questions_answered": total_answered, - "message": "Cognitive profile complete!", - "timestamp": datetime.now().isoformat(), - "bridge_version": BRIDGE_VERSION - } - - _atomic_write_json(STATE_FILE, state) - -# ============================================ -# Profile Generation (USD Cognitive Substrate v4.3.0) -# ============================================ - -def compute_checksum(dimensions: dict) -> str: - """Compute deterministic checksum for profile. Delegates to usd_bridge canonical implementation.""" - if HAS_USD_BRIDGE: - return _usd_compute_checksum(dimensions) - # Inline fallback when usd_bridge unavailable - sorted_dims = sorted(dimensions.items()) - serialized = "TRL_v1|" + "|".join(f"{k}:{v}" for k, v in sorted_dims) - hash_val = 5381 - for char in serialized: - hash_val = ((hash_val << 5) + hash_val) + ord(char) - hash_val &= 0xFFFFFFFF - return format(hash_val, '08x') - - -def generate_profile(answers: list) -> dict: - """Generate cognitive profile from answers.""" - traits = {} # Legacy: trait names - dimensions = {} # USD: numeric values (0.0-1.0) - - for i, answer in enumerate(answers): - q = QUESTIONS[i] - option_idx = answer.get("option_index", 0) - if "answer" in answer: - option_idx = answer["answer"].get("option_index", option_idx) - - if 0 <= option_idx < len(q["options"]): - option = q["options"][option_idx] - dimension_name = q.get("dimension", q["id"]) - traits[q["id"]] = option.get("trait", "") - dimensions[dimension_name] = option.get("value", 0.5) - - return { - "traits": traits, - "dimensions": dimensions, - "version": "TRL_v1" - } - -def export_usda(profile: dict, answers: list) -> tuple: - """Export profile as USDA file (USD Cognitive Substrate v4.3.0 format).""" - dimensions = profile.get("dimensions", {}) - traits = profile.get("traits", {}) - checksum = compute_checksum(dimensions) - timestamp = datetime.now().isoformat() - - # Build answer prims - answer_prims = [] - for i, answer in enumerate(answers): - q = QUESTIONS[i] - option_idx = answer.get("option_index", 0) - if "answer" in answer: - option_idx = answer["answer"].get("option_index", option_idx) - response_time = answer.get("response_time_ms", 0) - if "answer" in answer: - response_time = answer["answer"].get("response_time_ms", response_time) - - if 0 <= option_idx < len(q["options"]): - option = q["options"][option_idx] - answer_prims.append(f''' - def Xform "{q['id']}" {{ - int option_index = {option_idx} - string value = "{option.get('direction', '')}" - string trait = "{option.get('trait', '')}" - float response_time = {response_time} - }}''') - - usda_content = f'''#usda 1.0 -( - defaultPrim = "CognitiveSubstrate" - doc = """Cognitive Profile Generated by The Translators v{BRIDGE_VERSION} - USD Cognitive Substrate v4.3.0 compliant - Checksum: {checksum} - Generated: {timestamp} - """ -) - -def Xform "CognitiveSubstrate" ( - kind = "component" - customData = {{ - string generator = "UEBridge" - string version = "{profile.get('version', 'TRL_v1')}" - string checksum = "{checksum}" - string generated = "{timestamp}" - string translators_anchor = "[TRANSLATORS:{checksum}]" - }} -) -{{ - # === PROFILE LAYER === - # Cognitive dimensions (0.0 - 1.0) for AI consumption - # Maps to USD Cognitive Substrate L1 (Profile) - - def Xform "Profile" ( - doc = "Cognitive profile dimensions derived from questionnaire" - ) - {{ - float cognitive_density = {dimensions.get("cognitive_density", 0.5)} - float home_altitude = {dimensions.get("home_altitude", 0.5)} - float guidance_frequency = {dimensions.get("guidance_frequency", 0.5)} - float default_paradigm = {dimensions.get("default_paradigm", 0.5)} - float feedback_style = {dimensions.get("feedback_style", 0.5)} - float uncertainty_tolerance = {dimensions.get("uncertainty_tolerance", 0.5)} - float processing_pace = {dimensions.get("processing_pace", 0.5)} - float tangent_tolerance = {dimensions.get("tangent_tolerance", 0.5)} - }} - - # === SESSION LAYER === - # Runtime state (L13 in full substrate) - - def Xform "Session" {{ - string session_id = "{checksum[:8]}" - int questions_answered = {len(answers)} - float completion = 1.0 - string checksum = "{checksum}" - string active_mode = "calibrated" - }} - - # === TRAITS LAYER === - # Human-readable trait labels (legacy compatibility) - - def Xform "Traits" ( - doc = "Human-readable trait labels from questionnaire" - ) - {{ - string load = "{traits.get('load', '')}" - string pace = "{traits.get('pace', '')}" - string uncertainty = "{traits.get('uncertainty', '')}" - string feedback = "{traits.get('feedback', '')}" - string recovery = "{traits.get('recovery', '')}" - string starting = "{traits.get('starting', '')}" - string completion = "{traits.get('completion', '')}" - string essence = "{traits.get('essence', '')}" - }} - - # === ANSWERS LAYER === - # Raw answer data for audit trail - - def Xform "Answers" ( - doc = "Individual question responses" - ) - {{ -{"".join(answer_prims)} - }} -}} -''' - - # Write file atomically - fd, tmp = tempfile.mkstemp(dir=str(PROFILE_FILE.parent), suffix=".tmp") - try: - with os.fdopen(fd, "w", encoding="utf-8") as f: - f.write(usda_content) - os.replace(tmp, str(PROFILE_FILE)) - except BaseException: - try: - os.unlink(tmp) - except OSError: - pass - raise - - return str(PROFILE_FILE), checksum - -# ============================================ -# Main Orchestration -# ============================================ - -def initialize_usd_bridge(): - """Initialize USD bridge if available.""" - global USE_USD_MODE - - if not HAS_USD_BRIDGE: - print(f" {Colors.DIM}USD bridge not available, using JSON mode{Colors.RESET}") - USE_USD_MODE = False - return False - - try: - ensure_bridge_directory(BRIDGE_DIR) - write_ready_usda( - total_questions=len(QUESTIONS), - first_scene=QUESTIONS[0]["scene"] if QUESTIONS else "", - bridge_path=BRIDGE_DIR - ) - validation = validate_bridge_state(BRIDGE_DIR) - if validation["valid"]: - print(f" {Colors.GREEN}[OK]{Colors.RESET} USD bridge initialized") - USE_USD_MODE = True - return True - else: - print(f" {Colors.YELLOW}USD validation failed: {validation['errors']}{Colors.RESET}") - USE_USD_MODE = False - return False - except Exception as e: - print(f" {Colors.YELLOW}USD init failed: {e}{Colors.RESET}") - USE_USD_MODE = False - return False - - -def run_questionnaire(force_json: bool = False): - """Run the full questionnaire flow.""" - global USE_USD_MODE - - print_banner() - - # Setup - if not ensure_bridge_dir(): - print(f"{Colors.RED} ERROR: Could not create bridge directory{Colors.RESET}") - return False - - clear_bridge_files() - answers = [] - total = len(QUESTIONS) - - # Start heartbeat writer - heartbeat = HeartbeatWriter() - heartbeat.start() - - # Initialize USD bridge (unless JSON forced) - if force_json: - USE_USD_MODE = False - print(f" {Colors.DIM}JSON mode forced{Colors.RESET}") - else: - initialize_usd_bridge() - - # Write "ready" state so UE5 knows we're here - if not USE_USD_MODE: - ready_state = { - "$schema": "translators-state-v1", - "type": "ready", - "total_questions": total, - "first_scene": QUESTIONS[0]["scene"] if QUESTIONS else "", - "timestamp": datetime.now().isoformat(), - "bridge_version": BRIDGE_VERSION - } - _atomic_write_json(STATE_FILE, ready_state) - - mode_str = "USD" if USE_USD_MODE else "JSON" - print(f"\n {Colors.GREEN}<={Colors.RESET} Bridge ready at {BRIDGE_DIR}") - print(f" {Colors.DIM}Mode: {mode_str} | Press Play in UE5 to begin...{Colors.RESET}\n") - - # Wait for UE5 acknowledgment (adaptive polling) - print(f" Waiting for UE5 acknowledgment...") - ack_received = False - ack_start = time.time() - ack_poll = POLL_INTERVAL_MIN - while time.time() - ack_start < 120: - if ANSWER_FILE.exists(): - try: - with open(ANSWER_FILE, 'r', encoding='utf-8') as f: - ack = json.load(f) - if ack.get("type") == "ack": - ANSWER_FILE.unlink() - ack_received = True - print(f" {Colors.GREEN}<={Colors.RESET} UE5 connected! Starting questionnaire...\n") - break - except (json.JSONDecodeError, IOError): - pass - ack_poll = _adaptive_sleep(ack_poll) - - if not ack_received: - print(f" {Colors.YELLOW}No ack received, starting anyway...{Colors.RESET}\n") - - time.sleep(1) - - # Run through questions - for i, question in enumerate(QUESTIONS): - print_question(question, i, total) - - # Send question to UE5 - write_question(question, i, total) - - # Wait for answer (pass question for USD mode verification) - answer = wait_for_answer(question=question) - - if answer is None: - print(f"\n{Colors.YELLOW} Timeout waiting for answer. Exiting.{Colors.RESET}") - return False - - answers.append(answer) - - # Log behavioral signals if available (ADHD_MoE routing) - if "behavioral_signals" in answer: - signals = answer["behavioral_signals"] - state = signals.get("detected_state", "focused") - burnout = signals.get("burnout_level", "GREEN") - if state != "focused" or burnout != "GREEN": - print(f" {Colors.DIM}Behavioral: {state} / {burnout}{Colors.RESET}") - - # Send transition (except for last question) - if i < total - 1: - option_idx = answer.get("option_index", 0) - if "answer" in answer: - option_idx = answer["answer"].get("option_index", option_idx) - direction = question["options"][option_idx]["direction"] - next_scene = QUESTIONS[i + 1]["scene"] - progress = (i + 1) / total - write_transition(direction, next_scene, progress, question["id"]) - time.sleep(1) # Brief pause for transition - - # Generate and export profile - clear_screen() - print(f""" -{Colors.CYAN} +-----------------------------------------------------------+ - | | - | {Colors.BOLD}PROFILE COMPLETE{Colors.RESET}{Colors.CYAN} | - | | - +-----------------------------------------------------------+{Colors.RESET} -""") - - profile = generate_profile(answers) - profile_path, checksum = export_usda(profile, answers) - - print(f" {Colors.GREEN}[OK]{Colors.RESET} Profile generated (USD Cognitive Substrate v4.3.0)") - print(f" {Colors.GREEN}[OK]{Colors.RESET} Checksum: {Colors.CYAN}{checksum}{Colors.RESET}") - print(f" {Colors.GREEN}[OK]{Colors.RESET} Exported: {profile_path}") - print() - - # Your profile - show dimensions - print(f" {Colors.BOLD}Your Cognitive Dimensions:{Colors.RESET}") - for dim, value in sorted(profile.get("dimensions", {}).items()): - bar_len = int(value * 20) - bar = "#" * bar_len + "." * (20 - bar_len) - print(f" {dim:24} [{bar}] {value:.1f}") - print() - - # Show traits - print(f" {Colors.BOLD}Your Traits:{Colors.RESET}") - for qid, trait in sorted(profile.get("traits", {}).items()): - print(f" {qid}: {Colors.CYAN}{trait}{Colors.RESET}") - print() - - # Send finale to UE5 - write_finale(profile_path, checksum, len(answers)) - - # Stop heartbeat - heartbeat.stop() - - print(f" {Colors.DIM}[TRANSLATORS:{checksum}]{Colors.RESET}") - print() - - return True - -# ============================================ -# Entry Point -# ============================================ - -def main(): - parser = argparse.ArgumentParser( - description="Translators Bridge Orchestrator v2.0.0 - USD-native cognitive profiling" - ) - parser.add_argument( - "--json", - action="store_true", - help="Force JSON mode (disable USD communication)" - ) - parser.add_argument( - "--test", - action="store_true", - help="Test mode: write sample question and exit" - ) - parser.add_argument( - "--validate", - action="store_true", - help="Validate bridge_state.usda and exit" - ) - - args = parser.parse_args() - - if args.validate: - if not HAS_USD_BRIDGE: - print("USD bridge module not available") - sys.exit(1) - ensure_bridge_dir() - validation = validate_bridge_state(BRIDGE_DIR) - print(f"Bridge state: {'VALID' if validation['valid'] else 'INVALID'}") - print(f" Sync status: {validation['sync_status']}") - print(f" Message type: {validation['message_type']}") - if validation["errors"]: - print(f" Errors: {validation['errors']}") - sys.exit(0 if validation["valid"] else 1) - - if args.test: - print("Test mode: Writing sample question...") - ensure_bridge_dir() - if HAS_USD_BRIDGE and not args.json: - write_question_usda( - question_id="test", - text="This is a test question from bridge_orchestrator.py", - options=[ - {"label": "Option A (low)", "direction": "low", "semantic_tag": "test_a"}, - {"label": "Option B (mid)", "direction": "mid", "semantic_tag": "test_b"}, - {"label": "Option C (high)", "direction": "high", "semantic_tag": "test_c"}, - ], - index=0, - total=1, - scene="test_scene", - bridge_path=BRIDGE_DIR - ) - print(f"USD question written to: {get_bridge_file_path(BRIDGE_DIR)}") - else: - write_question(QUESTIONS[0], 0, 1) - print(f"JSON question written to: {STATE_FILE}") - sys.exit(0) - - try: - success = run_questionnaire(force_json=args.json) - if success: - print(f" {Colors.GREEN}Session complete. Your profile is ready for AI consumption.{Colors.RESET}") - if sys.stdout.isatty(): - input("\n Press Enter to exit...") - else: - print("\n Orchestrator finished. Waiting 60s for cleanup...") - time.sleep(60) - except KeyboardInterrupt: - print(f"\n\n {Colors.YELLOW}Session cancelled.{Colors.RESET}") - sys.exit(0) - except Exception as e: - print(f"\n\n {Colors.RED}ERROR: {e}{Colors.RESET}") - import traceback - traceback.print_exc() - sys.exit(1) - - -if __name__ == "__main__": - main()