fix(classifier): exact lattice check before a uniform verdict; ceiling cause is NOT false certificates
ParameterRoll's 5 false box verdicts are fixed: fine room-graph intervals are now checked against exact lattice samples before a uniform verdict is accepted, while the SDF bound keeps its conservative lower bound. Lattice sweep 600 tiles / 0 violations; ParameterRoll passes with 9,577 exact lattice leaves and 774 interval disagreements caught. The owner's ceiling holes are NOT explained by this: in his recorded session outer_classifier_mode=0, solid-proof skips were 0, and air-proof skips were outside the strate. The trace points at the far-field sheet system instead: sheet tile L5 (0,1,0) is deliberately empty inside the sheet hole while its bridge tile L4 (0,1,-1) meshed 662 ceiling triangles. Continuous ceiling is NOT verified: every render launch failed (D3D12 driver error, stalls before map load, the 6 GB cap), so the round produced no matched after-render. Cost note: this round used 3.46M tokens on Luna 6 at max, most of it burned on failed render launches. Verification by render is unreliable in this harness; verify ceiling continuity from tile/geometry data instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
30253f0e89
commit
ea3f0d9a52
7 files changed
+410
-18
No files matched your search
@@ -13,6 +13,7 @@
|
|||||||
#include "VoxelStrateComposer.h"
|
#include "VoxelStrateComposer.h"
|
||||||
#include "VoxelStrateMeasure.h"
|
#include "VoxelStrateMeasure.h"
|
||||||
#include "VoxelTerrainOpDefinition.h"
|
#include "VoxelTerrainOpDefinition.h"
|
||||||
|
#include "VoxelDensityOpStack.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -238,8 +239,11 @@ namespace
|
|||||||
int32 AllAir = 0;
|
int32 AllAir = 0;
|
||||||
int32 Proved = 0;
|
int32 Proved = 0;
|
||||||
int64 CheckedVoxels = 0;
|
int64 CheckedVoxels = 0;
|
||||||
|
int64 ExactIntervalChecks = 0;
|
||||||
|
int64 ExactIntervalDisagreements = 0;
|
||||||
int32 Violations = 0;
|
int32 Violations = 0;
|
||||||
FString FirstViolation;
|
FString FirstViolation;
|
||||||
|
TArray<FString> ViolationDetails;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -248,7 +252,8 @@ namespace
|
|||||||
* to expose. The report is aggregate: there is no per-sample logging.
|
* to expose. The report is aggregate: there is no per-sample logging.
|
||||||
*/
|
*/
|
||||||
FBoxVerdictReport VF_CheckRolledBoxVerdicts(
|
FBoxVerdictReport VF_CheckRolledBoxVerdicts(
|
||||||
const VoxelForgeTest::FTestWorld& World, int32 CandidateIndex, int32 StrateIndex)
|
const VoxelForgeTest::FTestWorld& World, int32 CandidateIndex, int32 StrateIndex,
|
||||||
|
const FVoxelStrateRollInfo& Roll)
|
||||||
{
|
{
|
||||||
FBoxVerdictReport Report;
|
FBoxVerdictReport Report;
|
||||||
const UVoxelGenerator* Generator = World.Generator.Get();
|
const UVoxelGenerator* Generator = World.Generator.Get();
|
||||||
@@ -286,8 +291,18 @@ namespace
|
|||||||
(float)(Origin.Y + (Cells + 1) * Step),
|
(float)(Origin.Y + (Cells + 1) * Step),
|
||||||
(float)(Origin.Z + (Cells + 1) * Step)));
|
(float)(Origin.Z + (Cells + 1) * Step)));
|
||||||
(void)Box; // The production API receives the equivalent origin/step/cell box.
|
(void)Box; // The production API receives the equivalent origin/step/cell box.
|
||||||
|
const FBox ClassifierBox(
|
||||||
|
FVector(static_cast<float>(Origin.X), static_cast<float>(Origin.Y),
|
||||||
|
static_cast<float>(Origin.Z)),
|
||||||
|
FVector(static_cast<float>(Origin.X + Cells * Step),
|
||||||
|
static_cast<float>(Origin.Y + Cells * Step),
|
||||||
|
static_cast<float>(Origin.Z + Cells * Step)));
|
||||||
|
|
||||||
const EVoxelTileClass Verdict = Generator->ClassifyTile(Origin, Step, Cells);
|
FVoxelTileClassificationStats ClassifyStats;
|
||||||
|
const EVoxelTileClass Verdict = Generator->ClassifyTile(
|
||||||
|
Origin, Step, Cells, &ClassifyStats);
|
||||||
|
Report.ExactIntervalChecks += ClassifyStats.ExactCoreIntervalChecks;
|
||||||
|
Report.ExactIntervalDisagreements += ClassifyStats.ExactCoreIntervalDisagreements;
|
||||||
if (Verdict == EVoxelTileClass::Mixed)
|
if (Verdict == EVoxelTileClass::Mixed)
|
||||||
{
|
{
|
||||||
++Report.Mixed;
|
++Report.Mixed;
|
||||||
@@ -305,6 +320,15 @@ namespace
|
|||||||
|
|
||||||
const bool bClaimsSolid = Verdict == EVoxelTileClass::AllSolid;
|
const bool bClaimsSolid = Verdict == EVoxelTileClass::AllSolid;
|
||||||
bool bBoxBad = false;
|
bool bBoxBad = false;
|
||||||
|
bool bDiagnosticStackReady = false;
|
||||||
|
bool bDiagnosticStackBuilt = false;
|
||||||
|
FVoxelOpStack DiagnosticStack;
|
||||||
|
FVoxelOpContext DiagnosticContext;
|
||||||
|
EVoxelTileClass DiagnosticStackVerdict = EVoxelTileClass::Mixed;
|
||||||
|
int32 DiagnosticSolidKiller = INDEX_NONE;
|
||||||
|
int32 DiagnosticAirKiller = INDEX_NONE;
|
||||||
|
TArray<FVoxelOpStack::FOpBoxDiagnostic> OpDiagnostics;
|
||||||
|
FString WriterRouteDetails;
|
||||||
// ClassifyTile covers the cell vertices passed to it: g is [0, Cells]. The mesher
|
// ClassifyTile covers the cell vertices passed to it: g is [0, Cells]. The mesher
|
||||||
// samples a separate halo, but that halo is not part of this production verdict's
|
// samples a separate halo, but that halo is not part of this production verdict's
|
||||||
// proof domain and must not turn a sound tile into a false failure.
|
// proof domain and must not turn a sound tile into a false failure.
|
||||||
@@ -325,6 +349,173 @@ namespace
|
|||||||
{
|
{
|
||||||
++Report.Violations;
|
++Report.Violations;
|
||||||
bBoxBad = true;
|
bBoxBad = true;
|
||||||
|
if (!bDiagnosticStackReady)
|
||||||
|
{
|
||||||
|
bDiagnosticStackReady = true;
|
||||||
|
int32 SlotTop = 0, SlotBottom = 0;
|
||||||
|
FVoxelStrateArchetypeParams RuntimeParams = Roll.ArchetypeParams;
|
||||||
|
bDiagnosticStackBuilt = World.GetSlotVoxelZRange(
|
||||||
|
StrateIndex, SlotTop, SlotBottom);
|
||||||
|
if (bDiagnosticStackBuilt)
|
||||||
|
{
|
||||||
|
VF_SetStrateArchetypeRuntimeBounds(
|
||||||
|
RuntimeParams, static_cast<float>(SlotTop) + 1.0f,
|
||||||
|
static_cast<float>(SlotBottom));
|
||||||
|
bDiagnosticStackBuilt = VF_BuildNativeStrateStackForCandidate(
|
||||||
|
Roll.Archetype, RuntimeParams, World.Settings->Seed,
|
||||||
|
World.Generator->OriginSpineRadius,
|
||||||
|
World.Generator->WorldRadiusVoxels,
|
||||||
|
World.Generator->EdgeSealThickness,
|
||||||
|
World.StrateManager.Get(), DiagnosticStack, DiagnosticContext);
|
||||||
|
}
|
||||||
|
if (bDiagnosticStackBuilt)
|
||||||
|
{
|
||||||
|
DiagnosticContext.ChunkCoord = FIntVector(
|
||||||
|
0, 0, VF_FloorDiv(Origin.Z, CHUNK_SIZE));
|
||||||
|
DiagnosticContext.Step = Step;
|
||||||
|
DiagnosticContext.LayoutVersion =
|
||||||
|
World.StrateManager->GetLayoutVersion();
|
||||||
|
DiagnosticContext.WorldRadiusVoxels =
|
||||||
|
World.Generator->WorldRadiusVoxels;
|
||||||
|
DiagnosticContext.EdgeSealThickness =
|
||||||
|
World.Generator->EdgeSealThickness;
|
||||||
|
DiagnosticContext.bUseLatticeProof = true;
|
||||||
|
DiagnosticContext.bTightenWarpProof = Step <= 2;
|
||||||
|
DiagnosticContext.LatticeOriginVoxels = Origin;
|
||||||
|
DiagnosticStack.PrepareChunk(DiagnosticContext);
|
||||||
|
DiagnosticStackVerdict = DiagnosticStack.ClassifyBoxAttributed(
|
||||||
|
ClassifierBox, DiagnosticContext,
|
||||||
|
DiagnosticSolidKiller, DiagnosticAirKiller);
|
||||||
|
DiagnosticStack.DiagnoseBox(
|
||||||
|
ClassifierBox, DiagnosticContext, Step, OpDiagnostics,
|
||||||
|
&DiagnosticStackVerdict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (WriterRouteDetails.IsEmpty())
|
||||||
|
{
|
||||||
|
const FIntVector SampleChunk(
|
||||||
|
VF_FloorDiv(static_cast<int32>(X), CHUNK_SIZE),
|
||||||
|
VF_FloorDiv(static_cast<int32>(Y), CHUNK_SIZE),
|
||||||
|
VF_FloorDiv(static_cast<int32>(Z), CHUNK_SIZE));
|
||||||
|
int32 SampleSlotTop = 0;
|
||||||
|
int32 SampleSlotBottom = 0;
|
||||||
|
int32 RecipeSeedAtSample = 0;
|
||||||
|
ECaveGeneratorType RecipeArchetypeAtSample = ECaveGeneratorType::TunnelNetwork;
|
||||||
|
FVoxelStrateArchetypeParams RecipeParamsAtSample;
|
||||||
|
FVoxelOpStackRecipe RecipeAtSample;
|
||||||
|
const bool bSampleHasSlot = World.StrateManager->GetStrateChunkZBounds(
|
||||||
|
SampleChunk.Z, SampleSlotTop, SampleSlotBottom);
|
||||||
|
const bool bSampleHasRecipe = World.StrateManager->GetRecipeForChunk(
|
||||||
|
SampleChunk, RecipeSeedAtSample, RecipeArchetypeAtSample,
|
||||||
|
RecipeParamsAtSample, RecipeAtSample);
|
||||||
|
const float PassageSdf = World.StrateManager->EvaluateModifierSDF(X, Y, Z);
|
||||||
|
const float MaxPassageCarve =
|
||||||
|
World.StrateManager->MaxPassageCarveFactorNearLattice(
|
||||||
|
ClassifierBox, Origin, Step);
|
||||||
|
const bool bPassageNear = World.StrateManager->AnyPassageNearLattice(
|
||||||
|
ClassifierBox, Origin, Step);
|
||||||
|
const bool bPassageAirPostNear = World.StrateManager->AnyPassageAirPostNearLattice(
|
||||||
|
ClassifierBox, Origin, Step, 8.0f, World.Generator->EdgeSealThickness);
|
||||||
|
const bool bLandingFloorNear =
|
||||||
|
World.StrateManager->AnyPassageLandingFloorNearLattice(
|
||||||
|
ClassifierBox, Origin, Step);
|
||||||
|
const bool bStructuralPostNear =
|
||||||
|
World.StrateManager->AnyPassageStructuralPostNearLattice(
|
||||||
|
ClassifierBox, Origin, Step);
|
||||||
|
WriterRouteDetails = FString::Printf(
|
||||||
|
TEXT("sample_chunk=(%d,%d,%d) gap=%d slot_bounds=%d:[%d..%d] "
|
||||||
|
"generator=%s operator_stack=%d recipe=%d world_radius=%.6g "
|
||||||
|
"passage_sdf=%.9g passage_max_carve=%.9g "
|
||||||
|
"passage_near=%d passage_air_post=%d landing_floor=%d structural_post=%d"),
|
||||||
|
SampleChunk.X, SampleChunk.Y, SampleChunk.Z,
|
||||||
|
World.StrateManager->IsGapChunk(SampleChunk) ? 1 : 0,
|
||||||
|
bSampleHasSlot ? 1 : 0, SampleSlotTop, SampleSlotBottom,
|
||||||
|
VF_GetStrateArchetypeName(
|
||||||
|
World.StrateManager->GetGeneratorTypeForChunk(SampleChunk)),
|
||||||
|
World.StrateManager->UsesOperatorStackForChunk(SampleChunk) ? 1 : 0,
|
||||||
|
bSampleHasRecipe ? 1 : 0, World.Generator->WorldRadiusVoxels,
|
||||||
|
PassageSdf, MaxPassageCarve, bPassageNear ? 1 : 0,
|
||||||
|
bPassageAirPostNear ? 1 : 0, bLandingFloorNear ? 1 : 0,
|
||||||
|
bStructuralPostNear ? 1 : 0);
|
||||||
|
}
|
||||||
|
float StackDensity = FLT_MAX;
|
||||||
|
FString StackAttribution = TEXT("stack diagnostic unavailable");
|
||||||
|
if (bDiagnosticStackBuilt)
|
||||||
|
{
|
||||||
|
StackDensity = DiagnosticStack.EvalMC(X, Y, Z);
|
||||||
|
FString BoundViolations;
|
||||||
|
FString SdfIntervalViolations;
|
||||||
|
FString OperatorIntervals;
|
||||||
|
for (const FVoxelOpStack::FOpBoxDiagnostic& OpDiagnostic : OpDiagnostics)
|
||||||
|
{
|
||||||
|
if (OpDiagnostic.ForcedVerdict != EVoxelTileClass::Mixed
|
||||||
|
|| OpDiagnostic.bHasDensityDelta
|
||||||
|
|| OpDiagnostic.bHasSdfValue)
|
||||||
|
{
|
||||||
|
if (!OperatorIntervals.IsEmpty()) { OperatorIntervals += TEXT("; "); }
|
||||||
|
OperatorIntervals += FString::Printf(
|
||||||
|
TEXT("%s(forced=%d,effect=%d,carve=%.6g,fill=%.6g,delta=[%.6g,%.6g],sdf_bound=[%.6g,%.6g],sdf_actual=[%.6g,%.6g])"),
|
||||||
|
*OpDiagnostic.Name,
|
||||||
|
static_cast<int32>(OpDiagnostic.ForcedVerdict),
|
||||||
|
static_cast<int32>(OpDiagnostic.Effect),
|
||||||
|
OpDiagnostic.MaxCarve, OpDiagnostic.MaxFill,
|
||||||
|
OpDiagnostic.ActualDensityDeltaMin,
|
||||||
|
OpDiagnostic.ActualDensityDeltaMax,
|
||||||
|
OpDiagnostic.SdfBoundMin, OpDiagnostic.SdfBoundMax,
|
||||||
|
OpDiagnostic.ActualSdfMin, OpDiagnostic.ActualSdfMax);
|
||||||
|
}
|
||||||
|
if (OpDiagnostic.bCarveBoundViolated
|
||||||
|
|| OpDiagnostic.bFillBoundViolated)
|
||||||
|
{
|
||||||
|
if (!BoundViolations.IsEmpty()) { BoundViolations += TEXT(", "); }
|
||||||
|
BoundViolations += FString::Printf(
|
||||||
|
TEXT("%s(carve=%d,fill=%d,delta=[%.6g,%.6g])"),
|
||||||
|
*OpDiagnostic.Name,
|
||||||
|
OpDiagnostic.bCarveBoundViolated ? 1 : 0,
|
||||||
|
OpDiagnostic.bFillBoundViolated ? 1 : 0,
|
||||||
|
OpDiagnostic.ActualDensityDeltaMin,
|
||||||
|
OpDiagnostic.ActualDensityDeltaMax);
|
||||||
|
}
|
||||||
|
if (OpDiagnostic.bHasSdfBound && OpDiagnostic.bHasSdfValue
|
||||||
|
&& (OpDiagnostic.ActualSdfMin < OpDiagnostic.SdfBoundMin - 1.0e-4f
|
||||||
|
|| OpDiagnostic.ActualSdfMax > OpDiagnostic.SdfBoundMax + 1.0e-4f))
|
||||||
|
{
|
||||||
|
if (!SdfIntervalViolations.IsEmpty())
|
||||||
|
{
|
||||||
|
SdfIntervalViolations += TEXT(", ");
|
||||||
|
}
|
||||||
|
SdfIntervalViolations += FString::Printf(
|
||||||
|
TEXT("%s(bound=[%.6g,%.6g],actual=[%.6g,%.6g])"),
|
||||||
|
*OpDiagnostic.Name, OpDiagnostic.SdfBoundMin,
|
||||||
|
OpDiagnostic.SdfBoundMax, OpDiagnostic.ActualSdfMin,
|
||||||
|
OpDiagnostic.ActualSdfMax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StackAttribution = FString::Printf(
|
||||||
|
TEXT("stack_verdict=%s solid_killer=%s air_killer=%s stack_density=%.9g "
|
||||||
|
"operator_bound_violations=[%s] sdf_interval_violations=[%s] "
|
||||||
|
"op_intervals=[%s] "
|
||||||
|
"refine_nodes=%u depth=%u exact_core_samples=%u exact_final_samples=%u "
|
||||||
|
"post_nodes=%u"),
|
||||||
|
DiagnosticStackVerdict == EVoxelTileClass::AllSolid ? TEXT("AllSolid")
|
||||||
|
: DiagnosticStackVerdict == EVoxelTileClass::AllAir ? TEXT("AllAir")
|
||||||
|
: TEXT("Mixed"),
|
||||||
|
DiagnosticStack.GetOpDebugName(DiagnosticSolidKiller),
|
||||||
|
DiagnosticStack.GetOpDebugName(DiagnosticAirKiller),
|
||||||
|
StackDensity,
|
||||||
|
BoundViolations.IsEmpty() ? TEXT("none") : *BoundViolations,
|
||||||
|
SdfIntervalViolations.IsEmpty() ? TEXT("none") : *SdfIntervalViolations,
|
||||||
|
OperatorIntervals.IsEmpty() ? TEXT("none") : *OperatorIntervals,
|
||||||
|
ClassifyStats.RefineNodes, ClassifyStats.MaxRefinementDepth,
|
||||||
|
ClassifyStats.ExactCoreSamples, ClassifyStats.ExactFinalSamples,
|
||||||
|
ClassifyStats.NeedsFinalFieldNodes);
|
||||||
|
}
|
||||||
|
Report.ViolationDetails.Add(FString::Printf(
|
||||||
|
TEXT("candidate %d box %d origin (%d,%d,%d) said %s; final density at "
|
||||||
|
"(%0.f,%0.f,%0.f) = %.9g; %s; %s"),
|
||||||
|
CandidateIndex, BoxIndex, Origin.X, Origin.Y, Origin.Z,
|
||||||
|
bClaimsSolid ? TEXT("AllSolid") : TEXT("AllAir"),
|
||||||
|
X, Y, Z, Density, *WriterRouteDetails, *StackAttribution));
|
||||||
if (Report.FirstViolation.IsEmpty())
|
if (Report.FirstViolation.IsEmpty())
|
||||||
{
|
{
|
||||||
Report.FirstViolation = FString::Printf(
|
Report.FirstViolation = FString::Printf(
|
||||||
@@ -801,6 +992,8 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
int32 TotalBoxAllAir = 0;
|
int32 TotalBoxAllAir = 0;
|
||||||
int32 TotalBoxProved = 0;
|
int32 TotalBoxProved = 0;
|
||||||
int64 TotalBoxCheckedVoxels = 0;
|
int64 TotalBoxCheckedVoxels = 0;
|
||||||
|
int64 TotalExactIntervalChecks = 0;
|
||||||
|
int64 TotalExactIntervalDisagreements = 0;
|
||||||
int32 TotalBoxViolations = 0;
|
int32 TotalBoxViolations = 0;
|
||||||
int32 NumTunnelLikeCandidates = 0;
|
int32 NumTunnelLikeCandidates = 0;
|
||||||
int32 NumCandidatesWithLiveDetail = 0;
|
int32 NumCandidatesWithLiveDetail = 0;
|
||||||
@@ -996,16 +1189,23 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FBoxVerdictReport BoxReport = VF_CheckRolledBoxVerdicts(
|
const FBoxVerdictReport BoxReport = VF_CheckRolledBoxVerdicts(
|
||||||
World, CandidateIndex, CandidateStrateIndex);
|
World, CandidateIndex, CandidateStrateIndex, Roll);
|
||||||
TotalBoxMixed += BoxReport.Mixed;
|
TotalBoxMixed += BoxReport.Mixed;
|
||||||
TotalBoxAllSolid += BoxReport.AllSolid;
|
TotalBoxAllSolid += BoxReport.AllSolid;
|
||||||
TotalBoxAllAir += BoxReport.AllAir;
|
TotalBoxAllAir += BoxReport.AllAir;
|
||||||
TotalBoxProved += BoxReport.Proved;
|
TotalBoxProved += BoxReport.Proved;
|
||||||
TotalBoxCheckedVoxels += BoxReport.CheckedVoxels;
|
TotalBoxCheckedVoxels += BoxReport.CheckedVoxels;
|
||||||
|
TotalExactIntervalChecks += BoxReport.ExactIntervalChecks;
|
||||||
|
TotalExactIntervalDisagreements += BoxReport.ExactIntervalDisagreements;
|
||||||
TotalBoxViolations += BoxReport.Violations;
|
TotalBoxViolations += BoxReport.Violations;
|
||||||
if (BoxReport.Violations > 0)
|
if (BoxReport.Violations > 0)
|
||||||
{
|
{
|
||||||
BoxViolationCandidates.Add(CandidateIndex);
|
BoxViolationCandidates.Add(CandidateIndex);
|
||||||
|
for (const FString& ViolationDetail : BoxReport.ViolationDetails)
|
||||||
|
{
|
||||||
|
AddInfo(FString::Printf(TEXT("ROLLED BOX-VERDICT COUNTEREXAMPLE: %s"),
|
||||||
|
*ViolationDetail));
|
||||||
|
}
|
||||||
if (FirstBoxViolation.IsEmpty())
|
if (FirstBoxViolation.IsEmpty())
|
||||||
{
|
{
|
||||||
FirstBoxViolation = BoxReport.FirstViolation;
|
FirstBoxViolation = BoxReport.FirstViolation;
|
||||||
@@ -1098,6 +1298,10 @@ bool FVoxelForgeComposerParameterRollTest::RunTest(const FString& Parameters)
|
|||||||
TotalBoxMixed, TotalBoxAllSolid, TotalBoxAllAir, TotalBoxProved,
|
TotalBoxMixed, TotalBoxAllSolid, TotalBoxAllAir, TotalBoxProved,
|
||||||
TotalBoxCheckedVoxels, TotalBoxViolations,
|
TotalBoxCheckedVoxels, TotalBoxViolations,
|
||||||
BoxViolationCandidates.Num() == 0 ? TEXT("none") : TEXT("see first violation below")));
|
BoxViolationCandidates.Num() == 0 ? TEXT("none") : TEXT("see first violation below")));
|
||||||
|
AddInfo(FString::Printf(
|
||||||
|
TEXT("Fine room-graph interval checks: %lld exact lattice leaves; %lld interval verdicts "
|
||||||
|
"disagreed with the exact child samples."),
|
||||||
|
TotalExactIntervalChecks, TotalExactIntervalDisagreements));
|
||||||
if (!BoxViolationCandidates.IsEmpty())
|
if (!BoxViolationCandidates.IsEmpty())
|
||||||
{
|
{
|
||||||
AddError(FString::Printf(
|
AddError(FString::Printf(
|
||||||
|
|||||||
@@ -6753,7 +6753,12 @@ namespace
|
|||||||
FVoxelBoxSdfInterval Own;
|
FVoxelBoxSdfInterval Own;
|
||||||
if (bUseExactLatticeWarp)
|
if (bUseExactLatticeWarp)
|
||||||
{
|
{
|
||||||
Own.Set(ExactSdfLower, FLT_MAX);
|
// Exact per-primitive scans are a tightening only. Their candidate-domain
|
||||||
|
// filters can miss a warped lattice query near a primitive, so retain the
|
||||||
|
// broad lower bound over the AABB that contains every warped sample. The
|
||||||
|
// minimum stays useful when both agree and remains safe when an exact scan is
|
||||||
|
// incomplete.
|
||||||
|
Own.Set(FMath::Min(ExactSdfLower, CheapSdfLower), FLT_MAX);
|
||||||
}
|
}
|
||||||
else if (!bAnyPrimitive)
|
else if (!bAnyPrimitive)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6328,6 +6328,34 @@ static EVoxelTileClass VF_ClassifyBoxRefined(const FVoxelOpStack& Stack,
|
|||||||
static_cast<float>(Box.Max.Z - Box.Min.Z));
|
static_cast<float>(Box.Max.Z - Box.Min.Z));
|
||||||
const bool bSmallFinalFieldBox = bNeedsFinalField
|
const bool bSmallFinalFieldBox = bNeedsFinalField
|
||||||
&& FinalFieldBoxExtent <= 8.0f;
|
&& FinalFieldBoxExtent <= 8.0f;
|
||||||
|
|
||||||
|
// A room-graph SDF interval can be conservative at the parent while a fine child still
|
||||||
|
// resolves uniformly through a state-aware SDF writer (for example the worm mask). Before
|
||||||
|
// using that fine interval to skip geometry, certify the exact MC lattice in the child.
|
||||||
|
// This runs only after a mixed parent has been refined to a small room-graph child; clear
|
||||||
|
// uniform roots and all non-room stacks keep the interval-only fast path.
|
||||||
|
const bool bNeedsExactRoomIntervalCheck = bHasRoomGeometry
|
||||||
|
&& Context.bUseLatticeProof && Context.bTightenWarpProof
|
||||||
|
&& !bNeedsFinalField && Whole != EVoxelTileClass::Mixed
|
||||||
|
&& Depth >= 3 && FinalFieldBoxExtent <= 8.0f;
|
||||||
|
if (bNeedsExactRoomIntervalCheck)
|
||||||
|
{
|
||||||
|
int32 ExactIX0 = 0, ExactIY0 = 0, ExactIZ0 = 0;
|
||||||
|
int32 ExactIX1 = 0, ExactIY1 = 0, ExactIZ1 = 0;
|
||||||
|
int64 ExactCount = 0;
|
||||||
|
if (GetLatticeBounds(Box, ExactIX0, ExactIY0, ExactIZ0,
|
||||||
|
ExactIX1, ExactIY1, ExactIZ1, ExactCount)
|
||||||
|
&& ExactCount <= 8192)
|
||||||
|
{
|
||||||
|
if (Stats) { ++Stats->ExactCoreIntervalChecks; }
|
||||||
|
const EVoxelTileClass Exact = ClassifyExactLatticeLeaf(Box, false);
|
||||||
|
if (Exact != Whole && Stats)
|
||||||
|
{
|
||||||
|
++Stats->ExactCoreIntervalDisagreements;
|
||||||
|
}
|
||||||
|
return Exact;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (bSmallFinalFieldBox)
|
if (bSmallFinalFieldBox)
|
||||||
{
|
{
|
||||||
int32 ExactIX0 = 0, ExactIY0 = 0, ExactIZ0 = 0;
|
int32 ExactIX0 = 0, ExactIY0 = 0, ExactIZ0 = 0;
|
||||||
|
|||||||
@@ -39,6 +39,8 @@
|
|||||||
#include "Materials/MaterialParameterCollection.h"
|
#include "Materials/MaterialParameterCollection.h"
|
||||||
#include "Kismet/KismetMaterialLibrary.h"
|
#include "Kismet/KismetMaterialLibrary.h"
|
||||||
#include "Engine/VolumeTexture.h"
|
#include "Engine/VolumeTexture.h"
|
||||||
|
#include "Engine/Engine.h"
|
||||||
|
#include "Camera/CameraActor.h"
|
||||||
#include "VoxelAtmosphereManager.h"
|
#include "VoxelAtmosphereManager.h"
|
||||||
#include "DrawDebugHelpers.h"
|
#include "DrawDebugHelpers.h"
|
||||||
#include "IImageWrapper.h"
|
#include "IImageWrapper.h"
|
||||||
@@ -1533,6 +1535,55 @@ void AVoxelWorld::BeginPlay()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32 TestRenderDistanceChunks = 0;
|
||||||
|
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.TestRenderDistanceChunks="),
|
||||||
|
TestRenderDistanceChunks)
|
||||||
|
&& TestRenderDistanceChunks > 0)
|
||||||
|
{
|
||||||
|
if (UVoxelSettings* TestSettings = DuplicateObject<UVoxelSettings>(Settings, this))
|
||||||
|
{
|
||||||
|
const int32 AuthoredRenderDistance = Settings->RenderDistanceChunks;
|
||||||
|
TestSettings->RenderDistanceChunks = TestRenderDistanceChunks;
|
||||||
|
Settings = TestSettings;
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeTestCeiling] transient_render_distance_chunks=%d authored=%d"),
|
||||||
|
TestRenderDistanceChunks, AuthoredRenderDistance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32 TestCeilingView = 0;
|
||||||
|
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingView="), TestCeilingView);
|
||||||
|
bTestCeilingViewEnabled = TestCeilingView != 0;
|
||||||
|
if (bTestCeilingViewEnabled)
|
||||||
|
{
|
||||||
|
TestCeilingCameraWorldPosition = FVector(10583.003176, 20477.525456, -38442.958976);
|
||||||
|
TestCeilingCameraRotation = FRotator(78.399901, 255.200004, 0.0);
|
||||||
|
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraX="),
|
||||||
|
TestCeilingCameraWorldPosition.X);
|
||||||
|
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraY="),
|
||||||
|
TestCeilingCameraWorldPosition.Y);
|
||||||
|
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraZ="),
|
||||||
|
TestCeilingCameraWorldPosition.Z);
|
||||||
|
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraPitch="),
|
||||||
|
TestCeilingCameraRotation.Pitch);
|
||||||
|
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCameraYaw="),
|
||||||
|
TestCeilingCameraRotation.Yaw);
|
||||||
|
FParse::Value(FCommandLine::Get(), TEXT("voxel.TestCeilingCaptureDelaySeconds="),
|
||||||
|
TestCeilingCaptureDelaySeconds);
|
||||||
|
TestCeilingCaptureDelaySeconds = FMath::Max(TestCeilingCaptureDelaySeconds, 1.0f);
|
||||||
|
TestCeilingViewBeginSeconds = FPlatformTime::Seconds();
|
||||||
|
bHeadlessStreamingTestCenterOverride = true;
|
||||||
|
HeadlessStreamingTestCenterVoxel = WorldToLocalVoxel(TestCeilingCameraWorldPosition);
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeTestCeiling] camera_world_cm=(%.3f,%.3f,%.3f) "
|
||||||
|
"camera_rotation=(%.3f,%.3f,%.3f) center_vox=(%.3f,%.3f,%.3f) capture_delay_s=%.1f"),
|
||||||
|
TestCeilingCameraWorldPosition.X, TestCeilingCameraWorldPosition.Y,
|
||||||
|
TestCeilingCameraWorldPosition.Z, TestCeilingCameraRotation.Pitch,
|
||||||
|
TestCeilingCameraRotation.Yaw, TestCeilingCameraRotation.Roll,
|
||||||
|
HeadlessStreamingTestCenterVoxel.X, HeadlessStreamingTestCenterVoxel.Y,
|
||||||
|
HeadlessStreamingTestCenterVoxel.Z, TestCeilingCaptureDelaySeconds);
|
||||||
|
}
|
||||||
|
|
||||||
// The owner's current DA_Settings is intentionally a one-strate surface layout, so it cannot
|
// The owner's current DA_Settings is intentionally a one-strate surface layout, so it cannot
|
||||||
// exercise the separate inter-strate traversal regression. The explicit headless crossing
|
// exercise the separate inter-strate traversal regression. The explicit headless crossing
|
||||||
// switch uses a transient copy of the authored four-definition corpus; no package is modified
|
// switch uses a transient copy of the authored four-definition corpus; no package is modified
|
||||||
@@ -3667,6 +3718,45 @@ void AVoxelWorld::Tick(float DeltaTime)
|
|||||||
ProcessUnloadQueue();
|
ProcessUnloadQueue();
|
||||||
MaybeFinishHeadlessStreamingTest();
|
MaybeFinishHeadlessStreamingTest();
|
||||||
|
|
||||||
|
if (bTestCeilingViewEnabled)
|
||||||
|
{
|
||||||
|
UWorld* World = GetWorld();
|
||||||
|
APlayerController* PC = World ? World->GetFirstPlayerController() : nullptr;
|
||||||
|
if (PC && !TestCeilingCamera.IsValid())
|
||||||
|
{
|
||||||
|
FActorSpawnParameters SpawnParams;
|
||||||
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
||||||
|
ACameraActor* Camera = World->SpawnActor<ACameraActor>(
|
||||||
|
ACameraActor::StaticClass(), TestCeilingCameraWorldPosition,
|
||||||
|
TestCeilingCameraRotation, SpawnParams);
|
||||||
|
if (IsValid(Camera))
|
||||||
|
{
|
||||||
|
TestCeilingCamera = Camera;
|
||||||
|
PC->SetViewTarget(Camera);
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeTestCeiling] camera_installed=1 applied_visible_tiles=%llu"),
|
||||||
|
static_cast<unsigned long long>(AppliedVisibleTileCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TestCeilingCamera.IsValid() && !bTestCeilingCaptureRequested
|
||||||
|
&& AppliedVisibleTileCount >= 16
|
||||||
|
&& FPlatformTime::Seconds() - TestCeilingViewBeginSeconds
|
||||||
|
>= static_cast<double>(TestCeilingCaptureDelaySeconds))
|
||||||
|
{
|
||||||
|
bTestCeilingCaptureRequested = true;
|
||||||
|
const bool bExecSucceeded = GEngine
|
||||||
|
&& GEngine->Exec(World, TEXT("HighResShot 1"));
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeTestCeiling] capture_requested=%d elapsed_s=%.3f "
|
||||||
|
"visible_tiles=%llu triangles=%llu"),
|
||||||
|
bExecSucceeded ? 1 : 0,
|
||||||
|
FPlatformTime::Seconds() - TestCeilingViewBeginSeconds,
|
||||||
|
static_cast<unsigned long long>(AppliedVisibleTileCount),
|
||||||
|
static_cast<unsigned long long>(AppliedTriangleCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if ENABLE_DRAW_DEBUG
|
#if ENABLE_DRAW_DEBUG
|
||||||
// Density-volume overlay (step 1a): cyan boxes for solid level-0 cells near the player.
|
// Density-volume overlay (step 1a): cyan boxes for solid level-0 cells near the player.
|
||||||
if (DensityVolume) { DensityVolume->DebugDraw(); }
|
if (DensityVolume) { DensityVolume->DebugDraw(); }
|
||||||
@@ -6068,7 +6158,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
/ static_cast<double>(WormEligibleSamples)
|
/ static_cast<double>(WormEligibleSamples)
|
||||||
: 0.0;
|
: 0.0;
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d sheet=%d aborted=%d empty=%d triangles=%d "
|
TEXT("[VoxelForgeTileProfile] tile=(%d,%d,%d) level=%d step=%d cells=%d sheet=%d aborted=%d empty=%d triangles=%d ceiling_triangles=%d "
|
||||||
"band=(%d,%d) "
|
"band=(%d,%d) "
|
||||||
"verdict=%d proof=%d air_proof=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f "
|
"verdict=%d proof=%d air_proof=%d classify=%.6f mesh=%.6f streams=%.6f seconds=%.6f "
|
||||||
"cache_build=%.6f evaluation=%.6f "
|
"cache_build=%.6f evaluation=%.6f "
|
||||||
@@ -6089,7 +6179,8 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
"worm_skip_rate=%.6f"),
|
"worm_skip_rate=%.6f"),
|
||||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, Step, Cells,
|
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, Step, Cells,
|
||||||
bSheetTile ? 1 : 0, Result.bAborted ? 1 : 0,
|
bSheetTile ? 1 : 0, Result.bAborted ? 1 : 0,
|
||||||
Result.bEmpty ? 1 : 0, Result.NumTriangles, BandChunkLo, BandChunkHi,
|
Result.bEmpty ? 1 : 0, Result.NumTriangles, Result.NumCeilingTriangles,
|
||||||
|
BandChunkLo, BandChunkHi,
|
||||||
ClassifyVerdict, Result.bSealedSolidProof ? 1 : 0,
|
ClassifyVerdict, Result.bSealedSolidProof ? 1 : 0,
|
||||||
Result.bOutOfLayoutAirProof ? 1 : 0,
|
Result.bOutOfLayoutAirProof ? 1 : 0,
|
||||||
ClassifySeconds, MeshSeconds, StreamSeconds,
|
ClassifySeconds, MeshSeconds, StreamSeconds,
|
||||||
@@ -6428,6 +6519,7 @@ void AVoxelWorld::GenerateTileResult(const FVoxelTileKey& Tile, const FIntVector
|
|||||||
// AND cap in one tile — entirely with the winner's material.)
|
// AND cap in one tile — entirely with the winner's material.)
|
||||||
const int32 NumTris = MeshData.Triangles.Num() / 3;
|
const int32 NumTris = MeshData.Triangles.Num() / 3;
|
||||||
Result.NumTriangles = NumTris;
|
Result.NumTriangles = NumTris;
|
||||||
|
Result.NumCeilingTriangles = MeshData.NumCeilingTriangles;
|
||||||
Result.bHasCeilingTris = MeshData.NumCeilingTriangles > 0;
|
Result.bHasCeilingTris = MeshData.NumCeilingTriangles > 0;
|
||||||
Result.bHasGroundTris = NumTris > MeshData.NumCeilingTriangles;
|
Result.bHasGroundTris = NumTris > MeshData.NumCeilingTriangles;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -504,6 +504,8 @@ struct FVoxelTileClassificationStats
|
|||||||
uint32 MaxRefinementDepth = 0;
|
uint32 MaxRefinementDepth = 0;
|
||||||
uint32 ExactCoreSamples = 0;
|
uint32 ExactCoreSamples = 0;
|
||||||
uint32 ExactFinalSamples = 0;
|
uint32 ExactFinalSamples = 0;
|
||||||
|
uint32 ExactCoreIntervalChecks = 0;
|
||||||
|
uint32 ExactCoreIntervalDisagreements = 0;
|
||||||
uint32 ExactCoreCacheHits = 0;
|
uint32 ExactCoreCacheHits = 0;
|
||||||
uint32 ExactFinalCacheHits = 0;
|
uint32 ExactFinalCacheHits = 0;
|
||||||
uint32 ExactCoreLeaves = 0;
|
uint32 ExactCoreLeaves = 0;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class UVolumeTexture;
|
|||||||
class UMaterialInterface;
|
class UMaterialInterface;
|
||||||
class UMaterialInstanceDynamic;
|
class UMaterialInstanceDynamic;
|
||||||
class FScopedGenerationPause;
|
class FScopedGenerationPause;
|
||||||
|
class ACameraActor;
|
||||||
namespace RealtimeMesh { struct FRealtimeMeshStreamSet; } // T1.f — worker-built geometry buffers
|
namespace RealtimeMesh { struct FRealtimeMeshStreamSet; } // T1.f — worker-built geometry buffers
|
||||||
|
|
||||||
/** Per-request cancellation state. The game thread flips the flag when a pending tile leaves the
|
/** Per-request cancellation state. The game thread flips the flag when a pending tile leaves the
|
||||||
@@ -107,7 +108,7 @@ struct FChunkResult
|
|||||||
uint64 GenerationStartCycles = 0;
|
uint64 GenerationStartCycles = 0;
|
||||||
uint64 GenerationEndCycles = 0;
|
uint64 GenerationEndCycles = 0;
|
||||||
uint64 ApplyStartCycles = 0;
|
uint64 ApplyStartCycles = 0;
|
||||||
int32 ClassifyVerdict = -1; double ClassifySeconds = 0.0; double MeshSeconds = 0.0; double StreamSeconds = 0.0; int32 NumTriangles = 0;
|
int32 ClassifyVerdict = -1; double ClassifySeconds = 0.0; double MeshSeconds = 0.0; double StreamSeconds = 0.0; int32 NumTriangles = 0; int32 NumCeilingTriangles = 0;
|
||||||
bool bSealedSolidProof = false; // true when the cheap gap/seal proof skipped meshing
|
bool bSealedSolidProof = false; // true when the cheap gap/seal proof skipped meshing
|
||||||
bool bOutOfLayoutAirProof = false; // true when the exact constant-air guard skipped meshing
|
bool bOutOfLayoutAirProof = false; // true when the exact constant-air guard skipped meshing
|
||||||
// Clean, low-overhead work counters. These are carried with the result so the worker can
|
// Clean, low-overhead work counters. These are carried with the result so the worker can
|
||||||
@@ -1011,6 +1012,16 @@ public:
|
|||||||
double HeadlessStreamingTestBeginSeconds = 0.0;
|
double HeadlessStreamingTestBeginSeconds = 0.0;
|
||||||
double HeadlessStreamingTestLastElapsedSeconds = 0.0;
|
double HeadlessStreamingTestLastElapsedSeconds = 0.0;
|
||||||
|
|
||||||
|
// Transient owner-view render hook. Enabled only by explicit voxel.TestCeilingView arguments;
|
||||||
|
// the camera and smaller test radius are held in memory and never saved to a package.
|
||||||
|
bool bTestCeilingViewEnabled = false;
|
||||||
|
bool bTestCeilingCaptureRequested = false;
|
||||||
|
TWeakObjectPtr<ACameraActor> TestCeilingCamera;
|
||||||
|
FVector TestCeilingCameraWorldPosition = FVector::ZeroVector;
|
||||||
|
FRotator TestCeilingCameraRotation = FRotator::ZeroRotator;
|
||||||
|
double TestCeilingViewBeginSeconds = 0.0;
|
||||||
|
float TestCeilingCaptureDelaySeconds = 18.0f;
|
||||||
|
|
||||||
// Explicit high-speed collision-gate repro. The streamer stays centred at the launch tile while
|
// Explicit high-speed collision-gate repro. The streamer stays centred at the launch tile while
|
||||||
// the character uses ordinary swept movement across the still-pending desired set. The test
|
// the character uses ordinary swept movement across the still-pending desired set. The test
|
||||||
// distinguishes a gate hold from a full horizontal stop so a fall-only gate can be validated.
|
// distinguishes a gate hold from a full horizontal stop so a fall-only gate can be validated.
|
||||||
|
|||||||
+61
-11
@@ -28,6 +28,7 @@ $EngineRoot = 'E:\Program Files\Epic Games\UE_5.7'
|
|||||||
$EditorExe = Join-Path $EngineRoot 'Engine\Binaries\Win64\UnrealEditor-Cmd.exe'
|
$EditorExe = Join-Path $EngineRoot 'Engine\Binaries\Win64\UnrealEditor-Cmd.exe'
|
||||||
$UbtDll = Join-Path $EngineRoot 'Engine\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.dll'
|
$UbtDll = Join-Path $EngineRoot 'Engine\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.dll'
|
||||||
$LaunchTimeoutSeconds = 1800
|
$LaunchTimeoutSeconds = 1800
|
||||||
|
$UnrealPrivateMemoryLimitBytes = 6GB
|
||||||
# The VoxelForge category includes two late, several-minute op-stack sweeps. They remain in
|
# The VoxelForge category includes two late, several-minute op-stack sweeps. They remain in
|
||||||
# the suite; when the serialized aggregate launch reaches this 30-minute guard, validate those
|
# the suite; when the serialized aggregate launch reaches this 30-minute guard, validate those
|
||||||
# named tests with -TestFilter so every test still runs under the standing launch limit.
|
# named tests with -TestFilter so every test still runs under the standing launch limit.
|
||||||
@@ -264,17 +265,48 @@ function Get-NewCrashes($Before, [datetime]$StartedUtc) {
|
|||||||
return @($new)
|
return @($new)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Wait-OwnedProcess($Process, [int]$TimeoutSeconds) {
|
function Wait-OwnedProcess($Process, [int]$TimeoutSeconds, [switch]$EnforceMemoryLimit) {
|
||||||
$finished = $Process.WaitForExit($TimeoutSeconds * 1000)
|
$watch = [Diagnostics.Stopwatch]::StartNew()
|
||||||
if ($finished) {
|
$peakPrivateBytes = 0L
|
||||||
[void]$Process.WaitForExit()
|
while ($watch.Elapsed.TotalSeconds -lt $TimeoutSeconds) {
|
||||||
return [ordered]@{ timed_out = $false; exit_code = [int]$Process.ExitCode }
|
if ($Process.HasExited) {
|
||||||
|
[void]$Process.WaitForExit()
|
||||||
|
return [ordered]@{
|
||||||
|
timed_out = $false
|
||||||
|
memory_limit_hit = $false
|
||||||
|
peak_private_bytes = $peakPrivateBytes
|
||||||
|
exit_code = [int]$Process.ExitCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($EnforceMemoryLimit) {
|
||||||
|
$owned = Get-Process -Id ([int]$Process.Id) -ErrorAction SilentlyContinue
|
||||||
|
if ($null -ne $owned) {
|
||||||
|
$peakPrivateBytes = [Math]::Max($peakPrivateBytes, [int64]$owned.PrivateMemorySize64)
|
||||||
|
if ([int64]$owned.PrivateMemorySize64 -gt $UnrealPrivateMemoryLimitBytes) {
|
||||||
|
# This PID came from our Start-Process call. Do not use a name-wide kill here.
|
||||||
|
try { Stop-Process -Id ([int]$Process.Id) -Force -ErrorAction Stop } catch { }
|
||||||
|
try { [void]$Process.WaitForExit(5000) } catch { }
|
||||||
|
return [ordered]@{
|
||||||
|
timed_out = $false
|
||||||
|
memory_limit_hit = $true
|
||||||
|
peak_private_bytes = $peakPrivateBytes
|
||||||
|
exit_code = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Start-Sleep -Milliseconds 1000
|
||||||
}
|
}
|
||||||
# This PID came from our Start-Process call. Never use a name-based kill or a broad process
|
# This PID came from our Start-Process call. Never use a name-based kill or a broad process
|
||||||
# query here: an owner's editor may be unrelated and must remain untouched.
|
# query here: an owner's editor may be unrelated and must remain untouched.
|
||||||
try { Stop-Process -Id ([int]$Process.Id) -Force -ErrorAction Stop } catch { }
|
try { Stop-Process -Id ([int]$Process.Id) -Force -ErrorAction Stop } catch { }
|
||||||
try { [void]$Process.WaitForExit(5000) } catch { }
|
try { [void]$Process.WaitForExit(5000) } catch { }
|
||||||
return [ordered]@{ timed_out = $true; exit_code = $null }
|
return [ordered]@{
|
||||||
|
timed_out = $true
|
||||||
|
memory_limit_hit = $false
|
||||||
|
peak_private_bytes = $peakPrivateBytes
|
||||||
|
exit_code = $null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPath,
|
function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPath,
|
||||||
@@ -293,6 +325,8 @@ function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPa
|
|||||||
ended_utc = $null
|
ended_utc = $null
|
||||||
exit_code = $null
|
exit_code = $null
|
||||||
timed_out = $false
|
timed_out = $false
|
||||||
|
memory_limit_hit = $false
|
||||||
|
peak_private_bytes = 0L
|
||||||
crash_reporters_reaped = @()
|
crash_reporters_reaped = @()
|
||||||
new_crashes = @()
|
new_crashes = @()
|
||||||
log = $LogPath
|
log = $LogPath
|
||||||
@@ -309,12 +343,22 @@ function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPa
|
|||||||
# -LocalDataCachePath are supplied. Keep this child process inside the run directory.
|
# -LocalDataCachePath are supplied. Keep this child process inside the run directory.
|
||||||
$env:LOCALAPPDATA = $isolatedLocalAppData
|
$env:LOCALAPPDATA = $isolatedLocalAppData
|
||||||
$process = Start-Process -FilePath $EditorExe -ArgumentList $Arguments -WorkingDirectory $HostRoot `
|
$process = Start-Process -FilePath $EditorExe -ArgumentList $Arguments -WorkingDirectory $HostRoot `
|
||||||
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath -WindowStyle Hidden -PassThru
|
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath -WindowStyle Hidden `
|
||||||
|
-PassThru
|
||||||
|
try { $process.PriorityClass = [Diagnostics.ProcessPriorityClass]::BelowNormal }
|
||||||
|
catch {
|
||||||
|
try { Stop-Process -Id ([int]$process.Id) -Force -ErrorAction Stop } catch { }
|
||||||
|
throw "Could not lower owned Unreal PID $($process.Id) to BelowNormal: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
$ledger.pid = [int]$process.Id
|
$ledger.pid = [int]$process.Id
|
||||||
$wait = Wait-OwnedProcess $process $LaunchTimeoutSeconds
|
$wait = Wait-OwnedProcess $process $LaunchTimeoutSeconds -EnforceMemoryLimit
|
||||||
$ledger.timed_out = [bool]$wait.timed_out
|
$ledger.timed_out = [bool]$wait.timed_out
|
||||||
|
$ledger.memory_limit_hit = [bool]$wait.memory_limit_hit
|
||||||
|
$ledger.peak_private_bytes = [int64]$wait.peak_private_bytes
|
||||||
$ledger.exit_code = $wait.exit_code
|
$ledger.exit_code = $wait.exit_code
|
||||||
$ledger.status = if ($ledger.timed_out) { 'timeout' } elseif ($ledger.exit_code -eq 0) { 'passed' } else { 'failed' }
|
$ledger.status = if ($ledger.timed_out) { 'timeout' }
|
||||||
|
elseif ($ledger.memory_limit_hit) { 'memory_limit' }
|
||||||
|
elseif ($ledger.exit_code -eq 0) { 'passed' } else { 'failed' }
|
||||||
} catch {
|
} catch {
|
||||||
$ledger.status = 'launch_error'
|
$ledger.status = 'launch_error'
|
||||||
$ledger.error = $_.Exception.Message
|
$ledger.error = $_.Exception.Message
|
||||||
@@ -356,7 +400,13 @@ function Invoke-BuildProcess([string[]]$Arguments, [string]$LogPath, [string]$Er
|
|||||||
$env:DOTNET_NOLOGO = '1'
|
$env:DOTNET_NOLOGO = '1'
|
||||||
$env:LOCALAPPDATA = $isolatedLocalAppData
|
$env:LOCALAPPDATA = $isolatedLocalAppData
|
||||||
$process = Start-Process -FilePath $dotnet -ArgumentList $Arguments -WorkingDirectory $PluginRoot `
|
$process = Start-Process -FilePath $dotnet -ArgumentList $Arguments -WorkingDirectory $PluginRoot `
|
||||||
-RedirectStandardOutput $StdoutPath -RedirectStandardError $ErrorPath -WindowStyle Hidden -PassThru
|
-RedirectStandardOutput $StdoutPath -RedirectStandardError $ErrorPath -WindowStyle Hidden `
|
||||||
|
-PassThru
|
||||||
|
try { $process.PriorityClass = [Diagnostics.ProcessPriorityClass]::BelowNormal }
|
||||||
|
catch {
|
||||||
|
try { Stop-Process -Id ([int]$process.Id) -Force -ErrorAction Stop } catch { }
|
||||||
|
throw "Could not lower owned build PID $($process.Id) to BelowNormal: $($_.Exception.Message)"
|
||||||
|
}
|
||||||
$wait = Wait-OwnedProcess $process $LaunchTimeoutSeconds
|
$wait = Wait-OwnedProcess $process $LaunchTimeoutSeconds
|
||||||
} finally {
|
} finally {
|
||||||
if ($null -eq $oldDotnetCliHome) { Remove-Item Env:DOTNET_CLI_HOME -ErrorAction SilentlyContinue } else { $env:DOTNET_CLI_HOME = $oldDotnetCliHome }
|
if ($null -eq $oldDotnetCliHome) { Remove-Item Env:DOTNET_CLI_HOME -ErrorAction SilentlyContinue } else { $env:DOTNET_CLI_HOME = $oldDotnetCliHome }
|
||||||
@@ -894,7 +944,7 @@ try {
|
|||||||
$buildStdout = Join-Path $RunRoot 'Logs\build.stdout.log'
|
$buildStdout = Join-Path $RunRoot 'Logs\build.stdout.log'
|
||||||
$buildError = Join-Path $RunRoot 'Logs\build.err.log'
|
$buildError = Join-Path $RunRoot 'Logs\build.err.log'
|
||||||
$buildArgs = @(('"{0}"' -f $UbtDll), 'UnrealEditor', 'Win64', 'Development', ('-Project="{0}"' -f $HostProject),
|
$buildArgs = @(('"{0}"' -f $UbtDll), 'UnrealEditor', 'Win64', 'Development', ('-Project="{0}"' -f $HostProject),
|
||||||
'-WaitMutex', '-FromMsBuild', '-architecture=x64', '-NoUBA', '-MaxParallelActions=4',
|
'-WaitMutex', '-FromMsBuild', '-architecture=x64', '-NoUBA', '-MaxParallelActions=2',
|
||||||
('-Log="{0}"' -f $buildLog))
|
('-Log="{0}"' -f $buildLog))
|
||||||
$BuildRecord = Invoke-BuildProcess $buildArgs $buildLog $buildError $buildStdout
|
$BuildRecord = Invoke-BuildProcess $buildArgs $buildLog $buildError $buildStdout
|
||||||
if ($BuildRecord.status -ne 'passed') { Add-Failure "staged build $($BuildRecord.status)" }
|
if ($BuildRecord.status -ne 'passed') { Add-Failure "staged build $($BuildRecord.status)" }
|
||||||
|
|||||||
Reference in new issue
Block a user