diff --git a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp index 7f41a72..1b0705a 100644 --- a/Game/GigLine/Source/GigLine/GigLineCharacter.cpp +++ b/Game/GigLine/Source/GigLine/GigLineCharacter.cpp @@ -266,10 +266,18 @@ void AGigLineCharacter::StartQASelfTest() UWorld* W = GetWorld(); int32 Holes = 0, Samples = 0; float Lowest = 1.0e9f, Highest = -1.0e9f; - static const float Lanes[] = {-1400.0f, -167.0f, 1000.0f}; // N walk, road, S walk - for (float LaneY : Lanes) + // Probed world-space (2026-11-08): N sidewalk -1912..-1053, kerb + // -1382..-1053, road -1382..+1047, kerb +718..+1047, S sidewalk + // +718..+1447. Rows include the kerb/road/sidewalk boundary Ys so + // seam gaps are sampled, not just lane centres. + static const float Rows[] = { + -1750.0f, -1500.0f, -1382.0f, -1280.0f, -1053.0f, -900.0f, + -167.0f, 0.0f, 300.0f, 800.0f, 1047.0f, 900.0f, 718.0f, + 900.0f, 1250.0f, 1447.0f + }; + for (float LaneY : Rows) { - for (float X = -14900.0f; X <= -9420.0f; X += 200.0f) + for (float X = -20000.0f; X <= -2000.0f; X += 200.0f) { FHitResult Hit; FCollisionQueryParams Q(TEXT("GigLineTransect"), false, this); @@ -289,6 +297,52 @@ void AGigLineCharacter::StartQASelfTest() UE_LOG(LogTemp, Log, TEXT("GigLine QA: transect %d samples, %d holes, ground Z %.0f..%.0f -> %s"), Samples, Holes, Lowest, Highest, Holes == 0 ? TEXT("PASS") : TEXT("FAIL")); + + // Invisible-wall / blocking-mesh probe: at each sample, first find + // the ground under the start point (vertical raycast), then run a + // horizontal trace 160 cm ABOVE that ground along the street row. + // A clean street should let the 30 m trace run clear; a short hit + // flags an invisible or misplaced blocker. Starting the horizontal + // trace at a fixed Z (e.g. 160) is wrong: the street is at Z ~366 + // there, so a 160 cm probe starts inside the earth (dist 0). + int32 Walls = 0, WallSamples = 0; + static const float TraceYs[] = {-1400.0f, -167.0f, 1000.0f, -1382.0f, 1047.0f}; + for (float WallY : TraceYs) + { + for (float X0 = -19500.0f; X0 <= -3000.0f; X0 += 2500.0f) + { + FHitResult GroundHit; + FCollisionQueryParams GQ(TEXT("GigLineWallGround"), false, this); + if (!W->LineTraceSingleByChannel(GroundHit, + FVector(X0, WallY, 3000.f), FVector(X0, WallY, -1000.f), + ECC_Visibility, GQ)) + { + continue; // no ground under start -> skip + } + const float ProbeZ = GroundHit.ImpactPoint.Z + 160.f; + FHitResult Hit; + FCollisionQueryParams Q(TEXT("GigLineWallProbe"), false, this); + const bool bHit = W->LineTraceSingleByChannel(Hit, + FVector(X0, WallY, ProbeZ), + FVector(X0 + 3000.f, WallY, ProbeZ), + ECC_Visibility, Q); + ++WallSamples; + if (!bHit) { continue; } // 30 m clear -> good + // Starting just above the ground, a hit within 21 m = blocker. + if (Hit.Distance < 2100.f) + { + ++Walls; + UE_LOG(LogTemp, Log, + TEXT("GigLine QA: wall probe short-hit at X %.0f Y %.0f dist %.0f actor=%s"), + X0, WallY, Hit.Distance, + Hit.GetActor() ? *Hit.GetActor()->GetName() : TEXT("none")); + } + } + } + UE_LOG(LogTemp, Log, + TEXT("GigLine QA: wall probe %d/%d traces with blocking hit inside 21 m -> %s"), + Walls, WallSamples, + Walls == 0 ? TEXT("PASS") : TEXT("FAIL")); } // 7.0: verdict, then stop ticking. diff --git a/Game/GigLine/Source/GigLine/GigLineShotActor.cpp b/Game/GigLine/Source/GigLine/GigLineShotActor.cpp index d8d819a..ba0cc15 100644 --- a/Game/GigLine/Source/GigLine/GigLineShotActor.cpp +++ b/Game/GigLine/Source/GigLine/GigLineShotActor.cpp @@ -21,6 +21,14 @@ void AGigLineShotActor::Tick(float DeltaSeconds) { Super::Tick(DeltaSeconds); + // QA mode: the -qaselftest run owns the process (it walks, resets, pauses, + // transects, then quits on its own). Skip the screenshot rounds so the game + // does not exit mid-test via bQuitWhenDone. + if (FParse::Param(FCommandLine::Get(), TEXT("qaselftest"))) + { + return; + } + if (bWaitingForSettle) { SettleCountdown -= DeltaSeconds;