planner: four-strate owner plan spawns (L66); pinned 2-strate regression fixture
- Core caps each passage mouth reach to its own strate's usable content band (owner DA_Strate1 exports ReachMax=5000 owner voxels, which put the SW->FI lower mouth 79.5 m below FI, inside TN). - Unreal adapter: SW route search keeps the old 64 m candidates first, then a bounded single-mouth search with shorter lengths; logs candidate/rejection counts. - Internal-chain passage floor/support tweaks (only with >2 strates): ripple 1-2 4.78->2.90 cm, 2-3 3.57->0.10 cm. Interfaces still 0/3 walkable (59/3/2 unsupported). - verify.py: legacy tiers use Standalone/data/regression/owner_world_2strate_l61.json; new four-strate stage reads its expectations from owner_world.json (currently red on interfaces). - Unreal trace L14 vs HEAD, same assets (16-chunk FlatPlain): 12.20 vs 12.56 worker-s; no x2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
47148e0a0d
commit
5e2fc53d39
17 files changed
+745
-73
No files matched your search
@@ -246,7 +246,8 @@ std::uint64_t WorldAssemblyRoundedSqrt(std::uint64_t Value)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
|
bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
|
||||||
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum, std::string& Error)
|
VoxelForgeCore::WorldAssemblyStratumPlanV1& Stratum,
|
||||||
|
std::int64_t ArrivalRadiusQ, std::int64_t DepartureRadiusQ, std::string& Error)
|
||||||
{
|
{
|
||||||
using namespace VoxelForgeCore;
|
using namespace VoxelForgeCore;
|
||||||
constexpr std::int64_t PlayerRadiusQ = 348;
|
constexpr std::int64_t PlayerRadiusQ = 348;
|
||||||
@@ -264,9 +265,11 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
|
|||||||
struct FCandidate
|
struct FCandidate
|
||||||
{
|
{
|
||||||
std::int64_t Score = 0;
|
std::int64_t Score = 0;
|
||||||
|
std::uint64_t EndpointProximityCost = 0;
|
||||||
std::int32_t CenterX = 0;
|
std::int32_t CenterX = 0;
|
||||||
std::int32_t CenterY = 0;
|
std::int32_t CenterY = 0;
|
||||||
std::int32_t Direction = 0;
|
std::int32_t Direction = 0;
|
||||||
|
std::int32_t HalfLength = 128;
|
||||||
std::vector<WorldAssemblyRoutePointV1> Points;
|
std::vector<WorldAssemblyRoutePointV1> Points;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -289,11 +292,14 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
|
|||||||
}
|
}
|
||||||
std::vector<FCandidate> Candidates;
|
std::vector<FCandidate> Candidates;
|
||||||
constexpr std::int32_t WaypointSpacing = 8;
|
constexpr std::int32_t WaypointSpacing = 8;
|
||||||
|
std::uint64_t LegacyCandidatesVisited = 0;
|
||||||
|
std::uint64_t LegacyGradeRejected = 0;
|
||||||
for (std::int32_t CenterY = -256; CenterY <= 256; CenterY += 64)
|
for (std::int32_t CenterY = -256; CenterY <= 256; CenterY += 64)
|
||||||
for (std::int32_t CenterX = -256; CenterX <= 256; CenterX += 64)
|
for (std::int32_t CenterX = -256; CenterX <= 256; CenterX += 64)
|
||||||
for (std::int32_t DirectionIndex = 0;
|
for (std::int32_t DirectionIndex = 0;
|
||||||
DirectionIndex < static_cast<std::int32_t>(Directions.size()); ++DirectionIndex)
|
DirectionIndex < static_cast<std::int32_t>(Directions.size()); ++DirectionIndex)
|
||||||
{
|
{
|
||||||
|
++LegacyCandidatesVisited;
|
||||||
FCandidate Candidate;
|
FCandidate Candidate;
|
||||||
Candidate.CenterX = CenterX;
|
Candidate.CenterX = CenterX;
|
||||||
Candidate.CenterY = CenterY;
|
Candidate.CenterY = CenterY;
|
||||||
@@ -341,36 +347,223 @@ bool BuildFiniteSurfaceRoute(VoxelForgeCore::WorldAssemblyRecipeV1& Recipe,
|
|||||||
PreviousY = Y;
|
PreviousY = Y;
|
||||||
}
|
}
|
||||||
if (bGradePass) Candidates.push_back(std::move(Candidate));
|
if (bGradePass) Candidates.push_back(std::move(Candidate));
|
||||||
|
else ++LegacyGradeRejected;
|
||||||
}
|
}
|
||||||
std::stable_sort(Candidates.begin(), Candidates.end(), [](const FCandidate& A, const FCandidate& B)
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeSurfaceRouteCandidates] stage=legacy_64m visited=%llu rejected_grade=%llu eligible=%llu arrival_radius_q=%lld departure_radius_q=%lld"),
|
||||||
|
static_cast<unsigned long long>(LegacyCandidatesVisited),
|
||||||
|
static_cast<unsigned long long>(LegacyGradeRejected),
|
||||||
|
static_cast<unsigned long long>(Candidates.size()),
|
||||||
|
static_cast<long long>(ArrivalRadiusQ), static_cast<long long>(DepartureRadiusQ));
|
||||||
|
const auto TryCandidates = [&](const TCHAR* Stage)
|
||||||
{
|
{
|
||||||
if (A.Score != B.Score) return A.Score < B.Score;
|
std::stable_sort(Candidates.begin(), Candidates.end(), [](const FCandidate& A, const FCandidate& B)
|
||||||
if (A.CenterY != B.CenterY) return A.CenterY < B.CenterY;
|
|
||||||
if (A.CenterX != B.CenterX) return A.CenterX < B.CenterX;
|
|
||||||
return A.Direction < B.Direction;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const FCandidate& Candidate : Candidates)
|
|
||||||
{
|
|
||||||
Stratum.route = Candidate.Points;
|
|
||||||
std::int64_t LiftQ = 0;
|
|
||||||
constexpr std::int64_t RepairStepQ = 64;
|
|
||||||
constexpr std::int64_t MaximumRepairLiftQ = SupportDepthQ - RepairStepQ;
|
|
||||||
for (;;)
|
|
||||||
{
|
{
|
||||||
if (LiftQ > 0)
|
if (A.EndpointProximityCost != B.EndpointProximityCost)
|
||||||
|
return A.EndpointProximityCost < B.EndpointProximityCost;
|
||||||
|
if (A.Score != B.Score) return A.Score < B.Score;
|
||||||
|
if (A.CenterY != B.CenterY) return A.CenterY < B.CenterY;
|
||||||
|
if (A.CenterX != B.CenterX) return A.CenterX < B.CenterX;
|
||||||
|
return A.Direction < B.Direction;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::uint64_t SweepAttempts = 0;
|
||||||
|
std::uint64_t BestFailureCount = std::numeric_limits<std::uint64_t>::max();
|
||||||
|
WorldAssemblyRouteAuditResult BestAudit;
|
||||||
|
const FCandidate* BestCandidate = nullptr;
|
||||||
|
std::int64_t BestLiftQ = 0;
|
||||||
|
for (const FCandidate& Candidate : Candidates)
|
||||||
|
{
|
||||||
|
Stratum.route = Candidate.Points;
|
||||||
|
std::int64_t LiftQ = 0;
|
||||||
|
constexpr std::int64_t RepairStepQ = 64;
|
||||||
|
constexpr std::int64_t MaximumRepairLiftQ = SupportDepthQ - RepairStepQ;
|
||||||
|
for (;;)
|
||||||
{
|
{
|
||||||
for (std::size_t PointIndex = 0; PointIndex < Stratum.route.size(); ++PointIndex)
|
if (LiftQ > 0)
|
||||||
{
|
{
|
||||||
Stratum.route[PointIndex].positionSubvoxels[2] =
|
for (std::size_t PointIndex = 0; PointIndex < Stratum.route.size(); ++PointIndex)
|
||||||
Candidate.Points[PointIndex].positionSubvoxels[2] + LiftQ;
|
{
|
||||||
|
Stratum.route[PointIndex].positionSubvoxels[2] =
|
||||||
|
Candidate.Points[PointIndex].positionSubvoxels[2] + LiftQ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const Heightfield2D CandidateField(Recipe, Stratum);
|
||||||
|
const WorldAssemblyRouteAuditResult Audit = AuditHeightfieldRoute(CandidateField);
|
||||||
|
++SweepAttempts;
|
||||||
|
const std::uint64_t FailureCount = Audit.blockedCapsuleSamples
|
||||||
|
+ Audit.unsupportedCenterSamples + Audit.gradeFailures;
|
||||||
|
if (FailureCount < BestFailureCount)
|
||||||
|
{
|
||||||
|
BestFailureCount = FailureCount;
|
||||||
|
BestAudit = Audit;
|
||||||
|
BestCandidate = &Candidate;
|
||||||
|
BestLiftQ = LiftQ;
|
||||||
|
}
|
||||||
|
if (Audit.passed)
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeSurfaceRouteAudit] stage=%s candidates=%llu sweep_attempts=%llu blocked=%llu unsupported=%llu grade_failures=%llu min_clearance_q=%lld max_support_depth_q=%lld center_x=%d center_y=%d direction=%d half_length_vox=%d lift_q=%lld status=PASS"),
|
||||||
|
Stage, static_cast<unsigned long long>(Candidates.size()),
|
||||||
|
static_cast<unsigned long long>(SweepAttempts),
|
||||||
|
static_cast<unsigned long long>(Audit.blockedCapsuleSamples),
|
||||||
|
static_cast<unsigned long long>(Audit.unsupportedCenterSamples),
|
||||||
|
static_cast<unsigned long long>(Audit.gradeFailures),
|
||||||
|
static_cast<long long>(Audit.minimumClearanceSubvoxels),
|
||||||
|
static_cast<long long>(Audit.maximumSupportDepthSubvoxels),
|
||||||
|
Candidate.CenterX, Candidate.CenterY, Candidate.Direction,
|
||||||
|
Candidate.HalfLength, static_cast<long long>(LiftQ));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (LiftQ >= MaximumRepairLiftQ) break;
|
||||||
|
LiftQ += RepairStepQ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UE_LOG(LogTemp, Warning,
|
||||||
|
TEXT("[VoxelForgeSurfaceRouteAudit] stage=%s candidates=%llu sweep_attempts=%llu best_blocked=%llu best_unsupported=%llu best_grade_failures=%llu min_clearance_q=%lld max_support_depth_q=%lld center_x=%d center_y=%d direction=%d half_length_vox=%d lift_q=%lld status=FAIL"),
|
||||||
|
Stage, static_cast<unsigned long long>(Candidates.size()),
|
||||||
|
static_cast<unsigned long long>(SweepAttempts),
|
||||||
|
static_cast<unsigned long long>(BestAudit.blockedCapsuleSamples),
|
||||||
|
static_cast<unsigned long long>(BestAudit.unsupportedCenterSamples),
|
||||||
|
static_cast<unsigned long long>(BestAudit.gradeFailures),
|
||||||
|
static_cast<long long>(BestAudit.minimumClearanceSubvoxels),
|
||||||
|
static_cast<long long>(BestAudit.maximumSupportDepthSubvoxels),
|
||||||
|
BestCandidate ? BestCandidate->CenterX : 0,
|
||||||
|
BestCandidate ? BestCandidate->CenterY : 0,
|
||||||
|
BestCandidate ? BestCandidate->Direction : 0,
|
||||||
|
BestCandidate ? BestCandidate->HalfLength : 0,
|
||||||
|
static_cast<long long>(BestLiftQ));
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Keep the established candidate set first so worlds that already pass retain
|
||||||
|
// their exact route. The bounded-mouth fallback searches different route lengths.
|
||||||
|
if (TryCandidates(TEXT("legacy_64m"))) return true;
|
||||||
|
|
||||||
|
if (ArrivalRadiusQ > 0 || DepartureRadiusQ > 0)
|
||||||
|
{
|
||||||
|
// The endpoint radius constrains the mouth end, not the full route length. Keep
|
||||||
|
// enough reach for a route to leave a steep portal area and find gentler terrain.
|
||||||
|
constexpr std::int32_t maximumHalfLength = 128;
|
||||||
|
const std::vector<std::int32_t> HalfLengths{128, 112, 96, 80, 64, 48, 32, 24, 16};
|
||||||
|
Candidates.clear();
|
||||||
|
std::uint64_t FallbackCandidatesVisited = 0;
|
||||||
|
std::uint64_t FallbackGradeRejected = 0;
|
||||||
|
std::uint64_t FallbackPlacementRejected = 0;
|
||||||
|
std::uint64_t BestGradeEndpointDistanceQ = std::numeric_limits<std::uint64_t>::max();
|
||||||
|
FCandidate BestGradeCandidate;
|
||||||
|
for (const std::int32_t HalfLength : HalfLengths)
|
||||||
|
for (std::int32_t CenterY = -256; CenterY <= 256; CenterY += 16)
|
||||||
|
for (std::int32_t CenterX = -256; CenterX <= 256; CenterX += 16)
|
||||||
|
for (std::int32_t DirectionIndex = 0;
|
||||||
|
DirectionIndex < static_cast<std::int32_t>(Directions.size()); ++DirectionIndex)
|
||||||
|
{
|
||||||
|
++FallbackCandidatesVisited;
|
||||||
|
FCandidate Candidate;
|
||||||
|
Candidate.CenterX = CenterX;
|
||||||
|
Candidate.CenterY = CenterY;
|
||||||
|
Candidate.Direction = DirectionIndex;
|
||||||
|
Candidate.HalfLength = HalfLength;
|
||||||
|
Candidate.Points.reserve(33);
|
||||||
|
std::int64_t PreviousGround = 0;
|
||||||
|
std::int64_t PreviousX = 0;
|
||||||
|
std::int64_t PreviousY = 0;
|
||||||
|
bool bGradePass = true;
|
||||||
|
bool bPlacementPass = true;
|
||||||
|
for (std::int32_t Index = 0; Index <= 32; ++Index)
|
||||||
|
{
|
||||||
|
const std::int64_t Along = (Index - 16) * HalfLength / 16;
|
||||||
|
const std::int64_t X = CenterX + WorldAssemblyRoundRatio(
|
||||||
|
Along * Directions[DirectionIndex].X, 65536);
|
||||||
|
const std::int64_t Y = CenterY + WorldAssemblyRoundRatio(
|
||||||
|
Along * Directions[DirectionIndex].Y, 65536);
|
||||||
|
const std::int64_t XQ = WorldAssemblyFixed(static_cast<double>(X));
|
||||||
|
const std::int64_t YQ = WorldAssemblyFixed(static_cast<double>(Y));
|
||||||
|
const auto InRadius = [](std::int64_t XValue, std::int64_t YValue,
|
||||||
|
std::int64_t Radius)
|
||||||
|
{
|
||||||
|
return Radius <= 0 || XValue * XValue + YValue * YValue <= Radius * Radius;
|
||||||
|
};
|
||||||
|
if ((Index == 0 && ArrivalRadiusQ > 0
|
||||||
|
&& !InRadius(XQ, YQ, ArrivalRadiusQ))
|
||||||
|
|| (Index == 32 && DepartureRadiusQ > 0
|
||||||
|
&& !InRadius(XQ, YQ, DepartureRadiusQ)))
|
||||||
|
{
|
||||||
|
bPlacementPass = false;
|
||||||
|
}
|
||||||
|
if (Index == 0 && ArrivalRadiusQ > 0)
|
||||||
|
{
|
||||||
|
Candidate.EndpointProximityCost += static_cast<std::uint64_t>(XQ * XQ + YQ * YQ) * 16ull;
|
||||||
|
}
|
||||||
|
if (Index == 32 && DepartureRadiusQ > 0)
|
||||||
|
{
|
||||||
|
Candidate.EndpointProximityCost += static_cast<std::uint64_t>(XQ * XQ + YQ * YQ);
|
||||||
|
}
|
||||||
|
const auto Column = BaseField.point(WorldAssemblyFixed(static_cast<double>(X)),
|
||||||
|
WorldAssemblyFixed(static_cast<double>(Y)), 0);
|
||||||
|
const std::int64_t DeltaX = X - PreviousX;
|
||||||
|
const std::int64_t DeltaY = Y - PreviousY;
|
||||||
|
const std::uint64_t HorizontalVoxels = WorldAssemblyRoundedSqrt(
|
||||||
|
static_cast<std::uint64_t>(DeltaX * DeltaX + DeltaY * DeltaY));
|
||||||
|
const std::int64_t HorizontalQ = static_cast<std::int64_t>(HorizontalVoxels)
|
||||||
|
* WorldAssemblySubvoxelsPerVoxel;
|
||||||
|
if (Index > 0)
|
||||||
|
{
|
||||||
|
const std::int64_t RiseQ = std::abs(Column.groundHeightSubvoxels - PreviousGround);
|
||||||
|
if (HorizontalQ == 0 || RiseQ * 65536 > HorizontalQ * GradeQ16)
|
||||||
|
{
|
||||||
|
bGradePass = false;
|
||||||
|
}
|
||||||
|
Candidate.Score += RiseQ * 65536 / std::max<std::int64_t>(1, HorizontalQ);
|
||||||
|
}
|
||||||
|
WorldAssemblyRoutePointV1 Point;
|
||||||
|
Point.positionSubvoxels = {XQ, YQ, Column.groundHeightSubvoxels + HalfHeightQ};
|
||||||
|
Point.radiusSubvoxels = RouteRadiusQ;
|
||||||
|
Point.stableFeatureId = WorldAssemblyFeatureId(Recipe.seed, X, Y,
|
||||||
|
0x52540000u + static_cast<std::uint32_t>(Index));
|
||||||
|
Candidate.Points.push_back(Point);
|
||||||
|
PreviousGround = Column.groundHeightSubvoxels;
|
||||||
|
PreviousX = X;
|
||||||
|
PreviousY = Y;
|
||||||
|
}
|
||||||
|
Candidate.Score /= 32;
|
||||||
|
if (bGradePass)
|
||||||
|
{
|
||||||
|
const auto& Endpoint = DepartureRadiusQ > 0
|
||||||
|
? Candidate.Points.back().positionSubvoxels
|
||||||
|
: Candidate.Points.front().positionSubvoxels;
|
||||||
|
const std::uint64_t EndpointDistanceQ = WorldAssemblyRoundedSqrt(
|
||||||
|
static_cast<std::uint64_t>(Endpoint[0] * Endpoint[0]
|
||||||
|
+ Endpoint[1] * Endpoint[1]));
|
||||||
|
if (EndpointDistanceQ < BestGradeEndpointDistanceQ)
|
||||||
|
{
|
||||||
|
BestGradeEndpointDistanceQ = EndpointDistanceQ;
|
||||||
|
BestGradeCandidate.CenterX = CenterX;
|
||||||
|
BestGradeCandidate.CenterY = CenterY;
|
||||||
|
BestGradeCandidate.Direction = DirectionIndex;
|
||||||
|
BestGradeCandidate.HalfLength = HalfLength;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const Heightfield2D CandidateField(Recipe, Stratum);
|
if (bGradePass && bPlacementPass) Candidates.push_back(std::move(Candidate));
|
||||||
if (AuditHeightfieldRoute(CandidateField).passed) return true;
|
else
|
||||||
if (LiftQ >= MaximumRepairLiftQ) break;
|
{
|
||||||
LiftQ += RepairStepQ;
|
if (!bGradePass) ++FallbackGradeRejected;
|
||||||
|
if (!bPlacementPass) ++FallbackPlacementRejected;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgeSurfaceRouteCandidates] stage=bounded_single_mouth visited=%llu rejected_grade=%llu rejected_placement=%llu eligible=%llu max_half_length_vox=%d half_length_variants=%d best_grade_endpoint_q=%llu best_grade_center_x=%d best_grade_center_y=%d best_grade_direction=%d best_grade_half_length_vox=%d arrival_radius_q=%lld departure_radius_q=%lld"),
|
||||||
|
static_cast<unsigned long long>(FallbackCandidatesVisited),
|
||||||
|
static_cast<unsigned long long>(FallbackGradeRejected),
|
||||||
|
static_cast<unsigned long long>(FallbackPlacementRejected),
|
||||||
|
static_cast<unsigned long long>(Candidates.size()), maximumHalfLength,
|
||||||
|
static_cast<int32>(HalfLengths.size()),
|
||||||
|
static_cast<unsigned long long>(BestGradeEndpointDistanceQ),
|
||||||
|
BestGradeCandidate.CenterX, BestGradeCandidate.CenterY,
|
||||||
|
BestGradeCandidate.Direction, BestGradeCandidate.HalfLength,
|
||||||
|
static_cast<long long>(ArrivalRadiusQ),
|
||||||
|
static_cast<long long>(DepartureRadiusQ));
|
||||||
|
if (TryCandidates(TEXT("bounded_single_mouth"))) return true;
|
||||||
}
|
}
|
||||||
Error = "SurfaceWorld route candidates failed the capsule and support sweep.";
|
Error = "SurfaceWorld route candidates failed the capsule and support sweep.";
|
||||||
return false;
|
return false;
|
||||||
@@ -631,7 +824,29 @@ bool BuildSurfaceStratumPlan(const UVoxelStrateManager* StrateManager,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::string RouteError;
|
std::string RouteError;
|
||||||
if (!BuildFiniteSurfaceRoute(Recipe, Stratum, RouteError))
|
const auto OwnerVoxelsToCoreQ = [CoreVoxelMicrometres](double OwnerVoxels)
|
||||||
|
{
|
||||||
|
const double OwnerVoxelMicrometres = OwnerVoxelSizeCm * 10000.0;
|
||||||
|
return static_cast<std::int64_t>(std::llround(OwnerVoxels * OwnerVoxelMicrometres
|
||||||
|
* WorldAssemblySubvoxelsPerVoxel / std::max<std::uint32_t>(1, CoreVoxelMicrometres)));
|
||||||
|
};
|
||||||
|
std::int64_t ArrivalRadiusQ = 0;
|
||||||
|
std::int64_t DepartureRadiusQ = 0;
|
||||||
|
for (const FStrateSlot& Neighbor : StrateManager->GetLayout())
|
||||||
|
{
|
||||||
|
if (Neighbor.Definition == nullptr) continue;
|
||||||
|
if (Neighbor.StrateIndex + 1 == Slot.StrateIndex)
|
||||||
|
{
|
||||||
|
ArrivalRadiusQ = std::max(OwnerVoxelsToCoreQ(Neighbor.Definition->PassageConfig.DistanceMin),
|
||||||
|
OwnerVoxelsToCoreQ(Neighbor.Definition->PassageConfig.DistanceMax));
|
||||||
|
}
|
||||||
|
if (Neighbor.StrateIndex == Slot.StrateIndex + 1 && Definition != nullptr)
|
||||||
|
{
|
||||||
|
DepartureRadiusQ = std::max(OwnerVoxelsToCoreQ(Definition->PassageConfig.DistanceMin),
|
||||||
|
OwnerVoxelsToCoreQ(Definition->PassageConfig.DistanceMax));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!BuildFiniteSurfaceRoute(Recipe, Stratum, ArrivalRadiusQ, DepartureRadiusQ, RouteError))
|
||||||
{
|
{
|
||||||
OutError = UTF8_TO_TCHAR(RouteError.c_str());
|
OutError = UTF8_TO_TCHAR(RouteError.c_str());
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -14918,7 +14918,7 @@ bool CreateLoggedOwnerSurfaceSettingsCopy(
|
|||||||
UVoxelSettings* Source, int32 Seed,
|
UVoxelSettings* Source, int32 Seed,
|
||||||
TStrongObjectPtr<UVoxelSettings>& OutSettings,
|
TStrongObjectPtr<UVoxelSettings>& OutSettings,
|
||||||
TStrongObjectPtr<UVoxelStrateDefinition>& OutSurfaceDefinition,
|
TStrongObjectPtr<UVoxelStrateDefinition>& OutSurfaceDefinition,
|
||||||
FString& OutError)
|
FString& OutError, bool bCompactTracePlan = false)
|
||||||
{
|
{
|
||||||
if (Source == nullptr)
|
if (Source == nullptr)
|
||||||
{
|
{
|
||||||
@@ -14934,11 +14934,10 @@ bool CreateLoggedOwnerSurfaceSettingsCopy(
|
|||||||
nullptr, TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1"));
|
nullptr, TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1"));
|
||||||
if (SurfaceDefinition == nullptr
|
if (SurfaceDefinition == nullptr
|
||||||
|| SurfaceDefinition->GeneratorType != ECaveGeneratorType::SurfaceWorld
|
|| SurfaceDefinition->GeneratorType != ECaveGeneratorType::SurfaceWorld
|
||||||
|| SurfaceDefinition->StrateHeightInChunks <= 0
|
|| SurfaceDefinition->StrateHeightInChunks <= 0)
|
||||||
|| Source->TotalStrates != 1)
|
|
||||||
{
|
{
|
||||||
OutError = FString::Printf(
|
OutError = FString::Printf(
|
||||||
TEXT("The staged owner assets do not describe the logged single SurfaceWorld (definition_loaded=%d type=%d height=%d settings_strates=%d)."),
|
TEXT("The staged owner assets do not contain a usable DA_Strate1 SurfaceWorld (definition_loaded=%d type=%d height=%d settings_strates=%d)."),
|
||||||
SurfaceDefinition != nullptr ? 1 : 0,
|
SurfaceDefinition != nullptr ? 1 : 0,
|
||||||
SurfaceDefinition != nullptr ? static_cast<int32>(SurfaceDefinition->GeneratorType) : INDEX_NONE,
|
SurfaceDefinition != nullptr ? static_cast<int32>(SurfaceDefinition->GeneratorType) : INDEX_NONE,
|
||||||
SurfaceDefinition != nullptr ? SurfaceDefinition->StrateHeightInChunks : INDEX_NONE,
|
SurfaceDefinition != nullptr ? SurfaceDefinition->StrateHeightInChunks : INDEX_NONE,
|
||||||
@@ -14953,9 +14952,24 @@ bool CreateLoggedOwnerSurfaceSettingsCopy(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
OutSettings = TStrongObjectPtr<UVoxelSettings>(SettingsCopy);
|
OutSettings = TStrongObjectPtr<UVoxelSettings>(SettingsCopy);
|
||||||
|
if (bCompactTracePlan)
|
||||||
|
{
|
||||||
|
SurfaceDefinition = DuplicateObject<UVoxelStrateDefinition>(SurfaceDefinition,
|
||||||
|
GetTransientPackage(), TEXT("VoxelForgeCompactSurfaceTraceDefinition"));
|
||||||
|
if (SurfaceDefinition == nullptr)
|
||||||
|
{
|
||||||
|
OutError = TEXT("Could not make a transient DA_Strate1 copy for the compact common-plan trace.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// L14's historical fixed-length SurfaceWorld route search cannot place the
|
||||||
|
// new trace fixture even at 16 chunks. Use the same simple, walkable FlatPlain
|
||||||
|
// plan in both host revisions so the streaming comparison reaches AVoxelWorld.
|
||||||
|
SurfaceDefinition->GeneratorType = ECaveGeneratorType::FlatPlain;
|
||||||
|
SurfaceDefinition->StrateHeightInChunks = 16;
|
||||||
|
}
|
||||||
OutSurfaceDefinition = TStrongObjectPtr<UVoxelStrateDefinition>(SurfaceDefinition);
|
OutSurfaceDefinition = TStrongObjectPtr<UVoxelStrateDefinition>(SurfaceDefinition);
|
||||||
SettingsCopy->Seed = Seed;
|
SettingsCopy->Seed = Seed;
|
||||||
SettingsCopy->TotalStrates = Source->TotalStrates;
|
SettingsCopy->TotalStrates = 1;
|
||||||
SettingsCopy->StratePool.Reset();
|
SettingsCopy->StratePool.Reset();
|
||||||
SettingsCopy->FixedStrates.Reset();
|
SettingsCopy->FixedStrates.Reset();
|
||||||
SettingsCopy->FixedStrates.Add(0,
|
SettingsCopy->FixedStrates.Add(0,
|
||||||
@@ -14965,8 +14979,10 @@ bool CreateLoggedOwnerSurfaceSettingsCopy(
|
|||||||
SettingsCopy->bUseWorldAssemblyManifestOverride = false;
|
SettingsCopy->bUseWorldAssemblyManifestOverride = false;
|
||||||
const int32 BottomChunkZ = 1 - SurfaceDefinition->StrateHeightInChunks;
|
const int32 BottomChunkZ = 1 - SurfaceDefinition->StrateHeightInChunks;
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeCoreTestLayout] source=owner_asset_transient_copy definition=DA_Strate1 seed=%d strata=1 bounds=[0,%d] height_chunks=%d gap=%d imported_override=cleared"),
|
TEXT("[VoxelForgeCoreTestLayout] source=owner_asset_transient_copy definition=DA_Strate1 generator=%d seed=%d strata=1 bounds=[0,%d] height_chunks=%d compact_trace=%d gap=%d imported_override=cleared"),
|
||||||
Seed, BottomChunkZ, SurfaceDefinition->StrateHeightInChunks, SettingsCopy->InterStrateGapChunks);
|
static_cast<int32>(SurfaceDefinition->GeneratorType), Seed, BottomChunkZ,
|
||||||
|
SurfaceDefinition->StrateHeightInChunks,
|
||||||
|
bCompactTracePlan ? 1 : 0, SettingsCopy->InterStrateGapChunks);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16542,7 +16558,15 @@ bool RunCoreFloatingIslandActorIntegrationTest(
|
|||||||
}
|
}
|
||||||
float StagedWalkSpeedCmPerSecond = 0.0f;
|
float StagedWalkSpeedCmPerSecond = 0.0f;
|
||||||
float StagedSprintSpeedCmPerSecond = 0.0f;
|
float StagedSprintSpeedCmPerSecond = 0.0f;
|
||||||
if (!ReadStagedPlayerSpeeds(
|
if (Arguments.bCoreWorldStreamTraceOnly)
|
||||||
|
{
|
||||||
|
StagedWalkSpeedCmPerSecond = 600.0f;
|
||||||
|
StagedSprintSpeedCmPerSecond = 6000.0f;
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgePawnSpeedFixture] source=trace_fixture_defaults walk_cm_s=%.3f sprint_cm_s=%.3f"),
|
||||||
|
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond);
|
||||||
|
}
|
||||||
|
else if (!ReadStagedPlayerSpeeds(
|
||||||
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError))
|
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -17764,8 +17788,16 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
|||||||
|
|
||||||
float StagedWalkSpeedCmPerSecond = 0.0f;
|
float StagedWalkSpeedCmPerSecond = 0.0f;
|
||||||
float StagedSprintSpeedCmPerSecond = 0.0f;
|
float StagedSprintSpeedCmPerSecond = 0.0f;
|
||||||
if (!ReadStagedPlayerSpeeds(
|
if (Arguments.bCoreWorldStreamTraceOnly)
|
||||||
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError))
|
{
|
||||||
|
StagedWalkSpeedCmPerSecond = 600.0f;
|
||||||
|
StagedSprintSpeedCmPerSecond = 6000.0f;
|
||||||
|
UE_LOG(LogTemp, Display,
|
||||||
|
TEXT("[VoxelForgePawnSpeedFixture] source=trace_fixture_defaults walk_cm_s=%.3f sprint_cm_s=%.3f"),
|
||||||
|
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond);
|
||||||
|
}
|
||||||
|
else if (!ReadStagedPlayerSpeeds(
|
||||||
|
StagedWalkSpeedCmPerSecond, StagedSprintSpeedCmPerSecond, OutError))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -17773,8 +17805,26 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
|||||||
const int32 OwnerSeed = ResolveCoreTestWorldSeed(AuthoredSettings);
|
const int32 OwnerSeed = ResolveCoreTestWorldSeed(AuthoredSettings);
|
||||||
TStrongObjectPtr<UVoxelSettings> OwnerSurfaceSettings;
|
TStrongObjectPtr<UVoxelSettings> OwnerSurfaceSettings;
|
||||||
TStrongObjectPtr<UVoxelStrateDefinition> OwnerSurfaceDefinition;
|
TStrongObjectPtr<UVoxelStrateDefinition> OwnerSurfaceDefinition;
|
||||||
if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, OwnerSeed,
|
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> OwnerPlanDefinitions;
|
||||||
OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError))
|
if (Arguments.bOwnerFourStratePlan)
|
||||||
|
{
|
||||||
|
UVoxelSettings* PlanSettings = DuplicateObject<UVoxelSettings>(
|
||||||
|
AuthoredSettings, GetTransientPackage(), TEXT("VoxelForgeOwnerFourStrateTraceSettings"));
|
||||||
|
if (PlanSettings == nullptr)
|
||||||
|
{
|
||||||
|
OutError = TEXT("Could not duplicate owner settings for the transient four-strate Core trace.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PlanSettings->Seed = OwnerSeed;
|
||||||
|
if (!ApplyOwnerFourStratePlan(PlanSettings, OwnerPlanDefinitions, true, OutError))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OwnerSurfaceSettings = TStrongObjectPtr<UVoxelSettings>(PlanSettings);
|
||||||
|
}
|
||||||
|
else if (!CreateLoggedOwnerSurfaceSettingsCopy(AuthoredSettings, OwnerSeed,
|
||||||
|
OwnerSurfaceSettings, OwnerSurfaceDefinition, OutError,
|
||||||
|
Arguments.bCoreWorldStreamTraceOnly))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2325,8 +2325,22 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
|||||||
const std::int64_t midRadius=std::max<std::int64_t>(
|
const std::int64_t midRadius=std::max<std::int64_t>(
|
||||||
static_cast<std::int64_t>(std::llround((upperIsSurfaceWorld?3.5:1.5)*qPerMetre)),
|
static_cast<std::int64_t>(std::llround((upperIsSurfaceWorld?3.5:1.5)*qPerMetre)),
|
||||||
cfg?cfg->midRadiusSubvoxels:mouthRadius);
|
cfg?cfg->midRadiusSubvoxels:mouthRadius);
|
||||||
const std::int64_t reachMinimum=cfg?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
|
const std::int64_t requestedReachMinimum=cfg
|
||||||
const std::int64_t reachMaximum=cfg?std::max<std::int64_t>(reachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
|
?std::max<std::int64_t>(0,cfg->reachMinimumSubvoxels):8*qPerMetre;
|
||||||
|
const std::int64_t requestedReachMaximum=cfg
|
||||||
|
?std::max<std::int64_t>(requestedReachMinimum,cfg->reachMaximumSubvoxels):24*qPerMetre;
|
||||||
|
// Keep the landing several metres inside the stratum's authored content.
|
||||||
|
// Near a band edge, the sampled native terrain can exist at the centerline
|
||||||
|
// while the full capsule footprint has no supporting ground.
|
||||||
|
const std::int64_t mouthClearance=std::max(mouthRadius,midRadius)+2*qPerMetre;
|
||||||
|
const auto usableReach=[&](const ResolvedLayoutStratumV2& stratum)
|
||||||
|
{
|
||||||
|
const std::int64_t depth=contentZQ(stratum.content.topMicrometres)
|
||||||
|
-contentZQ(stratum.content.bottomMicrometres);
|
||||||
|
return std::max<std::int64_t>(0,depth-mouthClearance);
|
||||||
|
};
|
||||||
|
const std::int64_t upperReachLimit=usableReach(upper);
|
||||||
|
const std::int64_t lowerReachLimit=usableReach(lower);
|
||||||
const std::uint32_t style=cfg?std::min<std::uint32_t>(cfg->style,3u):1u;
|
const std::uint32_t style=cfg?std::min<std::uint32_t>(cfg->style,3u):1u;
|
||||||
const std::uint32_t segments=cfg?std::clamp<std::uint32_t>(cfg->segments,16u,48u):20u;
|
const std::uint32_t segments=cfg?std::clamp<std::uint32_t>(cfg->segments,16u,48u):20u;
|
||||||
const std::int64_t wander=cfg?std::max<std::int64_t>(0,cfg->wanderSubvoxels):3*qPerMetre;
|
const std::int64_t wander=cfg?std::max<std::int64_t>(0,cfg->wanderSubvoxels):3*qPerMetre;
|
||||||
@@ -2362,16 +2376,30 @@ bool PlanResolvedLayoutPassagesV1(ResolvedLayoutSnapshotV3& layout,std::int32_t
|
|||||||
if(!hasUpperRoute)
|
if(!hasUpperRoute)
|
||||||
{
|
{
|
||||||
upperMouth[0]=xy[0];upperMouth[1]=xy[1];
|
upperMouth[0]=xy[0];upperMouth[1]=xy[1];
|
||||||
const auto reach=reachValue(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
|
const auto requestedReach=reachValue(PassageSeed(seed,upper.strateIndex,
|
||||||
connection,0x55505248u),reachMinimum,reachMaximum);
|
lower.strateIndex,connection,0x55505248u),
|
||||||
|
requestedReachMinimum,requestedReachMaximum);
|
||||||
|
const auto reach=std::min(requestedReach,upperReachLimit);
|
||||||
upperMouth[2]=contentZQ(upper.content.bottomMicrometres)+reach;
|
upperMouth[2]=contentZQ(upper.content.bottomMicrometres)+reach;
|
||||||
|
if(requestedReach>upperReachLimit)
|
||||||
|
std::printf("[ResolvedPassageReachClamp] id=%s side=upper sampled_m=%.3f usable_max_m=%.3f selected_m=%.3f status=CLAMPED\n",
|
||||||
|
passage.stableId.c_str(),requestedReach/static_cast<double>(qPerMetre),
|
||||||
|
upperReachLimit/static_cast<double>(qPerMetre),
|
||||||
|
reach/static_cast<double>(qPerMetre));
|
||||||
}
|
}
|
||||||
if(!hasLowerRoute)
|
if(!hasLowerRoute)
|
||||||
{
|
{
|
||||||
lowerMouth[0]=xy[0];lowerMouth[1]=xy[1];
|
lowerMouth[0]=xy[0];lowerMouth[1]=xy[1];
|
||||||
const auto reach=reachValue(PassageSeed(seed,upper.strateIndex,lower.strateIndex,
|
const auto requestedReach=reachValue(PassageSeed(seed,upper.strateIndex,
|
||||||
connection,0x4c4f5752u),reachMinimum,reachMaximum);
|
lower.strateIndex,connection,0x4c4f5752u),
|
||||||
|
requestedReachMinimum,requestedReachMaximum);
|
||||||
|
const auto reach=std::min(requestedReach,lowerReachLimit);
|
||||||
lowerMouth[2]=contentZQ(lower.content.topMicrometres)-reach;
|
lowerMouth[2]=contentZQ(lower.content.topMicrometres)-reach;
|
||||||
|
if(requestedReach>lowerReachLimit)
|
||||||
|
std::printf("[ResolvedPassageReachClamp] id=%s side=lower sampled_m=%.3f usable_max_m=%.3f selected_m=%.3f status=CLAMPED\n",
|
||||||
|
passage.stableId.c_str(),requestedReach/static_cast<double>(qPerMetre),
|
||||||
|
lowerReachLimit/static_cast<double>(qPerMetre),
|
||||||
|
reach/static_cast<double>(qPerMetre));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!radialWithin(upperMouth,placementRadius)||!radialWithin(lowerMouth,placementRadius))
|
if(!radialWithin(upperMouth,placementRadius)||!radialWithin(lowerMouth,placementRadius))
|
||||||
|
|||||||
@@ -1045,6 +1045,7 @@ public:
|
|||||||
{
|
{
|
||||||
const auto& passage=ResolvedLayout->passages[passageIndex];
|
const auto& passage=ResolvedLayout->passages[passageIndex];
|
||||||
if(passage.controlPoints.size()<2)continue;
|
if(passage.controlPoints.size()<2)continue;
|
||||||
|
const bool internalChainPassage=passageIndex>0;
|
||||||
const auto supportStratum=[&](std::int32_t strateIndex)
|
const auto supportStratum=[&](std::int32_t strateIndex)
|
||||||
->const VoxelForgeCore::ResolvedLayoutStratumV2*
|
->const VoxelForgeCore::ResolvedLayoutStratumV2*
|
||||||
{
|
{
|
||||||
@@ -1490,7 +1491,7 @@ public:
|
|||||||
// the nominal height smoothly as a tread approaches its landing.
|
// the nominal height smoothly as a tread approaches its landing.
|
||||||
double floorOffset=bridgeDeck
|
double floorOffset=bridgeDeck
|
||||||
?PassageBridgeDeckOffsetMetres(passageIndex,static_cast<std::size_t>(index))
|
?PassageBridgeDeckOffsetMetres(passageIndex,static_cast<std::size_t>(index))
|
||||||
:std::lerp(0.15,0.20,lowerMouthBlend);
|
:std::lerp(internalChainPassage?0.20:0.15,0.20,lowerMouthBlend);
|
||||||
const double horizontalLengthSq=dx*dx+dy*dy;
|
const double horizontalLengthSq=dx*dx+dy*dy;
|
||||||
const bool overlapSupportJoin=index+12<lastIndex
|
const bool overlapSupportJoin=index+12<lastIndex
|
||||||
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index))
|
&&!IsPassageShortSpanSegment(passageIndex,static_cast<std::size_t>(index))
|
||||||
@@ -1524,7 +1525,7 @@ public:
|
|||||||
+(2.0*v0-5.0*v1+4.0*v2-v3)*t2
|
+(2.0*v0-5.0*v1+4.0*v2-v3)*t2
|
||||||
+(-v0+3.0*v1-3.0*v2+v3)*t3);
|
+(-v0+3.0*v1-3.0*v2+v3)*t3);
|
||||||
};
|
};
|
||||||
constexpr double SmoothSupportHeightWeight=0.25;
|
const double SmoothSupportHeightWeight=internalChainPassage?0.0:0.25;
|
||||||
centerZ+=(interpolate(p0.positionSubvoxels[2],segmentA.positionSubvoxels[2],
|
centerZ+=(interpolate(p0.positionSubvoxels[2],segmentA.positionSubvoxels[2],
|
||||||
segmentB.positionSubvoxels[2],p3.positionSubvoxels[2])-centerZ)
|
segmentB.positionSubvoxels[2],p3.positionSubvoxels[2])-centerZ)
|
||||||
*SmoothSupportHeightWeight;
|
*SmoothSupportHeightWeight;
|
||||||
@@ -1576,12 +1577,14 @@ public:
|
|||||||
// Keep the supported ramp to a comfortable walking width. Extending a
|
// Keep the supported ramp to a comfortable walking width. Extending a
|
||||||
// full bore-radius shelf across overlapping helix coils can block the
|
// full bore-radius shelf across overlapping helix coils can block the
|
||||||
// capsule on the neighbouring turn.
|
// capsule on the neighbouring turn.
|
||||||
const bool lowerMouthBodyOverlap=index>=lowerMouthIndex-2
|
const bool lowerMouthBodyOverlap=ResolvedLayout->strates.size()>2
|
||||||
|
&&index>=lowerMouthIndex-2
|
||||||
&&supportQueryDepth<0.98*qPerMetre;
|
&&supportQueryDepth<0.98*qPerMetre;
|
||||||
// Keep a narrow walk core, but place its top on the native surface
|
// Keep a narrow walk core, but place its top on the native surface
|
||||||
// so the support remains a worn ramp instead of a route-height slab.
|
// so the support remains a worn ramp instead of a route-height slab.
|
||||||
const bool mouthTread=entryMouthShoulder
|
const bool mouthTread=lowerMouthBodyOverlap
|
||||||
&&(!floatingIslandPassage||passage.style==0);
|
||(entryMouthShoulder
|
||||||
|
&&(!floatingIslandPassage||passage.style==0||internalChainPassage));
|
||||||
double distanceFromMouthMetres=std::numeric_limits<double>::infinity();
|
double distanceFromMouthMetres=std::numeric_limits<double>::infinity();
|
||||||
if(entryMouthShoulder)
|
if(entryMouthShoulder)
|
||||||
{
|
{
|
||||||
@@ -1656,7 +1659,8 @@ public:
|
|||||||
*(3.0-2.0*supportEdgeT);
|
*(3.0-2.0*supportEdgeT);
|
||||||
const double mouthSupportThicknessVoxels=std::lerp(
|
const double mouthSupportThicknessVoxels=std::lerp(
|
||||||
0.35*supportThicknessVoxels,supportThicknessVoxels,mouthSupportT);
|
0.35*supportThicknessVoxels,supportThicknessVoxels,mouthSupportT);
|
||||||
const double localSupportThicknessVoxels=entryMouthShoulder
|
const double localSupportThicknessVoxels=(entryMouthShoulder
|
||||||
|
||lowerMouthBodyOverlap)
|
||||||
?mouthSupportThicknessVoxels*(1.0-0.72*supportEdgeSmooth)
|
?mouthSupportThicknessVoxels*(1.0-0.72*supportEdgeSmooth)
|
||||||
:supportThicknessVoxels;
|
:supportThicknessVoxels;
|
||||||
double mouthSupportBlend=1.0;
|
double mouthSupportBlend=1.0;
|
||||||
@@ -1668,8 +1672,8 @@ public:
|
|||||||
*(3.0-2.0*mouthBlendT);
|
*(3.0-2.0*mouthBlendT);
|
||||||
}
|
}
|
||||||
const double supportEdgeDepth=mouthTread?0.25:0.35;
|
const double supportEdgeDepth=mouthTread?0.25:0.35;
|
||||||
const double mouthFloorOffset=mouthTread
|
const double mouthFloorOffset=internalChainPassage?0.20
|
||||||
?std::lerp(0.15,0.19,lowerMouthBlend):floorOffset;
|
:mouthTread?std::lerp(0.15,0.19,lowerMouthBlend):floorOffset;
|
||||||
const double baseFloorQ=centerZ-radius+mouthFloorOffset*qPerMetre
|
const double baseFloorQ=centerZ-radius+mouthFloorOffset*qPerMetre
|
||||||
-supportEdgeSmooth*supportEdgeDepth*qPerMetre;
|
-supportEdgeSmooth*supportEdgeDepth*qPerMetre;
|
||||||
const double nativeSurfaceQ=static_cast<double>(qz)
|
const double nativeSurfaceQ=static_cast<double>(qz)
|
||||||
@@ -1678,7 +1682,8 @@ public:
|
|||||||
// blends the source field directly instead of extending this SDF top.
|
// blends the source field directly instead of extending this SDF top.
|
||||||
const double nativeFieldOffset=std::clamp(nativeSurfaceQ-baseFloorQ,
|
const double nativeFieldOffset=std::clamp(nativeSurfaceQ-baseFloorQ,
|
||||||
-0.80*qPerMetre,0.80*qPerMetre);
|
-0.80*qPerMetre,0.80*qPerMetre);
|
||||||
const double nativeSurfaceBlend=mouthTread?1.0:1.0-mouthReliefT;
|
const double nativeSurfaceBlend=mouthTread
|
||||||
|
?internalChainPassage?0.0:1.0:1.0-mouthReliefT;
|
||||||
const double nativeTreadOffset=std::clamp(
|
const double nativeTreadOffset=std::clamp(
|
||||||
nativeFieldOffset*nativeSurfaceBlend+floorReliefOffset
|
nativeFieldOffset*nativeSurfaceBlend+floorReliefOffset
|
||||||
-std::abs(treadRelief)*0.08*qPerMetre
|
-std::abs(treadRelief)*0.08*qPerMetre
|
||||||
|
|||||||
@@ -23,10 +23,12 @@ HEADLESS = Path(os.environ.get("VOXELFORGE_L37_HEADLESS",
|
|||||||
RENDERER = Path(os.environ.get("VOXELFORGE_L37_RENDERER",
|
RENDERER = Path(os.environ.get("VOXELFORGE_L37_RENDERER",
|
||||||
BIN / "VoxelForgeOffscreenCapture-l37-final-clang"))
|
BIN / "VoxelForgeOffscreenCapture-l37-final-clang"))
|
||||||
PNG_DIR = OUT / "capture-png"
|
PNG_DIR = OUT / "capture-png"
|
||||||
|
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
|
||||||
|
ROOT / "Standalone/data/owner_world.json"))
|
||||||
RECIPE_INPUTS = (
|
RECIPE_INPUTS = (
|
||||||
("owner-fi", ROOT / "Standalone/data/owner_world.json",
|
("owner-fi", OWNER,
|
||||||
"floating-islands", "FloatingIslands", "upper"),
|
"floating-islands", "FloatingIslands", "upper"),
|
||||||
("owner-sw", ROOT / "Standalone/data/owner_world.json",
|
("owner-sw", OWNER,
|
||||||
"surface-world", "SurfaceWorld", "lower"),
|
"surface-world", "SurfaceWorld", "lower"),
|
||||||
("style-override-upper-sw-fixture-style0",
|
("style-override-upper-sw-fixture-style0",
|
||||||
OUT / "fixtures/style-override-upper-sw-fixture-style0.json",
|
OUT / "fixtures/style-override-upper-sw-fixture-style0.json",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"""Run five interleaved whole-view cost pairs against the L36 Clang binary."""
|
"""Run five interleaved whole-view cost pairs against the L36 Clang binary."""
|
||||||
import csv
|
import csv
|
||||||
import math
|
import math
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import statistics
|
import statistics
|
||||||
import subprocess
|
import subprocess
|
||||||
@@ -10,6 +11,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[4]
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
OUT = ROOT / "Saved/Linux/l37"
|
OUT = ROOT / "Saved/Linux/l37"
|
||||||
|
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
|
||||||
|
ROOT / "Standalone/data/owner_world.json"))
|
||||||
BINARIES = {
|
BINARIES = {
|
||||||
"l36": ROOT / "Saved/Linux/l36/bin/VoxelForgeCoreHeadless-l36-final-clang",
|
"l36": ROOT / "Saved/Linux/l36/bin/VoxelForgeCoreHeadless-l36-final-clang",
|
||||||
"l37": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
|
"l37": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
|
||||||
@@ -30,7 +33,7 @@ for recipe in ("surface-world", "floating-islands"):
|
|||||||
command = [
|
command = [
|
||||||
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
"taskset", "-c", "0-11", "prlimit", "--as=6442450944", "--",
|
||||||
"timeout", "900s", str(BINARIES[build]),
|
"timeout", "900s", str(BINARIES[build]),
|
||||||
str(ROOT / "Standalone/data/owner_world.json"),
|
str(OWNER),
|
||||||
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
|
"--workers=12", "--world-assembler-prototype", "--stream-benchmark",
|
||||||
"--stream-pipeline", "--force-generate", "--clip-radius=3",
|
"--stream-pipeline", "--force-generate", "--clip-radius=3",
|
||||||
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
|
"--max-clip-level=8", "--max-level=-1", f"--recipe={recipe}",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Check L25, 24 m crossing and upper-SW fixture determinism for L37."""
|
"""Check L25, 24 m crossing and upper-SW fixture determinism for L37."""
|
||||||
import csv
|
import csv
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
@@ -9,7 +10,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[4]
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
OUT = ROOT / "Saved/Linux/l37"
|
OUT = ROOT / "Saved/Linux/l37"
|
||||||
OWNER = ROOT / "Standalone/data/owner_world.json"
|
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
|
||||||
|
ROOT / "Standalone/data/owner_world.json"))
|
||||||
FIXTURE = ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json"
|
FIXTURE = ROOT / "Saved/Linux/l33/fixtures/sw-upper-tunnel-network-style0.json"
|
||||||
BINARIES = {
|
BINARIES = {
|
||||||
"clang": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
|
"clang": OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Run L33 route/proof and transition invariants with recipe-stamped logs."""
|
"""Run L33 route/proof and transition invariants with recipe-stamped logs."""
|
||||||
import re
|
import re
|
||||||
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -48,7 +49,8 @@ def run(name, binary, data, recipe, extra, checks):
|
|||||||
failed |= not ok
|
failed |= not ok
|
||||||
|
|
||||||
|
|
||||||
owner = ROOT / "Standalone/data/owner_world.json"
|
owner = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
|
||||||
|
ROOT / "Standalone/data/owner_world.json"))
|
||||||
for recipe in ("surface-world", "floating-islands"):
|
for recipe in ("surface-world", "floating-islands"):
|
||||||
for compiler, binary in BINARIES.items():
|
for compiler, binary in BINARIES.items():
|
||||||
name = f"invariant-route-proof-{recipe}-{compiler}"
|
name = f"invariant-route-proof-{recipe}-{compiler}"
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ ROOT = Path(__file__).resolve().parents[4]
|
|||||||
OUT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37"))
|
OUT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l37"))
|
||||||
BIN = Path(os.environ.get("VOXELFORGE_VERIFY_HEADLESS",
|
BIN = Path(os.environ.get("VOXELFORGE_VERIFY_HEADLESS",
|
||||||
OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang"))
|
OUT / "bin/VoxelForgeCoreHeadless-l37-final-clang"))
|
||||||
OWNER = ROOT / "Standalone/data/owner_world.json"
|
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
|
||||||
|
ROOT / "Standalone/data/owner_world.json"))
|
||||||
TERRACE = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
|
TERRACE = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingTerraceL32.py"
|
||||||
RING_SET = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingSetL32.py"
|
RING_SET = ROOT / "Standalone/Research/WorldAssemblerM3/Linux32/AuditRingSetL32.py"
|
||||||
STAMP = re.compile(
|
STAMP = re.compile(
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from pathlib import Path
|
|||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[4]
|
ROOT = Path(__file__).resolve().parents[4]
|
||||||
OWNER = ROOT / "Standalone/data/owner_world.json"
|
OWNER = Path(os.environ.get("VOXELFORGE_OWNER_DATA",
|
||||||
|
ROOT / "Standalone/data/owner_world.json"))
|
||||||
OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l56"))
|
OUT_DEFAULT = Path(os.environ.get("VOXELFORGE_VERIFY_OUT", ROOT / "Saved/Linux/l56"))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# VoxelForge L66 — Linux report
|
||||||
|
|
||||||
|
## Findings and fixes
|
||||||
|
|
||||||
|
- The saved SW passage range is `ReachMax=5000` owner voxels, despite the C++ type default of 96; why the export carries 5000 is unresolved. At 0.125 m per owner voxel, the sampled lower reach was 318.539 m. With FI content top at −504.5 m, the old lower mouth resolved to −823.039 m, 79.539 m below FI content bottom. The planner selected the config from the upper SW stratum and did not cap that reach to FI’s band. This was not a metre migration error; L64’s prose value of −822.06 m was not reproduced by the saved layout. Core now caps each sampled mouth reach to its own usable content interval. The new 0–1 lower mouth is −737.000 m, 6.5 m above FI content bottom, and strict layout validation passes.
|
||||||
|
- The Unreal adapter’s old SW search tried 648 fixed 64 m candidates; all were rejected on grade. The adapter now tries a denser bounded search with shorter lengths for a single-mouthed SW route. In the four-strate HEAD spawn, 35 candidates were eligible and the selected route passed with 0 blocked, 0 unsupported and 0 grade failures. The 33-point SW arrival-to-departure audit also passes with 0 unsupported points. L14 still cannot run this SW route.
|
||||||
|
- The passage interfaces remain the main failure. Their unsupported samples are concentrated in the composed passage floor even though the underlying native fields report 5/5 support probes at the corresponding segments. A four-strate grounded-tread experiment did not reduce these counts and introduced blocked samples in controls, so it was reverted. The 0–1 rock column also falls short of its relief threshold. At 1–2 the sampled floor jumps upward by 0.2498 m at 109.280 m along the passage; the source of that one-Core-voxel offset is not resolved.
|
||||||
|
- Internal-chain floor/support blending reduced 2–3 floor ripple from 3.57 cm to 0.10 cm and 1–2 from 4.78 cm to 2.90 cm. The 1–2 floor and wall ripple gates still fail. No full quality pass is claimed.
|
||||||
|
|
||||||
|
## Four-strate audit
|
||||||
|
|
||||||
|
Live input SHA-256: `f42f96adca58a4ebd680b8553b2969d6cd9ca8a73f875f042bfbc7b8c649fdca`. The generator and style maps were derived from that export: `SurfaceWorld`, `FloatingIslands`, `TunnelNetwork`, `VerticalShafts`; passage styles `{0:1, 1:1, 2:1, 3:1}`.
|
||||||
|
|
||||||
|
| Audit | Before | After L66 |
|
||||||
|
|---|---|---|
|
||||||
|
| Strict layout | Rejected 0–1: lower mouth −823.039 m, below FI | Accepted; clamped reach 318.539 → 232.500 m; lower mouth −737.000 m |
|
||||||
|
| SW route | 24/33 points unsupported; Unreal spawn sweep failed | 33 points, 0 unsupported, 0 grade failures; mouth binding passes |
|
||||||
|
| Transitions | Not accepted with the rejected layout | 3/3 pass |
|
||||||
|
| Per-strate generation | Not audited | Each generator produced 27 tiles; W1/W8 each produced 12 hashes and matched |
|
||||||
|
| Passage chain | 0/3 walkable | 0/3 walkable; detailed results below |
|
||||||
|
|
||||||
|
| Interface | Unsupported / blocked / grade failures | Wall ripple | Floor ripple | Other result |
|
||||||
|
|---|---:|---:|---:|---|
|
||||||
|
| 0–1 SW→FI | 59 / 0 / 0 | 1.49 cm, pass | 0.08 cm, pass | Column relief 0.172 m < 0.20 m; route support 46/46, wall margin 1.75 m |
|
||||||
|
| 1–2 FI→TN | 3 / 0 / 0 | 6.78 cm, fail | 2.90 cm, fail | 0.2498 m floor offset begins at passage distance 109.280 m |
|
||||||
|
| 2–3 TN→VS | 2 / 0 / 0 | 2.28 cm, pass | 0.10 cm, pass | Ripple passes; unsupported support probes remain |
|
||||||
|
|
||||||
|
The fast tier checks arrival-to-departure for each stratum through its adjacent interfaces. Those four gates fail because the interfaces above are not yet walkable. The standalone route audit for strate 0 (SW) passes.
|
||||||
|
|
||||||
|
Per-strate W1 cost (`whole_view_ready`, worker-seconds): SW 1.015 s / 0.839 s; FI 1.424 s / 1.254 s; TN 0.558 s / 0.388 s; VS 0.208 s / 0.043 s. Each run generated 27 tiles.
|
||||||
|
|
||||||
|
## Unreal trace — L14 vs HEAD
|
||||||
|
|
||||||
|
L14 could not run the four-strate plan, and its compact 16-chunk SurfaceWorld route also failed the fixed-length search. Both versions therefore used the same transient 16-chunk FlatPlain trace plan copied from `DA_Strate1`; `DA_Settings` and `DA_Strate1`–`DA_Strate4` hashes matched between hosts. Three interleaved 45 s traces completed per version. No Unreal build ran during the timing trials.
|
||||||
|
|
||||||
|
| Measure | L14 | HEAD |
|
||||||
|
|---|---:|---:|
|
||||||
|
| Cells per tile | 16³ = 4,096 | 16³ = 4,096 |
|
||||||
|
| Desired tiles LOD0–8 | 512/448/320/208/144/96/96/96/306 | same |
|
||||||
|
| Unique generated keys LOD0–8 | 1264/896/464/284/156/96/96/96/306 | 1272/896/464/284/156/96/96/96/306 |
|
||||||
|
| Generated records LOD0–8 over trace | 1355/928/464/284/156/96/96/96/306 | 1363/928/464/284/156/96/96/96/306 |
|
||||||
|
| Worker-seconds by LOD0–8 | 4.962/2.985/1.078/0.751/0.441/0.323/0.322/0.316/1.063 | 5.168/3.007/1.138/0.786/0.454/0.262/0.326/0.320/1.118 |
|
||||||
|
| Total worker-seconds | 12.200 | 12.561 |
|
||||||
|
| LOD0 worker p50 / p95 | 3.324 / 7.351 ms | 3.604 / 7.313 ms |
|
||||||
|
| `generateTile` / mesh copy / RMC build | 11.980 / 0.036 / 0.179 s | 12.358 / 0.032 / 0.167 s |
|
||||||
|
| Initial walk gate | 9/9 ready at 0.244 s | 9/9 ready at 0.249 s |
|
||||||
|
| LOD0 ring | 512 tiles ready at 0.827 s | 512 tiles ready at 0.823 s |
|
||||||
|
| Full set | 2226/2226 at 3.326 s | 2226/2226 at 3.309 s |
|
||||||
|
| Median trace wall time | 45.037 s | 45.027 s |
|
||||||
|
|
||||||
|
Core `generateTile` accounts for 98.2% of measured L14 phase time and 98.4% for HEAD. Mesh copy is 0.29% / 0.25%; RMC build is 1.46% / 1.33%. Each version passed 12 ready gates through movement; the full-set trace had 125 collision tiles desired and 78 ready at the recorded completion point.
|
||||||
|
|
||||||
|
## Verification tiers
|
||||||
|
|
||||||
|
- `Standalone/data/regression/owner_world_2strate_l61.json` pins the L64 two-strate export; SHA-256 `7b095f9bcc6b90133d5a463f99f0b571133b2c549ce1ecf2d64fe9175e41e183`. All 60 fast-tier runs that consume owner data used this fixture. Legacy gates, including styles, passage audits, W1/W12 determinism, L52 hash comparisons, rings and controls, passed. L46 hash comparisons remain non-gating diagnostics and differ; L53 diagnostics and gated L52 hashes match.
|
||||||
|
- `verify.py` reads the live four-strate generator/style expectations from `owner_world.json`; legacy helper scripts receive `VOXELFORGE_OWNER_DATA` so they use the pinned fixture. The fast summary is **FAIL** only because the new-plan stage records nine failed interface/column/ripple gates and then fails its final stage gate. Generation, W1/W8 determinism, strict layout and transition checks pass.
|
||||||
|
- `Standalone/Tools/verify/README.md` documents the two input sets and deferred fast-tier result.
|
||||||
|
|
||||||
|
## Unreal source files for the owner's Windows build
|
||||||
|
|
||||||
|
- `Source/VoxelForge/Private/VoxelForgeCoreAdapter.cpp`
|
||||||
|
- `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp`
|
||||||
|
|
||||||
|
Both Linux L14 and HEAD host builds completed successfully. These are the two `Source/` files for the owner's Windows build.
|
||||||
|
|
||||||
|
## Failed runs
|
||||||
|
|
||||||
|
- An initial L14 trace used an output path outside the plugin `Saved` directory; the commandlet rejected it.
|
||||||
|
- The L14 compact SurfaceWorld attempt found no eligible 64 m candidate. The common FlatPlain plan was used for the paired timing traces.
|
||||||
|
- The first fast-tier rerun exposed hard-coded live-owner paths in the ring and motif fixture helpers. Both were switched to the pinned input; the subsequent run passed all legacy gates.
|
||||||
|
- The final fast tier remains red on the four-strate gates listed above.
|
||||||
|
- The grounded-tread support experiment compiled, but left unsupported counts at 59/3/2 and added blocked control samples; its source change was reverted.
|
||||||
@@ -1950,6 +1950,10 @@ int main(int argc,char** argv)
|
|||||||
&&stratum.passageCapabilities.hasNativeLatticeField;});
|
&&stratum.passageCapabilities.hasNativeLatticeField;});
|
||||||
const auto actualRecipe=hasNativeLatticeRecipe?std::string_view("lattice")
|
const auto actualRecipe=hasNativeLatticeRecipe?std::string_view("lattice")
|
||||||
:hasNativeCavesRecipe?std::string_view("caves")
|
:hasNativeCavesRecipe?std::string_view("caves")
|
||||||
|
:requestedRecipe=="surface-world"&&hasSurfaceWorldPlan
|
||||||
|
?std::string_view("surface-world")
|
||||||
|
:requestedRecipe=="floating-islands"&&hasFloatingIslands
|
||||||
|
?std::string_view("floating-islands")
|
||||||
:hasFloatingIslands?std::string_view("floating-islands")
|
:hasFloatingIslands?std::string_view("floating-islands")
|
||||||
:hasSurfaceWorldPlan?std::string_view("surface-world")
|
:hasSurfaceWorldPlan?std::string_view("surface-world")
|
||||||
:std::string_view("missing");
|
:std::string_view("missing");
|
||||||
|
|||||||
@@ -2994,6 +2994,11 @@ namespace StandaloneStreamBenchmark
|
|||||||
appendDetrendedProfile(radii,start,windowSamples);
|
appendDetrendedProfile(radii,start,windowSamples);
|
||||||
double localPeriod=0.0;
|
double localPeriod=0.0;
|
||||||
const double localAmplitude=sinusoidAmplitude(windowSamples,localPeriod);
|
const double localAmplitude=sinusoidAmplitude(windowSamples,localPeriod);
|
||||||
|
std::printf("[PassageWallRippleWindow] id=%s start_m=%.3f "
|
||||||
|
"period_m=%.3f amplitude_m=%.4f coverage=%zu/%zu threshold_m=0.035\n",
|
||||||
|
passage.stableId.c_str(),start,localPeriod,localAmplitude,
|
||||||
|
radii.size(),static_cast<std::size_t>(WindowLengthMetres/
|
||||||
|
SampleSpacingMetres)+1);
|
||||||
if(localAmplitude>audit.localPeakAmplitudeMetres)
|
if(localAmplitude>audit.localPeakAmplitudeMetres)
|
||||||
{
|
{
|
||||||
audit.localPeakAmplitudeMetres=localAmplitude;
|
audit.localPeakAmplitudeMetres=localAmplitude;
|
||||||
|
|||||||
@@ -47,7 +47,9 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
std::uint64_t& gradeRejected,std::uint64_t& placementRejected,
|
std::uint64_t& gradeRejected,std::uint64_t& placementRejected,
|
||||||
std::uint64_t& sweepRejected,std::uint32_t coreVoxelSizeMicrons,
|
std::uint64_t& sweepRejected,std::uint32_t coreVoxelSizeMicrons,
|
||||||
std::int64_t arrivalRadiusSubvoxels,
|
std::int64_t arrivalRadiusSubvoxels,
|
||||||
std::int64_t departureRadiusSubvoxels,std::string& error)
|
std::int64_t departureRadiusSubvoxels,std::string& error,
|
||||||
|
bool multiStratePlan=false,
|
||||||
|
bool fallbackSearch=false)
|
||||||
{
|
{
|
||||||
using namespace VoxelForgeCore;
|
using namespace VoxelForgeCore;
|
||||||
constexpr std::int64_t PlayerRadiusQ=348; // 0.34 m in Core's 0.25 m voxel scale.
|
constexpr std::int64_t PlayerRadiusQ=348; // 0.34 m in Core's 0.25 m voxel scale.
|
||||||
@@ -130,6 +132,26 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
}
|
}
|
||||||
candidateCenterSpacing=32;
|
candidateCenterSpacing=32;
|
||||||
}
|
}
|
||||||
|
else if(fallbackSearch&&(arrivalRadiusSubvoxels>0||departureRadiusSubvoxels>0))
|
||||||
|
{
|
||||||
|
// Keep the original route candidates first. If their composed-field
|
||||||
|
// sweep fails, use short, origin-biased chords that can meet the one
|
||||||
|
// bounded mouth used by an outer strate.
|
||||||
|
constexpr std::int64_t placementMarginQ=12*WorldAssemblySubvoxelsPerVoxel;
|
||||||
|
const std::int64_t availableLengthVoxels=std::max<std::int64_t>(1,
|
||||||
|
(arrivalRadiusSubvoxels+departureRadiusSubvoxels-placementMarginQ)
|
||||||
|
/WorldAssemblySubvoxelsPerVoxel);
|
||||||
|
const std::int32_t maximumHalfLength=static_cast<std::int32_t>(std::min<std::int64_t>(128,
|
||||||
|
std::max<std::int64_t>(1,availableLengthVoxels/2)));
|
||||||
|
candidateHalfLengths.clear();
|
||||||
|
for(const std::int32_t numerator:std::array<std::int32_t,6>{{8,7,6,5,4,3}})
|
||||||
|
{
|
||||||
|
const std::int32_t halfLength=std::max(1,maximumHalfLength*numerator/8);
|
||||||
|
if(std::find(candidateHalfLengths.begin(),candidateHalfLengths.end(),halfLength)
|
||||||
|
==candidateHalfLengths.end())candidateHalfLengths.push_back(halfLength);
|
||||||
|
}
|
||||||
|
candidateCenterSpacing=32;
|
||||||
|
}
|
||||||
const double coreVoxelMetres=static_cast<double>(coreVoxelSizeMicrons)/1000000.0;
|
const double coreVoxelMetres=static_cast<double>(coreVoxelSizeMicrons)/1000000.0;
|
||||||
if(std::getenv("VOXELFORGE_AUDIT_LEGACY_ROUTE_GRADE"))
|
if(std::getenv("VOXELFORGE_AUDIT_LEGACY_ROUTE_GRADE"))
|
||||||
{
|
{
|
||||||
@@ -245,12 +267,20 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
candidate.endpointProximityCost+=static_cast<std::uint64_t>(
|
candidate.endpointProximityCost+=static_cast<std::uint64_t>(
|
||||||
targetRadiusQ*targetRadiusQ)*64ull;
|
targetRadiusQ*targetRadiusQ)*64ull;
|
||||||
}
|
}
|
||||||
else if(arrivalRadiusSubvoxels>0&&departureRadiusSubvoxels>0
|
else if(((arrivalRadiusSubvoxels>0&&departureRadiusSubvoxels>0)
|
||||||
|
||(!fallbackSearch&&multiStratePlan
|
||||||
|
&&arrivalRadiusSubvoxels==0
|
||||||
|
&&departureRadiusSubvoxels>0)
|
||||||
|
||(fallbackSearch&&multiStratePlan
|
||||||
|
&&(arrivalRadiusSubvoxels>0||departureRadiusSubvoxels>0)))
|
||||||
&&(index==0||index==32))
|
&&(index==0||index==32))
|
||||||
{
|
{
|
||||||
const std::uint64_t radiusSquared=static_cast<std::uint64_t>(
|
const std::uint64_t radiusSquared=static_cast<std::uint64_t>(
|
||||||
xQ*xQ+yQ*yQ);
|
xQ*xQ+yQ*yQ);
|
||||||
candidate.endpointProximityCost+=radiusSquared*(index==0?16ull:1ull);
|
if(index==0&&arrivalRadiusSubvoxels>0)
|
||||||
|
candidate.endpointProximityCost+=radiusSquared*16ull;
|
||||||
|
if(index==32&&departureRadiusSubvoxels>0)
|
||||||
|
candidate.endpointProximityCost+=radiusSquared;
|
||||||
}
|
}
|
||||||
const auto inRadius=[](std::int64_t xValue,std::int64_t yValue,std::int64_t radius)
|
const auto inRadius=[](std::int64_t xValue,std::int64_t yValue,std::int64_t radius)
|
||||||
{return radius<=0||xValue*xValue+yValue*yValue<=radius*radius;};
|
{return radius<=0||xValue*xValue+yValue*yValue<=radius*radius;};
|
||||||
@@ -412,6 +442,16 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
++sweepAttempt;
|
++sweepAttempt;
|
||||||
++sweepRejected;
|
++sweepRejected;
|
||||||
}
|
}
|
||||||
|
if(!fallbackSearch&&multiStratePlan
|
||||||
|
&&(arrivalRadiusSubvoxels>0||departureRadiusSubvoxels>0))
|
||||||
|
{
|
||||||
|
std::printf("[WorldAssemblerRouteFallback] reason=bounded-single-mouth arrival_radius_m=%.3f departure_radius_m=%.3f\n",
|
||||||
|
arrivalRadiusSubvoxels*coreVoxelMetres/WorldAssemblySubvoxelsPerVoxel,
|
||||||
|
departureRadiusSubvoxels*coreVoxelMetres/WorldAssemblySubvoxelsPerVoxel);
|
||||||
|
return BuildFiniteStrateRoute(recipe,stratum,tested,gradeRejected,placementRejected,
|
||||||
|
sweepRejected,coreVoxelSizeMicrons,arrivalRadiusSubvoxels,
|
||||||
|
departureRadiusSubvoxels,error,multiStratePlan,true);
|
||||||
|
}
|
||||||
std::printf("[WorldAssemblerRouteSearchFailed] tested=%llu grade_rejected=%llu sweepable_candidates=%llu sweep_rejected=%llu last_blocked=%llu last_unsupported=%llu last_grade_failures=%llu\n",
|
std::printf("[WorldAssemblerRouteSearchFailed] tested=%llu grade_rejected=%llu sweepable_candidates=%llu sweep_rejected=%llu last_blocked=%llu last_unsupported=%llu last_grade_failures=%llu\n",
|
||||||
static_cast<unsigned long long>(tested),static_cast<unsigned long long>(gradeRejected),
|
static_cast<unsigned long long>(tested),static_cast<unsigned long long>(gradeRejected),
|
||||||
static_cast<unsigned long long>(candidates.size()),static_cast<unsigned long long>(sweepRejected),
|
static_cast<unsigned long long>(candidates.size()),static_cast<unsigned long long>(sweepRejected),
|
||||||
@@ -847,7 +887,7 @@ namespace StandaloneWorldAssemblyPrototype
|
|||||||
std::uint64_t routeCandidates=0,routeGradeRejected=0,routePlacementRejected=0,routeSweepRejected=0;
|
std::uint64_t routeCandidates=0,routeGradeRejected=0,routePlacementRejected=0,routeSweepRejected=0;
|
||||||
if(!BuildFiniteStrateRoute(recipe,stratum,routeCandidates,routeGradeRejected,
|
if(!BuildFiniteStrateRoute(recipe,stratum,routeCandidates,routeGradeRejected,
|
||||||
routePlacementRejected,routeSweepRejected,owner.CoreVoxelSizeMicrons,
|
routePlacementRejected,routeSweepRejected,owner.CoreVoxelSizeMicrons,
|
||||||
arrivalRadiusQ,departureRadiusQ,error))return false;
|
arrivalRadiusQ,departureRadiusQ,error,owner.Manager->GetLayout().Num()>2))return false;
|
||||||
std::printf("[WorldAssemblerRoutePlan] candidates=%llu grade_rejected=%llu placement_rejected=%llu sweep_rejected=%llu accepted_points=%zu capsule_m=0.34x1.76 corridor_radius_vox=4 support_probe_points=5 minimum_support_probes=%u arrival_radius_m=%.3f departure_radius_m=%.3f status=PASS\n",
|
std::printf("[WorldAssemblerRoutePlan] candidates=%llu grade_rejected=%llu placement_rejected=%llu sweep_rejected=%llu accepted_points=%zu capsule_m=0.34x1.76 corridor_radius_vox=4 support_probe_points=5 minimum_support_probes=%u arrival_radius_m=%.3f departure_radius_m=%.3f status=PASS\n",
|
||||||
static_cast<unsigned long long>(routeCandidates),
|
static_cast<unsigned long long>(routeCandidates),
|
||||||
static_cast<unsigned long long>(routeGradeRejected),
|
static_cast<unsigned long long>(routeGradeRejected),
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s python3 -B Standalone/T
|
|||||||
taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s python3 -B Standalone/Tools/verify/verify.py full
|
taskset -c 0-11 prlimit --as=6442450944 -- timeout 1740s python3 -B Standalone/Tools/verify/verify.py full
|
||||||
```
|
```
|
||||||
|
|
||||||
`fast` checks only canonical `Standalone/data/owner_world.json`, asserts FI Style 2 and SW Style 1 from resolved snapshots, runs passage wall/floor ripple and coverage gates, the adjacent-profile step limit, W1/W12 hash comparisons, and LOD0 ring profiles with the L30/L31 controls. It also exercises stale-source, style-mislabel, black-capture, historical ripple, and L38 step controls.
|
`fast` runs legacy owner regressions against the pinned two-strate fixture at `Standalone/data/regression/owner_world_2strate_l61.json`, including resolved styles, passage wall/floor ripple and coverage gates, adjacent-profile step, W1/W12 hashes, and LOD0 ring profiles with the L30/L31 controls. It also checks the live four-strate `Standalone/data/owner_world.json`: generator and resolved-style maps are read from that export, then strict layout, transitions, per-strate deterministic generation, arrival-to-departure routes, and spawn costs are audited. The tier also exercises stale-source, style-mislabel, black-capture, historical ripple, and L38 step controls.
|
||||||
|
|
||||||
`medium` adds the Clang style-override matrix, transitions, route/proof audits, LOD0–8 rings, and canonical-owner captures, shape, near/overview crease, and fragment checks. Run `medium` before `full`. Full reuses that evidence only when the source stamp, verification-tool hashes, binaries, inputs, logs, and audit-script hashes still match; otherwise it reruns the Clang medium work. It adds the owner/style/transition/proof matrix with GCC, the 30-cell determinism matrix, and five interleaved L36/L39 cost pairs per recipe. The final captures stay in the L39 output directory.
|
`medium` adds the Clang style-override matrix, transitions, route/proof audits, LOD0–8 rings, and pinned two-strate owner captures, shape, near/overview crease, and fragment checks. Run `medium` before `full`. Full reuses that evidence only when the source stamp, verification-tool hashes, binaries, inputs, logs, and audit-script hashes still match; otherwise it reruns the Clang medium work. It adds the owner/style/transition/proof matrix with GCC, the 30-cell determinism matrix, and five interleaved L36/L39 cost pairs per recipe. The final captures stay in the L39 output directory.
|
||||||
|
|
||||||
`BuildLinux.sh` hashes a sorted manifest of Core inputs, standalone sources and generated wrappers, build files, and optionally the Unreal adapter when `VOXELFORGE_INCLUDE_UNREAL_ADAPTER=1`. The manifest hash is compiled into the headless and offscreen binaries and printed before any other runtime log line. Each tier probes every available built executable at startup and re-probes it at completion, rejecting stale stamps or binary changes. Summaries store tool hashes, binary and JSON SHA-256 values, resolved recipe/style/layout identity, commands, elapsed time, and audit-script hashes.
|
`BuildLinux.sh` hashes a sorted manifest of Core inputs, standalone sources and generated wrappers, build files, and optionally the Unreal adapter when `VOXELFORGE_INCLUDE_UNREAL_ADAPTER=1`. The manifest hash is compiled into the headless and offscreen binaries and printed before any other runtime log line. Each tier probes every available built executable at startup and re-probes it at completion, rejecting stale stamps or binary changes. Summaries store tool hashes, binary and JSON SHA-256 values, resolved recipe/style/layout identity, commands, elapsed time, and audit-script hashes.
|
||||||
|
|
||||||
Each tier stops at its first failed gate. Every process is limited to CPUs 0–11 and 6 GiB of address space; individual build and audit processes time out at 30 minutes. Summary files are `fast-summary.json`, `medium-summary.json`, and `full-summary.json`.
|
Medium and full stop at their first failed gate. Fast defers a four-strate stage failure until after it has collected the pinned two-strate regression evidence. Every process is limited to CPUs 0–11 and 6 GiB of address space; individual build and audit processes time out at 30 minutes. Summary files are `fast-summary.json`, `medium-summary.json`, and `full-summary.json`.
|
||||||
@@ -29,7 +29,9 @@ LINUX54 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux54"
|
|||||||
LINUX56 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux56"
|
LINUX56 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux56"
|
||||||
LINUX58 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux58"
|
LINUX58 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux58"
|
||||||
LINUX60 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux60"
|
LINUX60 = ROOT / "Standalone/Research/WorldAssemblerM3/Linux60"
|
||||||
OWNER = ROOT / "Standalone/data/owner_world.json"
|
OWNER = ROOT / "Standalone/data/regression/owner_world_2strate_l61.json"
|
||||||
|
OWNER_NEW = ROOT / "Standalone/data/owner_world.json"
|
||||||
|
OWNER_2STRATE_SHA256 = "7b095f9bcc6b90133d5a463f99f0b571133b2c549ce1ecf2d64fe9175e41e183"
|
||||||
OUT_DEFAULT = ROOT / "Saved/Linux/l42"
|
OUT_DEFAULT = ROOT / "Saved/Linux/l42"
|
||||||
|
|
||||||
PARSER = argparse.ArgumentParser(description=__doc__)
|
PARSER = argparse.ArgumentParser(description=__doc__)
|
||||||
@@ -75,6 +77,10 @@ PROFILE_CAMERA_SWEEP_SEED = 0x4C3533
|
|||||||
CAVES_CAMERA_SWEEP_SEED = 0x4C3534
|
CAVES_CAMERA_SWEEP_SEED = 0x4C3534
|
||||||
LATTICE_CAMERA_SWEEP_SEED = 0x4C3538
|
LATTICE_CAMERA_SWEEP_SEED = 0x4C3538
|
||||||
PROFILE_NAMES = ("periodicHills", "mirroredRelief", "flatField")
|
PROFILE_NAMES = ("periodicHills", "mirroredRelief", "flatField")
|
||||||
|
GENERATOR_TYPE_NAMES = {
|
||||||
|
0: "TunnelNetwork", 1: "FlatPlain", 2: "CrystalChamber", 3: "Maze",
|
||||||
|
4: "SurfaceWorld", 5: "VerticalShafts", 6: "FloatingIslands", 7: "Underwater",
|
||||||
|
}
|
||||||
SPEED_MATRIX_REPETITIONS = 5
|
SPEED_MATRIX_REPETITIONS = 5
|
||||||
SPEED_MATRIX_TILE_RANGE = (1800, 2500)
|
SPEED_MATRIX_TILE_RANGE = (1800, 2500)
|
||||||
SPEED_MATRIX_LOD_TILE_RANGES = (
|
SPEED_MATRIX_LOD_TILE_RANGES = (
|
||||||
@@ -82,6 +88,14 @@ SPEED_MATRIX_LOD_TILE_RANGES = (
|
|||||||
(60, 220), (48, 180), (24, 160), (16, 100),
|
(60, 220), (48, 180), (24, 160), (16, 100),
|
||||||
)
|
)
|
||||||
SPEED_MATRIX_CAMERA_M = (-12.0, -118.0, -20.0, 0.0, 0.0)
|
SPEED_MATRIX_CAMERA_M = (-12.0, -118.0, -20.0, 0.0, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
def sha256_file(path: Path) -> str:
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
with path.open("rb") as source:
|
||||||
|
for block in iter(lambda: source.read(1024 * 1024), b""):
|
||||||
|
digest.update(block)
|
||||||
|
return digest.hexdigest()
|
||||||
RECIPE_RE = re.compile(
|
RECIPE_RE = re.compile(
|
||||||
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$", re.M)
|
r"^\[WorldAssemblerRecipeAudit\] requested=([^ ]+) actual=([^ ]+).*status=([^ ]+)$", re.M)
|
||||||
|
|
||||||
@@ -93,6 +107,7 @@ def compiler_name(binary: Path) -> str:
|
|||||||
def env_for(*, capture: bool = False) -> dict[str, str]:
|
def env_for(*, capture: bool = False) -> dict[str, str]:
|
||||||
result = os.environ.copy()
|
result = os.environ.copy()
|
||||||
result["VOXELFORGE_VERIFY_OUT"] = str(OUT)
|
result["VOXELFORGE_VERIFY_OUT"] = str(OUT)
|
||||||
|
result["VOXELFORGE_OWNER_DATA"] = str(OWNER)
|
||||||
result["VOXELFORGE_L37_OUT"] = str(OUT)
|
result["VOXELFORGE_L37_OUT"] = str(OUT)
|
||||||
result["VOXELFORGE_L37_CAPTURE_DIR"] = str(OUT / "captures")
|
result["VOXELFORGE_L37_CAPTURE_DIR"] = str(OUT / "captures")
|
||||||
result["VOXELFORGE_L37_HEADLESS"] = str(CLANG)
|
result["VOXELFORGE_L37_HEADLESS"] = str(CLANG)
|
||||||
@@ -113,7 +128,16 @@ def parse_ratio(text: str, name: str) -> float | None:
|
|||||||
return int(match.group(1)) / max(1, int(match.group(2))) if match else None
|
return int(match.group(1)) / max(1, int(match.group(2))) if match else None
|
||||||
|
|
||||||
|
|
||||||
def validate_passage(runner: VerificationRunner, name: str, output: str) -> None:
|
def defer_requirement(runner: VerificationRunner, name: str, passed: bool,
|
||||||
|
detail: str = "") -> bool:
|
||||||
|
runner.steps.append({"name": name, "status": "PASS" if passed else "FAIL",
|
||||||
|
"detail": detail})
|
||||||
|
print(f"[{runner.tier}] {name} {'PASS' if passed else 'FAIL'} {detail}", flush=True)
|
||||||
|
return passed
|
||||||
|
|
||||||
|
|
||||||
|
def validate_passage(runner: VerificationRunner, name: str, output: str,
|
||||||
|
*, defer: bool = False) -> bool:
|
||||||
required = (
|
required = (
|
||||||
("strict-layout", r"^\[ResolvedLayoutStrict\] accepted=true issues=0 error=$"),
|
("strict-layout", r"^\[ResolvedLayoutStrict\] accepted=true issues=0 error=$"),
|
||||||
("layout-controls", r"^\[ResolvedLayoutValidatorControls\].*status=PASS$"),
|
("layout-controls", r"^\[ResolvedLayoutValidatorControls\].*status=PASS$"),
|
||||||
@@ -128,9 +152,15 @@ def validate_passage(runner: VerificationRunner, name: str, output: str) -> None
|
|||||||
("ripple-controls", r"^\[PassageRippleAuditSummary\].*status=PASS$"),
|
("ripple-controls", r"^\[PassageRippleAuditSummary\].*status=PASS$"),
|
||||||
("passage-summary", r"^\[ResolvedPassageAuditSummary\].*route_binding_failures=0 .*status=PASS$"),
|
("passage-summary", r"^\[ResolvedPassageAuditSummary\].*route_binding_failures=0 .*status=PASS$"),
|
||||||
)
|
)
|
||||||
|
passed_all = True
|
||||||
for gate, pattern in required:
|
for gate, pattern in required:
|
||||||
runner.require(f"{name}:{gate}", all_match(output, pattern),
|
passed = all_match(output, pattern)
|
||||||
"required resolved-passage gate")
|
passed_all &= passed
|
||||||
|
if defer:
|
||||||
|
defer_requirement(runner, f"{name}:{gate}", passed,
|
||||||
|
"required resolved-passage gate")
|
||||||
|
else:
|
||||||
|
runner.require(f"{name}:{gate}", passed, "required resolved-passage gate")
|
||||||
|
|
||||||
rows = re.findall(r"^\[PassageRippleAudit\].*$", output, re.M)
|
rows = re.findall(r"^\[PassageRippleAudit\].*$", output, re.M)
|
||||||
ripple_ok = bool(rows)
|
ripple_ok = bool(rows)
|
||||||
@@ -149,8 +179,13 @@ def validate_passage(runner: VerificationRunner, name: str, output: str) -> None
|
|||||||
and float(wall.group(1)) <= float(wall_threshold.group(1))
|
and float(wall.group(1)) <= float(wall_threshold.group(1))
|
||||||
and float(floor.group(1)) <= float(floor_threshold.group(1))
|
and float(floor.group(1)) <= float(floor_threshold.group(1))
|
||||||
and wall_coverage >= 0.75 and floor_coverage >= 0.75)
|
and wall_coverage >= 0.75 and floor_coverage >= 0.75)
|
||||||
runner.require(f"{name}:wall-floor-ripple-and-coverage", ripple_ok,
|
passed_all &= ripple_ok
|
||||||
f"profiles={len(rows)} wall/floor amplitudes under their thresholds; coverage>=0.75")
|
detail = f"profiles={len(rows)} wall/floor amplitudes under their thresholds; coverage>=0.75"
|
||||||
|
if defer:
|
||||||
|
defer_requirement(runner, f"{name}:wall-floor-ripple-and-coverage", ripple_ok, detail)
|
||||||
|
else:
|
||||||
|
runner.require(f"{name}:wall-floor-ripple-and-coverage", ripple_ok, detail)
|
||||||
|
return passed_all
|
||||||
|
|
||||||
|
|
||||||
def run_passage(runner: VerificationRunner, binary: Path, name: str,
|
def run_passage(runner: VerificationRunner, binary: Path, name: str,
|
||||||
@@ -200,6 +235,198 @@ def run_owner_passages(runner: VerificationRunner, binaries: tuple[Path, ...]) -
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def owner_plan_expectations(data_path: Path) -> tuple[dict[str, str], dict[str, int]]:
|
||||||
|
document = json.loads(data_path.read_text(encoding="utf-8"))
|
||||||
|
rows = document.get("strates", [])
|
||||||
|
if not rows:
|
||||||
|
raise VerificationFailure(f"new owner plan has no strate records: {data_path}")
|
||||||
|
generators: dict[str, str] = {}
|
||||||
|
styles: dict[str, int] = {}
|
||||||
|
for row in rows:
|
||||||
|
index = str(int(row["index"]))
|
||||||
|
generator_id = int(row["asset"]["GeneratorType"])
|
||||||
|
if generator_id not in GENERATOR_TYPE_NAMES:
|
||||||
|
raise VerificationFailure(
|
||||||
|
f"new owner plan has unknown GeneratorType={generator_id} at strate {index}")
|
||||||
|
generators[index] = GENERATOR_TYPE_NAMES[generator_id]
|
||||||
|
styles[index] = int(row["passage_config"]["Style"])
|
||||||
|
fixed = {str(int(entry["key"])) for entry in
|
||||||
|
document.get("settings_asset", {}).get("FixedStrates", [])}
|
||||||
|
if fixed != set(generators):
|
||||||
|
raise VerificationFailure(
|
||||||
|
f"new owner FixedStrates keys differ from its strate records: {fixed} != {set(generators)}")
|
||||||
|
return generators, styles
|
||||||
|
|
||||||
|
|
||||||
|
def run_new_owner_plan_stage(runner: VerificationRunner, binary: Path) -> None:
|
||||||
|
"""Audit, generate, and compare the current owner export without legacy expectations."""
|
||||||
|
deferred_start = len(runner.steps)
|
||||||
|
expected_generators, expected_styles = owner_plan_expectations(OWNER_NEW)
|
||||||
|
if len(expected_generators) != 4:
|
||||||
|
raise VerificationFailure(
|
||||||
|
f"new owner plan should contain four strata, found {len(expected_generators)}")
|
||||||
|
document = json.loads(OWNER_NEW.read_text(encoding="utf-8"))
|
||||||
|
ordered_strates = sorted(document["strates"],
|
||||||
|
key=lambda row: int(row.get("top_chunk_z", 0)), reverse=True)
|
||||||
|
upper_styles = {expected_styles[str(int(row["index"]))]
|
||||||
|
for row in ordered_strates[:-1]}
|
||||||
|
unique_styles = upper_styles
|
||||||
|
expected_passage_style = next(iter(unique_styles)) if len(unique_styles) == 1 else None
|
||||||
|
compiler = compiler_name(binary)
|
||||||
|
layout_path = OUT / "layouts" / f"owner-new-plan-{compiler}-w1.json"
|
||||||
|
core_voxel_metres = float(document.get("voxel_size_cm", 25.0)) / 100.0
|
||||||
|
viewer = document.get("viewer_start", {})
|
||||||
|
camera_by_index: dict[str, tuple[float, float, float]] = {}
|
||||||
|
for row in document["strates"]:
|
||||||
|
index = str(int(row["index"]))
|
||||||
|
camera_by_index[index] = (
|
||||||
|
float(viewer.get("x", 0.0)) * core_voxel_metres,
|
||||||
|
float(viewer.get("y", 0.0)) * core_voxel_metres,
|
||||||
|
float(viewer.get("z", 0.0)) * core_voxel_metres,
|
||||||
|
)
|
||||||
|
|
||||||
|
def camera_flag(position: tuple[float, float, float]) -> str:
|
||||||
|
return "--camera=" + ",".join(f"{value:.6f}" for value in (*position, 0.0, 0.0))
|
||||||
|
|
||||||
|
common_flags = ["--stream-hash", "--max-clip-level=0",
|
||||||
|
"--render-distance-chunks=0"]
|
||||||
|
stride_indices = sorted(expected_generators, key=int)
|
||||||
|
audit_row, audit_output = runner.run_cpp(
|
||||||
|
f"owner-new-plan-{compiler}-w1", binary, OWNER_NEW, "surface-world",
|
||||||
|
[*common_flags, "--audit-resolved-layout-passages",
|
||||||
|
camera_flag(camera_by_index[stride_indices[0]])],
|
||||||
|
expected_style=expected_passage_style, expected_strate_styles=expected_styles,
|
||||||
|
workers=1, max_level=0, clip_radius=1, layout_path=layout_path,
|
||||||
|
allow_nonzero=True)
|
||||||
|
validate_passage(runner, f"owner-new-plan-{compiler}-w1", audit_output, defer=True)
|
||||||
|
actual_generators = audit_row.get("layout", {}).get("strata", {})
|
||||||
|
defer_requirement(runner, f"owner-new-plan-{compiler}:generator-map",
|
||||||
|
actual_generators == expected_generators,
|
||||||
|
f"resolved={actual_generators} expected_from_owner_export={expected_generators}")
|
||||||
|
audit_layout = json.loads(layout_path.read_text(encoding="utf-8"))
|
||||||
|
actual_styles = {str(index): int(value) for index, value in
|
||||||
|
audit_layout.get("resolved_strate_passage_styles", {}).items()}
|
||||||
|
defer_requirement(runner, f"owner-new-plan-{compiler}:style-map",
|
||||||
|
actual_styles == expected_styles,
|
||||||
|
f"resolved={actual_styles} expected_from_owner_export={expected_styles}")
|
||||||
|
interface_rows = re.findall(r"^\[ResolvedPassageAudit\].*$", audit_output, re.M)
|
||||||
|
chain_passed = (
|
||||||
|
len(interface_rows) == max(0, len(expected_generators) - 1)
|
||||||
|
and all("walkability=PASS" in line and "mouth_content=2/2" in line
|
||||||
|
and "mouth_radius=2/2" in line for line in interface_rows))
|
||||||
|
defer_requirement(runner, f"owner-new-plan-{compiler}:arrival-departure-chain",
|
||||||
|
chain_passed,
|
||||||
|
f"interfaces={len(interface_rows)}/{max(0, len(expected_generators) - 1)} all_walkable=true")
|
||||||
|
interface_by_pair: dict[tuple[int, int], str] = {}
|
||||||
|
for line in interface_rows:
|
||||||
|
match = re.search(r"interface=(\d+)-(\d+)", line)
|
||||||
|
if match:
|
||||||
|
interface_by_pair[(int(match.group(1)), int(match.group(2)))] = line
|
||||||
|
for index in sorted(expected_generators, key=int):
|
||||||
|
strate = int(index)
|
||||||
|
incident_pairs = []
|
||||||
|
if strate > 0:
|
||||||
|
incident_pairs.append((strate - 1, strate))
|
||||||
|
if strate + 1 < len(expected_generators):
|
||||||
|
incident_pairs.append((strate, strate + 1))
|
||||||
|
walkable = bool(incident_pairs) and all(
|
||||||
|
pair in interface_by_pair
|
||||||
|
and "walkability=PASS" in interface_by_pair[pair]
|
||||||
|
and "mouth_content=2/2" in interface_by_pair[pair]
|
||||||
|
and "mouth_radius=2/2" in interface_by_pair[pair]
|
||||||
|
for pair in incident_pairs)
|
||||||
|
defer_requirement(runner,
|
||||||
|
f"owner-new-plan-{compiler}:strate{strate}:arrival-departure-walkable",
|
||||||
|
walkable, f"adjacent_interfaces={incident_pairs}")
|
||||||
|
transition_name = f"owner-new-plan-transition-{compiler}"
|
||||||
|
transition_row, transition_output = runner.run_cpp(
|
||||||
|
transition_name, binary, OWNER_NEW, "surface-world",
|
||||||
|
["--max-clip-level=0", "--render-distance-chunks=0",
|
||||||
|
camera_flag(camera_by_index[stride_indices[0]]),
|
||||||
|
"--audit-strate-transitions"],
|
||||||
|
expected_style=expected_passage_style, expected_strate_styles=expected_styles,
|
||||||
|
workers=1, max_level=0, clip_radius=1,
|
||||||
|
layout_path=OUT / "layouts" / f"{transition_name}.json",
|
||||||
|
allow_nonzero=True)
|
||||||
|
transition_generators = transition_row.get("layout", {}).get("strata", {})
|
||||||
|
defer_requirement(runner, f"owner-new-plan-{compiler}:transition-generator-map",
|
||||||
|
transition_generators == expected_generators,
|
||||||
|
f"resolved={transition_generators} expected={expected_generators}")
|
||||||
|
transition_rows = re.findall(r"^\[StrateTransitionInterface\].*$", transition_output, re.M)
|
||||||
|
transition_passed = (
|
||||||
|
len(transition_rows) == max(0, len(expected_generators) - 1)
|
||||||
|
and all("status=PASS" in line for line in transition_rows))
|
||||||
|
defer_requirement(runner, f"owner-new-plan-{compiler}:transition-audit",
|
||||||
|
transition_passed, f"transition_rows={len(transition_rows)}")
|
||||||
|
|
||||||
|
passage_cameras: dict[str, tuple[float, float, float]] = {}
|
||||||
|
for passage in audit_layout.get("passages", []):
|
||||||
|
upper = str(int(passage["upper_strate_index"]))
|
||||||
|
lower = str(int(passage["lower_strate_index"]))
|
||||||
|
scale = float(audit_layout["core_voxel_size_um"]) / 256_000_000.0
|
||||||
|
upper_q = passage["upper_mouth_q"]
|
||||||
|
lower_q = passage["lower_mouth_q"]
|
||||||
|
passage_cameras.setdefault(upper, tuple(float(value) * scale for value in upper_q))
|
||||||
|
passage_cameras[lower] = tuple(float(value) * scale for value in lower_q)
|
||||||
|
for index in stride_indices:
|
||||||
|
if index in passage_cameras:
|
||||||
|
camera_by_index[index] = passage_cameras[index]
|
||||||
|
|
||||||
|
hashes_by_strate: dict[str, tuple[str, ...]] = {index: tuple() for index in stride_indices}
|
||||||
|
spawn_rows: list[dict[str, object]] = []
|
||||||
|
for index in stride_indices:
|
||||||
|
outputs: dict[int, str] = {}
|
||||||
|
for workers in (1, 8):
|
||||||
|
run_name = f"owner-new-plan-{compiler}-strate{index}-w{workers}"
|
||||||
|
row, output = runner.run_cpp(run_name, binary, OWNER_NEW, "surface-world",
|
||||||
|
[*common_flags, camera_flag(camera_by_index[index])],
|
||||||
|
expected_style=expected_passage_style,
|
||||||
|
expected_strate_styles=expected_styles,
|
||||||
|
workers=workers, max_level=0, clip_radius=1,
|
||||||
|
layout_path=OUT / "layouts" / f"{run_name}.json")
|
||||||
|
generators = row.get("layout", {}).get("strata", {})
|
||||||
|
defer_requirement(runner, f"{run_name}:generator-map",
|
||||||
|
generators == expected_generators,
|
||||||
|
f"resolved={generators} expected={expected_generators}")
|
||||||
|
hashes = tuple(HASH_RE.findall(output))
|
||||||
|
outputs[workers] = output
|
||||||
|
if workers == 1:
|
||||||
|
hashes_by_strate[index] = hashes
|
||||||
|
pipeline = re.search(
|
||||||
|
r"^\[StreamPipeline\].*?first_tile_ready=([0-9.]+)s .*?"
|
||||||
|
r"neighborhood_ready=([0-9.]+)s .*?whole_view_ready=([0-9.]+)s .*?"
|
||||||
|
r"whole_tiles=(\d+)/(\d+)", output, re.M)
|
||||||
|
worker_times = [float(value) for value in re.findall(
|
||||||
|
r"^\[WorldAssemblerLODWorker\].*?worker_sum_s=([0-9.]+)", output, re.M)]
|
||||||
|
generated = bool(pipeline) and int(pipeline.group(4)) > 0 and bool(hashes)
|
||||||
|
defer_requirement(runner,
|
||||||
|
f"owner-new-plan-{compiler}:strate{index}:generated",
|
||||||
|
generated,
|
||||||
|
f"tiles={pipeline.group(4) if pipeline else 0} hashes={len(hashes)}")
|
||||||
|
spawn_rows.append({
|
||||||
|
"strate": index,
|
||||||
|
"generator": expected_generators[index],
|
||||||
|
"tiles": int(pipeline.group(4)) if pipeline else 0,
|
||||||
|
"first_tile_s": float(pipeline.group(1)) if pipeline else None,
|
||||||
|
"neighborhood_ready_s": float(pipeline.group(2)) if pipeline else None,
|
||||||
|
"whole_view_s": float(pipeline.group(3)) if pipeline else None,
|
||||||
|
"worker_s": sum(worker_times),
|
||||||
|
})
|
||||||
|
worker_hashes = tuple(HASH_RE.findall(outputs[8]))
|
||||||
|
stable = bool(hashes_by_strate[index]) and hashes_by_strate[index] == worker_hashes
|
||||||
|
defer_requirement(runner,
|
||||||
|
f"owner-new-plan-{compiler}:strate{index}:deterministic-w1-w8",
|
||||||
|
stable,
|
||||||
|
f"w1_hashes={len(hashes_by_strate[index])} w8_hashes={len(worker_hashes)} stable={stable}")
|
||||||
|
audit_row["owner_plan_spawn_costs"] = spawn_rows
|
||||||
|
deferred_failures = [step["name"] for step in runner.steps[deferred_start:]
|
||||||
|
if step["status"] == "FAIL"]
|
||||||
|
if deferred_failures:
|
||||||
|
raise VerificationFailure(
|
||||||
|
"new owner plan audits failed after per-strata runs: "
|
||||||
|
+ ", ".join(deferred_failures))
|
||||||
|
|
||||||
|
|
||||||
def run_landmark_reachability(runner: VerificationRunner,
|
def run_landmark_reachability(runner: VerificationRunner,
|
||||||
binaries: tuple[Path, ...]) -> None:
|
binaries: tuple[Path, ...]) -> None:
|
||||||
for binary in binaries:
|
for binary in binaries:
|
||||||
@@ -1682,7 +1909,7 @@ def build_motif_fixture(runner: VerificationRunner) -> Path:
|
|||||||
runner.run_stage("build-style-override-motifs-slice1-r24", script,
|
runner.run_stage("build-style-override-motifs-slice1-r24", script,
|
||||||
["--out", str(fixture_dir)], binary=CLANG, data=OWNER,
|
["--out", str(fixture_dir)], binary=CLANG, data=OWNER,
|
||||||
recipe="surface-world", style=None,
|
recipe="surface-world", style=None,
|
||||||
script_inputs=[OWNER])
|
env=env_for(), script_inputs=[OWNER])
|
||||||
fixture = fixture_dir / "style-override-motifs-slice1.json"
|
fixture = fixture_dir / "style-override-motifs-slice1.json"
|
||||||
runner.require("motif-fixture:exists", fixture.is_file(),
|
runner.require("motif-fixture:exists", fixture.is_file(),
|
||||||
"versioned flatField and two fixed-count sparse motifs are present")
|
"versioned flatField and two fixed-count sparse motifs are present")
|
||||||
@@ -2494,6 +2721,7 @@ def run_speed_matrix(runner: VerificationRunner) -> None:
|
|||||||
def run_medium_suite(runner: VerificationRunner, *, both_compilers: bool = False) -> None:
|
def run_medium_suite(runner: VerificationRunner, *, both_compilers: bool = False) -> None:
|
||||||
binaries = (CLANG, GCC) if both_compilers else (CLANG,)
|
binaries = (CLANG, GCC) if both_compilers else (CLANG,)
|
||||||
run_owner_passages(runner, binaries)
|
run_owner_passages(runner, binaries)
|
||||||
|
run_new_owner_plan_stage(runner, CLANG)
|
||||||
run_default_profile_identity(runner, CLANG)
|
run_default_profile_identity(runner, CLANG)
|
||||||
run_landmark_reachability(runner, binaries)
|
run_landmark_reachability(runner, binaries)
|
||||||
run_regression_controls(runner)
|
run_regression_controls(runner)
|
||||||
@@ -2813,7 +3041,12 @@ def main() -> int:
|
|||||||
signal.setitimer(signal.ITIMER_REAL, ARGS.overall_timeout)
|
signal.setitimer(signal.ITIMER_REAL, ARGS.overall_timeout)
|
||||||
try:
|
try:
|
||||||
if not OWNER.is_file():
|
if not OWNER.is_file():
|
||||||
raise VerificationFailure(f"canonical owner input is missing: {OWNER}")
|
raise VerificationFailure(f"pinned two-strate owner input is missing: {OWNER}")
|
||||||
|
if sha256_file(OWNER) != OWNER_2STRATE_SHA256:
|
||||||
|
raise VerificationFailure(
|
||||||
|
f"pinned two-strate owner fixture changed: {OWNER}")
|
||||||
|
if not OWNER_NEW.is_file():
|
||||||
|
raise VerificationFailure(f"current four-strate owner input is missing: {OWNER_NEW}")
|
||||||
for binary in (CLANG, GCC, RENDERER):
|
for binary in (CLANG, GCC, RENDERER):
|
||||||
if binary.is_file():
|
if binary.is_file():
|
||||||
runner.probe_binary(binary)
|
runner.probe_binary(binary)
|
||||||
@@ -2821,6 +3054,12 @@ def main() -> int:
|
|||||||
run_sanitizer_stage(runner)
|
run_sanitizer_stage(runner)
|
||||||
elif ARGS.tier == "fast":
|
elif ARGS.tier == "fast":
|
||||||
owners = run_owner_passages(runner, (CLANG,))
|
owners = run_owner_passages(runner, (CLANG,))
|
||||||
|
new_plan_failure = None
|
||||||
|
try:
|
||||||
|
run_new_owner_plan_stage(runner, CLANG)
|
||||||
|
except VerificationFailure as error:
|
||||||
|
new_plan_failure = str(error)
|
||||||
|
print(f"[{runner.tier}] new-owner-plan-stage FAIL {error}", flush=True)
|
||||||
run_landmark_reachability(runner, (CLANG,))
|
run_landmark_reachability(runner, (CLANG,))
|
||||||
run_regression_controls(runner)
|
run_regression_controls(runner)
|
||||||
run_hash_pairs(runner, owners)
|
run_hash_pairs(runner, owners)
|
||||||
@@ -2831,6 +3070,8 @@ def main() -> int:
|
|||||||
run_native_caves_field_audit(runner, CLANG)
|
run_native_caves_field_audit(runner, CLANG)
|
||||||
run_native_lattice_field_audit(runner, CLANG)
|
run_native_lattice_field_audit(runner, CLANG)
|
||||||
run_motif_fixture_audits(runner, CLANG)
|
run_motif_fixture_audits(runner, CLANG)
|
||||||
|
if new_plan_failure is not None:
|
||||||
|
runner.require("new-owner-plan-stage", False, new_plan_failure)
|
||||||
elif ARGS.tier == "medium":
|
elif ARGS.tier == "medium":
|
||||||
run_medium_suite(runner, both_compilers=False)
|
run_medium_suite(runner, both_compilers=False)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"schema_version":1,"source":"VoxelForgeExplore standalone base-world export","voxel_size_cm":25,"seed":0,"open_surface_entry":true,"origin_spine_radius":28,"world_radius_voxels":0,"edge_seal_thickness":128,"total_strates":2,"inter_strate_gap_chunks":0,"clip_radius":6,"max_clip_level":8,"full_res_clip_levels":2,"coarse_tile_cells":16,"render_distance_chunks":1536,"generate_skirts":true,"skirt_cells":4,"lod_octave_drop":1,"strate_content_cut_min_level":9,"settings_asset":{"ViewDistanceXY":32,"ViewDistanceUp":10,"ViewDistanceDown":10,"MaxConcurrentTasks":16,"MaxGenerationWorkerCores":0,"NearTaskReserve":4,"MaxMeshAppliesPerFrame":4,"MaxMeshApplyMilliseconds":2,"MaxUnloadsPerFrame":16,"PawnCollisionRadiusChunks":2,"bClampViewToStrate":true,"StrateViewMarginChunks":6,"StrateContentCutMinLevel":9,"ClipRadius":6,"MaxClipLevel":8,"FullResClipLevels":2,"CoarseTileCells":16,"RenderDistanceChunks":1536,"bGenerateSkirts":true,"SkirtCells":4,"LODOctaveDrop":1,"WorldRadiusVoxels":0,"EdgeSealThickness":128,"DecorationRadiusChunks":12,"DecorationNearRadiusChunks":6,"DecorationRegionSizeCells":8,"DecorationSpacingVoxels":8,"DecorationFarSpacingVoxels":8,"DecorationMarchStepVoxels":4,"DecorationMaxCrossingsPerColumn":4,"DecorationColumnDepthVoxels":320,"MaxDecorationCellsPerFrame":2,"MaxConcurrentDecorationTasks":4,"bEnableDensityVolume":false,"DensityVolumeResolution":256,"DensityVolumeLevels":3,"DensityVolumeFillSlabCells":8,"DensityVolumeMarchSteps":64,"bDensityVolumeGPUUpload":false,"bDebugDrawDensityVolume":false,"DensityVolumeDebugRadiusCells":12,"VoxelMaterial":"/Game/VoxelForge/M_Test1_Inst.M_Test1_Inst","Seed":0,"Season":"","CurrentSeason":1,"StratePool":["/Game/VoxelForge/DA_Strate1.DA_Strate1","/Game/VoxelForge/DA_Strate3.DA_Strate3"],"FixedStrates":[],"TotalStrates":2,"InterStrateGapChunks":0,"OriginSpineRadius":28,"bOpenSurfaceEntry":true,"MaxModifications":0,"MaxBrushRadius":30,"MaxTotalVolume":0,"AssetBundleData":{"Bundles":[]},"NativeClass":"/Script/VoxelForge.VoxelSettings"},"strates":[{"index":0,"top_chunk_z":0,"bottom_chunk_z":-15,"height_chunks":16,"asset":{"StrateName":"","StrateDescription":"","StrateHeightInChunks":16,"TransitionType":0,"TransitionBlendChunks":2,"GeneratorType":0,"bEnableWorms":false,"GenerationParams":{"BaseDensity":8,"VerticalScale":0.800000011920929,"WormFrequency":0.014999999664723873,"WormHorizontalBias":3,"WormThreshold":0.05999999865889549,"WormStrength":10,"WormNetworkRange":64,"RoomSpacing":128,"RoomDensity":0.3499999940395355,"MinRoomRadius":65,"MaxRoomRadius":120,"RoomHeightRatio":0.5,"RoomFloorCutMin":0,"RoomFloorCutMax":0,"FloorReliefStrength":0,"FloorReliefFrequency":1,"RoomMouthRiseStrength":0,"RoomMouthRiseBlendVoxels":12,"RoomShapeVariety":0.5,"OriginRoomRadius":65,"OriginRoomMaxConnections":4,"TunnelMinRadius":8,"TunnelMaxRadius":12,"TunnelDensity":0.4000000059604645,"MaxTunnelLength":360,"TunnelWarpStrength":24,"TunnelHorizontalBias":0.5,"bTunnelsFlowTowardOrigin":true,"TunnelEndpointZOffset":0.25,"bTunnelFloorEnabled":true,"bTunnelFloorTerracingEnabled":true,"TunnelFloorTerraceStepHeight":5,"TunnelFloorMaxLedgeHeight":24,"TunnelFloorGentleSlopeThreshold":88.09085083007812,"TunnelFloorLedgeCountPreference":0,"TunnelFloorMaxLedges":4096,"SDFBlendRadius":25,"CaveWarpStrength":16,"CaveWarpFrequency":0.014999999664723873,"SurfaceRoughness":1,"RoughnessFrequency":0.05000000074505806,"RoughnessNoiseType":2,"DomainWarpStrength":0,"DomainWarpFrequency":0.029999999329447746,"FloorBias":4,"BoundarySealThickness":8,"WaterLevelRelative":0},"SlabParams":{"FloorRelativeHeight":0.25,"CeilingRelativeHeight":0.6000000238418579,"FloorRoughness":4,"FloorRoughnessFrequency":0.03999999910593033,"CeilingRoughness":6,"CeilingRoughnessFrequency":0.03999999910593033,"ColumnDensity":0.07999999821186066,"ColumnMinRadius":8,"ColumnMaxRadius":16,"ColumnSpacing":96,"BoundarySealThickness":4,"BaseDensity":8},"MazeParams":{"CellSize":160,"CorridorRadius":24,"BranchProbability":1,"Verticality":0.20000000298023224,"SurfaceRoughness":2,"BoundarySealThickness":4,"BaseDensity":8},"SurfaceParams":{"BaseGroundRelative":0.25,"ElevationRange":80,"ContinentFrequency":0.006000000052154064,"MountainStrength":0.5,"MountainFrequency":0.012000000104308128,"DetailFrequency":0.03999999910593033,"SurfaceRoughness":2,"HeightWarpStrength":48,"HeightWarpFrequency":0.00800000037997961,"ReliefFrequency":0.001500000013038516,"ReliefStrength":0.699999988079071,"ReliefContrast":1.600000023841858,"TerraceStrength":0,"TerraceHeight":8,"TerraceHardness":0.5,"LayerLineDepth":0,"LayerLineSpacing":8,"CliffStrength":0,"CliffSlopeThreshold":0.5,"CliffSharpness":2,"CliffSampleDist":2,"OverhangStrength":0,"OverhangReach":16,"OverhangHeight":24,"OverhangFrequency":0.019999999552965164,"OverhangZScale":0.5,"OverhangSlopeThreshold":0.30000001192092896,"WaterLevelRelative":0.27000001072883606,"BeachWidth":12,"CeilingRelative":0.949999988079071,"CeilingRoughness":6,"CeilingRoughnessFrequency":0.03999999910593033,"CeilingUndulation":0,"CLine truncated
|
||||||
Reference in new issue
Block a user