fix(core): worker-count-dependent tiles near passage support columns (L44); random-camera determinism sweep

L27 added passage support columns, but World::isTileShapeFree only bounded the route and bridge, so tiles
touched by a column could be approved for early native-only generation; depending on scheduling they used
that incomplete field or the compiled one (six LOD0 tiles differed between W1 and W12 at an SW camera near
the arrival mouth). The shape-free proof now includes a conservative envelope for every possible column.
The compiled world is unchanged (the old W1 variant), so no version bump. verify tiers gain a seeded
(SplitMix64 0x4c3434) random-camera W1/W8/W12 sweep across both recipes and several clip settings; it fails
before the fix and passes after. fast/medium/full PASS; cost within noise. Research R11: suspect review.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-23 23:00:21 +02:00
1 parent 422dd7761a
commit 036bd530fc
6 files changed
+288 -18

No files matched your search

+86 -16
View File
@@ -1207,27 +1207,97 @@ namespace VoxelForgeCore
const double tileMaximum[3]={static_cast<double>(maximum.X),
static_cast<double>(maximum.Y),static_cast<double>(maximum.Z)};
for(const auto& passage:layout.passages)
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
{
const auto& a=passage.controlPoints[segment-1];
const auto& b=passage.controlPoints[segment];
const double radius=static_cast<double>(std::max(
a.radiusSubvoxels,b.radiusSubvoxels))/Q;
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
const double low[3]={
std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent,
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent,
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius
-bridgeFootDepthVoxels};
const double high[3]={
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent,
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent,
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
for(std::size_t segment=1;segment<passage.controlPoints.size();++segment)
{
const auto& a=passage.controlPoints[segment-1];
const auto& b=passage.controlPoints[segment];
const double radius=static_cast<double>(std::max(
a.radiusSubvoxels,b.radiusSubvoxels))/Q;
const double horizontalExtent=std::max(radius,bridgeHalfWidthVoxels);
const double low[3]={
std::min(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q-horizontalExtent,
std::min(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q-horizontalExtent,
std::min(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q-radius
-bridgeFootDepthVoxels};
const double high[3]={
std::max(a.positionSubvoxels[0],b.positionSubvoxels[0])/Q+horizontalExtent,
std::max(a.positionSubvoxels[1],b.positionSubvoxels[1])/Q+horizontalExtent,
std::max(a.positionSubvoxels[2],b.positionSubvoxels[2])/Q+radius};
bool overlap=true;
for(int axis=0;axis<3;++axis)
overlap&=high[axis]>=tileMinimum[axis]&&low[axis]<=tileMaximum[axis];
if(overlap)return true;
}
// L27 can add a long support column around an unsupported passage span. Its
// radius can exceed the route and bridge bounds above by the column wall and
// flare allowance. The precompile shape-free proof runs before that crossing
// plan exists, so conservatively bound every possible column from the resolved
// route itself. The center is either the passage anchor or an average of route
// samples; in the latter case it lies inside the route's XY bounds.
if(passage.controlPoints.size()>=2)
{
double routeMinimum[3]={std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity()};
double routeMaximum[3]={-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity(),
-std::numeric_limits<double>::infinity()};
double maximumBoreRadius=0.0;
for(const auto& point:passage.controlPoints)
{
for(int axis=0;axis<3;++axis)
{
const double value=static_cast<double>(point.positionSubvoxels[axis])/Q;
routeMinimum[axis]=std::min(routeMinimum[axis],value);
routeMaximum[axis]=std::max(routeMaximum[axis],value);
}
maximumBoreRadius=std::max(maximumBoreRadius,
static_cast<double>(point.radiusSubvoxels)/Q);
}
double centerMinimumX=routeMinimum[0],centerMaximumX=routeMaximum[0];
double centerMinimumY=routeMinimum[1],centerMaximumY=routeMaximum[1];
double maximumRouteOffset=std::hypot(
routeMaximum[0]-routeMinimum[0],routeMaximum[1]-routeMinimum[1]);
const auto upper=std::find_if(layout.strates.begin(),layout.strates.end(),
[&passage](const auto& stratum)
{return stratum.strateIndex==passage.upperStrateIndex;});
if(upper!=layout.strates.end()
&&upper->passageCapabilities.usePassageAnchorForColumns)
{
const auto anchor=std::find_if(upper->anchors.begin(),upper->anchors.end(),
[](const auto& value)
{return value.kind=="passage_anchor"&&value.values.size()>=2;});
if(anchor!=upper->anchors.end())
{
const double centerX=static_cast<double>(anchor->values[0])/Q;
const double centerY=static_cast<double>(anchor->values[1])/Q;
centerMinimumX=centerMaximumX=centerX;
centerMinimumY=centerMaximumY=centerY;
maximumRouteOffset=0.0;
for(const auto& point:passage.controlPoints)
maximumRouteOffset=std::max(maximumRouteOffset,std::hypot(
static_cast<double>(point.positionSubvoxels[0])/Q-centerX,
static_cast<double>(point.positionSubvoxels[1])/Q-centerY));
}
}
// The generated maximum column radius is the route offset, bore radius,
// and 13.25 m of wall/irregularity/flare allowance.
const double columnRadius=maximumRouteOffset+maximumBoreRadius
+13.25*voxelsPerMetre;
const double columnMinimum[3]={centerMinimumX-columnRadius,
centerMinimumY-columnRadius,routeMinimum[2]-6.0*voxelsPerMetre};
const double columnMaximum[3]={centerMaximumX+columnRadius,
centerMaximumY+columnRadius,routeMaximum[2]+5.0*voxelsPerMetre};
bool overlap=true;
for(int axis=0;axis<3;++axis)
overlap&=high[axis]>=tileMinimum[axis]&&low[axis]<=tileMaximum[axis];
overlap=overlap&&columnMaximum[axis]>=tileMinimum[axis]
&&columnMinimum[axis]<=tileMaximum[axis];
if(overlap)return true;
}
}
return false;
}