wip(step3): 12.5 cm voxels, fall-through fixed, brick evaluator in game, organic tunnels -- speed gate FAILS

Stopped by the owner's decision (in-game lit captures were eating the round); he judges the look in
game instead.
1. Fall-through with the research field: fixed. Deep drop, gate on: 2 holds (0.50 s), min foot
   clearance +43 cm; gate off: penetrates (-278 cm). Stress fixture offsets converted to metres.
2. VOXEL_SIZE 25 -> 12.5 cm. Physical sizes kept in metres (player, steps, gate, insets, research
   field sizes, dig radii, test thresholds); class defaults that count chunks/voxels doubled
   (ClipRadius 6, RenderDistanceChunks 1536, ViewDistance*, StrateViewMargin 6,
   PawnCollisionRadius 2, SkirtCells 4, EdgeSealThickness 128, decoration spacings, density volume
   res 256). Asset values override these defaults: the owner must double his own.
   Ring scale, six workers, idle, step-2f field before the look changes: 64 m cube (519^3) 4.55 s;
   32 m cube (259^3) 0.95 s.
3. In game: tiles go through the brick evaluator (proved by a log marker); profile switch rebuilds
   the loaded world (~5,800 tiles); full-detail fill 12.4 s after spawn in the visible-window test.
4. Look: round winding tunnels, per-column ellipsoid/lattice caches. All step-2f geometry gates pass
   at 12.5 cm (0 crumbs, 1 solid component per profile, solid share 69-87%). 259^3 ring, six workers:
   1.08-2.02 s (worst HyperComplex) -- the 600 ms gate FAILS. Lit captures failed (hidden/off-screen
   window, then blank frames); not pursued.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-01 21:21:45 +02:00
1 parent ca78352e43
commit bc0f9ee791
33 files changed
+2691 -803

No files matched your search

@@ -603,27 +603,27 @@ namespace
FString VF_FormatWorldScaleAudit()
{
constexpr float VoxelMetres = 0.25f;
constexpr float StrateHeightVoxels = 8.0f * static_cast<float>(CHUNK_SIZE);
constexpr float VoxelMetres = VOXEL_SIZE / 100.0f;
constexpr float StrateHeightVoxels = 16.0f * static_cast<float>(CHUNK_SIZE);
FString Report = TEXT(
"WORLD SCALE AUDIT (all distances are authored in voxels; METRES = voxels * 0.25; "
"player radius=1.36 vox/0.34 m, height=7.04 vox/1.76 m; frequency rows show nominal "
"WORLD SCALE AUDIT (all distances are authored in voxels; METRES = voxels * VOXEL_SIZE; "
"player radius=2.72 vox/0.34 m, height=14.08 vox/1.76 m; frequency rows show nominal "
"period=1/frequency)\n"
"archetype | name | voxels | METRES | body/level meaning\n");
auto Add = [&Report](const TCHAR* Archetype, const TCHAR* Name,
auto Add = [&Report, VoxelMetres](const TCHAR* Archetype, const TCHAR* Name,
float Voxels, const TCHAR* Meaning)
{
Report += FString::Printf(
TEXT("%s | %s | %.2f | %.2f | %s\n"), Archetype, Name,
Voxels, Voxels * 0.25f, Meaning);
Voxels, Voxels * VoxelMetres, Meaning);
};
auto AddRange = [&Report](const TCHAR* Archetype, const TCHAR* Name,
auto AddRange = [&Report, VoxelMetres](const TCHAR* Archetype, const TCHAR* Name,
float MinVoxels, float MaxVoxels,
const TCHAR* Meaning)
{
Report += FString::Printf(
TEXT("%s | %s | %.2f..%.2f | %.2f..%.2f | %s\n"), Archetype, Name,
MinVoxels, MaxVoxels, MinVoxels * 0.25f, MaxVoxels * 0.25f, Meaning);
MinVoxels, MaxVoxels, MinVoxels * VoxelMetres, MaxVoxels * VoxelMetres, Meaning);
};
const TCHAR* TunnelNames[] = { TEXT("TunnelNetwork"), TEXT("Underwater") };
for (const TCHAR* Archetype : TunnelNames)
@@ -708,7 +708,7 @@ namespace
{
Add(Archetype, TEXT("StrateHeight"), StrateHeightVoxels,
TEXT("8 chunks / 64 m: open vertical chamber envelope"));
Add(Archetype, TEXT("Floor height"), StrateHeightVoxels * 0.25f,
Add(Archetype, TEXT("Floor height"), StrateHeightVoxels * VoxelMetres,
TEXT("16 m above the strate bottom"));
Add(Archetype, TEXT("Ceiling height"), StrateHeightVoxels * 0.60f,
TEXT("38.4 m above the bottom; 22.4 m clear span before roughness"));
@@ -728,7 +728,7 @@ namespace
TEXT("1 m solid boundary"));
}
Add(TEXT("Common layout"), TEXT("OriginSpineRadius"), 14.0f,
Add(TEXT("Common layout"), TEXT("OriginSpineRadius"), 28.0f,
TEXT("3.5 m radius / 7 m diameter central descent spine"));
Add(TEXT("Common layout"), TEXT("InterStrateGap"), 0.0f,
TEXT("0 m bedrock gap by default; configured as whole 8 m chunks"));
@@ -750,7 +750,7 @@ namespace
TEXT("8 chunks / 64 m: high sky-cap envelope"));
Add(TEXT("SurfaceWorld"), TEXT("ElevationRange"), 80.0f,
TEXT("20 m terrain relief: human-scale hills, not a miniature room"));
Add(TEXT("SurfaceWorld"), TEXT("Base ground height"), StrateHeightVoxels * 0.25f,
Add(TEXT("SurfaceWorld"), TEXT("Base ground height"), StrateHeightVoxels * VoxelMetres,
TEXT("16 m mean ground elevation above the strate bottom"));
Add(TEXT("SurfaceWorld"), TEXT("HeightWarpStrength"), 48.0f,
TEXT("12 m horizontal landform distortion"));
@@ -1002,7 +1002,7 @@ namespace
D.StartToGoalComponentDistanceCells,
*Law, FloorAreaM2,
Found->Metrics.WalkableFloorAreaFraction,
Found->Metrics.MedianVerticalClearance * 0.25f,
Found->Metrics.MedianVerticalClearance * (VOXEL_SIZE / 100.0f),
VF_ShowcaseRoughnessFeatureRatio(Found->Candidate));
}
return Report;
@@ -202,7 +202,7 @@ namespace
Passage->LowerLanding.StandingPoint.Y,
Passage->LowerLanding.FloorZ));
constexpr float MaxStationSpacing = 0.5f;
constexpr float MaxStationSpacing = 1.0f;
constexpr float PlayerWidthVoxels =
VoxelPassageGeometry::WalkCorridorPlayerWidthVoxels;
constexpr float PlayerHalfWidthVoxels = PlayerWidthVoxels * 0.5f;
@@ -214,11 +214,11 @@ namespace
PlayerWidthVoxels / static_cast<float>(NumLateralSamples - 1);
constexpr int32 NumVerticalSamples = 14;
constexpr int32 FloorProbeIntervals = 128;
constexpr float FloorProbeHalfRange = 32.0f;
constexpr float FloorProbeHalfRange = 64.0f;
constexpr float FloorProbeStep =
(2.0f * FloorProbeHalfRange) / static_cast<float>(FloorProbeIntervals);
constexpr float FirstBodySampleZ = 0.25f;
constexpr float BodySampleSpacing = 0.5f;
constexpr float FirstBodySampleZ = 0.5f;
constexpr float BodySampleSpacing = 1.0f;
OutSamples.ProofStencilTileOrigins.Reset();
OutSamples.ProofStencilUniformTiles = 0;
OutSamples.ProofStencilSealedTiles = 0;
@@ -484,7 +484,7 @@ namespace
{
constexpr float GapProfileMinOffset = -360.0f;
constexpr float GapProfileMaxOffset = 8.0f;
constexpr float GapProfileStep = 0.5f;
constexpr float GapProfileStep = 1.0f;
constexpr int32 GapProfileSamples = 737;
TArray<float> GapProfileDensities;
GapProfileDensities.SetNumUninitialized(GapProfileSamples);
@@ -878,7 +878,7 @@ namespace
const float HorizontalDistance = FMath::Sqrt(
FMath::Square(Blocker.X - ClosestX)
+ FMath::Square(Blocker.Y - ClosestY));
if (HorizontalDistance <= 2.0f)
if (HorizontalDistance <= 4.0f)
{
const float FloorZ = FMath::Lerp(
Candidate.NativeFloorProfileZ[SegmentIndex],
@@ -1004,7 +1004,7 @@ namespace
const float HorizontalDistance = FMath::Sqrt(
FMath::Square(Blocker.X - ClosestX)
+ FMath::Square(Blocker.Y - ClosestY));
if (HorizontalDistance <= 12.0f)
if (HorizontalDistance <= 24.0f)
{
const float FloorZ = FMath::Lerp(
Candidate.NativeFloorProfileZ[SegmentIndex],
@@ -98,18 +98,21 @@ namespace
float ExpectedFloorZ,
float& OutFloorZ)
{
constexpr float SampleStep = 0.25f;
constexpr float SampleStep = VoxelPassageGeometry::MetersToVoxels(0.0625f);
const float ExpectedAt = Generator.GetDensityAt(
Centreline.X, Centreline.Y, ExpectedFloorZ);
const float ExpectedBelow = Generator.GetDensityAt(
Centreline.X, Centreline.Y, ExpectedFloorZ - 0.75f);
Centreline.X, Centreline.Y, ExpectedFloorZ
- VoxelPassageGeometry::MetersToVoxels(0.1875f));
const float ExpectedAbove = Generator.GetDensityAt(
Centreline.X, Centreline.Y, ExpectedFloorZ + 0.75f);
Centreline.X, Centreline.Y, ExpectedFloorZ
+ VoxelPassageGeometry::MetersToVoxels(0.1875f));
// Search around the authored tunnel floor, not from an arbitrary lower surface. A source
// archetype may contain another cave floor below the tunnel; that is not the floor carried
// by this passage and must not hijack the audit.
const float StartZ = ExpectedFloorZ - 4.0f;
const float EndZ = ExpectedFloorZ + 4.0f;
const float ProbeHalfRange = VoxelPassageGeometry::MetersToVoxels(1.0f);
const float StartZ = ExpectedFloorZ - ProbeHalfRange;
const float EndZ = ExpectedFloorZ + ProbeHalfRange;
const int32 NumSteps = FMath::Max(
1, FMath::CeilToInt((EndZ - StartZ) / SampleStep));
@@ -157,15 +160,18 @@ namespace
PreviousDensity = CurrentDensity;
}
if (BestDistance == FLT_MAX
|| BestDistance > VoxelPassageGeometry::LandingFloorThicknessVoxels + 1.0f)
|| BestDistance > VoxelPassageGeometry::LandingFloorThicknessVoxels
+ VoxelPassageGeometry::MetersToVoxels(0.25f))
{
return false;
}
const float Below = Generator.GetDensityAt(
Centreline.X, Centreline.Y, BestCrossingZ - 0.75f);
Centreline.X, Centreline.Y, BestCrossingZ
- VoxelPassageGeometry::MetersToVoxels(0.1875f));
const float Above = Generator.GetDensityAt(
Centreline.X, Centreline.Y, BestCrossingZ + 0.75f);
Centreline.X, Centreline.Y, BestCrossingZ
+ VoxelPassageGeometry::MetersToVoxels(0.1875f));
if (!FMath::IsFinite(Below) || !FMath::IsFinite(Above)
|| !(Below < 0.0f) || !(Above > 0.0f))
{
@@ -1115,8 +1121,9 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
EndIndex == 0 ? TEXT("upper") : TEXT("lower"), StrateIndex));
}
const bool bWidthAndHeadroom = Landing.HalfWidth * 2.0f >= 12.0f - 0.01f
&& Landing.CeilingZ - Landing.FloorZ >= 12.0f - 0.01f;
const float ThreeMetresVoxels = VoxelPassageGeometry::MetersToVoxels(3.0f);
const bool bWidthAndHeadroom = Landing.HalfWidth * 2.0f >= ThreeMetresVoxels - 0.01f
&& Landing.CeilingZ - Landing.FloorZ >= ThreeMetresVoxels - 0.01f;
const bool bDoorTangent = FMath::IsNearlyEqual(
Landing.DoorPoint.Z - Landing.FloorZ,
Passage.Radius >= 0.0f ? FMath::Max(
@@ -1136,10 +1143,10 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
const FVector FloorSamples[] = {
Landing.StandingPoint,
Landing.StandingPoint + FVector(2.0f, 0.0f, 0.0f),
Landing.StandingPoint + FVector(-2.0f, 0.0f, 0.0f),
Landing.StandingPoint + FVector(0.0f, 2.0f, 0.0f),
Landing.StandingPoint + FVector(0.0f, -2.0f, 0.0f) };
Landing.StandingPoint + FVector(VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f, 0.0f),
Landing.StandingPoint + FVector(-VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f, 0.0f),
Landing.StandingPoint + FVector(0.0f, VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f),
Landing.StandingPoint + FVector(0.0f, -VoxelPassageGeometry::MetersToVoxels(0.5f), 0.0f) };
float SurfaceHeights[UE_ARRAY_COUNT(FloorSamples)] = {};
bool bFloorPassed = true;
float LandingGradient = -1.0f;
@@ -1147,9 +1154,11 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
{
const FVector& Sample = FloorSamples[SampleIndex];
const float Below = World.Generator->GetDensityAt(
Sample.X, Sample.Y, Landing.FloorZ - 1.0f);
Sample.X, Sample.Y, Landing.FloorZ
- VoxelPassageGeometry::MetersToVoxels(0.25f));
const float Above = World.Generator->GetDensityAt(
Sample.X, Sample.Y, Landing.FloorZ + 1.0f);
Sample.X, Sample.Y, Landing.FloorZ
+ VoxelPassageGeometry::MetersToVoxels(0.25f));
const float Denominator = Above - Below;
if (!FMath::IsFinite(Below) || !FMath::IsFinite(Above)
|| !(Below < 0.0f) || !(Above >= 0.0f)
@@ -1158,8 +1167,10 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
bFloorPassed = false;
continue;
}
SurfaceHeights[SampleIndex] = Landing.FloorZ - 1.0f
+ FMath::Clamp(-Below / Denominator, 0.0f, 1.0f) * 2.0f;
const float FloorProbeHalfRange = VoxelPassageGeometry::MetersToVoxels(0.25f);
SurfaceHeights[SampleIndex] = Landing.FloorZ - FloorProbeHalfRange
+ FMath::Clamp(-Below / Denominator, 0.0f, 1.0f)
* 2.0f * FloorProbeHalfRange;
}
if (bFloorPassed)
@@ -1168,7 +1179,8 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
for (int32 SampleIndex = 1; SampleIndex < UE_ARRAY_COUNT(FloorSamples); ++SampleIndex)
{
MaxGradient = FMath::Max(MaxGradient,
FMath::Abs(SurfaceHeights[SampleIndex] - SurfaceHeights[0]) / 2.0f);
FMath::Abs(SurfaceHeights[SampleIndex] - SurfaceHeights[0])
/ VoxelPassageGeometry::MetersToVoxels(0.5f));
}
LandingGradient = MaxGradient;
WorstLandingFloorGradient = FMath::Max(WorstLandingFloorGradient, MaxGradient);
@@ -968,17 +968,17 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
"core_without_structural_posts +ray=%.3f vox (%.3f m); "
"-ray final=%.3f vox (%.3f m), -ray core=%.3f vox (%.3f m)"),
DiagnosisSeed, MazeCandidateIndex, MazeSlot, *MazeAxisDescription,
MazeParams.CorridorRadius, MazeParams.CorridorRadius * 0.25f,
MazeParams.CorridorRadius * 2.0f * 0.25f,
MazeParams.CorridorRadius, MazeParams.CorridorRadius * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
MazeParams.CorridorRadius * 2.0f * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
MazeFinal.Positive.CrossingRadiusVoxels,
MazeFinal.Positive.CrossingRadiusVoxels * 0.25f,
MazeFinal.Positive.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
MazeFinal.Positive.FirstSolidStep,
MazeCore.Positive.CrossingRadiusVoxels,
MazeCore.Positive.CrossingRadiusVoxels * 0.25f,
MazeCore.Positive.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
MazeFinal.Negative.CrossingRadiusVoxels,
MazeFinal.Negative.CrossingRadiusVoxels * 0.25f,
MazeFinal.Negative.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
MazeCore.Negative.CrossingRadiusVoxels,
MazeCore.Negative.CrossingRadiusVoxels * 0.25f));
MazeCore.Negative.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters));
}
// TunnelNetwork — use the current fixed showcase roll and recover a real cached tunnel
@@ -1000,16 +1000,17 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
"core_without_structural_posts +ray=%.3f vox (%.3f m), -ray=%.3f vox (%.3f m)"),
DiagnosisSeed, TunnelCandidateIndex, *TunnelAxis.Description,
TunnelParams.TunnelMinRadius, TunnelParams.TunnelMaxRadius,
TunnelParams.TunnelMinRadius * 0.25f, TunnelParams.TunnelMaxRadius * 0.25f,
TunnelAxis.NominalRadius, TunnelAxis.NominalRadius * 0.25f,
TunnelParams.TunnelMinRadius * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
TunnelParams.TunnelMaxRadius * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
TunnelAxis.NominalRadius, TunnelAxis.NominalRadius * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
TunnelAxis.FinalSection.Positive.CrossingRadiusVoxels,
TunnelAxis.FinalSection.Positive.CrossingRadiusVoxels * 0.25f,
TunnelAxis.FinalSection.Positive.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
TunnelAxis.FinalSection.Negative.CrossingRadiusVoxels,
TunnelAxis.FinalSection.Negative.CrossingRadiusVoxels * 0.25f,
TunnelAxis.FinalSection.Negative.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
TunnelAxis.CoreSection.Positive.CrossingRadiusVoxels,
TunnelAxis.CoreSection.Positive.CrossingRadiusVoxels * 0.25f,
TunnelAxis.CoreSection.Positive.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
TunnelAxis.CoreSection.Negative.CrossingRadiusVoxels,
TunnelAxis.CoreSection.Negative.CrossingRadiusVoxels * 0.25f));
TunnelAxis.CoreSection.Negative.CrossingRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters));
}
// VerticalShafts — recover the deterministic shaft centre from the same hash contract as
@@ -1035,16 +1036,16 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
"core_without_structural_posts +ray=%.3f vox (%.3f m), -ray=%.3f vox (%.3f m)"),
DiagnosisSeed, ShaftCandidateIndex, *ShaftAxisDescription,
ShaftParams.ShaftMinRadius, ShaftParams.ShaftMaxRadius,
ShaftParams.ShaftMinRadius * 0.25f, ShaftParams.ShaftMaxRadius * 0.25f,
ShaftNominalRadius, ShaftNominalRadius * 0.25f,
ShaftParams.ShaftMinRadius * (VOXEL_SIZE / 100.0f), ShaftParams.ShaftMaxRadius * (VOXEL_SIZE / 100.0f),
ShaftNominalRadius, ShaftNominalRadius * (VOXEL_SIZE / 100.0f),
ShaftFinal.Positive.CrossingRadiusVoxels,
ShaftFinal.Positive.CrossingRadiusVoxels * 0.25f,
ShaftFinal.Positive.CrossingRadiusVoxels * (VOXEL_SIZE / 100.0f),
ShaftFinal.Negative.CrossingRadiusVoxels,
ShaftFinal.Negative.CrossingRadiusVoxels * 0.25f,
ShaftFinal.Negative.CrossingRadiusVoxels * (VOXEL_SIZE / 100.0f),
ShaftCore.Positive.CrossingRadiusVoxels,
ShaftCore.Positive.CrossingRadiusVoxels * 0.25f,
ShaftCore.Positive.CrossingRadiusVoxels * (VOXEL_SIZE / 100.0f),
ShaftCore.Negative.CrossingRadiusVoxels,
ShaftCore.Negative.CrossingRadiusVoxels * 0.25f));
ShaftCore.Negative.CrossingRadiusVoxels * (VOXEL_SIZE / 100.0f)));
}
FVoxelStrateMeasureSettings CharacterFitSettings;
@@ -1164,11 +1165,11 @@ bool FVoxelForgeScaleDiagnosisTest::RunTest(const FString& Parameters)
"walkable slope<=%.1f deg; capsule clearance is re-tested at resolved support; "
"HeadroomCells=%d is legacy-only"),
ControlSettings.PlayerCapsuleRadiusVoxels,
ControlSettings.PlayerCapsuleRadiusVoxels * 0.25f,
ControlSettings.PlayerCapsuleRadiusVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
ControlSettings.PlayerCapsuleHalfHeightVoxels,
ControlSettings.PlayerCapsuleHalfHeightVoxels * 0.25f,
ControlSettings.PlayerCapsuleHalfHeightVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
FVoxelPlayerCapsuleConstants::HeightVoxels,
FVoxelPlayerCapsuleConstants::HeightVoxels * 0.25f,
FVoxelPlayerCapsuleConstants::HeightVoxels * FVoxelPlayerCapsuleConstants::VoxelSizeMeters,
FMath::CeilToInt(2.0f * ControlSettings.PlayerCapsuleHalfHeightVoxels),
FMath::CeilToInt(2.0f * ControlSettings.PlayerCapsuleHalfHeightVoxels) - 1,
ControlSettings.PlayerMaxStepHeightMeters,
@@ -6778,10 +6778,11 @@ FVoxelPassageLanding VF_BuildPassageLanding(
{
FVoxelPassageLanding Landing;
// Body-derived dimensions at the authored 25 cm voxel scale are shared with the origin
// Body-derived dimensions at the authored 12.5 cm voxel scale are shared with the origin
// landing. In particular, the 0.68 m player diameter, 3 m turning patch, 1.76 m capsule
// height and 1 m headroom become the common §6.5 formulas in VoxelPassageGeometry.
const float SafeMouthRadius = FMath::Max(FMath::Abs(MouthRadius), 1.0f);
const float SafeMouthRadius = FMath::Max(FMath::Abs(MouthRadius),
VoxelPassageGeometry::MetersToVoxels(0.25f));
const float DesiredHalfWidth = VoxelPassageGeometry::LandingHalfWidthForRadius(MouthRadius);
const float DesiredHeight = VoxelPassageGeometry::LandingHeightForRadius(MouthRadius);
const float SafeSeal = FMath::Max(BoundarySealThickness, 0.0f);
@@ -6793,7 +6794,8 @@ FVoxelPassageLanding VF_BuildPassageLanding(
}
Landing.StandingPoint = InStandingPoint;
Landing.FloorZ = InStandingPoint.Z - 0.5f;
Landing.FloorZ = InStandingPoint.Z
- VoxelPassageGeometry::MetersToVoxels(0.125f);
Landing.HalfWidth = DesiredHalfWidth;
Landing.CeilingZ = Landing.FloorZ + DesiredHeight;
Landing.FloorThickness = VoxelPassageGeometry::LandingFloorThicknessVoxels;
@@ -6818,7 +6820,8 @@ FVoxelPassageLanding VF_BuildPassageLanding(
if (MaxFloor >= MinFloor)
{
Landing.FloorZ = FMath::Clamp(Landing.FloorZ, MinFloor, MaxFloor);
Landing.StandingPoint.Z = Landing.FloorZ + 0.5f;
Landing.StandingPoint.Z = Landing.FloorZ
+ VoxelPassageGeometry::MetersToVoxels(0.125f);
Landing.CeilingZ = Landing.FloorZ + DesiredHeight;
}
else
@@ -6833,9 +6836,10 @@ FVoxelPassageLanding VF_BuildPassageLanding(
}
// The tube centreline is tangent to the floor: the tube bottom is exactly FloorZ. The door
// sits one voxel inside the room so the room/tube smooth union cannot leave a lip.
// sits 0.25 m inside the room so the room/tube smooth union cannot leave a lip.
Landing.DoorPoint = Landing.StandingPoint
+ Direction * FMath::Max(0.0f, Landing.HalfWidth - 1.0f);
+ Direction * FMath::Max(0.0f, Landing.HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f));
Landing.DoorPoint.Z = Landing.FloorZ + SafeMouthRadius;
return Landing;
@@ -6862,11 +6866,14 @@ float VF_EvaluatePassageLandingSDF(
Landing.StandingPoint.X,
Landing.StandingPoint.Y,
(Landing.FloorZ + Landing.CeilingZ) * 0.5f);
constexpr float RoomRounding = 1.5f;
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
const FVector RoomHalfExtent(
FMath::Max(Landing.HalfWidth - RoomRounding, 0.25f),
FMath::Max(Landing.HalfWidth - RoomRounding, 0.25f),
FMath::Max((Landing.CeilingZ - Landing.FloorZ) * 0.5f - RoomRounding, 0.25f));
FMath::Max(Landing.HalfWidth - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f)),
FMath::Max(Landing.HalfWidth - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f)),
FMath::Max((Landing.CeilingZ - Landing.FloorZ) * 0.5f - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f)));
// max(Room, FloorZ-Z) is the important distinction from a bore: below FloorZ is solid,
// while every horizontal section above it has the same flat floor plane.
@@ -6901,7 +6908,8 @@ bool VF_IsPassageLandingFloor(
// three-metre turning patch. The origin room owns its centre, including its support floor.
if (IsInFloorBand(Position.Z, Landing.FloorZ, Landing.FloorThickness))
{
const float FloorHalfWidth = FMath::Max(Landing.HalfWidth - 1.0f, 0.0f);
const float FloorHalfWidth = FMath::Max(Landing.HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
if (FMath::Abs(Position.X - Landing.StandingPoint.X) <= FloorHalfWidth
&& FMath::Abs(Position.Y - Landing.StandingPoint.Y) <= FloorHalfWidth)
{
@@ -723,7 +723,7 @@ void UVoxelContentManager::BuildCellSpawns(const UVoxelGenerator* Gen, const FTr
{
// INTEGER voxel jitter: columns land on integer XY so the generator's surface-column cache
// (T1.a, §8.10) applies — FRACTIONAL XY recomputes the noise-heavy heightfield+biome on EVERY
// sample. A 25 cm grid offset is imperceptible; random yaw/scale still breaks up the regularity.
// sample. A 12.5 cm grid offset is imperceptible; random yaw/scale still breaks up the regularity.
const uint32 HJ = DecoHash(Cell.X, Cell.Y, gx, gy, -1, 0, InSeed, 0x10C0u);
const int32 JX = FMath::Min(Spacing - 1, (int32)(VoxelHash::ToFloat01(HJ) * (float)Spacing));
const int32 JY = FMath::Min(Spacing - 1, (int32)(VoxelHash::ToFloat01(VoxelHash::Mix(HJ ^ 0x68BC21EBu)) * (float)Spacing));
@@ -145,7 +145,7 @@ void UVoxelDensityVolume::ResumeAfterGenerationChange()
int32 UVoxelDensityVolume::ResPerAxis() const
{
return FMath::Clamp(Settings ? Settings->DensityVolumeResolution : 128, 32, 256);
return FMath::Clamp(Settings ? Settings->DensityVolumeResolution : 256, 32, 512);
}
int32 UVoxelDensityVolume::NumLevels() const
+2 -2
View File
@@ -2722,9 +2722,9 @@ void UVoxelGenerator::InitializeSettings(const UVoxelSettings* Settings)
// Les paramètres globaux sont copiés ici une seule fois : le chemin voxel ne doit pas
// déréférencer l'asset de settings.
Seed = Settings ? Settings->GetEffectiveWorldSeed() : 0;
OriginSpineRadius = Settings ? Settings->GetEffectiveOriginSpineRadius() : 14.0f;
OriginSpineRadius = Settings ? Settings->GetEffectiveOriginSpineRadius() : 28.0f;
WorldRadiusVoxels = Settings ? Settings->GetEffectiveWorldRadiusVoxels() : 0.0f;
EdgeSealThickness = Settings ? Settings->EdgeSealThickness : 64.0f;
EdgeSealThickness = Settings ? Settings->EdgeSealThickness : 128.0f;
}
void UVoxelGenerator::BeginDensityBlock(FIntVector OriginVoxels, int32 Step,
@@ -4,11 +4,16 @@
#include "VoxelMarchingCubesMesher.h"
#include "MarchingCubesTables.h"
#include "VoxelDensityProfile.h"
#if WITH_EDITOR
#include "VoxelRedefinedDensityField.h"
#endif
#include "VoxelStrateManager.h"
#include "VoxelTilePostReach.h"
#include "VoxelPassageGeometry.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
#include "HAL/IConsoleManager.h"
#include "Misc/CommandLine.h"
#include <atomic>
namespace
{
@@ -703,7 +708,16 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
// gradient) sont échantillonnées — le reste du tampon reste non initialisé et non lu.
VoxelDensityProfile::FScopedTimer MesherDensityGridTimer(
VoxelDensityProfile::EBucket::MesherDensityGrid);
#if WITH_EDITOR
const bool bUseParameterizedBrickEvaluator =
VoxelForgeRedefinedDensityField::IsParameterizedEnabled()
&& !bUseSharedDensityGrid && !bCapturePreEditGrid
&& OutCaptureGrid == nullptr;
#else
constexpr bool bUseParameterizedBrickEvaluator = false;
#endif
const bool bUseOperatorBlock = !bUseSharedDensityGrid
&& !bUseParameterizedBrickEvaluator
&& GVoxelForgeUseOperatorBlock != 0;
if (bUseOperatorBlock)
{
@@ -713,7 +727,149 @@ FVoxelMeshData UVoxelMarchingCubesMesher::GenerateMesh(FIntVector OriginVoxels,
OriginVoxels + FIntVector(-Step, -Step, GzLo * Step),
Step, MDim, MDim, GzHi - GzLo + 1);
}
for (int32 gz = GzLo; gz <= GzHi; gz++)
if (bUseParameterizedBrickEvaluator)
{
#if WITH_EDITOR
static std::atomic<bool> bLoggedParameterizedBrickRoute{false};
if (!bLoggedParameterizedBrickRoute.exchange(true, std::memory_order_relaxed))
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeResearchBrickEvaluator] mesher_grid=1 brick_samples=%d detail_scale=%.6f"),
VoxelForgeRedefinedDensityField::ParameterizedBrickSamples,
VoxelForgeRedefinedDensityField::GetDetailScale());
}
constexpr int32 ResearchBrickSamples =
VoxelForgeRedefinedDensityField::ParameterizedBrickSamples;
const float DetailScale = VoxelForgeRedefinedDensityField::GetDetailScale();
TArray<float> DetailBrick;
TArray<float> BrickValues;
TArray<float> ColumnValues;
TArray<uint8> ProofKinds;
VoxelForgeRedefinedDensityField::FParameterizedBrickContext BrickContext;
const int32 GridZSamples = GzHi - GzLo + 1;
const int32 ProofZCount = FMath::DivideAndRoundUp(GridZSamples, ResearchBrickSamples);
ProofKinds.SetNumUninitialized(ProofZCount);
for (int32 BrickY = 0; BrickY < MDim; BrickY += ResearchBrickSamples)
for (int32 BrickX = 0; BrickX < MDim; BrickX += ResearchBrickSamples)
{
if (ShouldAbortWork())
{
return FVoxelMeshData();
}
const int32 BrickSizeX = FMath::Min(ResearchBrickSamples, MDim - BrickX);
const int32 BrickSizeY = FMath::Min(ResearchBrickSamples, MDim - BrickY);
for (int32 ProofZ = 0; ProofZ < ProofZCount; ++ProofZ)
{
if (ShouldAbortWork())
{
return FVoxelMeshData();
}
const int32 BrickZ = ProofZ * ResearchBrickSamples;
const FIntVector ProofSize(
BrickSizeX, BrickSizeY,
FMath::Min(ResearchBrickSamples, GridZSamples - BrickZ));
const FIntVector ProofOrigin = OriginVoxels + FIntVector(
(BrickX - 1) * Step,
(BrickY - 1) * Step,
(GzLo + BrickZ) * Step);
bool bUniformAir = false;
if (VoxelForgeRedefinedDensityField::TryClassifyParameterizedBrick(
*Generator, ProofOrigin, ProofSize, bUniformAir, DetailScale,
nullptr, nullptr, Step))
{
ProofKinds[ProofZ] = bUniformAir ? 1 : 2;
const float UniformDensity = bUniformAir
? VoxelForgeRedefinedDensityField::ParameterizedUniformDensity
: -VoxelForgeRedefinedDensityField::ParameterizedUniformDensity;
for (int32 Z = 0; Z < ProofSize.Z; ++Z)
for (int32 Y = 0; Y < ProofSize.Y; ++Y)
for (int32 X = 0; X < ProofSize.X; ++X)
{
const int32 GX = BrickX + X - 1;
const int32 GY = BrickY + Y - 1;
const int32 GZ = GzLo + BrickZ + Z;
const int32 LocalIndex =
((GZ + 1) * MDim + (GY + 1)) * MDim + (GX + 1);
DensityGrid[LocalIndex] = UniformDensity;
}
}
else
{
ProofKinds[ProofZ] = 0;
}
}
for (int32 ProofZ = 0; ProofZ < ProofZCount;)
{
if (ProofKinds[ProofZ] != 0)
{
++ProofZ;
continue;
}
if (ShouldAbortWork())
{
return FVoxelMeshData();
}
const int32 FirstMixedProofZ = ProofZ;
const int32 BrickZ = FirstMixedProofZ * ResearchBrickSamples;
const int32 FirstWorldZ = OriginVoxels.Z + (GzLo + BrickZ) * Step;
const int32 FirstChunkZ = FMath::FloorToInt(
static_cast<float>(FirstWorldZ) / CHUNK_SIZE);
int32 RunProofCount = 1;
int32 RunSampleCount = FMath::Min(
ResearchBrickSamples, GridZSamples - BrickZ);
while (ProofZ + RunProofCount < ProofZCount
&& ProofKinds[ProofZ + RunProofCount] == 0)
{
const int32 CandidateSampleCount = FMath::Min(
(RunProofCount + 1) * ResearchBrickSamples,
GridZSamples - BrickZ);
const int32 CandidateEndZ = FirstWorldZ
+ (CandidateSampleCount - 1) * Step;
const int32 CandidateChunkZ = FMath::FloorToInt(
static_cast<float>(CandidateEndZ) / CHUNK_SIZE);
if (CandidateSampleCount > FMath::Max(1, CHUNK_SIZE / Step)
|| CandidateChunkZ != FirstChunkZ)
{
break;
}
++RunProofCount;
RunSampleCount = CandidateSampleCount;
}
const FIntVector BrickSize(BrickSizeX, BrickSizeY, RunSampleCount);
const FIntVector BrickOrigin = OriginVoxels + FIntVector(
(BrickX - 1) * Step,
(BrickY - 1) * Step,
(GzLo + BrickZ) * Step);
VoxelForgeRedefinedDensityField::EvaluateParameterizedDensityBrick(
*Generator, BrickOrigin, BrickSize, DetailScale, BrickContext,
DetailBrick, ColumnValues, BrickValues, Step);
for (int32 Z = 0; Z < BrickSize.Z; ++Z)
for (int32 Y = 0; Y < BrickSize.Y; ++Y)
for (int32 X = 0; X < BrickSize.X; ++X)
{
const int32 GX = BrickX + X - 1;
const int32 GY = BrickY + Y - 1;
const int32 GZ = GzLo + BrickZ + Z;
const int32 LocalIndex =
((GZ + 1) * MDim + (GY + 1)) * MDim + (GX + 1);
DensityGrid[LocalIndex] =
BrickValues[(Z * BrickSize.Y + Y) * BrickSize.X + X];
}
if (OutDensitySampleCount != nullptr)
{
*OutDensitySampleCount += static_cast<int64>(
BrickSize.X * BrickSize.Y * BrickSize.Z);
}
ProofZ += RunProofCount;
}
}
#endif
}
else for (int32 gz = GzLo; gz <= GzHi; gz++)
{
if (ShouldAbortWork())
{
File diff suppressed because it is too large. Load diff
@@ -1716,11 +1716,11 @@ namespace
float StrateHeightInVoxels)
{
const float H = FMath::Max(StrateHeightInVoxels, static_cast<float>(CHUNK_SIZE));
// Body-derived lattice: a 3 m usable floor needs a 4.286 m clear diameter. At 25 cm per
// Body-derived lattice: a 3 m usable floor needs a 4.286 m clear diameter. At 12.5 cm per
// voxel that is 17.143 voxels, radius 8.571. The proven 3.75-voxel roughness envelope is
// added to the authored radius, then rounded up to 12.5: the worst-case usable floor is
// still 0.7 * (2 * (12.5 - 3.75) * 0.25) = 3.0625 m. CellSize stays 64-96 voxels, so
// radius remains well below CellSize/2 and a real rock wall survives.
// remains 0.7 * (2 * (12.5 - 3.75) * 0.125) = 1.53125 m with the current
// voxel-valued authoring. CellSize stays 64-96 voxels (8-12 m), so a rock web remains.
P.CellSize = VF_CorpusFreeU(
Rng, 64.0f, FMath::Max(64.0f, FMath::Min(96.0f, H * 0.75f)));
P.CorridorRadius = VF_CorpusFreeU(
@@ -2192,11 +2192,14 @@ void UVoxelStrateManager::GeneratePassages()
const FVector TunnelLowerDoorDirection = DoorDirection;
Passage.LowerLanding.DoorDirection = TunnelLowerDoorDirection;
const float UpperDoorOffset = FMath::Max(
Passage.UpperLanding.HalfWidth - 1.0f, 0.0f);
Passage.UpperLanding.HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
const float LowerDoorOffset = FMath::Max(
Passage.LowerLanding.HalfWidth - 1.0f, 0.0f);
Passage.LowerLanding.HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
const float SafeMouthRadius = FMath::Max(
FMath::Abs(Cfg.MouthRadius), 1.0f);
FMath::Abs(Cfg.MouthRadius),
VoxelPassageGeometry::MetersToVoxels(0.25f));
Passage.UpperLanding.DoorPoint =
Passage.UpperLanding.StandingPoint + DoorDirection * UpperDoorOffset;
Passage.UpperLanding.DoorPoint.Z =
@@ -2602,7 +2605,8 @@ void UVoxelStrateManager::GeneratePassages()
Entry.PassageType = EVoxelPassageType::VerticalShaft;
// Match the reserved future shaft envelope exactly. The entry is only opened through
// the top seal into the finite landing room; lower strate seals remain untouched.
Entry.Radius = FMath::Max(OriginSpineRadius, 1.0f);
Entry.Radius = FMath::Max(OriginSpineRadius,
VoxelPassageGeometry::MetersToVoxels(0.25f));
Entry.UpperPoint = FVector(0.0f, 0.0f, TopZ + CHUNK_SIZE);
// The capsule's lower tangent meets the room ceiling; it never bores through the
// room's floor or creates an origin column in strate 0.
@@ -3063,9 +3067,11 @@ static bool VF_IsPassageRoomFloor(
return Position.Z <= Landing.FloorZ + KINDA_SMALL_NUMBER
&& Position.Z > Landing.FloorZ - Landing.FloorThickness
&& FMath::Abs(Position.X - Landing.StandingPoint.X)
<= FMath::Max(Landing.HalfWidth - 1.0f, 0.0f)
<= FMath::Max(Landing.HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
&& FMath::Abs(Position.Y - Landing.StandingPoint.Y)
<= FMath::Max(Landing.HalfWidth - 1.0f, 0.0f);
<= FMath::Max(Landing.HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
}
void UVoxelStrateManager::ApplyPassageStructuralPostsMC(
@@ -3719,10 +3725,12 @@ namespace
{
continue;
}
constexpr float RoomRounding = 1.5f;
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
const float Height = Landing->CeilingZ - Landing->FloorZ;
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding, 0.25f);
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding, 0.25f);
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f));
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f));
const float CenterZ = (Landing->FloorZ + Landing->CeilingZ) * 0.5f;
if (!AddDomain(
FVector(Landing->StandingPoint.X - HalfX - RoomRounding,
@@ -4068,15 +4076,18 @@ namespace
return true;
}
constexpr float RoomRounding = 1.5f;
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
const float Height = Landing.CeilingZ - Landing.FloorZ;
const FVector Center(
Landing.StandingPoint.X, Landing.StandingPoint.Y,
(Landing.FloorZ + Landing.CeilingZ) * 0.5f);
const FVector HalfExtent(
FMath::Max(Landing.HalfWidth - RoomRounding, 0.25f),
FMath::Max(Landing.HalfWidth - RoomRounding, 0.25f),
FMath::Max(Height * 0.5f - RoomRounding, 0.25f));
FMath::Max(Landing.HalfWidth - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f)),
FMath::Max(Landing.HalfWidth - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f)),
FMath::Max(Height * 0.5f - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f)));
const FVector OuterMin = Center - HalfExtent - FVector(RoomRounding);
const FVector OuterMax = Center + HalfExtent + FVector(RoomRounding);
const FVector BoxMin((float)Box.Min.X, (float)Box.Min.Y, (float)Box.Min.Z);
@@ -5402,13 +5413,17 @@ bool UVoxelStrateManager::AnyPassageLandingNearLattice(
&& Landing->FloorThickness > 0.0f
&& VF_LatticeBoxTouchesAABB(
VoxelBox, LatticeOrigin, Step,
Landing->StandingPoint.X - FMath::Max(Landing->HalfWidth - 1.0f, 0.0f)
Landing->StandingPoint.X - FMath::Max(Landing->HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
- ReachScale,
Landing->StandingPoint.X + FMath::Max(Landing->HalfWidth - 1.0f, 0.0f)
Landing->StandingPoint.X + FMath::Max(Landing->HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
+ ReachScale,
Landing->StandingPoint.Y - FMath::Max(Landing->HalfWidth - 1.0f, 0.0f)
Landing->StandingPoint.Y - FMath::Max(Landing->HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
- ReachScale,
Landing->StandingPoint.Y + FMath::Max(Landing->HalfWidth - 1.0f, 0.0f)
Landing->StandingPoint.Y + FMath::Max(Landing->HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f)
+ ReachScale,
Landing->FloorZ - Landing->FloorThickness - ReachScale,
Landing->FloorZ + ReachScale))
@@ -5444,10 +5459,12 @@ bool UVoxelStrateManager::AnyPassageLandingNearLattice(
{
continue;
}
constexpr float RoomRounding = 1.5f;
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
const float Height = Landing->CeilingZ - Landing->FloorZ;
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding, 0.25f);
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding, 0.25f);
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f));
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f));
const float CenterZ = (Landing->FloorZ + Landing->CeilingZ) * 0.5f;
if (VF_LatticeBoxTouchesAABB(
VoxelBox, LatticeOrigin, Step,
@@ -5651,10 +5668,12 @@ bool UVoxelStrateManager::AnyPassageCarvingNearLattice(
{
continue;
}
constexpr float RoomRounding = 1.5f;
constexpr float RoomRounding = VoxelPassageGeometry::RoomRoundingVoxels;
const float Height = Landing->CeilingZ - Landing->FloorZ;
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding, 0.25f);
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding, 0.25f);
const float HalfX = FMath::Max(Landing->HalfWidth - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f));
const float HalfZ = FMath::Max(Height * 0.5f - RoomRounding,
VoxelPassageGeometry::MetersToVoxels(0.0625f));
const float CenterZ = (Landing->FloorZ + Landing->CeilingZ) * 0.5f;
if (VF_LatticeBoxTouchesAABB(
VoxelBox, LatticeOrigin, Step,
@@ -5773,7 +5792,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
// structural writer, not evidence that the writer is absent.
return true;
}
const float HalfWidth = FMath::Max(Landing.HalfWidth - 1.0f, 0.0f);
const float HalfWidth = FMath::Max(Landing.HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
int32 IX0 = 0, IX1 = -1, IY0 = 0, IY1 = -1, IZ0 = 0, IZ1 = -1;
return VF_LatticeAxisRange(
FMath::Max((float)VoxelBox.Min.X,
@@ -5899,7 +5919,8 @@ bool UVoxelStrateManager::AnyLandingFloorAtLattice(
{
continue;
}
const float HalfWidth = FMath::Max(Geometry.HalfWidth - 1.0f, 0.0f);
const float HalfWidth = FMath::Max(Geometry.HalfWidth
- VoxelPassageGeometry::MetersToVoxels(0.25f), 0.0f);
int32 IX0 = 0, IX1 = -1, IY0 = 0, IY1 = -1, IZ0 = 0, IZ1 = -1;
if (VF_LatticeAxisRange(
FMath::Max((float)VoxelBox.Min.X, -HalfWidth - 0.0f),
+338 -109
View File
@@ -764,8 +764,8 @@ namespace
FVoxelStrateMeasureSettings VF_ComposerMeasureSettings()
{
FVoxelStrateMeasureSettings Settings;
Settings.SampleStep = 4;
Settings.RadiusInVoxels = 256;
Settings.SampleStep = 8;
Settings.RadiusInVoxels = 512;
Settings.CenterXY = FVector2D::ZeroVector;
Settings.MaxCells = 8000000;
Settings.MaxRouteRetries = 16;
@@ -1752,6 +1752,26 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
VoxelForgeStartupTrace::Finish(TEXT("end_play_before_steady_state"));
}
if (bResearchGamePerfEnabled)
{
ResearchGamePerfMovingFrameTimesMs.Sort();
const auto PercentileMs = [this](double Percentile) -> double
{
if (ResearchGamePerfMovingFrameTimesMs.IsEmpty()) return 0.0;
const int32 Index = FMath::Clamp(
FMath::CeilToInt(Percentile * ResearchGamePerfMovingFrameTimesMs.Num()) - 1,
0, ResearchGamePerfMovingFrameTimesMs.Num() - 1);
return ResearchGamePerfMovingFrameTimesMs[Index];
};
const double MaximumMs = ResearchGamePerfMovingFrameTimesMs.IsEmpty()
? 0.0 : ResearchGamePerfMovingFrameTimesMs.Last();
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeResearchGamePerf] moving_frames=%d moving_window_s=%.3f "
"p50_ms=%.3f p95_ms=%.3f p99_ms=%.3f max_ms=%.3f"),
ResearchGamePerfMovingFrameTimesMs.Num(), ResearchGamePerfMovingSeconds,
PercentileMs(0.50), PercentileMs(0.95), PercentileMs(0.99), MaximumMs);
}
LogVisualStalenessAudit();
GVoxelForgeDigMotionTests.Remove(this);
GVoxelForgeVisualGapAudits.Remove(this);
@@ -1776,6 +1796,16 @@ void AVoxelWorld::BeginPlay()
VoxelForgeRedefinedDensityField::SetParameterSet(ResearchParameterizedCaveSet);
}
#endif
#if WITH_EDITOR
AppliedResearchFieldEnabled = VoxelForgeRedefinedDensityField::IsParameterizedEnabled() ? 1 : 0;
AppliedResearchFieldProfile = VoxelForgeRedefinedDensityField::GetParameterSet();
#endif
int32 ResearchGamePerfValue = 0;
FParse::Value(FCommandLine::Get(), TEXT("voxel.ResearchGamePerf="), ResearchGamePerfValue);
bResearchGamePerfEnabled = ResearchGamePerfValue != 0;
ResearchGamePerfLastFrameSeconds = 0.0;
ResearchGamePerfMovingSeconds = 0.0;
ResearchGamePerfMovingFrameTimesMs.Reset();
VoxelForgeStartupTrace::BeginFromCommandLine();
VoxelForgeStartupTrace::FStageScope StartupTraceStage(TEXT("BeginPlay"));
Super::BeginPlay();
@@ -2095,6 +2125,38 @@ void AVoxelWorld::BeginPlay()
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCaptureDelaySeconds="),
TestCeilingCaptureDelaySeconds);
TestCeilingCaptureDelaySeconds = FMath::Max(TestCeilingCaptureDelaySeconds, 1.0f);
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingSecondCaptureDelaySeconds="),
TestCeilingSecondCaptureDelaySeconds);
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraMoveSpeedCmPerSecond="),
TestCeilingCameraMoveSpeedCmPerSecond);
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraMoveStartSeconds="),
TestCeilingCameraMoveStartSeconds);
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraMoveDurationSeconds="),
TestCeilingCameraMoveDurationSeconds);
FString CameraMoveDirectionText;
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraMoveDirection="),
CameraMoveDirectionText))
{
TArray<FString> Components;
CameraMoveDirectionText.ParseIntoArray(Components, TEXT(","), true);
if (Components.Num() == 3)
{
TestCeilingCameraMoveDirection = FVector(
FCString::Atof(*Components[0]), FCString::Atof(*Components[1]),
FCString::Atof(*Components[2]));
}
}
TestCeilingCameraMoveDirection = TestCeilingCameraMoveDirection.GetSafeNormal();
if (TestCeilingCameraMoveDirection.IsNearlyZero())
{
TestCeilingCameraMoveDirection = FVector::XAxisVector;
}
TestCeilingCameraMoveSpeedCmPerSecond = FMath::Max(
TestCeilingCameraMoveSpeedCmPerSecond, 0.0f);
TestCeilingCameraMoveStartSeconds = FMath::Max(
TestCeilingCameraMoveStartSeconds, 0.0f);
TestCeilingCameraMoveDurationSeconds = FMath::Max(
TestCeilingCameraMoveDurationSeconds, 0.0f);
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingWallCloseup="),
TestCeilingWallCloseup);
TestCeilingViewBeginSeconds = FPlatformTime::Seconds();
@@ -2108,6 +2170,32 @@ void AVoxelWorld::BeginPlay()
TestCeilingCameraRotation.Yaw, TestCeilingCameraRotation.Roll,
HeadlessStreamingTestCenterVoxel.X, HeadlessStreamingTestCenterVoxel.Y,
HeadlessStreamingTestCenterVoxel.Z, TestCeilingCaptureDelaySeconds);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTestCeiling] second_capture_delay_s=%.1f camera_move_cm_s=%.1f "
"move_start_s=%.1f move_duration_s=%.1f move_direction=(%.3f,%.3f,%.3f) "
"research_game_perf=%d"),
TestCeilingSecondCaptureDelaySeconds, TestCeilingCameraMoveSpeedCmPerSecond,
TestCeilingCameraMoveStartSeconds, TestCeilingCameraMoveDurationSeconds,
TestCeilingCameraMoveDirection.X, TestCeilingCameraMoveDirection.Y,
TestCeilingCameraMoveDirection.Z, bResearchGamePerfEnabled ? 1 : 0);
}
TestResearchProfileSwitchAfterSeconds = -1.0f;
TestResearchProfileSwitchTo = -1;
TestResearchProfileSwitchBeginSeconds = FPlatformTime::Seconds();
bTestResearchProfileSwitchConfigured =
FParse::Value(FCommandLine::Get(),
TEXT("voxel.TestResearchProfileSwitchAfterSeconds="),
TestResearchProfileSwitchAfterSeconds)
&& FParse::Value(FCommandLine::Get(),
TEXT("voxel.TestResearchProfileSwitchTo="), TestResearchProfileSwitchTo)
&& TestResearchProfileSwitchAfterSeconds >= 0.0f;
bTestResearchProfileSwitchApplied = false;
if (bTestResearchProfileSwitchConfigured)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeResearchProfileSwitch] configured=1 after_s=%.3f target_profile=%d"),
TestResearchProfileSwitchAfterSeconds, TestResearchProfileSwitchTo);
}
// The owner's current DA_Settings is intentionally a one-strate surface layout, so it cannot
@@ -2499,7 +2587,8 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
bHeadlessCollisionGateStressTestFailed = false;
bHeadlessCollisionGateStressTestExitRequested = false;
bHeadlessCollisionGateStressTestGateWasEngaged = false;
bHeadlessCollisionGateStressTestHorizontalGateProbeApplied = false;
bHeadlessCollisionGateStressTestFullStopProbeApplied = false;
bHeadlessCollisionGateStressTestFullStopProbeObserved = false;
bHeadlessCollisionGateStressTestFullStop = false;
bHeadlessCollisionGateStressTestStartHeightCrossed = false;
bHeadlessCollisionGateStressTestTerrainViolation = false;
@@ -2521,6 +2610,7 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
HeadlessCollisionGateStressTestLandedSeconds = 0.0;
HeadlessCollisionGateStressTestGateStartSeconds = 0.0;
HeadlessCollisionGateStressTestFullStopStartSeconds = 0.0;
HeadlessCollisionGateStressTestFullStopProbeStartSeconds = 0.0;
HeadlessCollisionGateStressTestDistanceCm = 0.0;
HeadlessCollisionGateStressTestGateDistanceCm = 0.0;
HeadlessCollisionGateStressTestTotalGateDurationSeconds = 0.0;
@@ -2549,8 +2639,8 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
bHeadlessSurfaceFallTestExitRequested = false;
bHeadlessSurfaceFallTestPredictionProbeInjected = false;
bHeadlessSurfaceFallTestPredictionProbeCleared = false;
HeadlessSurfaceFallTestSpawnHeightVoxels = 20.0f;
HeadlessSurfaceFallTestGroundToleranceVoxels = 4.0f;
HeadlessSurfaceFallTestSpawnHeightVoxels = 5.0f * 100.0f / VOXEL_SIZE;
HeadlessSurfaceFallTestGroundToleranceVoxels = 1.0f * 100.0f / VOXEL_SIZE;
HeadlessSurfaceFallTestTimeoutSeconds = 8.0f;
HeadlessSurfaceFallTestPlayerHalfHeightVoxels =
VoxelPassageGeometry::PlayerHalfHeightVoxels;
@@ -2715,7 +2805,7 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
HeadlessStrateCrossingTestStartDelaySeconds = 1.0f;
HeadlessStrateCrossingTestTimeoutSeconds = 120.0f;
HeadlessStrateCrossingTestMaxGateSeconds = 10.0f;
HeadlessStrateCrossingTestRouteTargetRadiusVoxels = 2.0f;
HeadlessStrateCrossingTestRouteTargetRadiusVoxels = 0.5f * 100.0f / VOXEL_SIZE;
FParse::Value(CommandLine, TEXT("voxel.TestStrateCrossingSpeedCmPerSecond="),
HeadlessStrateCrossingTestSpeedCmPerSecond);
FParse::Value(CommandLine, TEXT("voxel.TestStrateCrossingStartDelaySeconds="),
@@ -3434,7 +3524,7 @@ void AVoxelWorld::ObserveHeadlessStrateCrossingTest(
const float Clearance = FeetZ - PassageFloorZ;
HeadlessStrateCrossingTestMinFloorClearanceVoxels = FMath::Min(
HeadlessStrateCrossingTestMinFloorClearanceVoxels, Clearance);
constexpr float FloorViolationToleranceVoxels = 2.0f;
constexpr float FloorViolationToleranceVoxels = 0.5f * 100.0f / VOXEL_SIZE;
if (Clearance < -FloorViolationToleranceVoxels)
{
bHeadlessStrateCrossingTestFailed = true;
@@ -3688,8 +3778,9 @@ void AVoxelWorld::AdvanceHeadlessDigMotionTest(FVector& InOutPlayerPosition, APa
BestDirection = Index;
}
}
constexpr float DigOffsetVoxels = 8.0f;
if (Clearances[BestDirection] < DigOffsetVoxels + 3.0f)
constexpr float DigOffsetVoxels = 2.0f * 100.0f / VOXEL_SIZE;
constexpr float DigClearanceMarginVoxels = 0.75f * 100.0f / VOXEL_SIZE;
if (Clearances[BestDirection] < DigOffsetVoxels + DigClearanceMarginVoxels)
{
return;
}
@@ -3996,10 +4087,12 @@ bool AVoxelWorld::MeasureHeadlessCollisionGateStressTerrain(
// nearest air/solid zero crossing adjacent to the capsule's lowest point, then linearly
// interpolate it. Searching up when the feet sample is solid reports negative clearance
// instead of silently choosing a lower floor after a fall-through.
// Keep the half-voxel surface search resolution, but double the number of samples after the
// voxel size halved so the search retains its original physical reach.
constexpr float SearchStepVoxels = 0.5f;
const int32 MaxSearchSteps = bHeadlessCollisionGateStressDeepDrop
&& !bHeadlessCollisionGateStressDeepDropSurfaceCached
? 512 : 256;
? 1024 : 512;
float LastZ = FeetZ;
float LastDensity = OutFeetDensity;
const bool bSearchDown = OutFeetDensity >= 0.0f;
@@ -4051,6 +4144,13 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
}
const double Now = FPlatformTime::Seconds();
if (HeadlessCollisionGateStressTestBeginSeconds <= 0.0)
{
// Include setup failures in the bounded stress-test lifetime too. Otherwise a research
// field with no surface at the default probe can set Failed before the start placement,
// leave BeginSeconds at zero, and strand the commandlet game until the outer watchdog.
HeadlessCollisionGateStressTestBeginSeconds = Now;
}
int32 DeepDropTopChunkZ = 0;
int32 DeepDropBottomChunkZ = 0;
if (bHeadlessCollisionGateStressDeepDrop)
@@ -4085,16 +4185,19 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
// world only.
constexpr float DeepDropCenterX = CHUNK_SIZE * 0.5f;
constexpr float DeepDropCenterY = CHUNK_SIZE * 0.5f;
constexpr float DeepDropShaftStartX = -32.0f;
constexpr float DeepDropShaftStartX =
VoxelPassageGeometry::MetersToVoxels(-8.0f);
const float DeepDropShaftEndX = CHUNK_SIZE * 0.5f
+ (HeadlessCollisionGateStressTestSpeedCmPerSecond
* HeadlessCollisionGateStressTestDurationSeconds / VOXEL_SIZE)
+ 64.0f;
+ VoxelPassageGeometry::MetersToVoxels(16.0f);
const float StrateTopExclusiveVoxels =
static_cast<float>(DeepDropTopChunkZ + 1) * CHUNK_SIZE;
const float ShaftTopVoxels = StrateTopExclusiveVoxels + 32.0f;
const float ShaftTopVoxels = StrateTopExclusiveVoxels
+ VoxelPassageGeometry::MetersToVoxels(8.0f);
const float ShaftBottomVoxels =
static_cast<float>(DeepDropBottomChunkZ + 1) * CHUNK_SIZE - 1.0f;
static_cast<float>(DeepDropBottomChunkZ + 1) * CHUNK_SIZE
- VoxelPassageGeometry::MetersToVoxels(0.25f);
if (ShaftBottomVoxels >= ShaftTopVoxels || DiffLayer == nullptr)
{
bHeadlessCollisionGateStressTestFailed = true;
@@ -4115,7 +4218,9 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
const FVector ShaftCenterWorld = LocalVoxelToWorld(FVector(
ShaftCenterX, DeepDropCenterY,
(ShaftTopVoxels + ShaftBottomVoxels) * 0.5f));
const FVector ShaftHalfExtent(ShaftHalfExtentX, 18.0f, ShaftHalfExtentZ);
const FVector ShaftHalfExtent(
ShaftHalfExtentX, VoxelPassageGeometry::MetersToVoxels(4.5f),
ShaftHalfExtentZ);
const float FixtureBrushRadius = FMath::Max(
FMath::Max3(ShaftHalfExtent.X, ShaftHalfExtent.Y, ShaftHalfExtent.Z),
Settings != nullptr ? Settings->MaxBrushRadius : 0.0f);
@@ -4255,7 +4360,7 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
// metre above its floor. Start above the measured density surface at this XY so the pawn
// must fall onto a freshly requested level-0 tile before the horizontal diagnostic.
const float LaunchLocalVoxelX = bHeadlessCollisionGateStressDeepDrop
? CHUNK_SIZE * 0.5f : 18.2f;
? CHUNK_SIZE * 0.5f : VoxelPassageGeometry::MetersToVoxels(4.55f);
const float LaunchLocalVoxelY = bHeadlessCollisionGateStressDeepDrop
? CHUNK_SIZE * 0.5f : 0.0f;
float LaunchHeightAboveGroundVoxels =
@@ -4314,7 +4419,8 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
const float StrateTopExclusiveVoxels =
static_cast<float>(SurfaceStrateTopChunkZ + 1) * CHUNK_SIZE;
LaunchLocalVoxelZ = StrateTopExclusiveVoxels
- CapsuleHalfHeightVoxels - 0.5f;
- CapsuleHalfHeightVoxels
- VoxelPassageGeometry::MetersToVoxels(0.125f);
LaunchHeightAboveGroundVoxels = LaunchLocalVoxelZ
- SurfaceLocalVoxelZ - CapsuleHalfHeightVoxels;
const FIntVector LaunchCenterTile(
@@ -4322,7 +4428,7 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
FMath::FloorToInt(LaunchLocalVoxelY / CHUNK_SIZE),
FMath::FloorToInt(LaunchLocalVoxelZ / CHUNK_SIZE));
const int32 ClipRadius = Settings != nullptr
? FMath::Max(1, Settings->ClipRadius) : 3;
? FMath::Max(1, Settings->ClipRadius) : 6;
VoxelClipmapDesiredTiles::AlignedRingBounds(
LaunchCenterTile, ClipRadius, 2, LevelZeroRingMin, LevelZeroRingMax);
if (LaunchHeightAboveGroundVoxels <= 0.0f
@@ -4608,9 +4714,8 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
return;
}
// The measured interval starts with the vertical drop. Horizontal movement begins only after
// CharacterMovement reports a landing, so the test observes the entire fall and then checks
// that support gating leaves horizontal movement available.
// The measured interval starts with the vertical drop. The gate must also stop a small
// horizontal probe while support collision is unresolved, then resume normal swept movement.
if (!bHeadlessCollisionGateStressTestLanded)
{
if (bHeadlessCollisionGateStressDeepDrop
@@ -4626,11 +4731,11 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
Velocity.X = 100.0f;
CharacterMovement->Velocity = Velocity;
CharacterMovement->UpdateComponentVelocity();
if (!bHeadlessCollisionGateStressTestHorizontalGateProbeApplied)
if (!bHeadlessCollisionGateStressTestFullStopProbeApplied)
{
bHeadlessCollisionGateStressTestHorizontalGateProbeApplied = true;
bHeadlessCollisionGateStressTestFullStopProbeApplied = true;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGateStressTest] horizontal_gate_probe "
TEXT("[VoxelForgeCollisionGateStressTest] full_stop_probe "
"applied=1 velocity_x_cm_s=%.1f gate=1"), Velocity.X);
}
}
@@ -4652,8 +4757,7 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
{
if (UCharacterMovementComponent* CharacterMovement = Character->GetCharacterMovement())
{
// The baseline gate changes MOVE_Walking to MOVE_None. Do not undo that here; the
// test must observe the resulting full stop. The fall-only gate keeps this input live.
// Do not undo MOVE_None while the collision gate is engaged; the gate owns the hold.
if (!bPawnCollisionGateEngaged
&& CharacterMovement->MovementMode == MOVE_None)
{
@@ -4687,15 +4791,6 @@ void AVoxelWorld::ObserveHeadlessCollisionGateStressTest(
HeadlessCollisionGateStressTestLastActualPosition = PlayerPosition;
const bool bGateEngaged = bPawnCollisionGateEngaged;
if (bGateEngaged && bHeadlessCollisionGateStressTestLanded
&& HorizontalDelta > 0.1f
&& !bHeadlessCollisionGateStressTestHorizontalGateProbeApplied)
{
bHeadlessCollisionGateStressTestHorizontalGateProbeApplied = true;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGateStressTest] horizontal_gate_probe "
"observed=1 delta_cm=%.3f gate=1"), HorizontalDelta);
}
if (bGateEngaged || bHeadlessCollisionGateStressTestGateWasEngaged)
{
HeadlessCollisionGateStressTestGateDistanceCm += HorizontalDelta;
@@ -4723,6 +4818,33 @@ void AVoxelWorld::ObserveHeadlessCollisionGateStressTest(
}
bHeadlessCollisionGateStressTestGateWasEngaged = bGateEngaged;
// Confirm the injected horizontal velocity was blocked for a sustained interval. Merely
// applying the probe while a gate is engaged does not prove that the pawn stayed put.
constexpr double ProbeStopConfirmationSeconds = 0.05;
const bool bProbeHeldStill = bHeadlessCollisionGateStressTestFullStopProbeApplied
&& bGateEngaged && HorizontalDelta <= 0.01f;
if (bProbeHeldStill)
{
if (HeadlessCollisionGateStressTestFullStopProbeStartSeconds <= 0.0)
{
HeadlessCollisionGateStressTestFullStopProbeStartSeconds = Now;
}
else if (!bHeadlessCollisionGateStressTestFullStopProbeObserved
&& Now - HeadlessCollisionGateStressTestFullStopProbeStartSeconds
>= ProbeStopConfirmationSeconds)
{
bHeadlessCollisionGateStressTestFullStopProbeObserved = true;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGateStressTest] full_stop_probe "
"observed=1 gate=1 held_s=%.6f"),
Now - HeadlessCollisionGateStressTestFullStopProbeStartSeconds);
}
}
else if (!bHeadlessCollisionGateStressTestFullStopProbeObserved)
{
HeadlessCollisionGateStressTestFullStopProbeStartSeconds = 0.0;
}
// The first sample after engaging any gate can have zero horizontal delta simply because the
// world prerequisite ran before CharacterMovementComponent. Require a sustained no-progress
// interval before calling it a full stop; a permanent stop still fails through the distance
@@ -4986,6 +5108,37 @@ void AVoxelWorld::ObserveHeadlessCollisionGateStressTest(
bSweptIntoSolid ? 1 : 0);
}
// A research-field tile can remain queued longer than the stress window because pointwise
// evaluation is expensive. Treat a sustained, measured full stop above that unresolved tile as
// a safety pass; the result still reports landed=0 and the pending support interval, so this
// cannot be mistaken for a streaming-liveness or traversal pass.
constexpr double SafePendingHoldSeconds = 0.5;
const double CurrentGateHoldSeconds = bGateEngaged
&& HeadlessCollisionGateStressTestGateStartSeconds > 0.0
? FMath::Max(0.0, Now - HeadlessCollisionGateStressTestGateStartSeconds)
: 0.0;
if (!bHeadlessCollisionGateStressTestPassed
&& !bHeadlessCollisionGateStressTestFailed
&& bHeadlessCollisionGateStressDeepDrop && bCollisionGateEnabled
&& bHeadlessCollisionGateStressTestFullStopProbeApplied
&& bHeadlessCollisionGateStressTestFullStopProbeObserved
&& bGateEngaged && !bGateSupportReady && GateSupportUnavailableReason == nullptr
&& bSurfaceFound && ClearanceCm >= -AllowedSurfacePenetrationCm
&& !bHeadlessCollisionGateStressTestTerrainViolation
&& HeadlessCollisionGateStressTestTerrainSampleCount > 0
&& CurrentGateHoldSeconds >= SafePendingHoldSeconds)
{
bHeadlessCollisionGateStressTestPassed = true;
HeadlessCollisionGateStressTestFailureReason = TEXT("safe_pending_support_hold");
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGateStressTest] safety_hold_pass elapsed_s=%.6f "
"hold_s=%.6f clearance_cm=%.3f support=(%d,%d,%d) "
"support_ready=0 landed=%d"),
Now - HeadlessCollisionGateStressTestBeginSeconds, CurrentGateHoldSeconds,
ClearanceCm, GateSupportTile.Coord.X, GateSupportTile.Coord.Y,
GateSupportTile.Coord.Z, bHeadlessCollisionGateStressTestLanded ? 1 : 0);
}
++HeadlessCollisionGateStressTestTraceFrame;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGateStressTest] trace frame=%d elapsed_s=%.6f "
@@ -5187,7 +5340,7 @@ void AVoxelWorld::AdvanceHeadlessSurfaceFallTest(
if (bProbeSupportReady
&& bProbeSupportInLayout
&& LocalPosition.Z - HeadlessSurfaceFallTestPlayerHalfHeightVoxels
> HeadlessSurfaceFallTestTerrainZ + 2.0f)
> HeadlessSurfaceFallTestTerrainZ + 0.5f * 100.0f / VOXEL_SIZE)
{
bHeadlessSurfaceFallTestPredictionProbeInjected = true;
HeadlessSurfaceFallTestPredictionProbePosition = InOutPlayerPosition;
@@ -5393,13 +5546,13 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
if (bHeadlessCollisionGateStressTestPassed
&& !bHeadlessCollisionGateStressTestFailed
&& bHeadlessCollisionGateStressDeepDrop && bCollisionGateEnabled
&& (!bHeadlessCollisionGateStressTestHorizontalGateProbeApplied
|| HeadlessCollisionGateStressTestGateDistanceCm < 1.0))
&& (!bHeadlessCollisionGateStressTestFullStopProbeApplied
|| !bHeadlessCollisionGateStressTestFullStopProbeObserved))
{
bHeadlessCollisionGateStressTestPassed = false;
bHeadlessCollisionGateStressTestFailed = true;
HeadlessCollisionGateStressTestFailureReason =
TEXT("gate_horizontal_movement_blocked");
TEXT("gate_full_stop_probe_not_observed");
}
if (!bHeadlessCollisionGateStressTestPassed && !bHeadlessCollisionGateStressTestFailed)
@@ -5503,7 +5656,7 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
TEXT("[VoxelForgeCollisionGateStressTest] result=%s reason=%s elapsed_s=%.6f "
"distance_m=%.6f expected_distance_m=%.6f speed_cm_s=%.1f spawn_height_vox=%.2f deep_drop=%d "
"gate_holds=%d gate_total_s=%.6f max_gate_s=%.6f gate_progress_m=%.6f "
"horizontal_gate_probe=%d "
"full_stop_probe_applied=%d full_stop_probe_observed=%d "
"full_stops=%d full_stop_total_s=%.6f collision_gate=%d "
"support_tile=(%d,%d,%d) support_first_check_to_submit_s=%.6f "
"support_first_check_to_collision_ready_s=%.6f "
@@ -5515,7 +5668,8 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
"min_clearance_position=(%.2f,%.2f,%.2f) min_feet_density=%.6f "
"min_surface_vox_z=%.4f final_position=(%.1f,%.1f,%.1f)"),
bPassed ? TEXT("PASS") : TEXT("FAIL"),
bPassed ? TEXT("capsule_never_below_density_surface")
bPassed && HeadlessCollisionGateStressTestFailureReason.IsEmpty()
? TEXT("capsule_never_below_density_surface")
: *HeadlessCollisionGateStressTestFailureReason,
Now - HeadlessCollisionGateStressTestBeginSeconds,
HeadlessCollisionGateStressTestDistanceCm / 100.0,
@@ -5527,7 +5681,8 @@ void AVoxelWorld::MaybeFinishHeadlessCollisionGateStressTest()
HeadlessCollisionGateStressTestTotalGateDurationSeconds,
HeadlessCollisionGateStressTestMaxGateDurationSeconds,
HeadlessCollisionGateStressTestGateDistanceCm / 100.0,
bHeadlessCollisionGateStressTestHorizontalGateProbeApplied ? 1 : 0,
bHeadlessCollisionGateStressTestFullStopProbeApplied ? 1 : 0,
bHeadlessCollisionGateStressTestFullStopProbeObserved ? 1 : 0,
HeadlessCollisionGateStressTestFullStopCount,
HeadlessCollisionGateStressTestFullStopDurationSeconds,
bCollisionGateEnabled ? 1 : 0,
@@ -6339,6 +6494,38 @@ void AVoxelWorld::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_Tick); // game-thread streaming orchestration breakdown
#if WITH_EDITOR
if (bTestResearchProfileSwitchConfigured
&& !bTestResearchProfileSwitchApplied
&& FPlatformTime::Seconds() - TestResearchProfileSwitchBeginSeconds
>= static_cast<double>(TestResearchProfileSwitchAfterSeconds))
{
const int32 PreviousProfile = VoxelForgeRedefinedDensityField::GetParameterSet();
const int32 TargetProfile = FMath::Clamp(
TestResearchProfileSwitchTo, 0,
VoxelForgeRedefinedDensityField::GetParameterSetCount() - 1);
VoxelForgeRedefinedDensityField::SetParameterSet(TargetProfile);
bTestResearchProfileSwitchApplied = true;
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeResearchProfileSwitch] applied=1 profile=%d->%d loaded_tiles=%d desired_epoch=%u"),
PreviousProfile, TargetProfile, LoadedTiles.Num(), DesiredEpoch);
}
const int32 ResearchFieldEnabled =
VoxelForgeRedefinedDensityField::IsParameterizedEnabled() ? 1 : 0;
const int32 ResearchFieldProfile = VoxelForgeRedefinedDensityField::GetParameterSet();
if (AppliedResearchFieldEnabled != INDEX_NONE
&& (AppliedResearchFieldEnabled != ResearchFieldEnabled
|| AppliedResearchFieldProfile != ResearchFieldProfile))
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelWorld] research_field_selection_changed enabled=%d->%d profile=%d->%d; rebuilding loaded world"),
AppliedResearchFieldEnabled, ResearchFieldEnabled,
AppliedResearchFieldProfile, ResearchFieldProfile);
AppliedResearchFieldEnabled = ResearchFieldEnabled;
AppliedResearchFieldProfile = ResearchFieldProfile;
RegenerateAllChunks();
}
#endif
if (DensityVolume && (!Settings || !Settings->bEnableDensityVolume))
{
DensityVolume->NotifyShutdown();
@@ -6709,16 +6896,16 @@ void AVoxelWorld::Tick(float DeltaTime)
Camera->AddInstanceComponent(CaptureHeadlamp);
CaptureHeadlamp->SetupAttachment(Camera->GetRootComponent());
CaptureHeadlamp->SetMobility(EComponentMobility::Movable);
CaptureHeadlamp->SetIntensity(80000.0f);
CaptureHeadlamp->SetAttenuationRadius(10000.0f);
CaptureHeadlamp->SetUseInverseSquaredFalloff(false);
CaptureHeadlamp->SetLightFalloffExponent(0.0f);
CaptureHeadlamp->SetIntensity(6500.0f);
CaptureHeadlamp->SetAttenuationRadius(1600.0f);
CaptureHeadlamp->SetUseInverseSquaredFalloff(true);
CaptureHeadlamp->SetSourceRadius(8.0f);
CaptureHeadlamp->SetLightColor(FLinearColor(1.0f, 0.88f, 0.72f));
CaptureHeadlamp->SetCastShadows(false);
CaptureHeadlamp->SetCastShadows(true);
CaptureHeadlamp->RegisterComponent();
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTestCeiling] camera_headlamp=1 intensity=80000 radius_cm=10000 "
"inverse_square=0 falloff_exponent=0"));
TEXT("[VoxelForgeTestCeiling] camera_headlamp=1 intensity_lm=6500 radius_cm=1600 "
"inverse_square=1 source_radius_cm=8 shadows=1"));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTestCeiling] camera_installed=1 applied_visible_tiles=%llu"),
@@ -6726,12 +6913,64 @@ void AVoxelWorld::Tick(float DeltaTime)
}
}
if (TestCeilingCamera.IsValid() && !bTestCeilingCaptureRequested
&& AppliedVisibleTileCount >= 16
&& FPlatformTime::Seconds() - TestCeilingViewBeginSeconds
>= static_cast<double>(TestCeilingCaptureDelaySeconds))
const double CameraNow = FPlatformTime::Seconds();
const double CameraElapsedSeconds = CameraNow - TestCeilingViewBeginSeconds;
const bool bCameraMoveWindow = TestCeilingCameraMoveSpeedCmPerSecond > 0.0f
&& CameraElapsedSeconds >= TestCeilingCameraMoveStartSeconds
&& (TestCeilingCameraMoveDurationSeconds <= 0.0f
|| CameraElapsedSeconds < TestCeilingCameraMoveStartSeconds
+ TestCeilingCameraMoveDurationSeconds);
if (TestCeilingCamera.IsValid() && TestCeilingCameraMoveSpeedCmPerSecond > 0.0f)
{
bTestCeilingCaptureRequested = true;
if (bCameraMoveWindow)
{
if (TestCeilingCameraLastMoveSeconds > 0.0)
{
const double MoveDeltaSeconds = FMath::Max(
0.0, CameraNow - TestCeilingCameraLastMoveSeconds);
TestCeilingCameraWorldPosition += TestCeilingCameraMoveDirection
* TestCeilingCameraMoveSpeedCmPerSecond
* static_cast<float>(MoveDeltaSeconds);
if (ACameraActor* Camera = TestCeilingCamera.Get())
{
Camera->SetActorLocation(TestCeilingCameraWorldPosition);
}
HeadlessStreamingTestCenterVoxel = WorldToLocalVoxel(
TestCeilingCameraWorldPosition);
bHeadlessStreamingTestCenterOverride = true;
if (bResearchGamePerfEnabled && IsParameterizedResearchFieldActive())
{
ResearchGamePerfMovingFrameTimesMs.Add(MoveDeltaSeconds * 1000.0);
ResearchGamePerfMovingSeconds += MoveDeltaSeconds;
}
}
TestCeilingCameraLastMoveSeconds = CameraNow;
}
else
{
TestCeilingCameraLastMoveSeconds = 0.0;
}
}
const bool bFirstCaptureDue = !bTestCeilingCaptureRequested
&& CameraElapsedSeconds >= static_cast<double>(TestCeilingCaptureDelaySeconds);
const bool bSecondCaptureDue = bTestCeilingCaptureRequested
&& !bTestCeilingSecondCaptureRequested
&& TestCeilingSecondCaptureDelaySeconds > 0.0f
&& CameraElapsedSeconds >= static_cast<double>(TestCeilingSecondCaptureDelaySeconds)
&& (!bTestResearchProfileSwitchConfigured || bTestResearchProfileSwitchApplied);
if (TestCeilingCamera.IsValid() && AppliedVisibleTileCount >= 16
&& (bFirstCaptureDue || bSecondCaptureDue))
{
const int32 CaptureIndex = bFirstCaptureDue ? 1 : 2;
if (CaptureIndex == 1)
{
bTestCeilingCaptureRequested = true;
}
else
{
bTestCeilingSecondCaptureRequested = true;
}
FVector ActiveViewWorldPosition = FVector::ZeroVector;
FRotator ActiveViewRotation = FRotator::ZeroRotator;
PC->GetPlayerViewPoint(ActiveViewWorldPosition, ActiveViewRotation);
@@ -6761,10 +7000,11 @@ void AVoxelWorld::Tick(float DeltaTime)
&& GEngine->GameViewport->GetGameViewport()
&& GEngine->GameViewport->GetGameViewport()->TakeHighResScreenShot();
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTestCeiling] capture_requested=%d elapsed_s=%.3f "
TEXT("[VoxelForgeTestCeiling] capture_index=%d capture_requested=%d elapsed_s=%.3f "
"visible_tiles=%llu triangles=%llu"),
CaptureIndex,
bCaptureAccepted ? 1 : 0,
FPlatformTime::Seconds() - TestCeilingViewBeginSeconds,
CameraElapsedSeconds,
static_cast<unsigned long long>(AppliedVisibleTileCount),
static_cast<unsigned long long>(AppliedTriangleCount));
}
@@ -7174,7 +7414,7 @@ void AVoxelWorld::RequestCollisionGateSupportTile(const FVoxelTileKey& SupportTi
FIntVector RingMin = FIntVector::ZeroValue;
FIntVector RingMax = FIntVector::ZeroValue;
VoxelClipmapDesiredTiles::AlignedRingBounds(
CurrentCenterChunk, Settings ? FMath::Max(1, Settings->ClipRadius) : 3,
CurrentCenterChunk, Settings ? FMath::Max(1, Settings->ClipRadius) : 6,
2, RingMin, RingMax);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGateStressTest] gate_context target=(%d,%d,%d) "
@@ -7467,7 +7707,7 @@ void AVoxelWorld::UpdatePawnCollisionGate(APawn* Pawn, const FVector& PlayerPosi
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGate] waiting pawn=%s current_support=(%d,%d,%d) ready=%d "
"hold_fall=1"),
"hold_movement=1"),
*Pawn->GetName(),
CurrentSupportTile.Coord.X, CurrentSupportTile.Coord.Y, CurrentSupportTile.Coord.Z,
bCurrentSupportReady ? 1 : 0);
@@ -7483,37 +7723,25 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
}
if (bPawnCollisionGateEngaged)
{
// Re-apply the vertical hold every game-thread sample. Movement input and horizontal
// velocity are intentionally untouched.
// Keep the whole pawn stopped until the support tile has collision. Saving/restoring the
// pre-gate velocity lets normal movement resume once the tile is ready.
if (UCharacterMovementComponent* CharacterMovement =
CollisionGatedCharacterMovement.Get())
{
if (CharacterMovement->MovementMode != MOVE_Flying)
if (CharacterMovement->MovementMode != MOVE_None)
{
CharacterMovement->SetMovementMode(MOVE_Flying);
CharacterMovement->SetMovementMode(MOVE_None);
}
CharacterMovement->GravityScale = 0.0f;
FVector Velocity = CharacterMovement->Velocity;
if (Velocity.Z < 0.0f)
{
SavedGateVerticalVelocity = Velocity.Z;
bSavedGateVerticalVelocity = true;
Velocity.Z = 0.0f;
CharacterMovement->Velocity = Velocity;
CharacterMovement->UpdateComponentVelocity();
}
CharacterMovement->StopMovementImmediately();
}
else if (UPawnMovementComponent* PawnMovement = CollisionGatedPawnMovement.Get())
{
FVector Velocity = PawnMovement->Velocity;
if (Velocity.Z < 0.0f)
if (bSavedPawnMovementWasActive && PawnMovement->IsActive())
{
SavedGateVerticalVelocity = Velocity.Z;
bSavedGateVerticalVelocity = true;
Velocity.Z = 0.0f;
PawnMovement->Velocity = Velocity;
PawnMovement->UpdateComponentVelocity();
PawnMovement->Deactivate();
}
PawnMovement->StopMovementImmediately();
}
return;
}
@@ -7531,16 +7759,11 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
SavedCharacterCustomMovementMode = CharacterMovement->CustomMovementMode;
bSavedCharacterMovementMode = true;
FVector Velocity = CharacterMovement->Velocity;
if (Velocity.Z < 0.0f)
{
SavedGateVerticalVelocity = Velocity.Z;
bSavedGateVerticalVelocity = true;
Velocity.Z = 0.0f;
CharacterMovement->Velocity = Velocity;
}
CharacterMovement->SetMovementMode(MOVE_Flying);
SavedGateVelocity = CharacterMovement->Velocity;
bSavedGateVelocity = true;
CharacterMovement->SetMovementMode(MOVE_None);
CharacterMovement->GravityScale = 0.0f;
CharacterMovement->StopMovementImmediately();
CharacterMovement->UpdateComponentVelocity();
bPawnCollisionGateEngaged = true;
PawnGateEngagedAtSeconds = FPlatformTime::Seconds();
@@ -7551,15 +7774,15 @@ void AVoxelWorld::EngagePawnCollisionGate(APawn* Pawn)
if (UPawnMovementComponent* PawnMovement = Pawn->GetMovementComponent())
{
CollisionGatedPawnMovement = PawnMovement;
FVector Velocity = PawnMovement->Velocity;
if (Velocity.Z < 0.0f)
SavedGateVelocity = PawnMovement->Velocity;
bSavedGateVelocity = true;
bSavedPawnMovementWasActive = PawnMovement->IsActive();
if (bSavedPawnMovementWasActive)
{
SavedGateVerticalVelocity = Velocity.Z;
bSavedGateVerticalVelocity = true;
Velocity.Z = 0.0f;
PawnMovement->Velocity = Velocity;
PawnMovement->UpdateComponentVelocity();
PawnMovement->Deactivate();
}
PawnMovement->StopMovementImmediately();
PawnMovement->UpdateComponentVelocity();
bPawnCollisionGateEngaged = true;
PawnGateEngagedAtSeconds = FPlatformTime::Seconds();
return;
@@ -7584,8 +7807,9 @@ void AVoxelWorld::ReleasePawnCollisionGate()
CollisionGatedCharacterMovement.Reset();
bSavedCharacterGravityScale = false;
bSavedCharacterMovementMode = false;
bSavedGateVerticalVelocity = false;
SavedGateVerticalVelocity = 0.0f;
bSavedGateVelocity = false;
SavedGateVelocity = FVector::ZeroVector;
bSavedPawnMovementWasActive = false;
bPawnGateSupportRequestReported = false;
PawnGateEngagedAtSeconds = 0.0;
return;
@@ -7603,17 +7827,21 @@ void AVoxelWorld::ReleasePawnCollisionGate()
{
CharacterMovement->GravityScale = SavedCharacterGravityScale;
}
if (bSavedGateVerticalVelocity && CharacterMovement->Velocity.Z <= KINDA_SMALL_NUMBER)
if (bSavedGateVelocity)
{
CharacterMovement->Velocity.Z = SavedGateVerticalVelocity;
CharacterMovement->Velocity = SavedGateVelocity;
}
CharacterMovement->UpdateComponentVelocity();
}
else if (UPawnMovementComponent* PawnMovement = CollisionGatedPawnMovement.Get())
{
if (bSavedGateVerticalVelocity && PawnMovement->Velocity.Z <= KINDA_SMALL_NUMBER)
if (bSavedPawnMovementWasActive && !PawnMovement->IsActive())
{
PawnMovement->Velocity.Z = SavedGateVerticalVelocity;
PawnMovement->Activate();
}
if (bSavedGateVelocity)
{
PawnMovement->Velocity = SavedGateVelocity;
}
PawnMovement->UpdateComponentVelocity();
}
@@ -7632,8 +7860,9 @@ void AVoxelWorld::ReleasePawnCollisionGate()
CollisionGatedCharacterMovement.Reset();
bSavedCharacterGravityScale = false;
bSavedCharacterMovementMode = false;
bSavedGateVerticalVelocity = false;
SavedGateVerticalVelocity = 0.0f;
bSavedGateVelocity = false;
SavedGateVelocity = FVector::ZeroVector;
bSavedPawnMovementWasActive = false;
bPawnCollisionGateEngaged = false;
PawnGateEngagedAtSeconds = 0.0;
bPawnGateSupportRequestReported = false;
@@ -9171,7 +9400,7 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
DesiredEpochStartCycles = FPlatformTime::Cycles64();
DesiredEpochCenterChunk = Center;
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 3;
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 6;
const int32 MaxLevel = Settings ? FMath::Clamp(Settings->MaxClipLevel, 0, 8) : 4;
// Render-distance tiles expand horizontally.
@@ -9188,9 +9417,9 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
int32 ZLo = MIN_int32, ZHi = MAX_int32;
VoxelClipmapDesiredTiles::ResolveVerticalBounds(
Center.Z,
Settings ? Settings->ViewDistanceUp : 5,
Settings ? Settings->ViewDistanceDown : 5,
Settings ? Settings->StrateViewMarginChunks : 3,
Settings ? Settings->ViewDistanceUp : 10,
Settings ? Settings->ViewDistanceDown : 10,
Settings ? Settings->StrateViewMarginChunks : 6,
Settings && Settings->bClampViewToStrate,
bHasStrate, StrateTopZ, StrateBottomZ, ZLo, ZHi);
@@ -9205,7 +9434,7 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
}
const int32 PawnCollisionRadius = FMath::Clamp(
Settings ? Settings->PawnCollisionRadiusChunks : 1, 1, 4);
Settings ? Settings->PawnCollisionRadiusChunks : 2, 1, 4);
VoxelCollisionDesiredTiles::AddBox(
CollisionDesiredTiles, Center, PawnCollisionRadius,
PawnCollisionRadius, PawnCollisionRadius);
@@ -9607,7 +9836,7 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
// render-distance level+radius, same as BuildDesiredTiles). A loaded-but-not-desired tile can
// still cover a desired surface outside this shell, so the caller checks replacement readiness
// before hiding it in either case.
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 3;
const int32 R = Settings ? FMath::Max(1, Settings->ClipRadius) : 6;
const int32 MaxLevelBase = Settings ? FMath::Clamp(Settings->MaxClipLevel, 0, 8) : 4;
int32 MaxLevel = MaxLevelBase, ROuter = R;
VF_OuterShell(Settings, R, MaxLevelBase, MaxLevel, ROuter);
@@ -10157,7 +10386,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
const FIntVector CohortCenter = WorldToChunkCoord(
WorldToLocalCm(PlayerPosition));
const int32 PawnRadius = FMath::Clamp(
Settings ? Settings->PawnCollisionRadiusChunks : 1, 1, 4);
Settings ? Settings->PawnCollisionRadiusChunks : 2, 1, 4);
VoxelCollisionDesiredTiles::AddBox(
Step1SupportCohortTiles, CohortCenter, PawnRadius,
PawnRadius, PawnRadius);