qa wip: spawn in front of facade + input/sim diagnostics

- PlayerStart moved to (-10722,-2400,800) clear air, facing the facade.
- Force game input mode + hide cursor at BeginPlay.
- Diagnostics: every-second world/Z/gravity/fall log; one HighResShot at 1.5s.

Verified (packaged, engine log): controller connects to pawn input=yes, ctx
pushed, actions bound, world ticks, gravity -980, but capsule stays Z=909
fall=0 forever and no input fires. Static airborne pawn + no fall + no input
= pawn simulated without an active autonomous local player. Recorded for next
debug; do not merge to main.
This commit is contained in:
zwitschi committed 2026-11-08 10:35:48 +01:00
1 parent ea16818e2a
commit 2199a546bd
8 files changed
+97 -24

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,9 +7,7 @@
#include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/CharacterMovementComponent.h"
#include "GameFramework/PlayerController.h" #include "GameFramework/PlayerController.h"
#include "InputAction.h" #include "InputAction.h"
#include "GameFramework/PlayerController.h"
#include "InputMappingContext.h" #include "InputMappingContext.h"
#include "Components/CapsuleComponent.h"
#include "Engine/World.h" #include "Engine/World.h"
#include "TimerManager.h" #include "TimerManager.h"
@@ -89,9 +87,9 @@ AGigLineCharacter::AGigLineCharacter()
void AGigLineCharacter::LoadInputAssets() void AGigLineCharacter::LoadInputAssets()
{ {
// The assets are hard-referenced and loaded in the constructor; this deferred // The assets are hard-referenced and loaded in the constructor. This pushes
// call pushes the now-valid mapping context to the local player, after the // the now-valid mapping context to the local player's subsystem; called from
// pawn has been possessed and its PlayerController exists. // SetupPlayerInputComponent at possession, when the controller exists.
if (MappingContext) if (MappingContext)
{ {
if (const APlayerController* PC = Cast<APlayerController>(GetController())) if (const APlayerController* PC = Cast<APlayerController>(GetController()))
@@ -100,7 +98,17 @@ void AGigLineCharacter::LoadInputAssets()
ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer())) ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PC->GetLocalPlayer()))
{ {
Subsystem->AddMappingContext(MappingContext, 0); Subsystem->AddMappingContext(MappingContext, 0);
UE_LOG(LogTemp, Log, TEXT("GigLine IN: pushed mapping context %s"),
*MappingContext->GetName());
} }
else
{
UE_LOG(LogTemp, Log, TEXT("GigLine IN: no enhanced input subsystem on local player"));
}
}
else
{
UE_LOG(LogTemp, Log, TEXT("GigLine IN: no PlayerController at LoadInputAssets"));
} }
} }
} }
@@ -112,11 +120,39 @@ void AGigLineCharacter::BeginPlay()
StartLocation = GetActorLocation(); StartLocation = GetActorLocation();
StartRotation = GetActorRotation(); StartRotation = GetActorRotation();
// Input assets are hard-referenced in the constructor. Defer the mapping // Force game input mode + focus + mouse capture so keyboard/mouse reach the
// context push to just after spawn, when the PlayerController exists and a // pawn regardless of window focus quirks.
// packaged game has finished mounting its pak IoStore. if (APlayerController* PC = Cast<APlayerController>(GetController()))
FTimerHandle InputHandle; {
GetWorldTimerManager().SetTimer(InputHandle, this, &AGigLineCharacter::LoadInputAssets, 1.0f, false); PC->SetInputMode(FInputModeGameOnly());
PC->bShowMouseCursor = false;
}
// TEMP QA: one screenshot at +1.5s so the actual frame is reviewable.
FTimerHandle ShotHandle;
GetWorldTimerManager().SetTimer(ShotHandle, [this]()
{
if (APlayerController* PC = Cast<APlayerController>(GetController()))
{
PC->ConsoleCommand(TEXT("HighResShot 1280 720"));
}
}, 1.5f, false);
// TEMP QA: every second log world time + capsule Z + gravity + fall state so
// we can see whether the world is simulating the pawn at all (symptom:
// "spawns above ground but never falls, no input").
FTimerHandle SimHandle;
GetWorldTimerManager().SetTimer(SimHandle, [this]()
{
UCharacterMovementComponent* M = GetCharacterMovement();
UE_LOG(LogTemp, Log,
TEXT("GigLine SIM: worldTime=%.2f Z=%.0f grav=%.1f fall=%d pawn=%s"),
GetWorld() ? GetWorld()->GetTimeSeconds() : -1.f,
GetActorLocation().Z,
M ? M->GetGravityZ() : 0.f,
(M && M->IsFalling()) ? 1 : 0,
*GetName());
}, 1.0f, true);
} }
void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
@@ -130,6 +166,10 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
return; return;
} }
// Push the mapping context for this controller's local player so the bound
// actions receive input (runs at possession, when the controller exists).
LoadInputAssets();
// One action per direction keeps the sign explicit: W and S differ only by // One action per direction keeps the sign explicit: W and S differ only by
// which handler runs, not by a modifier in the asset. // which handler runs, not by a modifier in the asset.
if (MoveAction) if (MoveAction)
@@ -165,6 +205,12 @@ void AGigLineCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputCo
Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump); Input->BindAction(JumpAction, ETriggerEvent::Started, this, &AGigLineCharacter::StartJump);
Input->BindAction(JumpAction, ETriggerEvent::Completed, this, &AGigLineCharacter::StopJump); Input->BindAction(JumpAction, ETriggerEvent::Completed, this, &AGigLineCharacter::StopJump);
} }
UE_LOG(LogTemp, Log, TEXT("GigLine IN: bound %d actions; ctx=%s move=%s look=%s jmp=%s"),
(Input ? 1 : 0),
MappingContext ? TEXT("ok") : TEXT("nil"),
MoveAction ? TEXT("ok") : TEXT("nil"),
LookAction ? TEXT("ok") : TEXT("nil"),
JumpAction ? TEXT("ok") : TEXT("nil"));
} }
namespace namespace
@@ -191,6 +237,8 @@ namespace
void AGigLineCharacter::MoveForward(const FInputActionValue& Value) void AGigLineCharacter::MoveForward(const FInputActionValue& Value)
{ {
UE_LOG(LogTemp, Log, TEXT("GigLine IN: MoveForward fired, axis=%.3f type=%d"),
FMath::Abs(SignedAxis(Value, false)), (int)Value.GetValueType());
ApplyMove(FMath::Abs(SignedAxis(Value, false)), 0.0f); ApplyMove(FMath::Abs(SignedAxis(Value, false)), 0.0f);
} }
@@ -212,11 +260,16 @@ void AGigLineCharacter::MoveLeft(const FInputActionValue& Value)
void AGigLineCharacter::ApplyMove(float Forward, float Strafe) void AGigLineCharacter::ApplyMove(float Forward, float Strafe)
{ {
const AController* Controller = GetController(); const AController* Controller = GetController();
if (!Controller || (FMath::IsNearlyZero(Forward) && FMath::IsNearlyZero(Strafe))) if (!Controller)
{
UE_LOG(LogTemp, Log, TEXT("GigLine IN: ApplyMove called but NO controller (fwd %.2f strafe %.2f)"),
Forward, Strafe);
return;
}
if (FMath::IsNearlyZero(Forward) && FMath::IsNearlyZero(Strafe))
{ {
return; return;
} }
// Forward/right on the controller yaw keeps the capsule upright on slopes. // Forward/right on the controller yaw keeps the capsule upright on slopes.
const FRotator YawRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f); const FRotator YawRotation(0.0f, Controller->GetControlRotation().Yaw, 0.0f);
const FVector ForwardDir = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X); const FVector ForwardDir = FRotationMatrix(YawRotation).GetUnitAxis(EAxis::X);
@@ -224,6 +277,8 @@ void AGigLineCharacter::ApplyMove(float Forward, float Strafe)
AddMovementInput(ForwardDir, Forward); AddMovementInput(ForwardDir, Forward);
AddMovementInput(RightDir, Strafe); AddMovementInput(RightDir, Strafe);
UE_LOG(LogTemp, Log, TEXT("GigLine IN: ApplyMove fwd %.2f strafe %.2f controller=%s yaw=%.0f"),
Forward, Strafe, *Controller->GetName(), Controller->GetControlRotation().Yaw);
} }
void AGigLineCharacter::Look(const FInputActionValue& Value) void AGigLineCharacter::Look(const FInputActionValue& Value)
@@ -1,5 +1,6 @@
#include "GigLineGameModeBase.h" #include "GigLineGameModeBase.h"
#include "GameFramework/PlayerController.h"
#include "GigLineCharacter.h" #include "GigLineCharacter.h"
#include "GigLineHUD.h" #include "GigLineHUD.h"
@@ -9,3 +10,12 @@ AGigLineGameModeBase::AGigLineGameModeBase()
// ODbL attribution must be visible in the running demo, not only in docs. // ODbL attribution must be visible in the running demo, not only in docs.
HUDClass = AGigLineHUD::StaticClass(); HUDClass = AGigLineHUD::StaticClass();
} }
void AGigLineGameModeBase::HandleStartingNewPlayer_Implementation(APlayerController* NewPlayer)
{
Super::HandleStartingNewPlayer_Implementation(NewPlayer);
UE_LOG(LogTemp, Log, TEXT("GigLine GM: player %s connected; pawn=%s input=%s"),
NewPlayer ? *NewPlayer->GetName() : TEXT("null"),
NewPlayer && NewPlayer->GetPawn() ? *NewPlayer->GetPawn()->GetName() : TEXT("NONE"),
NewPlayer && NewPlayer->PlayerInput ? TEXT("yes") : TEXT("no"));
}
@@ -18,4 +18,8 @@ class GIGLINE_API AGigLineGameModeBase : public AGameModeBase
public: public:
AGigLineGameModeBase(); AGigLineGameModeBase();
protected:
/** Logs whether a local player connected to a pawn with input (QA). */
virtual void HandleStartingNewPlayer_Implementation(class APlayerController* NewPlayer) override;
}; };
+8 -4
View File
@@ -85,7 +85,11 @@ def build():
# component and D/A carry strafe, combined by the character. A 2D value would # component and D/A carry strafe, combined by the character. A 2D value would
# need per-key axis modifiers, and modifiers do not survive the save path here. # need per-key axis modifiers, and modifiers do not survive the save path here.
ia_move = fresh("IA_Move", unreal.InputAction, unreal.InputAction_Factory) ia_move = fresh("IA_Move", unreal.InputAction, unreal.InputAction_Factory)
ia_move.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D) # Boolean (pressed = 1), not AXIS1D: the C++ handler reads Get<bool>() and
# moves at full walk speed. An axis action from a keyboard key can deliver 0
# depending on the trigger, which showed up as "character stands, W does not
# move" in QA. WASD are four separate per-direction actions below.
ia_move.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
save(ia_move) save(ia_move)
ia_look = fresh("IA_Look", unreal.InputAction, unreal.InputAction_Factory) ia_look = fresh("IA_Look", unreal.InputAction, unreal.InputAction_Factory)
@@ -107,15 +111,15 @@ def build():
# Two actions feed the one-dimensional move: IA_Move for forward/back, and a # Two actions feed the one-dimensional move: IA_Move for forward/back, and a
# second action for strafe, both reporting on the value's single component. # second action for strafe, both reporting on the value's single component.
ia_strafe = fresh("IA_Strafe", unreal.InputAction, unreal.InputAction_Factory) ia_strafe = fresh("IA_Strafe", unreal.InputAction, unreal.InputAction_Factory)
ia_strafe.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D) ia_strafe.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
save(ia_strafe) save(ia_strafe)
ia_back = fresh("IA_MoveBack", unreal.InputAction, unreal.InputAction_Factory) ia_back = fresh("IA_MoveBack", unreal.InputAction, unreal.InputAction_Factory)
ia_back.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D) ia_back.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
save(ia_back) save(ia_back)
ia_left = fresh("IA_StrafeLeft", unreal.InputAction, unreal.InputAction_Factory) ia_left = fresh("IA_StrafeLeft", unreal.InputAction, unreal.InputAction_Factory)
ia_left.set_editor_property("value_type", unreal.InputActionValueType.AXIS1D) ia_left.set_editor_property("value_type", unreal.InputActionValueType.BOOLEAN)
save(ia_left) save(ia_left)
# Reset and pause are boolean: one press, no accumulator. # Reset and pause are boolean: one press, no accumulator.