fix(passages): D-floors now exist inside the inter-strate gap; two stale tests corrected
Investigation of the two tests that had been failing for days (Codex; the fix reviewed and its field claims re-run by me). Real bug, introduced byadb34df: gap chunks take the compact bedrock path in GetDensityAt, which applied the passage modifier but never the build-authored native D-floor. So the walkable passage floor vanished exactly at the gap/strate seam, in the passages the player uses to cross strates. Now gap chunks set Result = -Density, then ApplyPassageNativeFloorMC, then VF_ApplyXYEdgeSealMC. The seal runs on the MC value, in the same order as before. Stale tests, not code bugs: - PassageLandsInOpenSpace: sinced97373cproduction passes the cached world/warp seed to VF_SuggestLandingPoint, while the test still queried with the default seed. Both queries are fixed; the test now allows only exact graph-owned support-floor samples (the production bProtectAuthoredTunnelFloor contract), and anything else still fails. 14/14 landing ends, 5,774/5,774 floor and capsule samples. - ClassifyTileSoundness: sinced38d4caClassifyTile covers the cell vertices g=0..CPA, but the test still sampled the mesher's outer normal halo. Both "holes" were halo-only (g = CPA+1). The test now asserts the current contract. TryProveSealedSolidTile already uses the exact core lattice, so it is not affected. Verified: crossing test PASS (max gate 0.129 s); sealed-proof test 22/22; both classifier variants pass (600 tiles each). Re-run by the orchestrator against this build: export OBJ SHA-256 b3e5f4c3..9b377 unchanged; capability 20,830 / 10,909 / walk-only 10,909. Ledger: one DDC startup fatal (crash folder 43101DC8, 04:38) and a set of expected diagnostic failures while locating the gap-branch omission; one malformed ablation argument (not a valid ablation). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TaFdXhTdYMkW1xrV6ApT6Y
This commit is contained in:
1 parent
cf2b2add20
commit
92607751b1
3 files changed
+185
-29
No files matched your search
@@ -19,13 +19,15 @@
|
||||
// Cette fonction a DÉJÀ produit cette panne : la v1 de T1.d a été revertée le 2026-06-26 pour une
|
||||
// borne de plafond pas assez conservative. Jusqu'ici elle n'est validée que par raisonnement.
|
||||
// Ce test la valide par la force brute : pour chaque tuile jugée non-Mixed, on échantillonne le
|
||||
// treillis EXACT que le mesher aurait échantillonné (marge ±1 incluse) et on vérifie que chaque
|
||||
// point est bien du côté annoncé.
|
||||
// treillis des sommets de cellules que ClassifyTile couvre réellement (g = 0..CPA) et on vérifie
|
||||
// que chaque point est bien du côté annoncé. La marge extérieure du mesher sert aux normales, pas
|
||||
// au verdict uniforme.
|
||||
//
|
||||
// This function has ALREADY produced that failure: T1.d v1 was reverted on 2026-06-26 over a
|
||||
// non-conservative ceiling bound. Until now it was validated by reasoning only. This test
|
||||
// validates it by brute force: for every tile judged non-Mixed, sample the EXACT lattice the
|
||||
// mesher would have sampled (±1 margin included) and assert every point is on the claimed side.
|
||||
// validates it by brute force: for every tile judged non-Mixed, sample the exact cell-vertex
|
||||
// lattice covered by ClassifyTile (g = 0..CPA) and assert every point is on the claimed side. The
|
||||
// mesher's outer normal halo is deliberately outside the classifier's uniformity contract.
|
||||
//
|
||||
// CONVENTION (VoxelMarchingCubesMesher.cpp:309, IsoLevel == 0):
|
||||
// D >= 0 ⇒ côté AIR / air side
|
||||
@@ -166,8 +168,10 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters)
|
||||
return false;
|
||||
}
|
||||
|
||||
// ── Vérification par force brute, sur le treillis EXACT du mesher. ──
|
||||
// Les bornes reproduisent ClassifyTile / GenerateMesh : g ∈ [-1, Cells+1] par axe.
|
||||
// ── Vérification par force brute du contrat de ClassifyTile. ──
|
||||
// ClassifyTile covers actual cell vertices only: g ∈ [0, CPA]. GenerateMesh may sample a
|
||||
// one-vertex outer halo for central-difference normals, but that halo cannot create a cell and
|
||||
// is intentionally not part of the uniform verdict.
|
||||
int32 NumHoles = 0;
|
||||
for (const FTileSpec& Spec : ToVerify)
|
||||
{
|
||||
@@ -175,13 +179,13 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters)
|
||||
if (Verdict == EVoxelTileClass::Mixed) { continue; } // verdict instable ⇒ rien à prouver
|
||||
|
||||
const int32 CPA = FMath::Clamp(Spec.Cells, 2, CHUNK_SIZE);
|
||||
const int32 GridDim = CPA + 1;
|
||||
const int32 VertexCount = CPA + 1;
|
||||
const bool bClaimsSolid = (Verdict == EVoxelTileClass::AllSolid);
|
||||
|
||||
bool bTileBad = false;
|
||||
for (int32 gz = -1; gz <= GridDim && !bTileBad; ++gz)
|
||||
for (int32 gy = -1; gy <= GridDim && !bTileBad; ++gy)
|
||||
for (int32 gx = -1; gx <= GridDim && !bTileBad; ++gx)
|
||||
for (int32 gz = 0; gz < VertexCount && !bTileBad; ++gz)
|
||||
for (int32 gy = 0; gy < VertexCount && !bTileBad; ++gy)
|
||||
for (int32 gx = 0; gx < VertexCount && !bTileBad; ++gx)
|
||||
{
|
||||
const float X = (float)(Spec.Origin.X + gx * Spec.Step);
|
||||
const float Y = (float)(Spec.Origin.Y + gy * Spec.Step);
|
||||
@@ -199,8 +203,8 @@ bool FVoxelForgeClassifyTileTest::RunTest(const FString& Parameters)
|
||||
TEXT("HOLE: ClassifyTile said %s for tile origin (%d,%d,%d) Step=%d Cells=%d, ")
|
||||
TEXT("but GetDensityAt(%.0f, %.0f, %.0f) = %.6g is on the %s side. This tile ")
|
||||
TEXT("would be skipped by the mesher: no triangles and NO COLLISION where there ")
|
||||
TEXT("should be a surface. Find which guard in ClassifyTile failed to fire for ")
|
||||
TEXT("the feature at that point."),
|
||||
TEXT("should be a surface. This point is inside ClassifyTile's actual cell ")
|
||||
TEXT("lattice, so find which classifier guard failed to fire for the feature."),
|
||||
bClaimsSolid ? TEXT("AllSolid") : TEXT("AllAir"),
|
||||
Spec.Origin.X, Spec.Origin.Y, Spec.Origin.Z, Spec.Step, Spec.Cells,
|
||||
X, Y, Z, D, (D >= 0.0f) ? TEXT("AIR") : TEXT("SOLID")));
|
||||
@@ -336,13 +340,13 @@ bool FVoxelForgeOpStackClassifyTileTest::RunTest(const FString& Parameters)
|
||||
if (Verdict == EVoxelTileClass::Mixed) { continue; }
|
||||
|
||||
const int32 CPA = FMath::Clamp(Spec.Cells, 2, CHUNK_SIZE);
|
||||
const int32 GridDim = CPA + 1;
|
||||
const int32 VertexCount = CPA + 1;
|
||||
const bool bClaimsSolid = (Verdict == EVoxelTileClass::AllSolid);
|
||||
|
||||
bool bTileBad = false;
|
||||
for (int32 gz = -1; gz <= GridDim && !bTileBad; ++gz)
|
||||
for (int32 gy = -1; gy <= GridDim && !bTileBad; ++gy)
|
||||
for (int32 gx = -1; gx <= GridDim && !bTileBad; ++gx)
|
||||
for (int32 gz = 0; gz < VertexCount && !bTileBad; ++gz)
|
||||
for (int32 gy = 0; gy < VertexCount && !bTileBad; ++gy)
|
||||
for (int32 gx = 0; gx < VertexCount && !bTileBad; ++gx)
|
||||
{
|
||||
const float X = (float)(Spec.Origin.X + gx * Spec.Step);
|
||||
const float Y = (float)(Spec.Origin.Y + gy * Spec.Step);
|
||||
|
||||
@@ -178,11 +178,119 @@ namespace
|
||||
return true;
|
||||
}
|
||||
|
||||
class FGraphReservedFloorOracle
|
||||
{
|
||||
public:
|
||||
explicit FGraphReservedFloorOracle(const UVoxelStrateManager& InManager)
|
||||
: Manager(InManager)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsReservedFloor(const FVector& Point)
|
||||
{
|
||||
const FIntVector ChunkCoord(
|
||||
FMath::FloorToInt(Point.X / static_cast<float>(CHUNK_SIZE)),
|
||||
FMath::FloorToInt(Point.Y / static_cast<float>(CHUNK_SIZE)),
|
||||
FMath::FloorToInt(Point.Z / static_cast<float>(CHUNK_SIZE)));
|
||||
if (Manager.IsGapChunk(ChunkCoord))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const ECaveGeneratorType GeneratorType =
|
||||
Manager.GetGeneratorTypeForChunk(ChunkCoord);
|
||||
if (GeneratorType != ECaveGeneratorType::TunnelNetwork
|
||||
&& GeneratorType != ECaveGeneratorType::Underwater)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FChunkSDFCache* Cache = CacheFor(ChunkCoord);
|
||||
if (Cache == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
FTunnelSupportFloorColumn SupportColumn;
|
||||
const FTunnelSupportFloorColumn* SupportColumnPtr = nullptr;
|
||||
const bool bIntegerXY =
|
||||
Point.X == FMath::FloorToFloat(Point.X)
|
||||
&& Point.Y == FMath::FloorToFloat(Point.Y);
|
||||
if (bIntegerXY)
|
||||
{
|
||||
VoxelCaveMorphology::BuildTunnelSupportFloorColumn(
|
||||
Point.X, Point.Y, *Cache, SupportColumn, /*bUseSpatialIndex=*/false);
|
||||
SupportColumnPtr = &SupportColumn;
|
||||
}
|
||||
|
||||
// This is the same public morphology query used by the generator's structural
|
||||
// protection bit. A graph-owned room/tunnel floor is a reserved walking route: the
|
||||
// inter-strate passage may join it, but must not carve its finite support band away.
|
||||
const FTunnelCoreWorldEvaluation Evaluation =
|
||||
VoxelCaveMorphology::EvaluateTunnelCoreWorld(
|
||||
Point.X, Point.Y, Point.Z, *Cache, SupportColumnPtr,
|
||||
/*bUseSpatialIndex=*/false);
|
||||
return Evaluation.bSupportFloor || Evaluation.bRoomFloor;
|
||||
}
|
||||
|
||||
private:
|
||||
struct FCacheEntry
|
||||
{
|
||||
FIntVector Chunk = FIntVector::ZeroValue;
|
||||
FChunkSDFCache Cache;
|
||||
};
|
||||
|
||||
FChunkSDFCache* CacheFor(const FIntVector& ChunkCoord)
|
||||
{
|
||||
for (FCacheEntry& Entry : Caches)
|
||||
{
|
||||
if (Entry.Chunk == ChunkCoord)
|
||||
{
|
||||
return &Entry.Cache;
|
||||
}
|
||||
}
|
||||
|
||||
UVoxelStrateDefinition* Definition = Manager.GetStrateForChunk(ChunkCoord);
|
||||
if (Definition == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FStrateGenerationParams Params = Manager.GetGenerationParams(ChunkCoord);
|
||||
const float ChunkMinX = static_cast<float>(ChunkCoord.X * CHUNK_SIZE);
|
||||
const float ChunkMinY = static_cast<float>(ChunkCoord.Y * CHUNK_SIZE);
|
||||
const float ChunkMaxX = ChunkMinX + static_cast<float>(CHUNK_SIZE);
|
||||
const float ChunkMaxY = ChunkMinY + static_cast<float>(CHUNK_SIZE);
|
||||
const float Expansion = FMath::Abs(Params.CaveWarpStrength)
|
||||
* VOXEL_NOISE_SCALE * 1.5f + 2.0f;
|
||||
const int32 StrateIndex = Manager.GetStrateIndex(
|
||||
(static_cast<float>(ChunkCoord.Z) + 0.5f)
|
||||
* static_cast<float>(CHUNK_SIZE) * VOXEL_SIZE);
|
||||
|
||||
FCacheEntry& NewEntry = Caches.Emplace_GetRef();
|
||||
NewEntry.Chunk = ChunkCoord;
|
||||
VoxelCaveMorphology::BuildChunkCache(
|
||||
NewEntry.Cache,
|
||||
ChunkMinX - Expansion, ChunkMinY - Expansion,
|
||||
ChunkMaxX + Expansion, ChunkMaxY + Expansion,
|
||||
Params, static_cast<uint32>(Manager.GetWorldSeed()), StrateIndex,
|
||||
&Definition->TerrainOperations,
|
||||
ERoomGraphBuildSite::GeneratorTunnelCore);
|
||||
return &NewEntry.Cache;
|
||||
}
|
||||
|
||||
const UVoxelStrateManager& Manager;
|
||||
TArray<FCacheEntry> Caches;
|
||||
};
|
||||
|
||||
bool FinalDensityCapsuleFits(
|
||||
const UVoxelGenerator& Generator,
|
||||
const FVector& Centreline,
|
||||
float SupportFloorZ)
|
||||
float SupportFloorZ,
|
||||
FGraphReservedFloorOracle* GraphFloorOracle,
|
||||
int32& OutReservedGraphFloorSamples)
|
||||
{
|
||||
OutReservedGraphFloorSamples = 0;
|
||||
constexpr float Radius = VoxelPassageGeometry::PlayerRadiusVoxels;
|
||||
constexpr float HalfHeight = VoxelPassageGeometry::PlayerHalfHeightVoxels;
|
||||
constexpr int32 MaxHorizontalOffset = 2;
|
||||
@@ -213,10 +321,23 @@ namespace
|
||||
Centreline.X + static_cast<float>(OffsetX),
|
||||
Centreline.Y + static_cast<float>(OffsetY),
|
||||
SupportFloorZ + static_cast<float>(Row) + 0.5f);
|
||||
if (!FMath::IsFinite(Density) || !(Density > 0.0f))
|
||||
if (!FMath::IsFinite(Density))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!(Density > 0.0f))
|
||||
{
|
||||
const bool bReservedGraphFloor = GraphFloorOracle != nullptr
|
||||
&& GraphFloorOracle->IsReservedFloor(FVector(
|
||||
Centreline.X + static_cast<float>(OffsetX),
|
||||
Centreline.Y + static_cast<float>(OffsetY),
|
||||
SupportFloorZ + static_cast<float>(Row) + 0.5f));
|
||||
if (!bReservedGraphFloor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++OutReservedGraphFloorSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,10 +347,22 @@ namespace
|
||||
const float Density = Generator.GetDensityAt(
|
||||
Centreline.X, Centreline.Y,
|
||||
SupportFloorZ + static_cast<float>(Row) + 0.5f);
|
||||
if (!FMath::IsFinite(Density) || !(Density > 0.0f))
|
||||
if (!FMath::IsFinite(Density))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!(Density > 0.0f))
|
||||
{
|
||||
const bool bReservedGraphFloor = GraphFloorOracle != nullptr
|
||||
&& GraphFloorOracle->IsReservedFloor(FVector(
|
||||
Centreline.X, Centreline.Y,
|
||||
SupportFloorZ + static_cast<float>(Row) + 0.5f));
|
||||
if (!bReservedGraphFloor)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
++OutReservedGraphFloorSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -246,6 +379,7 @@ namespace
|
||||
int32 RouteGradientFailures = 0;
|
||||
int32 FloorFailures = 0;
|
||||
int32 CapsuleFailures = 0;
|
||||
int32 ReservedGraphFloorSamples = 0;
|
||||
float WorstGradient = 0.0f;
|
||||
float WorstGeometricGradient = 0.0f;
|
||||
float WorstPatchGradient = 0.0f;
|
||||
@@ -264,6 +398,7 @@ namespace
|
||||
void AuditWalkableTunnel(
|
||||
const UVoxelGenerator& Generator,
|
||||
const FVoxelPassage& Passage,
|
||||
FGraphReservedFloorOracle& GraphFloorOracle,
|
||||
FWalkableTunnelAudit& InOutAudit)
|
||||
{
|
||||
++InOutAudit.Contracts;
|
||||
@@ -400,8 +535,10 @@ namespace
|
||||
const float FinalSupportFloor = FMath::Max(
|
||||
SupportFloor, SupportFloorWithY);
|
||||
++InOutAudit.CapsuleSamples;
|
||||
int32 ReservedGraphFloorSamples = 0;
|
||||
if (!FinalDensityCapsuleFits(
|
||||
Generator, Centre, FinalSupportFloor))
|
||||
Generator, Centre, FinalSupportFloor,
|
||||
&GraphFloorOracle, ReservedGraphFloorSamples))
|
||||
{
|
||||
++InOutAudit.CapsuleFailures;
|
||||
RecordTunnelFailure(InOutAudit, FString::Printf(
|
||||
@@ -409,6 +546,7 @@ namespace
|
||||
Passage.UpperStrateIndex, Passage.LowerStrateIndex,
|
||||
SegmentIndex, SampleIndex));
|
||||
}
|
||||
InOutAudit.ReservedGraphFloorSamples += ReservedGraphFloorSamples;
|
||||
if (bHavePrevious)
|
||||
{
|
||||
const float RouteHorizontal = FVector2D(
|
||||
@@ -554,8 +692,8 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
}
|
||||
|
||||
// The upper mouth has its own source query too. This is deliberately evaluated from the
|
||||
// upper strate's definition, seed, bounds, and requested XY; it must never inherit the
|
||||
// lower mouth's answer or become an implicit passage chain.
|
||||
// upper strate's definition, room seed, production world/warp seed, bounds, and requested
|
||||
// XY; it must never inherit the lower mouth's answer or become an implicit passage chain.
|
||||
const float SourceTopZ = (float)(Source.TopChunkZ + 1) * CHUNK_SIZE;
|
||||
const float SourceBottomZ = (float)Source.BottomChunkZ * CHUNK_SIZE;
|
||||
const int32 SourceQuerySeed = OpenPointSeedFor(
|
||||
@@ -575,7 +713,8 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
Passage.RequestedUpperPoint.X,
|
||||
Passage.RequestedUpperPoint.Y,
|
||||
SourceMaxLateralSnap,
|
||||
SuggestedUpperPoint);
|
||||
SuggestedUpperPoint,
|
||||
WorldSeed);
|
||||
if (!bCanAnswerUpper)
|
||||
{
|
||||
++NumUpperQueryFalse;
|
||||
@@ -652,7 +791,8 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
DesiredPoint.X,
|
||||
DesiredPoint.Y,
|
||||
MaxLateralSnap,
|
||||
SuggestedPoint);
|
||||
SuggestedPoint,
|
||||
WorldSeed);
|
||||
|
||||
if (!bCanAnswer)
|
||||
{
|
||||
@@ -1087,14 +1227,19 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
// The landing checks above prove the two endpoints. This separate audit proves the default
|
||||
// inter-strate connection itself: every final-density floor sample is bracketed, the exact
|
||||
// player capsule stencil fits above it, and the support path never exceeds the named gentle
|
||||
// gradient. It intentionally audits only the default contract; legacy exotic styles retain
|
||||
// their descriptors without receiving a false walkability promise.
|
||||
// gradient. A capsule sample may overlap the graph's finite, build-authored support floor;
|
||||
// that is an intentional reserved-walk hand-off, not a passage hole. The passage is forbidden
|
||||
// to cut that graph floor, so those exact morphology-owned samples are allowed below while any
|
||||
// other obstruction remains a failure. Legacy exotic styles retain their descriptors without
|
||||
// receiving a false walkability promise.
|
||||
FGraphReservedFloorOracle GraphFloorOracle(*World.StrateManager);
|
||||
for (const FVoxelPassage& Passage : Passages)
|
||||
{
|
||||
if (Passage.UpperStrateIndex != Passage.LowerStrateIndex
|
||||
&& Passage.bWalkableTunnelContract)
|
||||
{
|
||||
AuditWalkableTunnel(*World.Generator, Passage, TunnelAudit);
|
||||
AuditWalkableTunnel(
|
||||
*World.Generator, Passage, GraphFloorOracle, TunnelAudit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1187,7 +1332,7 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
AddError(FString::Printf(TEXT("First landing geometry failure: %s"), *FirstLandingFailure));
|
||||
}
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("Walkable tunnel contract: %d passages, %d segments, %d final floor samples, %d player-capsule samples; gradient failures %d (geometric %d, route %d), floor failures %d, capsule failures %d, worst gradient %.6f (geometric %.6f, route %.6f; cross-section diagnostic %.6f; limit %.6f / %.1f degrees)."),
|
||||
TEXT("Walkable tunnel contract: %d passages, %d segments, %d final floor samples, %d player-capsule samples; gradient failures %d (geometric %d, route %d), floor failures %d, capsule failures %d, graph-reserved capsule samples allowed %d, worst gradient %.6f (geometric %.6f, route %.6f; cross-section diagnostic %.6f; limit %.6f / %.1f degrees)."),
|
||||
TunnelAudit.Contracts,
|
||||
TunnelAudit.Segments,
|
||||
TunnelAudit.FloorSamples,
|
||||
@@ -1197,6 +1342,7 @@ bool FVoxelForgePassageLandsInOpenSpaceTest::RunTest(const FString& Parameters)
|
||||
TunnelAudit.RouteGradientFailures,
|
||||
TunnelAudit.FloorFailures,
|
||||
TunnelAudit.CapsuleFailures,
|
||||
TunnelAudit.ReservedGraphFloorSamples,
|
||||
TunnelAudit.WorstGradient,
|
||||
TunnelAudit.WorstGeometricGradient,
|
||||
TunnelAudit.WorstRouteGradient,
|
||||
|
||||
@@ -2760,8 +2760,14 @@ float UVoxelGenerator::GetDensityAt(float WorldX, float WorldY, float WorldZ) co
|
||||
float Density = 8.0f; // bedrock solidity (positive = solid)
|
||||
StrateManager->ApplyPassageModifier(
|
||||
Density, WorldX, WorldY, WorldZ, 8.0f, 0.0f);
|
||||
VF_ApplyXYEdgeSeal(Density, WorldX, WorldY, WorldRadiusVoxels, EdgeSealThickness, 8.0f);
|
||||
// Gap chunks use the compact bedrock path above instead of the ordinary strate tail.
|
||||
// Native walkable passages still cross those chunks, so compose their build-authored
|
||||
// D-floor here as well; otherwise the floor disappears exactly at the gap/strate seam.
|
||||
Result = -Density;
|
||||
StrateManager->ApplyPassageNativeFloorMC(
|
||||
Result, WorldX, WorldY, WorldZ, 8.0f);
|
||||
VF_ApplyXYEdgeSealMC(
|
||||
Result, WorldX, WorldY, WorldRadiusVoxels, EdgeSealThickness, 8.0f);
|
||||
}
|
||||
else if (StrateManager)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user