wip(assembler): round S1 -- one Core voxel-size source; per-tile feature lists (islands)

AVoxelWorld::CoreVoxelSizeCm (default 25, 32 voxels/tile) flows through plan, field,
mesher, content, water, atmosphere, edits and collision. Staged-owner BuildHost passes at
20 cm (SurfaceWorld + islands, W1/W8/W16, anchors, BP edits, collision) with a fixed-25cm
negative control; default-size hashes match round 4; Core off stays legacy 12.5 cm.
Reusable BuildWorldAssemblyFeatureTileListV1; wired into FloatingIslands only so far.
256-island stress: tile p95 112.5 -> 2.0 ms, 0 mismatches, dropped-feature control fails.
Content placement measured (no populated cells): island ground search 7.25 ms/cell vs
legacy 0.29 ms -- open. Outside-review evaluation in Round5/REPORT.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-18 18:48:38 +02:00
1 parent 111cf3d262
commit d6e69420e8
25 files changed
+2136 -250

No files matched your search

@@ -24,6 +24,8 @@
namespace VoxelForgeCore
{
inline constexpr std::uint32_t CoreTileVoxelCount = 32;
struct Vec2f { float x = 0.0f, y = 0.0f; };
struct Vec3d { double x = 0.0, y = 0.0, z = 0.0; };
struct Vec3i
@@ -34,11 +36,26 @@ namespace VoxelForgeCore
struct TileKey
{
Vec3i coordinate{}; // Units of (32 << lod) voxels.
Vec3i coordinate{}; // Units of (CoreTileVoxelCount << lod) voxels.
std::int32_t lod = 0;
friend bool operator==(const TileKey&, const TileKey&) = default;
};
// Reusable broad-phase input for any bounded world-assembly feature family. Bounds are
// conservative and inclusive; an invalid box makes the builder fail open by returning all
// feature indices. Les bornes sont inclusives; une borne invalide garde toutes les features.
struct WorldAssemblyFeatureBoundsV1
{
std::uint64_t stableId = 0;
std::array<std::int64_t, 3> minimumSubvoxels{};
std::array<std::int64_t, 3> maximumSubvoxels{};
};
VFCORE_API std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1(
std::span<const WorldAssemblyFeatureBoundsV1> features,
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
const std::array<std::int64_t, 3>& tileMaximumSubvoxels);
struct VFCORE_API TileKeyHash
{
std::size_t operator()(const TileKey& key) const noexcept;
@@ -125,6 +142,11 @@ namespace VoxelForgeCore
struct WorldConfiguration
{
// Physical resolution is part of the resolved world identity. Tile coordinates remain in
// abstract voxel units; meshing, collision and Unreal transforms use this size in cm.
// La resolution physique fait partie de l'identite resolue; les tuiles restent en voxels.
std::uint32_t voxelSizeMicrons = 250000;
std::uint32_t tileVoxelCount = CoreTileVoxelCount;
std::int32_t seed = 0;
std::int32_t clipRadius = 0;
std::int32_t maxLod = 0;
@@ -292,8 +314,8 @@ namespace VoxelForgeCore
// bounded lobed roots and arched spans without changing the feature-family schema.
std::uint32_t geometryVersion = 2;
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
// Import scale is explicit: legacy owner voxels are 12.5 cm and Core voxels are 25 cm.
// L'echelle d'import est explicite : voxel owner de 12,5 cm et voxel Core de 25 cm.
// Import scale is explicit in microns; the reduced ratio maps owner-authored lengths to Core voxels.
// L'echelle d'import est explicite en microns; le rapport reduit convertit les longueurs owner.
std::uint32_t ownerVoxelSizeMicrons = 125000;
std::uint32_t coreVoxelSizeMicrons = 250000;
std::uint32_t ownerToCoreScaleNumerator = 1;
@@ -306,8 +328,8 @@ namespace VoxelForgeCore
std::int64_t layoutMaximumZSubvoxels = 0;
std::uint32_t primaryIslandCount = 3;
std::int64_t islandDensityQ16 = 1ll << 15;
// Legacy owner lengths use 12.5 cm voxels; Core uses 25 cm, so halve the owner lengths.
// Les longueurs owner utilisent des voxels de 12,5 cm ; Core utilise 25 cm, donc les diviser par deux.
// Legacy owner lengths use ownerVoxelSizeMicrons; the scale ratio converts them to Core voxels.
// Les longueurs owner utilisent ownerVoxelSizeMicrons; le rapport les convertit en voxels Core.
std::int64_t spacingSubvoxels = 56 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t minimumRadiusSubvoxels = 12 * WorldAssemblySubvoxelsPerVoxel;
std::int64_t maximumRadiusSubvoxels = 24 * WorldAssemblySubvoxelsPerVoxel;
@@ -364,6 +386,27 @@ namespace VoxelForgeCore
std::uint64_t contentHash = 0;
};
struct FloatingIslandFeatureGroupBoundsV1
{
std::array<std::int64_t, 3> minimumSubvoxels{};
std::array<std::int64_t, 3> maximumSubvoxels{};
};
struct FloatingIslandTileFeatureListV1
{
// Flat stable-order IDs for island and connector features that touch this tile.
// Identifiants ordonnes des features qui touchent ce carreau.
std::vector<std::uint32_t> featureIndices;
// Cheap conservative group bounds preserve exact positive SDF distances while distant
// feature evaluators are skipped. Bornes legeres pour garder les distances SDF exactes.
std::vector<FloatingIslandFeatureGroupBoundsV1> groupBounds;
// Groups are ordered by a tile-wide conservative distance lower bound. Samples stop at
// the first group that cannot beat their current density. Ordre par distance minimale.
std::vector<std::uint32_t> groupSearchOrder;
std::vector<std::int64_t> groupDistanceLowerBoundsSubvoxels;
bool groupSearchComplete = true;
};
struct VFCORE_API FloatingIslandManifestV1
{
FloatingIslandRecipeV1 recipe;
@@ -464,6 +507,12 @@ namespace VoxelForgeCore
bool isValid() const noexcept;
std::int64_t densitySubvoxels(std::int64_t xSubvoxels,
std::int64_t ySubvoxels, std::int64_t zSubvoxels) const noexcept;
FloatingIslandTileFeatureListV1 buildFeatureTileList(
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const;
std::int64_t densitySubvoxelsForTile(std::int64_t xSubvoxels,
std::int64_t ySubvoxels, std::int64_t zSubvoxels,
const FloatingIslandTileFeatureListV1& featureList) const noexcept;
FieldInterval interval(std::int64_t minimumXSubvoxels,
std::int64_t minimumYSubvoxels, std::int64_t minimumZSubvoxels,
std::int64_t maximumXSubvoxels, std::int64_t maximumYSubvoxels,
+7 -3
View File
@@ -553,7 +553,8 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
Radial.Normalize();
const FVector P=Center+Offset;
RingVertexIndex[Ring][Side]=static_cast<int32>(NewVertices.size());
NewVertices.emplace_back(P.X*25.0,P.Y*25.0,P.Z*25.0);
const double VoxelSizeCm=Shape.GetCoreVoxelSizeCm();
NewVertices.emplace_back(P.X*VoxelSizeCm,P.Y*VoxelSizeCm,P.Z*VoxelSizeCm);
NewNormals.emplace_back(-Radial.X,-Radial.Y,-Radial.Z);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>(Rings[Ring].T*Length/256.0),
static_cast<float>(Side)/SideCount);
@@ -703,7 +704,8 @@ inline void AppendFarConnectorShells(const FStandaloneShapeWorld& Shape,const FV
if(!ProjectToSurface(P,Normal)){bProjectionSucceeded=false;break;}
const int32 LocalIndex=static_cast<int32>(PatchVertices.size());
Grid[static_cast<size_t>(Y)*(Subdivisions+1)+X]=LocalIndex;
PatchVertices.emplace_back(P.X*25.0,P.Y*25.0,P.Z*25.0);
const double VoxelSizeCm=Shape.GetCoreVoxelSizeCm();
PatchVertices.emplace_back(P.X*VoxelSizeCm,P.Y*VoxelSizeCm,P.Z*VoxelSizeCm);
PatchNormals.push_back(Normal);
if(bHasUVs)
{
@@ -1013,7 +1015,9 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
const int32 Base=static_cast<int32>(NewVertices.size());
for(const FPatchVertex& Vertex:Patch)
{
NewVertices.emplace_back(Vertex.X*25.0,Vertex.Y*25.0,Vertex.Z*25.0);
const double VoxelSizeCm=World.CoreVoxelSizeMicrons/10000.0;
NewVertices.emplace_back(Vertex.X*VoxelSizeCm,Vertex.Y*VoxelSizeCm,
Vertex.Z*VoxelSizeCm);
NewNormals.push_back(Vertex.Normal);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Vertex.X-Origin.X)/256.0),
static_cast<float>((Vertex.Y-Origin.Y)/256.0));
+284 -4
View File
@@ -9,6 +9,7 @@
#include <cmath>
#include <cstdint>
#include <limits>
#include <numeric>
#include <sstream>
#include <string_view>
#include <unordered_map>
@@ -258,14 +259,20 @@ namespace floating_island_detail
recipe.routeConstraints.capsuleRadiusSubvoxels,
recipe.routeConstraints.capsuleHeightSubvoxels,
recipe.routeConstraints.maximumGradeQ16};
const std::uint32_t ScaleGcd = std::gcd(recipe.ownerVoxelSizeMicrons,
recipe.coreVoxelSizeMicrons);
const std::uint32_t ExpectedNumerator = ScaleGcd > 0
? recipe.ownerVoxelSizeMicrons / ScaleGcd : 0;
const std::uint32_t ExpectedDenominator = ScaleGcd > 0
? recipe.coreVoxelSizeMicrons / ScaleGcd : 0;
if (recipe.schemaVersion != 1 || (recipe.geometryVersion != 1 && recipe.geometryVersion != 2)
|| (recipe.geometryVersion == 1
&& (recipe.crustDepthBonusQ16 != 0 || recipe.rootDepthRatioQ16 != 0))
|| recipe.unitsPerVoxel != Q
|| recipe.ownerVoxelSizeMicrons != 125000
|| recipe.coreVoxelSizeMicrons != 250000
|| recipe.ownerToCoreScaleNumerator != 1
|| recipe.ownerToCoreScaleDenominator != 2
|| recipe.coreVoxelSizeMicrons < 125000 || recipe.coreVoxelSizeMicrons > 250000
|| recipe.ownerToCoreScaleNumerator != ExpectedNumerator
|| recipe.ownerToCoreScaleDenominator != ExpectedDenominator
|| recipe.primaryIslandCount < 3 || recipe.primaryIslandCount > 9
|| recipe.layoutMaximumZSubvoxels <= recipe.layoutMinimumZSubvoxels
|| recipe.layoutMaximumZSubvoxels - recipe.layoutMinimumZSubvoxels > 2048 * Q
@@ -742,6 +749,103 @@ namespace floating_island_detail
return std::clamp(RoundQ(result), -MaxDensity, MaxDensity);
}
inline std::int64_t FieldDensityForTile(const FloatingIslandRecipeV1& recipe,
const FloatingIslandPlanV1& plan, std::int64_t x, std::int64_t y, std::int64_t z,
const FloatingIslandTileFeatureListV1& featureList)
{
if (plan.islands.empty()) return MaxDensity;
if (featureList.groupBounds.size() != plan.islands.size())
return FieldDensity(recipe, plan, x, y, z);
const auto& featureIndices = featureList.featureIndices;
const std::size_t islandCount = plan.islands.size();
const double blend = static_cast<double>(recipe.blendRadiusSubvoxels);
double result = static_cast<double>(MaxDensity);
const auto evaluateLocalGroup = [&](std::size_t islandIndex)
{
const auto& island = plan.islands[islandIndex];
double group = IslandDensity(recipe, island,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z));
for (const std::uint32_t edgeIndex : featureIndices)
{
if (edgeIndex < islandCount) continue;
const std::size_t planIndex = static_cast<std::size_t>(edgeIndex) - islandCount;
if (planIndex >= plan.clusterEdges.size()) continue;
const auto& edge = plan.clusterEdges[planIndex];
if (edge.fromIslandId != island.stableId) continue;
bool topSurface = false;
double alongFraction = 0.0;
double connector = ConnectorDensity(recipe, edge,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
&topSurface, &alongFraction);
if (recipe.geometryVersion >= 2)
connector = ApplySurfaceDetail(recipe, edge.stableId, connector,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
topSurface || alongFraction < 0.12 || alongFraction > 0.88 ? 0.25 : 1.0);
group = SmoothMin(group, connector, blend);
}
return group;
};
const auto evaluateDistantGroup = [&](std::size_t islandIndex)
{
const auto& island = plan.islands[islandIndex];
double group = IslandDensity(recipe, island,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z));
for (const auto& edge : plan.clusterEdges)
{
if (edge.fromIslandId != island.stableId) continue;
bool topSurface = false;
double alongFraction = 0.0;
double connector = ConnectorDensity(recipe, edge,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
&topSurface, &alongFraction);
if (recipe.geometryVersion >= 2)
connector = ApplySurfaceDetail(recipe, edge.stableId, connector,
static_cast<double>(x), static_cast<double>(y), static_cast<double>(z),
topSurface || alongFraction < 0.12 || alongFraction > 0.88 ? 0.25 : 1.0);
group = SmoothMin(group, connector, blend);
}
return group;
};
// Each local group keeps its complete ordered connector chain in the tile list.
// Les connecteurs d'un groupe local restent complets et dans leur ordre canonique.
for (const std::uint32_t index : featureIndices)
{
if (index >= islandCount) break;
result = std::min(result, evaluateLocalGroup(index));
}
// Air-side density magnitudes are part of the canonical field too. The per-tile list
// handles nearby groups; conservative group-box lower bounds prune distant exact SDFs.
// Les magnitudes d'air restent canoniques; les bornes de groupes eliminent les SDF lointains.
if (result >= 0.0)
{
if ((featureList.groupSearchOrder.size() != islandCount
|| featureList.groupDistanceLowerBoundsSubvoxels.size() != islandCount)
&& featureList.groupSearchComplete)
return FieldDensity(recipe, plan, x, y, z);
const std::size_t searchCount = std::min(featureList.groupSearchOrder.size(),
featureList.groupDistanceLowerBoundsSubvoxels.size());
for (std::size_t orderIndex = 0; orderIndex < searchCount; ++orderIndex)
{
const std::size_t islandIndex = featureList.groupSearchOrder[orderIndex];
if (islandIndex >= islandCount) return FieldDensity(recipe, plan, x, y, z);
if (std::binary_search(featureIndices.begin(), featureIndices.end(),
static_cast<std::uint32_t>(islandIndex))) continue;
const auto& bounds = featureList.groupBounds[islandIndex];
for (std::size_t axis = 0; axis < 3; ++axis)
if (bounds.minimumSubvoxels[axis] > bounds.maximumSubvoxels[axis])
return FieldDensity(recipe, plan, x, y, z);
if (static_cast<double>(featureList.groupDistanceLowerBoundsSubvoxels[orderIndex]) > result)
break;
result = std::min(result, evaluateDistantGroup(islandIndex));
}
}
return std::clamp(RoundQ(result), -MaxDensity, MaxDensity);
}
inline void SetIslandBounds(const FloatingIslandRecipeV1& recipe,
FloatingIslandAnchorV1& island)
{
@@ -1528,7 +1632,7 @@ bool FloatingIslandFieldV1::isValid() const noexcept
if (!recipe_ || !plan_ || recipe_->schemaVersion != 1
|| recipe_->unitsPerVoxel != floating_island_detail::Q
|| recipe_->primaryIslandCount < 3 || plan_->islands.size() < recipe_->primaryIslandCount
|| plan_->islands.size() > recipe_->primaryIslandCount + 2
|| plan_->islands.size() > 4096 || plan_->clusterEdges.size() > 4096
|| recipe_->minimumRadiusSubvoxels <= 0 || recipe_->maximumRadiusSubvoxels < recipe_->minimumRadiusSubvoxels
|| recipe_->blendRadiusSubvoxels < 0 || recipe_->contourWarpSubvoxels < 0) return false;
return std::all_of(plan_->islands.begin(), plan_->islands.end(), [](const auto& island)
@@ -1547,6 +1651,182 @@ std::int64_t FloatingIslandFieldV1::densitySubvoxels(std::int64_t x,
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
}
FloatingIslandTileFeatureListV1 FloatingIslandFieldV1::buildFeatureTileList(
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
const std::array<std::int64_t, 3>& tileMaximumSubvoxels) const
{
FloatingIslandTileFeatureListV1 result;
if (!isValid()) return result;
std::vector<WorldAssemblyFeatureBoundsV1> features;
features.reserve(plan_->islands.size() + plan_->clusterEdges.size());
for (const auto& island : plan_->islands)
features.push_back({island.stableId, island.influenceMinimumSubvoxels,
island.influenceMaximumSubvoxels});
for (const auto& edge : plan_->clusterEdges)
features.push_back({edge.stableId, edge.influenceMinimumSubvoxels,
edge.influenceMaximumSubvoxels});
result.featureIndices = BuildWorldAssemblyFeatureTileListV1(features,
tileMinimumSubvoxels, tileMaximumSubvoxels);
const std::size_t islandCount = plan_->islands.size();
result.groupBounds.resize(islandCount);
for (std::size_t islandIndex = 0; islandIndex < islandCount; ++islandIndex)
{
result.groupBounds[islandIndex].minimumSubvoxels = plan_->islands[islandIndex].influenceMinimumSubvoxels;
result.groupBounds[islandIndex].maximumSubvoxels = plan_->islands[islandIndex].influenceMaximumSubvoxels;
}
const auto findSourceIsland = [&](const FloatingIslandConnectorV1& edge)
{
return std::find_if(plan_->islands.begin(), plan_->islands.end(),
[&edge](const auto& island) { return island.stableId == edge.fromIslandId; });
};
for (const auto& edge : plan_->clusterEdges)
{
const auto source = findSourceIsland(edge);
if (source == plan_->islands.end())
{ result.groupBounds.clear(); result.featureIndices.clear(); return result; }
auto& group = result.groupBounds[static_cast<std::size_t>(source - plan_->islands.begin())];
for (std::size_t axis = 0; axis < 3; ++axis)
{
group.minimumSubvoxels[axis] = std::min(group.minimumSubvoxels[axis],
edge.influenceMinimumSubvoxels[axis]);
group.maximumSubvoxels[axis] = std::max(group.maximumSubvoxels[axis],
edge.influenceMaximumSubvoxels[axis]);
}
}
result.groupSearchOrder.resize(islandCount);
std::iota(result.groupSearchOrder.begin(), result.groupSearchOrder.end(), 0u);
const auto tileDistanceLowerBound = [&](
const std::array<std::int64_t, 3>& featureMinimum,
const std::array<std::int64_t, 3>& featureMaximum)
{
std::int64_t maximumGap = 0;
for (std::size_t axis = 0; axis < 3; ++axis)
{
if (featureMinimum[axis] > tileMaximumSubvoxels[axis])
maximumGap = std::max(maximumGap,
featureMinimum[axis] - tileMaximumSubvoxels[axis]);
else if (featureMaximum[axis] < tileMinimumSubvoxels[axis])
maximumGap = std::max(maximumGap,
tileMinimumSubvoxels[axis] - featureMaximum[axis]);
}
return maximumGap;
};
result.groupDistanceLowerBoundsSubvoxels.resize(islandCount);
for (std::size_t islandIndex = 0; islandIndex < islandCount; ++islandIndex)
{
const auto& island = plan_->islands[islandIndex];
const std::int64_t islandGap = tileDistanceLowerBound(
island.influenceMinimumSubvoxels, island.influenceMaximumSubvoxels);
double islandLowerBound = static_cast<double>(islandGap)
- floating_island_detail::IslandBlendMargin(*recipe_) - recipe_->roughnessSubvoxels;
if (recipe_->geometryVersion >= 2)
{
// Root lobes use a scaled ellipsoid distance, so their positive values can be
// substantially smaller than distance to the island AABB. Supported root radii
// guarantee minAxis/maxAxis >= 0.22; include their smooth-min budget before pruning.
// Les racines utilisent une distance d'ellipsoide mise a l'echelle; borner leur pente.
const double radius = static_cast<double>(island.radiusSubvoxels);
const double rootBlend = std::clamp(radius * 0.12,
2.0 * floating_island_detail::Q, 5.0 * floating_island_detail::Q);
const double rootSmoothBudget = static_cast<double>(
floating_island_detail::RootCount(*recipe_, island)) * rootBlend * 0.25;
const double rootLowerBound = 0.22 * static_cast<double>(islandGap)
- recipe_->roughnessSubvoxels - rootSmoothBudget;
islandLowerBound = std::min(islandLowerBound, rootLowerBound);
}
double groupLowerBound = islandLowerBound;
for (const auto& edge : plan_->clusterEdges)
{
if (edge.fromIslandId != island.stableId) continue;
const std::int64_t edgeGap = tileDistanceLowerBound(
edge.influenceMinimumSubvoxels, edge.influenceMaximumSubvoxels);
const double edgeLowerBound = static_cast<double>(edgeGap) - recipe_->roughnessSubvoxels;
groupLowerBound = std::min(groupLowerBound, edgeLowerBound)
- static_cast<double>(recipe_->blendRadiusSubvoxels) * 0.25;
}
// Floor with one subvoxel of slack so quantization cannot make a lower bound optimistic.
// Arrondir vers le bas avec une marge pour ne jamais surestimer la borne.
result.groupDistanceLowerBoundsSubvoxels[islandIndex] =
static_cast<std::int64_t>(std::floor(groupLowerBound)) - 1;
}
std::sort(result.groupSearchOrder.begin(), result.groupSearchOrder.end(),
[&result](std::uint32_t a, std::uint32_t b)
{
const auto distanceA = result.groupDistanceLowerBoundsSubvoxels[a];
const auto distanceB = result.groupDistanceLowerBoundsSubvoxels[b];
return distanceA == distanceB ? a < b : distanceA < distanceB;
});
// Store distance in search order so query samples use one sequential early-out walk.
// Ranger les distances dans le meme ordre pour arreter le parcours au premier echec.
std::vector<std::int64_t> orderedDistances;
orderedDistances.reserve(islandCount);
for (const std::uint32_t islandIndex : result.groupSearchOrder)
orderedDistances.push_back(result.groupDistanceLowerBoundsSubvoxels[islandIndex]);
result.groupDistanceLowerBoundsSubvoxels = std::move(orderedDistances);
std::vector<std::uint8_t> selectedGroups(islandCount, 0);
for (const std::uint32_t featureIndex : result.featureIndices)
{
if (featureIndex < islandCount) selectedGroups[featureIndex] = 1;
else
{
const std::size_t edgeIndex = static_cast<std::size_t>(featureIndex) - islandCount;
if (edgeIndex >= plan_->clusterEdges.size())
{ result.groupBounds.clear(); result.featureIndices.clear(); return result; }
const auto source = findSourceIsland(plan_->clusterEdges[edgeIndex]);
if (source == plan_->islands.end())
{ result.groupBounds.clear(); result.featureIndices.clear(); return result; }
selectedGroups[static_cast<std::size_t>(source - plan_->islands.begin())] = 1;
}
}
// A connector is composed into its source island's group. Keep that source and every
// outgoing connector in the tile list so the ordered smooth-min chain remains exact.
// Un connecteur fait partie de son ile source : garder toute la chaine de fondu exacte.
for (std::size_t islandIndex = 0; islandIndex < islandCount; ++islandIndex)
{
if (!selectedGroups[islandIndex]) continue;
auto insertSorted = [&](std::uint32_t value)
{
const auto insertion = std::lower_bound(result.featureIndices.begin(),
result.featureIndices.end(), value);
if (insertion == result.featureIndices.end() || *insertion != value)
result.featureIndices.insert(insertion, value);
};
insertSorted(static_cast<std::uint32_t>(islandIndex));
for (std::size_t edgeIndex = 0; edgeIndex < plan_->clusterEdges.size(); ++edgeIndex)
if (plan_->clusterEdges[edgeIndex].fromIslandId == plan_->islands[islandIndex].stableId)
insertSorted(static_cast<std::uint32_t>(islandCount + edgeIndex));
}
return result;
}
std::int64_t FloatingIslandFieldV1::densitySubvoxelsForTile(std::int64_t x,
std::int64_t y, std::int64_t z,
const FloatingIslandTileFeatureListV1& featureList) const noexcept
{
if (!isValid() || !floating_island_detail::ValidScalar(x)
|| !floating_island_detail::ValidScalar(y) || !floating_island_detail::ValidScalar(z))
return floating_island_detail::MaxDensity;
std::uint32_t previous = 0;
bool first = true;
const std::size_t featureCount = plan_->islands.size() + plan_->clusterEdges.size();
if (featureList.groupBounds.size() != plan_->islands.size())
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
for (const std::uint32_t index : featureList.featureIndices)
{
if (index >= featureCount || (!first && index <= previous))
return floating_island_detail::FieldDensity(*recipe_, *plan_, x, y, z);
first = false;
previous = index;
}
return floating_island_detail::FieldDensityForTile(*recipe_, *plan_, x, y, z, featureList);
}
FieldInterval FloatingIslandFieldV1::interval(std::int64_t minX, std::int64_t minY,
std::int64_t minZ, std::int64_t maxX, std::int64_t maxY, std::int64_t maxZ) const noexcept
{
+73 -17
View File
@@ -32,6 +32,58 @@
class FStandaloneShapeWorld
{
public:
struct FNativeIslandTileFeatureContext
{
const VoxelForgeCore::FloatingIslandPlanV1* Plan = nullptr;
const VoxelForgeCore::FloatingIslandTileFeatureListV1* FeatureList = nullptr;
};
class FScopedNativeIslandTileFeatureContext
{
public:
FScopedNativeIslandTileFeatureContext(const VoxelForgeCore::FloatingIslandPlanV1* plan,
const VoxelForgeCore::FloatingIslandTileFeatureListV1* featureList)
{
Context.Plan = plan;
Context.FeatureList = featureList;
Previous = SetNativeIslandTileFeatureContext(plan && featureList ? &Context : nullptr);
}
~FScopedNativeIslandTileFeatureContext()
{ RestoreNativeIslandTileFeatureContext(Previous); }
FScopedNativeIslandTileFeatureContext(const FScopedNativeIslandTileFeatureContext&) = delete;
FScopedNativeIslandTileFeatureContext& operator=(const FScopedNativeIslandTileFeatureContext&) = delete;
private:
FNativeIslandTileFeatureContext Context{};
const FNativeIslandTileFeatureContext* Previous = nullptr;
};
static const FNativeIslandTileFeatureContext* SetNativeIslandTileFeatureContext(
const FNativeIslandTileFeatureContext* context)
{
auto& active = ActiveNativeIslandTileFeatureContext();
const auto* previous = active;
active = context;
return previous;
}
static void RestoreNativeIslandTileFeatureContext(const FNativeIslandTileFeatureContext* context)
{ ActiveNativeIslandTileFeatureContext() = context; }
static const FNativeIslandTileFeatureContext* GetNativeIslandTileFeatureContext()
{ return ActiveNativeIslandTileFeatureContext(); }
double GetCoreVoxelSizeCm() const
{
return Owner == nullptr || Owner->CoreVoxelSizeMicrons == 0
? static_cast<double>(VOXEL_SIZE)
: static_cast<double>(Owner->CoreVoxelSizeMicrons) / 10000.0;
}
double GetCoreVoxelsPerOwnerChunk() const
{
const double OwnerVoxelSizeCm = Owner == nullptr || Owner->OwnerVoxelSizeMicrons == 0
? 0.5 * static_cast<double>(VOXEL_SIZE)
: static_cast<double>(Owner->OwnerVoxelSizeMicrons) / 10000.0;
return static_cast<double>(CHUNK_SIZE) * OwnerVoxelSizeCm / GetCoreVoxelSizeCm();
}
enum class EDetailVariant : uint8 { Coherent, Smooth, PerVoxel };
struct FBoxClassification
{
@@ -1088,6 +1140,12 @@ public:
}
private:
static const FNativeIslandTileFeatureContext*& ActiveNativeIslandTileFeatureContext()
{
static thread_local const FNativeIslandTileFeatureContext* Context = nullptr;
return Context;
}
static uint64 PackLatticeKey(int32 X,int32 Y,int32 Z)
{
constexpr uint64 Mask=0x1FFFFFull;
@@ -1249,10 +1307,9 @@ public:
bOtherStrateOverlap=true;
}
// Floating islands use the explicit 12.5 cm owner to 25 cm Core mapping:
// one legacy 4 m chunk occupies 16 Core voxels.
// Les iles flottantes suivent l'echelle explicite proprietaire/Core: 16 voxels Core.
constexpr int32 CoreVoxelsPerIslandChunk=CHUNK_SIZE/2;
// Island strata preserve owner-authored physical bounds through the resolved scale ratio.
// Les strates d'iles conservent les bornes physiques owner via le rapport de resolution.
const double CoreVoxelsPerIslandChunk=GetCoreVoxelsPerOwnerChunk();
const int32 FirstIslandChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
/static_cast<float>(CoreVoxelsPerIslandChunk));
const int32 LastIslandChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
@@ -1340,11 +1397,8 @@ public:
{
if(!IslandPlan)return Finish(EVoxelTileClass::Mixed,true);
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t CoreVoxelsPerLegacyChunk=CHUNK_SIZE/2;
const std::int64_t Bottom=static_cast<std::int64_t>(IslandPlan->bottomChunkZ)
*CoreVoxelsPerLegacyChunk*Scale;
const std::int64_t Top=(static_cast<std::int64_t>(IslandPlan->topChunkZ)+1)
*CoreVoxelsPerLegacyChunk*Scale;
const std::int64_t Bottom=IslandPlan->manifest.recipe.layoutMinimumZSubvoxels;
const std::int64_t Top=IslandPlan->manifest.recipe.layoutMaximumZSubvoxels;
const std::int64_t Seal=IslandPlan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t BoxBottom=static_cast<std::int64_t>(BMin[2])*Scale;
const std::int64_t BoxTop=static_cast<std::int64_t>(BMax[2])*Scale;
@@ -1889,7 +1943,7 @@ private:
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return false;
// Convert Core query Z back to the owner's legacy chunk coordinate.
// Convertir Z du Core vers la coordonnee de chunk legacy du proprietaire.
constexpr int32 CoreVoxelsPerLegacyChunk=CHUNK_SIZE/2;
const double CoreVoxelsPerLegacyChunk=GetCoreVoxelsPerOwnerChunk();
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CoreVoxelsPerLegacyChunk));
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
!=ECaveGeneratorType::FloatingIslands)return false;
@@ -1907,13 +1961,15 @@ private:
const VoxelForgeCore::FloatingIslandFieldV1 Field(
Plan->manifest.recipe,Plan->manifest.plan);
if(!Field.isValid())return false;
std::int64_t Density=Field.densitySubvoxels(ToFixed(X),ToFixed(Y),ToFixed(Z));
// A legacy 4 m chunk is 16 Core voxels at the published 12.5 cm to 25 cm scale.
// Un chunk legacy de 4 m represente 16 voxels Core avec l'echelle publiee.
const std::int64_t Bottom=static_cast<std::int64_t>(Plan->bottomChunkZ)
*CoreVoxelsPerLegacyChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::int64_t Top=(static_cast<std::int64_t>(Plan->topChunkZ)+1)
*CoreVoxelsPerLegacyChunk*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
std::int64_t Density=0;
const auto* TileContext=GetNativeIslandTileFeatureContext();
if(TileContext&&TileContext->Plan==&Plan->manifest.plan&&TileContext->FeatureList)
Density=Field.densitySubvoxelsForTile(ToFixed(X),ToFixed(Y),ToFixed(Z),
*TileContext->FeatureList);
else
Density=Field.densitySubvoxels(ToFixed(X),ToFixed(Y),ToFixed(Z));
const std::int64_t Bottom=Plan->manifest.recipe.layoutMinimumZSubvoxels;
const std::int64_t Top=Plan->manifest.recipe.layoutMaximumZSubvoxels;
const std::int64_t Seal=Plan->manifest.recipe.boundarySealSubvoxels;
const std::int64_t Zq=ToFixed(Z);
if(Zq>=Bottom&&Zq<Bottom+Seal)Density=std::min(Density,-std::max<std::int64_t>(1,Zq-Bottom));
+75 -12
View File
@@ -21,6 +21,8 @@ namespace vfcore::detail
namespace VoxelForgeCore
{
using namespace ::vfcore::detail;
static_assert(CHUNK_SIZE == static_cast<int32>(CoreTileVoxelCount),
"Core tile indexing and VoxelForgeCore::CoreTileVoxelCount must agree");
struct ResolvedWorldSnapshot::Data
{
@@ -75,7 +77,9 @@ namespace VoxelForgeCore
WorldConfiguration MakeConfiguration(const FStandaloneOwnerWorld& owner)
{
WorldConfiguration out;
WorldConfiguration out;
out.voxelSizeMicrons=owner.CoreVoxelSizeMicrons;
out.tileVoxelCount=CoreTileVoxelCount;
out.seed=owner.Seed;
out.clipRadius=owner.ClipRadius;
out.maxLod=owner.MaxClipLevel;
@@ -370,8 +374,9 @@ namespace VoxelForgeCore
const int32 GY=static_cast<int32>(Index/static_cast<std::size_t>(Cells+1));
const double XSub=static_cast<double>(TileOrigin.X+GX*GridStepVoxels)*Scale;
const double YSub=static_cast<double>(TileOrigin.Y+GY*GridStepVoxels)*Scale;
AddedVertices.emplace_back(XSub/Scale*25.0,YSub/Scale*25.0,
Sample.Cap/Scale*25.0);
const double VoxelSizeCm=static_cast<double>(Owner.CoreVoxelSizeMicrons)/10000.0;
AddedVertices.emplace_back(XSub/Scale*VoxelSizeCm,YSub/Scale*VoxelSizeCm,
Sample.Cap/Scale*VoxelSizeCm);
AddedNormals.push_back(Sample.Normal);
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(XSub/Scale),
static_cast<float>(YSub/Scale));
@@ -426,8 +431,9 @@ namespace VoxelForgeCore
for(int32 I=0;I<Count;++I)
{
const auto& V=PolygonB[I];
AddedVertices.emplace_back(V.XSub/Scale*25.0,
V.YSub/Scale*25.0,V.ZSub/Scale*25.0);
const double VoxelSizeCm=static_cast<double>(Owner.CoreVoxelSizeMicrons)/10000.0;
AddedVertices.emplace_back(V.XSub/Scale*VoxelSizeCm,
V.YSub/Scale*VoxelSizeCm,V.ZSub/Scale*VoxelSizeCm);
AddedNormals.push_back(V.Normal);
if(bHasUVs)AddedUVs.emplace_back(static_cast<float>(V.XSub/Scale),
static_cast<float>(V.YSub/Scale));
@@ -894,12 +900,14 @@ namespace VoxelForgeCore
const FStandaloneShapeWorld* tileShapeContext=page?page.get():impl_->shape.get();
UVoxelMarchingCubesMesher mesher;
mesher.SetGenerator(world.Generator.get());
mesher.SetGeometryVoxelSizeCm(25.0f);
mesher.SetGeometryVoxelSizeCm(static_cast<float>(world.CoreVoxelSizeMicrons)/10000.0f);
mesher.bStandaloneSkipUvAndColors=options.skipUvAndColors;
mesher.bStandaloneSkipUV=options.skipUv;mesher.bStandaloneSkipColors=options.skipColors;
mesher.bStandaloneSkipMaterialGroups=options.skipMaterialGroups;
mesher.bGenerateSkirts=world.bGenerateSkirts;
mesher.SkirtCells=static_cast<float>(world.SkirtCells)*12.5f/25.0f;
mesher.SkirtCells=static_cast<float>(world.SkirtCells)
*static_cast<float>(world.OwnerVoxelSizeMicrons)
/static_cast<float>(world.CoreVoxelSizeMicrons);
mesher.LODOctaveDrop=world.LODOctaveDrop;
const int32 level=key.lod,extent=CHUNK_SIZE<<level;
const FVoxelTileKey nativeKey=ToNative(key);
@@ -1052,9 +1060,13 @@ namespace VoxelForgeCore
// La surface 2D et les bornes de volume du manifeste definissent tout le support Z.
// The 2D surface and manifest volume bounds cover every possible density crossing.
const double TileCenterZ=static_cast<double>(origin.Z)+static_cast<double>(extent)*0.5;
const int32 CenterChunkZ=FMath::FloorToInt(TileCenterZ/static_cast<double>(CHUNK_SIZE));
const double CoreVoxelSizeCm=static_cast<double>(impl_->configuration.voxelSizeMicrons)/10000.0;
const double OwnerVoxelSizeCm=impl_->owner!=nullptr&&impl_->owner->OwnerVoxelSizeMicrons>0
?static_cast<double>(impl_->owner->OwnerVoxelSizeMicrons)/10000.0:0.5*static_cast<double>(VOXEL_SIZE);
const double CoreVoxelsPerOwnerChunk=static_cast<double>(CHUNK_SIZE)*OwnerVoxelSizeCm/CoreVoxelSizeCm;
const int32 CenterChunkZ=FMath::FloorToInt(TileCenterZ/CoreVoxelsPerOwnerChunk);
const int32 StrateIndex=world.Manager->GetStrateIndex(
static_cast<float>(TileCenterZ*VOXEL_SIZE));
static_cast<float>(TileCenterZ*CoreVoxelSizeCm));
const auto PlanIt=std::find_if(impl_->worldAssembly->plan.strates.begin(),
impl_->worldAssembly->plan.strates.end(),[StrateIndex,CenterChunkZ](const auto& Plan)
{return Plan.strateIndex==StrateIndex&&CenterChunkZ<=Plan.topChunkZ
@@ -1142,9 +1154,60 @@ namespace VoxelForgeCore
}
}
}
FVoxelMeshData mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
const WorldAssemblyFloatingIslandPlanV1* IslandTilePlan=nullptr;
FloatingIslandTileFeatureListV1 IslandTileFeatureList;
if(bHasWorldAssembly)
{
for(const auto& entry:impl_->worldAssembly->floatingIslands)
{
const auto& scale=entry.manifest.recipe;
const std::int64_t ownerToCoreNumerator=scale.ownerToCoreScaleNumerator;
const std::int64_t ownerToCoreDenominator=scale.ownerToCoreScaleDenominator;
if(ownerToCoreNumerator<=0||ownerToCoreDenominator<=0)continue;
const std::int64_t coreVoxelsPerOwnerChunk=static_cast<std::int64_t>(CHUNK_SIZE)
*ownerToCoreNumerator/ownerToCoreDenominator;
if(coreVoxelsPerOwnerChunk<=0)continue;
const std::int64_t stratumBottom=static_cast<std::int64_t>(entry.bottomChunkZ)
*coreVoxelsPerOwnerChunk;
const std::int64_t stratumTop=(static_cast<std::int64_t>(entry.topChunkZ)+1)
*coreVoxelsPerOwnerChunk;
if(static_cast<std::int64_t>(origin.Z)>stratumTop
||static_cast<std::int64_t>(origin.Z)+extent<stratumBottom)continue;
IslandTilePlan=&entry;
break;
}
if(IslandTilePlan)
{
const FloatingIslandFieldV1 field(IslandTilePlan->manifest.recipe,
IslandTilePlan->manifest.plan);
const std::int64_t scale=WorldAssemblySubvoxelsPerVoxel;
// Include the mesher's one-sample gradient halo on each face.
// Inclure la marge d'un echantillon du maillage sur chaque face.
const std::int64_t sampleHalo=static_cast<std::int64_t>(step)*scale;
const std::array<std::int64_t,3> tileMinimum={
static_cast<std::int64_t>(origin.X)*scale-sampleHalo,
static_cast<std::int64_t>(origin.Y)*scale-sampleHalo,
static_cast<std::int64_t>(origin.Z)*scale-sampleHalo};
const std::array<std::int64_t,3> tileMaximum={
(static_cast<std::int64_t>(origin.X)+extent)*scale+sampleHalo,
(static_cast<std::int64_t>(origin.Y)+extent)*scale+sampleHalo,
(static_cast<std::int64_t>(origin.Z)+extent)*scale+sampleHalo};
IslandTileFeatureList=field.buildFeatureTileList(tileMinimum,tileMaximum);
}
}
FVoxelMeshData mesh;
if(IslandTilePlan)
{
FStandaloneShapeWorld::FScopedNativeIslandTileFeatureContext tileFeatureContext(
&IslandTilePlan->manifest.plan,&IslandTileFeatureList);
mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
}
else
mesh=mesher.GenerateMesh(origin,step,cells,nullptr,
MeshBandMinZVoxel,MeshBandMaxZVoxel,&sampleCount,nullptr,nullptr,
(options.hashDensity||options.captureDensitySamples)?&densityValues:nullptr);
const double MeshingMilliseconds=std::chrono::duration<double,std::milli>(
std::chrono::steady_clock::now()-MeshingStart).count();
const FStandaloneShapeWorld& farShape=tileShape.HasFarWorld()?tileShape:*impl_->shape;
+50 -6
View File
@@ -11,9 +11,47 @@
#include <iomanip>
#include <limits>
#include <sstream>
#include <stdexcept>
namespace VoxelForgeCore
{
std::vector<std::uint32_t> BuildWorldAssemblyFeatureTileListV1(
std::span<const WorldAssemblyFeatureBoundsV1> features,
const std::array<std::int64_t, 3>& tileMinimumSubvoxels,
const std::array<std::int64_t, 3>& tileMaximumSubvoxels)
{
if (features.size() > std::numeric_limits<std::uint32_t>::max())
throw std::length_error("world-assembly feature list exceeds 32-bit indexing");
const auto allFeatures = [&]()
{
std::vector<std::uint32_t> result;
result.reserve(features.size());
for (std::size_t index = 0; index < features.size(); ++index)
result.push_back(static_cast<std::uint32_t>(index));
return result;
};
for (std::size_t axis = 0; axis < 3; ++axis)
if (tileMinimumSubvoxels[axis] > tileMaximumSubvoxels[axis]) return allFeatures();
for (const auto& feature : features)
for (std::size_t axis = 0; axis < 3; ++axis)
if (feature.minimumSubvoxels[axis] > feature.maximumSubvoxels[axis]) return allFeatures();
std::vector<std::uint32_t> result;
result.reserve(features.size());
for (std::size_t index = 0; index < features.size(); ++index)
{
const auto& feature = features[index];
bool intersects = true;
for (std::size_t axis = 0; axis < 3; ++axis)
intersects = intersects
&& feature.minimumSubvoxels[axis] <= tileMaximumSubvoxels[axis]
&& feature.maximumSubvoxels[axis] >= tileMinimumSubvoxels[axis];
if (intersects) result.push_back(static_cast<std::uint32_t>(index));
}
return result;
}
namespace world_assembly_detail
{
constexpr std::int64_t Q16 = 1ll << 16;
@@ -1514,14 +1552,20 @@ bool WorldAssemblyManifest::finalize(std::string& error)
if (!islands.manifest.validate(error)) return false;
if (islands.manifest.recipe.seed != recipe.seed)
{ error = "floating-island seed does not match the world assembly season seed"; return false; }
constexpr std::int64_t CoreVoxelsPerLegacyChunk = 16;
const std::int64_t bottomQ = static_cast<std::int64_t>(islands.bottomChunkZ)
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t topQ = (static_cast<std::int64_t>(islands.topChunkZ) + 1)
* CoreVoxelsPerLegacyChunk * WorldAssemblySubvoxelsPerVoxel;
const auto& Scale = islands.manifest.recipe;
if (Scale.ownerToCoreScaleNumerator == 0 || Scale.ownerToCoreScaleDenominator == 0)
{ error = "floating-island owner-to-Core scale is invalid"; return false; }
const double CoreVoxelsPerOwnerChunk = static_cast<double>(CoreTileVoxelCount)
* Scale.ownerToCoreScaleNumerator / Scale.ownerToCoreScaleDenominator;
const std::int64_t bottomQ = static_cast<std::int64_t>(std::llround(
static_cast<double>(islands.bottomChunkZ) * CoreVoxelsPerOwnerChunk
* WorldAssemblySubvoxelsPerVoxel));
const std::int64_t topQ = static_cast<std::int64_t>(std::llround(
static_cast<double>(static_cast<std::int64_t>(islands.topChunkZ) + 1)
* CoreVoxelsPerOwnerChunk * WorldAssemblySubvoxelsPerVoxel));
if (islands.manifest.recipe.layoutMinimumZSubvoxels < bottomQ
|| islands.manifest.recipe.layoutMaximumZSubvoxels > topQ)
{ error = "floating-island recipe layout escapes the resolved 12.5 cm legacy strate bounds"; return false; }
{ error = "floating-island recipe layout escapes the resolved owner strate bounds"; return false; }
islandStrateIds.push_back(islands.strateIndex);
}
recipe.contentHash = world_assembly_detail::HashRecipe(recipe);
@@ -1077,3 +1077,64 @@ The staged owner settings did contain the old manifest, and the plan test retain
One remaining fixture caveat is recorded in [`WorldAssemblerM3/Round4/REPORT.md`](../../WorldAssemblerM3/Round4/REPORT.md): the staged saved `DA_Settings` points to `DA_Strate4`, while the owner’s read-only Play log resolves the active single SurfaceWorld from `DA_Strate1`. The BuildHost commandlet therefore reconstructed that logged one-slot layout as a transient copy at the real `[0,-63]` bounds. No Season asset was assigned in the staged settings, so the season-seed path was not tested. The report includes this distinction, the full pass/fail ledger, crash check, and editor hand-off. No owner project/editor was touched, no `.uasset` was edited, and no commit, push, or stash was made.
No new source file was added. The post-change UHT/BuildHost compile and Core link audit passed at four actions; the Core-only Standalone rebuild/audit passed. No `0xC0000142` retry or new UECC folder occurred.
## World assembler M3 Round 5 / Round S1 — 2026-10-18
The assembler now builds stable per-tile candidate feature lists for Floating Islands. The generic
list builder uses conservative inclusive bounds and can serve caves, shafts, pits and passages too.
At the current four-island plan, sampled p95 was 0.919 ms full scan and 0.910 ms with lists; building
72 lists took 0.0396 ms total. At 256 islands and 240 connectors, sampled tile p95 fell from
112.458 ms to 1.972 ms (about 57x); building 3,280 lists took 95.133 ms total (0.0461 ms p95/list).
Both dropped-feature controls were caught.
The first default island hash comparison caught a real bug: empty-list air tiles skipped a distant
root because version-2 root lobes use scaled ellipsoid density, which is not the geometric distance
assumed by the initial search bound. The bound now includes the validated root slope and smooth-min
reductions. The staged owner plan's every-sample candidate/full comparison had zero mismatches, and
the 2,802-tile default W1/W8/W16 aggregate now exactly matches Round 4 for density, mesh, payload and
topology.
After removing the temporary comparison code, the clean production 25 cm island actor passed again
in 3m48s at 3.16 GiB peak. Its density/mesh/payload/topology hashes were
`4dcfb37585d7d986 / 725a18374c5623de / fb345d8cb319d18b / 76f2846be8efb5f4`, matching Round 4.
Core scale now comes from AVoxelWorld.CoreVoxelSizeCm, default 25 cm, with 32 voxels per tile.
Core-off stays on 12.5 cm legacy scale. Staged-owner BuildHost checks at 20 cm passed for SurfaceWorld
and Floating Islands, including W1/W8/W16, exact surface anchors, water/atmosphere, BP carve/fill,
collision and travel/streaming. The 20 cm controls detect the old 25 cm tile conversion and stale
brush strength. Default SurfaceWorld and Floating Islands hashes match Round 4. The 20 cm Floating
Islands run generated 2,850 tiles; island ground matched exactly, the connector span was within
0.349 cm, water and atmosphere followed the resolved scale, and capsule contacts and BP edits passed.
The default V2 actor run passed in 11m57s with 3.67 GiB peak; fixture repeats, borders and 125
doorway phases passed too.
I kept per-strate sizes deferred because they need a shared physical boundary field, deterministic
transition LODs, edit rasterization across both grids and a stable size map in world identity. The
placement-cost measurement found SurfaceWorld already uses its planned ground directly. Island ground
marching is expensive, but the tested top-anchor shortcut was slower, so no shortcut was kept. The
sample had no actual placements; the report calls that out.
The outside review's strongest implemented suggestion is per-tile feature filtering. Per-strate
resolution and macro-only far LOD are promising but need seam, edit, silhouette and no-pop evidence.
Heading priority already exists; visibility weighting must preserve collision readiness. Keep bridges
as field geometry because player edits stay unrestricted. Separate meshes remain appropriate for
self-contained dreamcore set pieces.
CoreVoxelSizeCm is the new per-world setting. In plain terms, it controls how many centimetres one
Core voxel covers; at 20 cm, a 32-voxel tile is 640 cm wide, while the default 25 cm tile is 800 cm.
Core-off continues using the legacy 12.5 cm world. Per-strate sizes remain deferred until boundary
sampling, transition LODs, cross-boundary edits and size-map hashing have explicit contracts.
The outside review is evaluated in the report. Per-strate size and macro-only far LOD rank highest
among deferred ideas; heading scheduling already exists, and rock bridges stay in the editable field
because carve/fill remains unrestricted. Placement measurements found that SurfaceWorld already
reads its planned height directly. Island density marching is expensive, but the tested top-anchor
shortcut was slower, so I kept no shortcut. Sampled content cells had zero placements.
The full report, exact run ledger, compile spots, source list, M2 checks and owner setting note are in
[WorldAssemblerM3/Round5/REPORT.md](../../WorldAssemblerM3/Round5/REPORT.md). New source files are
the standalone tile-list audit and its PowerShell build/run script. UHT/BuildHost compile and Core
link-safety passed. No 0xC0000142 retry or new UECC folder occurred. I did not open the owner's
editor or build his project, edit any .uasset, create a link, commit, push or stash.
— Luna
@@ -0,0 +1,107 @@
[CmdletBinding()]
param(
[string]$ManifestPath,
[string]$OutputDirectory
)
$ErrorActionPreference = 'Stop'
$BuildHostProcess = Get-Process -Id $PID
$BuildHostProcess.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
$BuildHostProcess.ProcessorAffinity = [IntPtr]0xFFFF
if ([string]::IsNullOrWhiteSpace($ManifestPath))
{ $ManifestPath = Join-Path $PSScriptRoot '..\Round3\results\floating_island_manifest.json' }
if ([string]::IsNullOrWhiteSpace($OutputDirectory))
{ $OutputDirectory = Join-Path $PSScriptRoot 'results' }
$PluginRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..\..'))
$StandaloneRoot = Join-Path $PluginRoot 'Standalone'
$BuildRoot = Join-Path $StandaloneRoot 'Build\VoxelForgeCore'
$SourcePath = Join-Path $PSScriptRoot 'FloatingIslandTileListAudit.cpp'
$CoreLibrary = Join-Path $BuildRoot 'VoxelForgeCore.lib'
$AuditExe = Join-Path $BuildRoot 'FloatingIslandTileListAudit.exe'
$ResponsePath = Join-Path $BuildRoot 'floating-island-tile-list-audit.rsp'
$VcVars = 'E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat'
$ManifestPath = [IO.Path]::GetFullPath($ManifestPath)
$OutputDirectory = [IO.Path]::GetFullPath($OutputDirectory)
foreach ($Required in @($SourcePath, $CoreLibrary, $ManifestPath, $VcVars)) {
if (-not (Test-Path -LiteralPath $Required -PathType Leaf)) { throw "Required audit input is missing: $Required" }
}
[IO.Directory]::CreateDirectory($OutputDirectory) | Out-Null
$ResultPath = Join-Path $OutputDirectory 'tile_list_audit.json'
$StdoutPath = Join-Path $OutputDirectory 'tile_list_audit.stdout.log'
$StderrPath = Join-Path $OutputDirectory 'tile_list_audit.stderr.log'
$LedgerPath = Join-Path $OutputDirectory 'tile_list_audit_run_ledger.txt'
$IncludeArgs = @('/nologo','/std:c++20','/O2','/arch:AVX2','/fp:precise','/EHsc','/D_CRT_SECURE_NO_WARNINGS',
('/I"{0}"' -f (Join-Path $StandaloneRoot 'Core\include')))
$ResponseLines = $IncludeArgs + @(('/Fe"{0}"' -f $AuditExe),('"{0}"' -f $SourcePath),
('"{0}"' -f $CoreLibrary))
[IO.File]::WriteAllLines($ResponsePath,$ResponseLines,[Text.UTF8Encoding]::new($false))
Push-Location $BuildRoot
try {
$BuildSucceeded = $false
for ($Attempt = 1; $Attempt -le 2 -and -not $BuildSucceeded; ++$Attempt) {
$CompileCommand = 'call "{0}" >nul && cl.exe @"{1}"' -f $VcVars,$ResponsePath
& $env:ComSpec /d /s /c $CompileCommand
if ($LASTEXITCODE -eq -1073741502 -and $Attempt -eq 1) {
Write-Warning 'Audit build exited with 0xC0000142; retrying once.'
continue
}
if ($LASTEXITCODE -ne 0) { throw "Floating-island tile-list audit build failed with exit code $LASTEXITCODE" }
$BuildSucceeded = $true
}
} finally { Pop-Location }
$RunRecords = [Collections.Generic.List[string]]::new()
$Succeeded = $false
for ($Attempt = 1; $Attempt -le 2 -and -not $Succeeded; ++$Attempt) {
$Process = Start-Process -FilePath $AuditExe -ArgumentList @('"' + $ManifestPath + '"','"' + $ResultPath + '"') `
-WorkingDirectory $BuildRoot -WindowStyle Hidden -RedirectStandardOutput $StdoutPath `
-RedirectStandardError $StderrPath -PassThru
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
$Process.ProcessorAffinity = [IntPtr]0xFFFF
$Clock = [Diagnostics.Stopwatch]::StartNew()
[int64]$PeakPrivateBytes = 0
$LimitFailure = $null
while (-not $Process.WaitForExit(250)) {
$Process.Refresh()
$PeakPrivateBytes = [Math]::Max($PeakPrivateBytes,$Process.PrivateMemorySize64)
if ($PeakPrivateBytes -gt 8GB) {
$LimitFailure = '8 GiB private-memory limit exceeded'
Stop-Process -Id $Process.Id -Force
break
}
if ($Clock.Elapsed.TotalMinutes -ge 15.0) {
$LimitFailure = '15-minute time box exceeded'
Stop-Process -Id $Process.Id -Force
break
}
}
$Process.WaitForExit()
$Clock.Stop()
$ExitCode = $Process.ExitCode
$StdoutText = if (Test-Path -LiteralPath $StdoutPath -PathType Leaf) { Get-Content -LiteralPath $StdoutPath -Raw } else { '' }
$ExplicitPass = $StdoutText -match '(?m)^TILE_LIST_AUDIT_PASS\s*$'
$RunPassed = $ExitCode -eq 0 -or ([string]::IsNullOrEmpty([string]$ExitCode) -and $ExplicitPass)
$Status = if ($LimitFailure) { $LimitFailure } elseif ($RunPassed) { 'passed' } else { "failed exit=$ExitCode" }
$RunRecords.Add("attempt=$Attempt status=$Status elapsed_s=$([Math]::Round($Clock.Elapsed.TotalSeconds,2)) peak_private_MiB=$([Math]::Round($PeakPrivateBytes/1MB,1)) exit=$ExitCode")
if ($LimitFailure) { break }
if ($RunPassed) { $Succeeded = $true; break }
if ($ExitCode -eq -1073741502 -and $Attempt -eq 1) {
$RunRecords.Add('retry_reason=0xC0000142 single permitted retry')
continue
}
break
}
[IO.File]::WriteAllLines($LedgerPath,$RunRecords,[Text.UTF8Encoding]::new($false))
if (-not $Succeeded) {
Get-Content -LiteralPath $StdoutPath -ErrorAction SilentlyContinue
Get-Content -LiteralPath $StderrPath -ErrorAction SilentlyContinue | Write-Error
throw "Audit failed or exceeded a process limit. See $LedgerPath"
}
if (-not (Test-Path -LiteralPath $ResultPath -PathType Leaf)) { throw "Audit did not write $ResultPath" }
Write-Output "audit_results=$ResultPath"
Get-Content -LiteralPath $LedgerPath | Write-Output
Get-Content -LiteralPath $StdoutPath | Write-Output
Get-Content -LiteralPath $StderrPath | Write-Output
Get-Content -LiteralPath $ResultPath
@@ -0,0 +1,435 @@
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <numeric>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
namespace
{
using namespace VoxelForgeCore;
using Clock = std::chrono::steady_clock;
constexpr std::int64_t Q = WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t TileQ = 32 * Q;
constexpr std::int64_t TileStepQ = 4 * Q;
constexpr std::int64_t MesherHaloQ = Q;
struct Tile
{
std::array<std::int64_t, 3> minimum{};
std::array<std::int64_t, 3> maximum{};
FloatingIslandTileFeatureListV1 featureList;
double buildMilliseconds = 0.0;
};
struct Measurement
{
std::uint64_t tilesBuilt = 0;
std::uint64_t sampledTiles = 0;
std::uint64_t samples = 0;
std::uint64_t candidateEvaluations = 0;
std::uint64_t fullEvaluations = 0;
std::uint64_t exactMismatches = 0;
std::uint64_t droppedFeatureDetected = 0;
double listBuildTotalMilliseconds = 0.0;
double listBuildP95Milliseconds = 0.0;
double beforeP95Milliseconds = 0.0;
double afterP95Milliseconds = 0.0;
double beforeMedianMilliseconds = 0.0;
double afterMedianMilliseconds = 0.0;
double meanCandidateFeatures = 0.0;
double p95CandidateFeatures = 0.0;
std::string beforeHash;
std::string afterHash;
bool negativeControlPassed = false;
};
std::string Hex64(std::uint64_t value)
{
std::ostringstream output;
output << std::hex << std::setfill('0') << std::setw(16) << value;
return output.str();
}
std::uint64_t HashValues(const std::vector<std::int64_t>& values)
{
std::uint64_t hash = 14695981039346656037ull;
for (const auto value : values)
for (int byte = 0; byte < 8; ++byte)
{
hash ^= static_cast<std::uint8_t>(static_cast<std::uint64_t>(value) >> (byte * 8));
hash *= 1099511628211ull;
}
return hash;
}
double Percentile95(std::vector<double> values)
{
if (values.empty()) return 0.0;
std::sort(values.begin(), values.end());
const std::size_t index = (values.size() * 95 + 99) / 100 - 1;
return values[std::min(index, values.size() - 1)];
}
double Median(std::vector<double> values)
{
if (values.empty()) return 0.0;
std::sort(values.begin(), values.end());
const std::size_t middle = values.size() / 2;
if (values.size() & 1u) return values[middle];
return 0.5 * (values[middle - 1] + values[middle]);
}
std::int64_t FloorDiv(std::int64_t value, std::int64_t divisor)
{
std::int64_t quotient = value / divisor;
const std::int64_t remainder = value % divisor;
if (remainder != 0 && ((remainder < 0) != (divisor < 0))) --quotient;
return quotient;
}
void ShiftBounds(std::array<std::int64_t, 3>& minimum,
std::array<std::int64_t, 3>& maximum, const std::array<std::int64_t, 3>& delta)
{
for (std::size_t axis = 0; axis < 3; ++axis)
{ minimum[axis] += delta[axis]; maximum[axis] += delta[axis]; }
}
std::uint64_t SyntheticId(std::uint32_t index, std::uint64_t salt)
{
std::uint64_t value = (static_cast<std::uint64_t>(index) << 32) ^ salt;
value += 0x9e3779b97f4a7c15ull;
value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9ull;
value = (value ^ (value >> 27)) * 0x94d049bb133111ebull;
value ^= value >> 31;
return value == 0 ? 1 : value;
}
FloatingIslandPlanV1 MakeStressPlan(const FloatingIslandRecipeV1& recipe,
const FloatingIslandPlanV1& source, std::size_t islandCount)
{
if (source.islands.empty()) throw std::runtime_error("source plan has no island template");
FloatingIslandPlanV1 result;
result.islands.reserve(islandCount);
const auto& prototype = source.islands.front();
constexpr std::size_t Columns = 16;
const std::int64_t spacing = 80 * Q;
for (std::size_t index = 0; index < islandCount; ++index)
{
FloatingIslandAnchorV1 island = prototype;
const std::int64_t dx = static_cast<std::int64_t>(index % Columns) * spacing;
const std::int64_t dy = static_cast<std::int64_t>(index / Columns) * spacing;
const std::int64_t dz = static_cast<std::int64_t>(index % 3) * 4 * Q;
const std::array<std::int64_t, 3> delta{dx, dy, dz};
island.centerSubvoxels = {prototype.centerSubvoxels[0] + dx,
prototype.centerSubvoxels[1] + dy, prototype.centerSubvoxels[2] + dz};
island.topZSubvoxels += dz;
island.bottomZSubvoxels += dz;
ShiftBounds(island.influenceMinimumSubvoxels, island.influenceMaximumSubvoxels, delta);
island.ordinal = static_cast<std::uint32_t>(index);
island.stableId = SyntheticId(static_cast<std::uint32_t>(index), 0x49534c414e44ull);
result.islands.push_back(island);
}
result.clusterEdges.reserve(islandCount - islandCount / Columns);
for (std::size_t index = 0; index < islandCount; ++index)
{
if (index % Columns + 1 >= Columns || index + 1 >= islandCount) continue;
const auto& from = result.islands[index];
const auto& to = result.islands[index + 1];
FloatingIslandConnectorV1 edge;
edge.stableId = SyntheticId(static_cast<std::uint32_t>(index), 0x425249444745ull);
edge.fromIslandId = from.stableId;
edge.toIslandId = to.stableId;
edge.startSubvoxels = {from.centerSubvoxels[0] + from.radiusSubvoxels,
from.centerSubvoxels[1], from.topZSubvoxels};
edge.endSubvoxels = {to.centerSubvoxels[0] - to.radiusSubvoxels,
to.centerSubvoxels[1], to.topZSubvoxels};
edge.halfWidthSubvoxels = recipe.bridgeHalfWidthSubvoxels;
edge.thicknessSubvoxels = recipe.bridgeThicknessSubvoxels;
const std::int64_t pad = recipe.blendRadiusSubvoxels + recipe.roughnessSubvoxels;
const std::int64_t archDepth = recipe.geometryVersion >= 2
? 2 * edge.thicknessSubvoxels : 0;
const std::int64_t xyPad = edge.halfWidthSubvoxels + pad;
edge.influenceMinimumSubvoxels = {
std::min(edge.startSubvoxels[0], edge.endSubvoxels[0]) - xyPad,
std::min(edge.startSubvoxels[1], edge.endSubvoxels[1]) - xyPad,
std::min(edge.startSubvoxels[2], edge.endSubvoxels[2])
- edge.thicknessSubvoxels - pad - archDepth};
edge.influenceMaximumSubvoxels = {
std::max(edge.startSubvoxels[0], edge.endSubvoxels[0]) + xyPad,
std::max(edge.startSubvoxels[1], edge.endSubvoxels[1]) + xyPad,
std::max(edge.startSubvoxels[2], edge.endSubvoxels[2]) + pad};
result.clusterEdges.push_back(edge);
}
return result;
}
std::vector<Tile> BuildMixedTiles(const FloatingIslandFieldV1& field,
const std::array<std::int64_t, 3>& tileLimitMinimum,
const std::array<std::int64_t, 3>& tileLimitMaximum,
Measurement& measurement)
{
std::vector<Tile> tiles;
for (std::int64_t z = tileLimitMinimum[2]; z <= tileLimitMaximum[2]; z += TileQ)
for (std::int64_t y = tileLimitMinimum[1]; y <= tileLimitMaximum[1]; y += TileQ)
for (std::int64_t x = tileLimitMinimum[0]; x <= tileLimitMaximum[0]; x += TileQ)
{
Tile tile;
tile.minimum = {x, y, z};
tile.maximum = {x + TileQ, y + TileQ, z + TileQ};
const std::array<std::int64_t, 3> sampleMinimum{
x - MesherHaloQ, y - MesherHaloQ, z - MesherHaloQ};
const std::array<std::int64_t, 3> sampleMaximum{
x + TileQ + MesherHaloQ, y + TileQ + MesherHaloQ, z + TileQ + MesherHaloQ};
const auto begin = Clock::now();
tile.featureList = field.buildFeatureTileList(sampleMinimum, sampleMaximum);
tile.buildMilliseconds = std::chrono::duration<double, std::milli>(Clock::now() - begin).count();
measurement.listBuildTotalMilliseconds += tile.buildMilliseconds;
++measurement.tilesBuilt;
if (!tile.featureList.featureIndices.empty()) tiles.push_back(std::move(tile));
}
return tiles;
}
Measurement MeasureField(const FloatingIslandFieldV1& field,
std::array<std::int64_t, 3> limitMinimum,
std::array<std::int64_t, 3> limitMaximum)
{
Measurement measurement;
for (std::size_t axis = 0; axis < 3; ++axis)
{
limitMinimum[axis] = FloorDiv(limitMinimum[axis], TileQ) * TileQ;
limitMaximum[axis] = FloorDiv(limitMaximum[axis], TileQ) * TileQ;
}
auto tiles = BuildMixedTiles(field, limitMinimum, limitMaximum, measurement);
if (tiles.empty()) throw std::runtime_error("tile bounds produced no candidate tiles");
std::vector<double> buildTimes, candidateCounts;
buildTimes.reserve(tiles.size()); candidateCounts.reserve(tiles.size());
for (const Tile& tile : tiles)
{ buildTimes.push_back(tile.buildMilliseconds); candidateCounts.push_back(static_cast<double>(tile.featureList.featureIndices.size())); }
measurement.listBuildP95Milliseconds = Percentile95(std::move(buildTimes));
measurement.meanCandidateFeatures = std::accumulate(candidateCounts.begin(), candidateCounts.end(), 0.0)
/ static_cast<double>(candidateCounts.size());
measurement.p95CandidateFeatures = Percentile95(std::move(candidateCounts));
constexpr std::size_t MaximumSampleTiles = 64;
if (tiles.size() > MaximumSampleTiles)
{
std::vector<Tile> selected;
selected.reserve(MaximumSampleTiles);
for (std::size_t slot = 0; slot < MaximumSampleTiles; ++slot)
{
const std::size_t index = slot * (tiles.size() - 1) / (MaximumSampleTiles - 1);
selected.push_back(std::move(tiles[index]));
}
tiles = std::move(selected);
}
std::vector<double> beforeTimes, afterTimes;
std::vector<std::int64_t> beforeValues, afterValues;
beforeTimes.reserve(tiles.size()); afterTimes.reserve(tiles.size());
for (const Tile& tile : tiles)
{
// Cover the mesher's one-sample LOD0 halo as well as the tile at four-voxel spacing.
// Couvrir la marge d'un echantillon du maillage LOD0 et la tuile a pas de quatre voxels.
const auto buildAxis = [](std::int64_t minimum, std::int64_t maximum)
{
std::vector<std::int64_t> values;
const std::int64_t start = minimum - MesherHaloQ;
const std::int64_t end = maximum + MesherHaloQ;
for (std::int64_t value = start; value <= end; value += TileStepQ)
values.push_back(value);
if (values.empty() || values.back() != end) values.push_back(end);
return values;
};
const auto xs = buildAxis(tile.minimum[0], tile.maximum[0]);
const auto ys = buildAxis(tile.minimum[1], tile.maximum[1]);
const auto zs = buildAxis(tile.minimum[2], tile.maximum[2]);
std::vector<std::array<std::int64_t, 3>> points;
points.reserve(xs.size() * ys.size() * zs.size());
for (const std::int64_t z : zs)
for (const std::int64_t y : ys)
for (const std::int64_t x : xs)
points.push_back({x, y, z});
measurement.samples += points.size();
measurement.fullEvaluations += points.size();
measurement.candidateEvaluations += points.size() * tile.featureList.featureIndices.size();
const auto beforeStart = Clock::now();
for (const auto& point : points)
beforeValues.push_back(field.densitySubvoxels(point[0], point[1], point[2]));
beforeTimes.push_back(std::chrono::duration<double, std::milli>(Clock::now() - beforeStart).count());
const auto afterStart = Clock::now();
for (const auto& point : points)
afterValues.push_back(field.densitySubvoxelsForTile(
point[0], point[1], point[2], tile.featureList));
afterTimes.push_back(std::chrono::duration<double, std::milli>(Clock::now() - afterStart).count());
++measurement.sampledTiles;
}
measurement.beforeP95Milliseconds = Percentile95(beforeTimes);
measurement.afterP95Milliseconds = Percentile95(afterTimes);
measurement.beforeMedianMilliseconds = Median(beforeTimes);
measurement.afterMedianMilliseconds = Median(afterTimes);
measurement.beforeHash = Hex64(HashValues(beforeValues));
measurement.afterHash = Hex64(HashValues(afterValues));
const std::size_t common = std::min(beforeValues.size(), afterValues.size());
for (std::size_t index = 0; index < common; ++index)
if (beforeValues[index] != afterValues[index]) ++measurement.exactMismatches;
if (field.plan() && !field.plan()->islands.empty())
{
constexpr std::uint32_t droppedFeature = 0;
const auto& island = field.plan()->islands.front();
const std::array<std::int64_t, 3> point{island.centerSubvoxels[0], island.centerSubvoxels[1],
island.bottomZSubvoxels + (island.topZSubvoxels - island.bottomZSubvoxels) / 2};
std::array<std::int64_t, 3> minimum{};
std::array<std::int64_t, 3> maximum{};
for (std::size_t axis = 0; axis < 3; ++axis)
{
minimum[axis] = FloorDiv(point[axis], TileQ) * TileQ;
maximum[axis] = minimum[axis] + TileQ;
}
auto sampleMinimum = minimum;
auto sampleMaximum = maximum;
for (std::size_t axis = 0; axis < 3; ++axis)
{
sampleMinimum[axis] -= MesherHaloQ;
sampleMaximum[axis] += MesherHaloQ;
}
FloatingIslandTileFeatureListV1 dropped = field.buildFeatureTileList(
sampleMinimum, sampleMaximum);
auto candidate = std::lower_bound(dropped.featureIndices.begin(), dropped.featureIndices.end(),
droppedFeature);
if (candidate != dropped.featureIndices.end() && *candidate == droppedFeature)
{
dropped.featureIndices.erase(candidate);
for (std::size_t axis = 0; axis < 3; ++axis)
{
dropped.groupBounds[droppedFeature].minimumSubvoxels[axis] += 100000000000ll;
dropped.groupBounds[droppedFeature].maximumSubvoxels[axis] += 100000000000ll;
}
const auto order = std::find(dropped.groupSearchOrder.begin(),
dropped.groupSearchOrder.end(), droppedFeature);
if (order != dropped.groupSearchOrder.end())
{
const std::size_t orderIndex = static_cast<std::size_t>(order - dropped.groupSearchOrder.begin());
dropped.groupSearchOrder.erase(order);
dropped.groupDistanceLowerBoundsSubvoxels.erase(
dropped.groupDistanceLowerBoundsSubvoxels.begin() + orderIndex);
dropped.groupSearchComplete = false;
const auto full = field.densitySubvoxels(point[0], point[1], point[2]);
const auto bad = field.densitySubvoxelsForTile(point[0], point[1], point[2], dropped);
if (full != bad) ++measurement.droppedFeatureDetected;
}
}
}
measurement.negativeControlPassed = measurement.droppedFeatureDetected > 0;
return measurement;
}
void Print(const char* label, const Measurement& measurement,
std::size_t islandCount, std::size_t connectorCount)
{
std::cout << label << " islands=" << islandCount << " connectors=" << connectorCount
<< " list_tiles_built=" << measurement.tilesBuilt
<< " sampled_tiles=" << measurement.sampledTiles
<< " sample_count=" << measurement.samples
<< " mean_candidates=" << measurement.meanCandidateFeatures
<< " p95_candidates=" << measurement.p95CandidateFeatures
<< " list_build_total_ms=" << measurement.listBuildTotalMilliseconds
<< " list_build_p95_ms=" << measurement.listBuildP95Milliseconds
<< " full_tile_p95_ms=" << measurement.beforeP95Milliseconds
<< " candidate_tile_p95_ms=" << measurement.afterP95Milliseconds
<< " full_tile_median_ms=" << measurement.beforeMedianMilliseconds
<< " candidate_tile_median_ms=" << measurement.afterMedianMilliseconds
<< " exact_mismatches=" << measurement.exactMismatches
<< " full_hash=" << measurement.beforeHash
<< " candidate_hash=" << measurement.afterHash
<< " negative_control_detected=" << measurement.negativeControlPassed << '\n';
}
}
int main(int argc, char** argv)
{
try
{
if (argc != 3)
{ std::cerr << "usage: FloatingIslandTileListAudit <manifest.json> <output.json>\n"; return 2; }
std::ifstream input(argv[1], std::ios::binary);
if (!input) throw std::runtime_error("could not open the recorded round-3 island manifest");
const std::string json{std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>()};
std::string error;
const auto manifest = FloatingIslandManifestV1::FromJson(json, error);
if (!manifest || !manifest->validate(error)) throw std::runtime_error("manifest invalid: " + error);
FloatingIslandFieldV1 field(manifest->recipe, manifest->plan);
if (!field.isValid()) throw std::runtime_error("round-3 production field did not validate");
const auto bounds = field.influenceBounds();
const Measurement today = MeasureField(field, bounds.minimumSubvoxels, bounds.maximumSubvoxels);
const FloatingIslandPlanV1 stressPlan = MakeStressPlan(manifest->recipe, manifest->plan, 256);
FloatingIslandFieldV1 stressField(manifest->recipe, stressPlan);
if (!stressField.isValid()) throw std::runtime_error("synthetic stress field did not validate");
const auto stressBounds = stressField.influenceBounds();
const Measurement stress = MeasureField(stressField,
stressBounds.minimumSubvoxels, stressBounds.maximumSubvoxels);
Print("today", today, manifest->plan.islands.size(), manifest->plan.clusterEdges.size());
Print("stress", stress, stressPlan.islands.size(), stressPlan.clusterEdges.size());
std::ofstream output(argv[2], std::ios::trunc);
if (!output) throw std::runtime_error("could not create audit result JSON");
output << std::setprecision(12) << "{\n"
<< " \"baseline\":{\"islands\":" << manifest->plan.islands.size()
<< ",\"connectors\":" << manifest->plan.clusterEdges.size()
<< ",\"tiles_built\":" << today.tilesBuilt << ",\"sampled_tiles\":" << today.sampledTiles
<< ",\"samples\":" << today.samples << ",\"mean_candidates\":" << today.meanCandidateFeatures
<< ",\"p95_candidates\":" << today.p95CandidateFeatures
<< ",\"list_build_total_ms\":" << today.listBuildTotalMilliseconds
<< ",\"list_build_p95_ms\":" << today.listBuildP95Milliseconds
<< ",\"before_p95_ms\":" << today.beforeP95Milliseconds
<< ",\"after_p95_ms\":" << today.afterP95Milliseconds
<< ",\"before_median_ms\":" << today.beforeMedianMilliseconds
<< ",\"after_median_ms\":" << today.afterMedianMilliseconds
<< ",\"exact_mismatches\":" << today.exactMismatches
<< ",\"before_hash\":\"" << today.beforeHash << "\",\"after_hash\":\"" << today.afterHash
<< "\",\"negative_control\":" << (today.negativeControlPassed ? "true" : "false") << "},\n"
<< " \"stress\":{\"islands\":" << stressPlan.islands.size()
<< ",\"connectors\":" << stressPlan.clusterEdges.size()
<< ",\"tiles_built\":" << stress.tilesBuilt << ",\"sampled_tiles\":" << stress.sampledTiles
<< ",\"samples\":" << stress.samples << ",\"mean_candidates\":" << stress.meanCandidateFeatures
<< ",\"p95_candidates\":" << stress.p95CandidateFeatures
<< ",\"list_build_total_ms\":" << stress.listBuildTotalMilliseconds
<< ",\"list_build_p95_ms\":" << stress.listBuildP95Milliseconds
<< ",\"before_p95_ms\":" << stress.beforeP95Milliseconds
<< ",\"after_p95_ms\":" << stress.afterP95Milliseconds
<< ",\"before_median_ms\":" << stress.beforeMedianMilliseconds
<< ",\"after_median_ms\":" << stress.afterMedianMilliseconds
<< ",\"exact_mismatches\":" << stress.exactMismatches
<< ",\"before_hash\":\"" << stress.beforeHash << "\",\"after_hash\":\"" << stress.afterHash
<< "\",\"negative_control\":" << (stress.negativeControlPassed ? "true" : "false") << "}\n}\n";
if (!today.negativeControlPassed || !stress.negativeControlPassed)
throw std::runtime_error("dropped-feature negative control was not detected");
if (today.exactMismatches || stress.exactMismatches)
throw std::runtime_error("tile feature evaluation changed canonical field values");
std::cout << "TILE_LIST_AUDIT_PASS\n";
return 0;
}
catch (const std::exception& exception)
{
std::cerr << "FLOATING_ISLAND_TILE_LIST_AUDIT_FAIL: " << exception.what() << '\n';
return 1;
}
}
@@ -0,0 +1,169 @@
# World Assembler M3 Round 5 / World Assembler Round S1
**Date:** 2026-10-18
**Scope:** tile-local feature lists first, one runtime Core voxel-size source, staged-owner placement-cost measurements, and evaluation of the supplied outside review.
## Result
The generic conservative tile-feature-list API is implemented and used by Floating Islands. Its field values and aggregate probe hashes match the full-scan control at four islands and a 256-island stress count; the deliberately dropped-feature control is detected.
Core now resolves physical voxel size from AVoxelWorld::CoreVoxelSizeCm. The default is 25 cm, the supported range in this integration is 12.5–25 cm, and CoreTileVoxelCount is 32. Core-off remains on the legacy 12.5 cm path. The size is carried in Core world configuration and non-default sizes affect world identity. This is per-world for now; the boundary, edit, LOD and hashing contract required for per-strate sizes is listed below.
The staged owner SurfaceWorld and Floating Islands routes passed end-to-end BuildHost checks at 20 cm, including W1/W8/W16, field/height anchors, content/water/atmosphere, BP carve/fill, collision and movement/streaming checks. Default 25 cm SurfaceWorld and Floating Islands aggregate hashes now match Round 4 exactly. A root-aware air-side search bound was needed to keep the tile-list field bit-identical outside island geometry; the staged 2,802-tile actor plan compared candidate and full-scan density at every sampled point with zero mismatches.
## 1. One Core voxel-size source
### Contract and implementation
- AVoxelWorld::CoreVoxelSizeCm is the one world-level Core voxel-size setting. GetActiveVoxelSizeCm() resolves it at runtime. CoreTileVoxelCount is 32 voxels; physical tile width at LOD L is derived from the active size, 32 and 2^L.
- The size is represented in microns in VoxelForgeCore::WorldConfiguration alongside tileVoxelCount. Plan serialization and the season/content identity include the resolved non-default size. At the default 25 cm, the new identity suffix is omitted to preserve the old default identity.
- Core-off reads the legacy VOXEL_SIZE path and does not use the Core setting.
- Active Core physical lengths and coordinate conversions now derive from the getter for field/mesher geometry, transforms, height probes, content and tree placement, water, atmosphere, brushes and edit strength, collision bounds, streaming, clipmap and LOD. The legacy-grid manager bridge remains explicit and derives its ratio from the active size. Core tile coordinates stay in voxel units.
- The SurfaceWorld Core-to-owner chunk-coordinate mapping is computed from active size; it no longer assumes a fixed 25-to-12.5 ratio.
- The setting is a reflected AVoxelWorld field. Plain-language owner note: it changes how many centimetres one Core voxel covers; at the default of 25 cm the world keeps its old Core scale, and a value of 20 cm makes the same 32-voxel tile 640 cm wide instead of 800 cm. It does not change the legacy world when Core is off.
The staged 20 cm identity check reported SHA-1 4A62DB4F07FD958075856FA5A2F1B508937F757C; the alternate 25 cm identity was CCF5E42EC8405CDAB4065952AFFAA0DC932D3594. The default 25 cm check reported the inverse pair and distinct=1. This proves that peers using different active Core sizes do not share the same season/content identity.
### BuildHost proof
The staged-owner 20 cm SurfaceWorld and Floating Islands runs both completed in BuildHost. The SurfaceWorld run took 12m11s (731.65 seconds) and peaked at about 3.95 GiB. It reported W1/W8/W16-identical hashes:
| SurfaceWorld hash | 20 cm |
|---|---|
| Density | 0x0cd2a1a4152ee4aa |
| Mesh | 0x540f8671bb53e7c8 |
| Payload | 0x250aed7c9f875659 |
| Topology | 0xed49c64851e0ce73 |
The exact native height and content ground were both -30,126.797 cm (0 cm delta); water was -29,260.781 cm; atmosphere bounds were (-40,320, 640) cm. Content anchors, water and atmosphere all followed the same planned scale. The BP carve/fill fixture passed with a 75 cm brush radius, Core strength 6.25, exact center density changes of +6.25/-6.25, a partial value at 60 cm and zero change at 80 cm. Collision bodies, anchor motion and W1/W8/W16 passed. The Core-off content gate reported legacy 12.5 cm.
The 20 cm Floating Islands actor route also passed W1/W8/W16, native island/span content anchors, water and atmosphere, Blueprint edits, capsule contacts on the island and connector, and Core-off legacy behavior. Its assembly hash was 6bc8e57a763a6ca5; recipe hash f12c1b9c291ff311; plan hash fbf909dc4bddcf18. Island top agreement was exact; connector span delta was 0.349 cm. Water was -29,260.801 cm and atmosphere bounds were (-40,320, 640) cm. Density/mesh/payload/topology matrix: 0x9dbd7dc8f3fe872f / 0xa120324982cd49b2 / 0xf836bd7f7288a9e3 / 0xbb6402d5aa697991.
The 20 cm checks also include two falsifiable scale controls:
1. Reintroducing a fixed 25 cm tile conversion predicts 800 cm for a 32-voxel tile; the active 20 cm result is 640 cm. The expected 160 cm span error is detected by the geometry audit.
2. Reintroducing the default-size brush strength at 20 cm uses 5 instead of the required 6.25 Core density units. The BP carve/fill center-value assertion detects the stale magnitude and sign/direction checks still distinguish carve from fill.
At the default 25 cm, the fresh SurfaceWorld W1/W8/W16 hashes exactly match Round 4: 0x0cd2a1a4152ee4aa / 0x17775c835711e5a3 / 0xa27224958fc27d7b / 0x3821fea90b134ce4. Height delta was 0 cm, water was -36,575.977 cm, atmosphere bounds were (-50,400, 800) cm, and the BP edit, collision and streaming gates passed.
The default Floating Islands assembly, recipe and plan hashes remain 9c372e7bfb5b8310 / 5e0370d9f95f1c15 / dbbda4932378047a. After correcting the distant-group bound, the default W1/W8/W16 hashes are exactly the Round 4 aggregate: density 0x4dcfb37585d7d986, mesh 0x725a18374c5623de, payload 0xfb345d8cb319d18b, topology 0x76f2846be8efb5f4. The actor run used the same 2,802-key clipmap, and the temporary diagnostic compared candidate and full-scan density for every field call with no mismatches. Its height/content, water/atmosphere, capsule contact, Blueprint edit and Core-off checks passed.
At 20 cm, the corrected Floating Islands actor generated 2,850 tiles and passed W1/W8/W16: density/mesh/payload/topology 0xb4282f8a486c6e92 / 0x83a186d80937d98a / 0x92fe9b3bcfc25e44 / 0xaee5ad83fca9da82. Island ground matched terrain exactly; connector-span residual was 0.349 cm. Water was -29,260.801 cm, atmosphere bounds (-40,320, 640) cm, both capsule contacts passed, the 75 cm BP carve/fill test passed, and Core-off remained 12.5 cm legacy.
The default M2 V1/V2 analytic fixture-only validation passed exact repeats for five fixtures, same-LOD border identity, V2 marching-cubes identity and all 125 doorway phases. The full default V2 actor run also passed; the run ledger records its timing, hashes and log.
### Per-strate size decision
Per-strate resolution is deferred. It is not a safe scalar substitution: one physical query can cross a boundary where sample spacing changes. Before enabling it, define:
1. Canonical physical-space field queries and ownership rules at a strate boundary.
2. Deterministic transition meshes and common LOD ancestry so both sides agree on shared borders.
3. Edit rasterization/replay that reaches both grids with no holes or double application, including edits crossing the boundary.
4. A stable sorted strate-ID-to-size map in plan/season identity, plus worker and border hashes that include both sizes.
5. Streaming and collision policies for a tile spanning or supporting two resolutions.
## 2. Per-tile feature lists
BuildWorldAssemblyFeatureTileListV1 is the reusable API. It consumes conservative inclusive feature bounds and returns stable ordered feature indices for a tile. FloatingIslandFieldV1 builds the list once for each tile and the worker-scoped evaluation context sends densityAt through that candidate list. Bounds include the features' full influence; a feature that cannot be proven irrelevant stays in the list. This supports future caves, shafts, pits and passages without making each field family invent its own skip rule.
The Round 5 audit compares full scan and candidate-list output at exactly the same sample locations. The hash and mismatch assertions make a false skip observable.
| Plan | Features | List build | Full scan p95 / median per sampled tile | Candidate p95 / median | Candidates p95 | Equality |
|---|---:|---:|---:|---:|---:|---|
| Current | 4 islands + 3 connectors | 72 lists, 0.0396 ms total; 0.0009 ms p95/list | 0.9189 / 0.8644 ms | 0.9097 / 0.5010 ms | 6 | 60,000 samples; 0 mismatches; hashes 1ae5da3039bdcbdd / 1ae5da3039bdcbdd |
| Stress | 256 islands + 240 connectors | 3,280 lists, 95.133 ms total; 0.0461 ms p95/list | 112.458 / 111.660 ms | 1.972 / 1.326 ms | 4 | 64,000 samples; 0 mismatches; hashes 7b92fa4e8d6acf49 / 7b92fa4e8d6acf49 |
The stress plan reduced sampled tile p95 from 112.46 ms to 1.97 ms (about 57×). List construction is a one-time per-plan/per-tile preparation cost in the audit; the stress build took 95.133 ms total, with the 95th percentile list taking 0.0461 ms. The negative control drops an island and its summary bound; the field-hash assertion fails and reports negative_control_detected=1. Both audit plans have exact_mismatches=0. The staged owner plan adds a broader check: the 2,802-tile default actor run compared candidate and full scan at each queried point and reproduced the Round 4 aggregate.
## 3. Content and tree placement measurement
The BuildHost commandlet timed the staged owner settings at 64 placement-grid cells and eight columns per cell. There are six authored decoration entries. The sampled cells produced zero actual placements, so the numbers below isolate ground-search cost and steady manager update overhead. They do not include spawn, component creation, instance upload or a populated placement cell. The per-frame number is the stationary manager Update measurement; it is not a rendered frame capture.
| Route | Voxel / cell width | Density queries per cell | Planned-surface queries per cell | Cell p95 | Stationary update p95 | Placements |
|---|---:|---:|---:|---:|---:|---:|
| SurfaceWorld Core 25 cm | 25 / 800 cm | 0 | 160 | 0.1945 ms | 0.0006 ms | 0 |
| SurfaceWorld Core 20 cm | 20 / 640 cm | 0 | 160 | 0.2127 ms | 0.0005 ms | 0 |
| SurfaceWorld legacy (20 cm comparison run) | 12.5 / 400 cm | 64 | 160 | 0.3326 ms | 0.0004 ms | 0 |
| SurfaceWorld legacy (25 cm comparison run) | 12.5 / 400 cm | 64 | 160 | 0.3215 ms | 0.0004 ms | 0 |
| Floating Islands Core 25 cm | 25 / 800 cm | 7,763.38 | 0 | 7.2511 ms | 0.0005 ms | 0 |
| Floating Islands Core 20 cm | 20 / 640 cm | 7,528.38 | 0 | 7.2045 ms | 0.0007 ms | 0 |
| Floating Islands legacy | 12.5 / 400 cm | 193.25 | 0 | 0.2928 ms | 0.0004 ms | 0 |
SurfaceWorld already reads its planned Heightfield2D directly; this eliminated density-column marching in the Core measurement while keeping the actor's content/terrain height agreement exact. Keep that path.
Floating Islands' generic density march is expensive on this sampled route. I tested using the existing island GetTopAnchor path in place of the march. It preserved the tested anchor to a maximum residual of 0.027344 Core density units but increased p95 from 7.2045 ms to 7.9887 ms and density queries from 7,528.38 to 7,569.69 per cell. The candidate change was reverted. The immediate recommendation is to build an exact, cached island-top query over the tile-local feature list, measure populated cells and total active frame cost, then adopt it only if it lowers cost while preserving exact anchor hashes and no-floating-tree checks. No production shortcut was retained.
## 4. Outside review evaluation
The supplied share is generic advice; it correctly separates planning, density evaluation and mesh generation and warns that a small feature cannot survive a coarse sampling grid merely because it has an SDF.
### What VoxelForge already has
- A startup director/adapter that builds a deterministic plan from resolved owner settings and authored strate layout; the plan and season identity are hashed.
- Analytic Core field families, bounded features, conservative interval proofs and a stable construction plan for SurfaceWorld and Floating Islands.
- A clipmap for Core streaming, worker-count-stable generation, an edit diff layer, collision-support radius/policy and Blueprint carve/fill through the canonical field.
- Native SurfaceWorld heightfield sampling and a planned route with capsule support for Floating Islands.
- M2 MC, hybrid and DC fixture routes, including repeated geometry and border checks.
- Heading-aware near scheduling already promotes the current support tile and next tile along pawn heading. The supplied advice's general topology-first principle is already in the design.
The edit diff layer is an in-memory overlay in this route; this report does not claim save/load persistence or network transport. The current far LOD is not a complete macro-only representation that is proven to agree across every LOD.
### What the advice gets wrong for this game
The claim that bridges should generally be separate meshes ignores the owner's unrestricted player carve/fill rule. A terrain-connected bridge must remain in the editable canonical field, or any mesh needs a field and collision proxy with an explicit edit interaction contract. Keep rock connectors as field features. Independent meshes are suitable for self-contained dreamcore/megastructure set pieces and ordinary decorations where terrain edits need not cut through the object.
The advice also treats adaptive resolution as a mostly local sampling optimization. In this game it changes shared boundary samples, LOD transitions, collision support and how a player edit crosses resolutions. It is a useful direction but not a quick per-feature toggle. Trees can be separate instanced content because they do not need to modify the terrain field, but their roots/anchors still need exact planned ground placement.
### Ideas ranked by expected payoff
| Rank | Idea | Expected payoff | Evidence required |
|---:|---|---|---|
| 1 | Conservative per-tile feature lists | High and already implemented. The 256-island stress case cut sampled tile p95 from 112.46 ms to 1.97 ms with identical sampled values and a dropped-feature control. | Keep exhaustive/randomized bound probes, full-versus-candidate hashes, negative controls and list-build costs as each feature family is added. |
| 2 | Per-strate resolution | High potential for broad mountains with fine caves, but high seam/edit risk. Deferred until the boundary contract is designed. | Boundary and transition LOD hashes on every worker count; no visible/collision cracks; edits across both resolutions replay identically; stable size-map identity. |
| 3 | Far LOD from macro shape with cross-LOD agreement | Medium-high. It can reduce far detail and improve horizon shape. Current cap and repair work is not a proof of this full scheme. | Compare silhouette, ground profile and water datum across all LOD transitions; test camera approach and retreat for pops; verify edit regions retain the authoritative field. |
| 4 | Visibility and heading-aware scheduling | Medium. Heading prefix exists; add visibility weighting only if it improves time-to-visible geometry without starving collision/support tiles or causing turn-around stalls. | Same pawn path at fixed speeds with visible tile-ready latency, collision-ready latency, rapid 180-degree turns and occluded-but-near tests. |
| 5 | Separate structure meshes for selected set pieces | Conditional and lower payoff for this editable terrain. Use them for non-terrain-connected authored dreamcore pieces; keep bridges and traversable rock in the field. | Carve/fill through and around a structure, collision parity, seam behavior, and deterministic placement/streaming. |
## Run ledger, limits and failures
- BuildHost UBT/UHT and Core link-safety passed after the production edits. The Core link audit passed with 18 shim objects. Final production build passed on attempt 1.
- The 20 cm SurfaceWorld actor run passed in 12m11s with about 3.95 GiB peak. The default 25 cm SurfaceWorld actor run passed in 11m53s. The corrected 20 cm Floating Islands actor run passed in about 4m11s with 3.18 GiB peak; it produced 2,850 tiles and passed W1/W8/W16, island/span anchors, water/atmosphere, capsule contacts, BP carve/fill and Core-off.
- The corrected default 25 cm Floating Islands actor run passed in about 5m45s with 3.18 GiB peak. It compared each candidate density sample with the full scan and logged no mismatches. Its 2,802-tile W1/W8/W16 aggregate exactly matched Round 4 across density, mesh, payload and topology.
- After removing the temporary comparison code, the clean production 25 cm Floating Islands run passed again in 3m48s with 3.16 GiB peak. It reproduced the same Round 4 aggregate and passed the live height/content anchor, water/atmosphere, capsule, BP edit and Core-off checks.
- The hybrid fixture-only run passed at default 25 cm: V1/V2 five analytic fixtures, exact repeats, same-LOD borders, V2 MC identity and 125 doorway phases.
- The full default V2 actor check passed: 11m57s (717.31 seconds), 3,944,955,904 peak commandlet private bytes (3.67 GiB). Its W1/W8/W16 SurfaceWorld hashes matched the 25 cm Round 4 hashes; height/content delta was 0 cm, BP carve and collision passed, and the V2 five-fixture, same-LOD border and 125-phase doorway checks passed. Log: Saved/BuildHost/HostProject/Plugins/VoxelForge/Saved/CoreWorldValidation/Logs/CoreWorldValidation-owner_surface.log.
- Per-tile audit iterations: the first wrapper invocation used a PowerShell variable scope that lost the script-root path; it was corrected. A naive AABB candidate filter mismatched 493/10,565 samples and was replaced with a conservative sorted lower-bound query. A compile probe had a lambda mixed-return C3487/C2672 error and was corrected. The stress negative control initially retained the central island and its summary bounds, so it did not fail; the control was corrected to remove both. The wrapper also lost Process.ExitCode before an explicit pass-marker fallback was added. The final audit wrapper printed a missing `vswhere.exe` notice, but compiled and ran successfully on its first measured attempt.
- The first post-halo default island candidate run passed the actor harness but failed the historical-hash invariant: density/mesh/payload/topology were 0x2f18fe65d88140f9 / 0x29fa0e8876a5a839 / 0xb1098a1ab6afa4bd / 0xec859a6373a47022. A full-scan control for the same 2,802 tiles reproduced Round 4 exactly. A runtime comparison then found candidate/full density mismatches on empty-list air tiles (for example fixed subvoxels `(-18944,-16896,-180736)`: 25,801 versus 18,135). The old skip bound assumed distance-like root magnitudes; v2 root lobes use a scaled ellipsoid distance. The bound now includes the validated root slope and smooth-min budget. The 4/256 audit and owner-plan full-scan comparison then passed bit-identically, restoring the Round 4 aggregate.
- A first runtime diagnostic used `UE_LOG` in the Core shim, where logging is compiled out; the build warned C4390 and the run produced no probe markers. I replaced it with temporary stdout diagnostics, captured the mismatch above, then removed the diagnostic code. The final production build and corrected 20 cm actor run passed afterward.
- The direct island top-anchor shortcut was a measured performance regression and was reverted after the full 20 cm island actor checks passed.
- No run returned 0xC0000142; no retry for that status occurred. No process exceeded 8 GiB or its 15-minute step time box. No viewer was launched.
- Crash audit: BuildHost has only the pre-existing UECC-Windows-F1A3EFD5469F5DD62E68EBBACAC84376_0000 folder dated 2026-09-17. No new UECC-* folder appeared after these runs.
- The full SurfaceWorld and island actor tests used the staged copies of the owner's five assets and the transient owner-height layout described in Round 4. No owner asset or project file was edited. The owner's project/editor was not opened, closed or built. No .uasset, junction, symlink, commit, push or stash was created.
## Files and likely compile spots
New source files:
- Standalone/Research/WorldAssemblerM3/Round5/FloatingIslandTileListAudit.cpp
- Standalone/Research/WorldAssemblerM3/Round5/BuildFloatingIslandTileListAudit.ps1 (audit build/run script)
New report: Standalone/Research/WorldAssemblerM3/Round5/REPORT.md. Dialogue entry: Standalone/Research/OwnerShapeWorld/gpu_field_port/LUNA_DIALOGUE.md.
Modified production/build sources:
- Source/VoxelForge/Public/VoxelWorld.h and Private/VoxelWorld.cpp
- Source/VoxelForge/Public/VoxelContentManager.h and Private/VoxelContentManager.cpp
- Source/VoxelForge/Public/VoxelForgeCoreAssetAdapter.h and Private/VoxelForgeCoreAdapter.cpp
- Source/VoxelForge/Private/VoxelAtmosphereManager.cpp
- Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp
- Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h
- Standalone/Core/src/FarShellsInternal.h, FloatingIslandsInternal.h, ShapeWorldInternal.h, VoxelForgeCore.cpp and WorldAssemblerInternal.h
- Standalone/Source/OwnerWorldLoader.h
- Tools/BuildVoxelForgeCoreHost.ps1 and Tools/TestVoxelForgeCoreWorldHost.ps1
- CODEMAP.md and ARCHITECTURE.md
Likely compile touchpoints are UHT/reflection for VoxelWorld.h and VoxelContentManager.h; runtime compilation of VoxelWorld.cpp, VoxelContentManager.cpp, VoxelForgeCoreAdapter.cpp and VoxelAtmosphereManager.cpp; Core API and implementation shims; and the editor commandlet. The completed BuildHost UHT/build and Core link-safety audit cover these touchpoints.
## Completion ledger
Implementation and requested proof runs are complete. The standalone tile-list audit passes at current and stress counts with zero mismatches and a detected dropped-feature control. The 20 cm BuildHost SurfaceWorld and Floating Islands cross-system checks pass; Core size changes world identity; default 25 cm SurfaceWorld, Floating Islands and M2 compatibility hashes match their recorded baselines. Content placement measurement found no measured spawn cost because the sampled cells produced zero placements; the direct SurfaceWorld heightfield path is retained and the slower island shortcut was reverted. Outside-review evaluation and the per-strate boundary/edit/LOD/hash plan are recorded above.
@@ -0,0 +1,4 @@
{
"baseline":{"islands":4,"connectors":3,"tiles_built":72,"sampled_tiles":60,"samples":60000,"mean_candidates":2.63333333333,"p95_candidates":6,"list_build_total_ms":0.0396,"list_build_p95_ms":0.0009,"before_p95_ms":0.9189,"after_p95_ms":0.9097,"before_median_ms":0.8644,"after_median_ms":0.501,"exact_mismatches":0,"before_hash":"1ae5da3039bdcbdd","after_hash":"1ae5da3039bdcbdd","negative_control":true},
"stress":{"islands":256,"connectors":240,"tiles_built":3280,"sampled_tiles":64,"samples":64000,"mean_candidates":3.13170731707,"p95_candidates":4,"list_build_total_ms":95.1326,"list_build_p95_ms":0.0461,"before_p95_ms":112.458,"after_p95_ms":1.972,"before_median_ms":111.66035,"after_median_ms":1.32645,"exact_mismatches":0,"before_hash":"7b92fa4e8d6acf49","after_hash":"7b92fa4e8d6acf49","negative_control":true}
}
@@ -0,0 +1 @@
attempt=1 status=passed elapsed_s=7.54 peak_private_MiB=64.7 exit=
+35 -11
View File
@@ -9,6 +9,9 @@
#include "VoxelStrateManager.h"
#include "VoxelTerrainOpDefinition.h"
#include <cmath>
#include <cstdint>
struct FStandaloneParitySample
{
FVector PositionVoxels;
@@ -38,6 +41,8 @@ struct FStandaloneOwnerWorld
bool bGenerateSkirts = true;
int32 SkirtCells = 2;
int32 LODOctaveDrop = 0;
std::uint32_t OwnerVoxelSizeMicrons = 125000;
std::uint32_t CoreVoxelSizeMicrons = 250000;
};
namespace StandaloneOwnerLoader
@@ -294,22 +299,41 @@ namespace StandaloneOwnerLoader
inline bool Resolve(const VFJson::Value& Root, FStandaloneOwnerWorld& Out, std::string& Error)
{
if (Root.Get("voxel_size_cm").Number() != VOXEL_SIZE)
{
Error = "export voxel_size_cm does not match the standalone VOXEL_SIZE";
return false;
}
const VFJson::Value& OwnerVoxelSize = Root.Get("owner_voxel_size_cm");
const VFJson::Value& CoreVoxelSize = Root.Get("core_voxel_size_cm");
const VFJson::Value& CoreVoxelSizeMicrons = Root.Get("core_voxel_size_microns");
const VFJson::Value& OwnerVoxelSize = Root.Get("owner_voxel_size_cm");
const VFJson::Value& OwnerVoxelSizeMicrons = Root.Get("owner_voxel_size_microns");
const VFJson::Value& OwnerToCoreScale = Root.Get("owner_to_core_length_scale");
if (!std::holds_alternative<std::nullptr_t>(OwnerVoxelSize.Data)
&& (!OwnerVoxelSize.IsNumber() || OwnerVoxelSize.Number() != 12.5
|| !CoreVoxelSize.IsNumber() || CoreVoxelSize.Number() != 25.0
|| !OwnerToCoreScale.IsNumber() || OwnerToCoreScale.Number() != 0.5))
const double CoreCm = CoreVoxelSize.Number(Root.Get("voxel_size_cm").Number(VOXEL_SIZE));
const double OwnerCm = OwnerVoxelSize.Number(VOXEL_SIZE * 0.5);
const double CoreMicrons = CoreVoxelSizeMicrons.Number(CoreCm * 10000.0);
const double OwnerMicrons = OwnerVoxelSizeMicrons.Number(OwnerCm * 10000.0);
if (!std::isfinite(CoreCm) || !std::isfinite(OwnerCm)
|| CoreCm < VOXEL_SIZE * 0.5 || CoreCm > VOXEL_SIZE || OwnerCm <= 0.0
|| std::abs(CoreMicrons - CoreCm * 10000.0) > 0.01
|| std::abs(OwnerMicrons - OwnerCm * 10000.0) > 0.01
|| !Root.Get("voxel_size_cm").IsNumber()
|| std::abs(Root.Get("voxel_size_cm").Number() - CoreCm) > 0.0001)
{
Error = "owner-to-Core import scale must be 12.5 cm to 25 cm at a 0.5 length ratio";
Error = "exported Core voxel size is invalid or inconsistent: core_cm="
+ std::to_string(CoreCm) + " owner_cm=" + std::to_string(OwnerCm)
+ " root_voxel_cm=" + std::to_string(Root.Get("voxel_size_cm").Number())
+ " core_microns=" + std::to_string(CoreMicrons)
+ " owner_microns=" + std::to_string(OwnerMicrons);
return false;
}
const double ExpectedRatio = OwnerCm / CoreCm;
const bool bHasImportScale = OwnerVoxelSize.IsNumber()
|| CoreVoxelSize.IsNumber() || OwnerToCoreScale.IsNumber();
if (bHasImportScale && (!OwnerVoxelSize.IsNumber() || !CoreVoxelSize.IsNumber()
|| !OwnerToCoreScale.IsNumber()
|| std::abs(OwnerToCoreScale.Number() - ExpectedRatio) > 1.0e-9))
{
Error = "owner-to-Core import scale does not match the resolved voxel sizes";
return false;
}
Out.OwnerVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(OwnerMicrons));
Out.CoreVoxelSizeMicrons = static_cast<std::uint32_t>(std::llround(CoreMicrons));
Out.Seed = static_cast<int32>(Root.Get("seed").Integer());
Out.ClipRadius = static_cast<int32>(Root.Get("clip_radius").Integer(6));
Out.MaxClipLevel = static_cast<int32>(Root.Get("max_clip_level").Integer(8));