wip(assembler): M1 round 4 PARTIAL -- stopped at owner's request (GPU coil whine at night)

Unverified mid-round state (cliff plates / horizon slab / routes in progress). Resume from here.

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-09 02:12:39 +02:00
1 parent 2c20f9eaea
commit 20c6d86f0e
11 files changed
+839 -47

No files matched your search

+26 -10
View File
@@ -79,14 +79,15 @@ Pass `analyticOnly = false` to retain the reference packed-page compiler. Packed
parent-cell coordinates and an 8-voxel apron; each covers a 128-voxel cube or one larger coarse
tile. This path remains available for parity checks and workloads that need the packed lattice.
## World assembler manifest v1 (prototype; evaluator v1 and v2)
## World assembler manifest v1 (evaluator v1–v4)
`ResolvedWorldSnapshot::FromJson` accepts an optional `world_assembly` object alongside the owner
snapshot. The object is the shipped-plan payload shape: a fixed-scale recipe and finite-strate plan
with hashes, no generated mesh or voxel samples. The current evaluator-v2 review payload is
[`round2/world_assembly.json`](../Research/WorldAssemblerM1/round2/world_assembly.json); the
evaluator-v1 sample remains at [`round1-manifest.json`](../Research/WorldAssemblerM1/round1-manifest.json).
Round 4 still needs to connect this payload to the season asset/hash path.
with hashes, no generated mesh or voxel samples. The current review payload is written to
[`round4/world_assembly.json`](../Research/WorldAssemblerM1/round4/world_assembly.json).
Evaluator 1–3 payloads remain readable with their original hashes and field semantics. Evaluator 4
adds a minimum two-voxel shelf thickness and a versioned route support contract; it requires at
least two route waypoints.
Every coordinate and scalar uses signed integer units of 1/256 voxel (`units_per_voxel: 256`),
independent of the plugin's 25 cm Core voxel and of tile size. The schema version is 1; evaluator
@@ -96,8 +97,10 @@ order and hashed as 4- or 8-byte little-endian words with FNV-1a 64. Hashes are
lowercase hexadecimal characters; JSON key order and whitespace do not affect them. The root,
recipe, plan and each stratum carry a content hash. The recipe stores its seed, warped biome-map
settings and one to eight biome profiles; the plan stores finite-strate extents, cap settings,
stable-ID features, fixed-season water and route points. Bounds, feature count and route count are
validated before a manifest is accepted.
stable-ID features, fixed-season water, route points and (for evaluator 4) player-capsule, grade,
support and sweep-spacing constraints. Route points are fixed-scale capsule-center locations with
stable IDs and a protected automatic-change corridor radius. Bounds, feature count, route count, route
IDs, corridor widths and finite-strate capsule extents are validated before a manifest is accepted.
Evaluator 2 adds climate-based biome assignment, output-space blending of both ground and cap
profiles, per-biome relief and height warp, beach width, cliff limits, and optional per-biome cap
@@ -120,8 +123,11 @@ neighbor biome IDs, Q16 blend weight and signed density. Evaluator 2 also return
surface and water-fill sample values. Negative density is solid. Its contract reads `biome` and
writes `height`, `rock`, `water` and `waterSurface`; route and `surfaceTags` remain declared channels
for later blocks. The detail envelope reports maximum absolute displacement and minimum feature
size. Influence is global in XY and limited to the finite stratum's Z extent, except for the
explicitly bounded evaluator-v2 feature boxes.
size. Evaluator 4 defines the shelf's minimum solid thickness as two Core voxels (0.5 m) and applies
its feather to the horizontal footprint so it cannot thin the roof-to-floor section.
`AuditHeightfieldShelfThickness` samples shelf sections against the pre-feature field and includes
the previous sub-voxel taper as a negative control. Influence is global in XY and limited to the
finite stratum's Z extent, except for the explicitly bounded feature boxes.
`interval(minXQ, minYQ, minZQ, maxXQ, maxYQ, maxZQ)` returns a conservative density range or
`known == false`; callers must keep evaluating when it is unknown. The current heightfield range
@@ -131,9 +137,19 @@ negative-coordinate and overhang boxes, and includes a deliberately false one-si
negative control. A pass means no sampled point escaped and the negative control was rejected; it
is an audit of this block implementation, not a proof for future blocks.
`AuditHeightfieldRoute` sweeps the recorded route through the final composed field using the
versioned player capsule (0.34 m radius, 1.76 m height), checks a 15-degree maximum grade, samples
capsule clearance at no more than half-voxel intervals, and probes support below five foot locations
at each swept center. Automatic changes must be composed first and pass this audit before the plan is
accepted. Player carve/fill edits remain unrestricted. This validates analytic field collision and
support; it does not cook an Unreal physics mesh.
## Queries and tiles
`densityAt(position)` returns signed density; negative is solid and nonnegative is air.
`densityAt(position)` returns signed density; negative is solid and nonnegative is air. Server and
authority code can compile/query a `World` and run `AuditHeightfieldRoute` without calling
`generateTile`; generated render meshes are not part of this query path. Unreal physics collision
cooking remains the adapter's separate readiness step.
`isSolidAt` wraps that sign test. `isSolidAlongSegment(start, end, spacing)` samples evenly along
the segment with no interval wider than `spacing` voxels; it returns true if any sample is solid.
`classifyBox(origin, step, cells)` returns `allSolid`, `allAir` or `mixed` for mesher tile
@@ -82,6 +82,8 @@ namespace VoxelForgeCore
double meshingMilliseconds = 0.0;
bool tileWasClassified = false;
bool meshingSkipped = false;
bool featureRefinedResolution = false;
std::uint64_t farCapTrianglesSuppressed = 0;
};
struct TileMesh
@@ -126,7 +128,7 @@ namespace VoxelForgeCore
// L'assembleur v1 encode coordonnees et scalaires en 1/256 de voxel, independamment des tuiles.
inline constexpr std::int64_t WorldAssemblySubvoxelsPerVoxel = 256;
inline constexpr std::uint32_t WorldAssemblySchemaVersion = 1;
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 3;
inline constexpr std::uint32_t WorldAssemblyEvaluatorVersion = 4;
enum class FieldChannel : std::uint32_t
{
@@ -193,11 +195,26 @@ namespace VoxelForgeCore
struct WorldAssemblyRoutePointV1
{
// Stable ID and fixed-scale capsule-center waypoint. Radius is the protected
// automatic-change corridor around this centerline point.
std::uint64_t stableFeatureId = 0;
std::array<std::int64_t, 3> positionSubvoxels{};
std::int64_t radiusSubvoxels = 0;
};
struct WorldAssemblyRouteConstraintsV1
{
std::uint32_t version = 0;
std::int64_t capsuleRadiusSubvoxels = 0;
std::int64_t capsuleHeightSubvoxels = 0;
std::int64_t maximumGradeQ16 = 0;
std::int64_t sweepSpacingSubvoxels = 0;
std::int64_t supportProbeRadiusSubvoxels = 0;
std::int64_t supportSearchDepthSubvoxels = 0;
std::int64_t minimumClearanceSubvoxels = 0;
std::uint32_t minimumSupportProbes = 0;
};
struct WorldAssemblyFeatureV1
{
// Stable numeric type IDs are part of the plan contract: 1 is a heightfield cliff plane,
@@ -228,6 +245,7 @@ namespace VoxelForgeCore
std::int64_t waterSurfaceSubvoxels = 0;
bool waterEnabled = false;
std::vector<WorldAssemblyFeatureV1> features;
WorldAssemblyRouteConstraintsV1 routeConstraints;
std::vector<WorldAssemblyRoutePointV1> route;
std::uint64_t contentHash = 0;
};
@@ -357,10 +375,50 @@ namespace VoxelForgeCore
bool passed = false;
};
struct WorldAssemblyShelfThicknessAuditResult
{
std::uint64_t featuresTested = 0;
std::uint64_t columnsTested = 0;
std::uint64_t activeColumns = 0;
std::uint64_t negativeRuns = 0;
std::uint64_t subVoxelColumns = 0;
std::uint64_t subVoxelRuns = 0;
std::uint64_t legacySubVoxelColumns = 0;
std::uint64_t legacySubVoxelRuns = 0;
std::int64_t minimumObservedThicknessSubvoxels = 0;
std::int64_t minimumThicknessColumnXSubvoxels = 0;
std::int64_t minimumThicknessColumnYSubvoxels = 0;
std::int64_t minimumThicknessGroundSubvoxels = 0;
std::int64_t minimumThicknessBottomSubvoxels = 0;
std::int64_t minimumThicknessTopSubvoxels = 0;
bool negativeControlDetected = false;
bool passed = false;
};
struct WorldAssemblyRouteAuditResult
{
std::uint64_t segmentsTested = 0;
std::uint64_t centerSamples = 0;
std::uint64_t capsuleFieldSamples = 0;
std::uint64_t blockedCapsuleSamples = 0;
std::uint64_t supportProbeSamples = 0;
std::uint64_t unsupportedCenterSamples = 0;
std::uint64_t gradeFailures = 0;
std::int64_t minimumClearanceSubvoxels = 0;
std::int64_t maximumSupportDepthSubvoxels = 0;
bool passed = false;
};
VFCORE_API WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(
const Heightfield2D& block, std::uint32_t boxCount = 256,
std::uint64_t seed = 0x574f524c44415331ull);
VFCORE_API WorldAssemblyShelfThicknessAuditResult AuditHeightfieldShelfThickness(
const Heightfield2D& block);
VFCORE_API WorldAssemblyRouteAuditResult AuditHeightfieldRoute(
const Heightfield2D& block);
class VFCORE_API ResolvedWorldSnapshot
{
public:
+30 -3
View File
@@ -967,11 +967,38 @@ inline void AppendFarStratumShells(const FStandaloneOwnerWorld& World,const FSta
};
const int32 Base=static_cast<int32>(NewVertices.size());
const double Xs[4]={X0,X1,X1,X0},Ys[4]={Y0,Y0,Y1,Y1};
std::array<double,4> CornerZ{};
std::array<FVector,4> CornerNormals{};
bool bValidPatch=true;
for(int32 Corner=0;Corner<4;++Corner)
{
const double Z=SurfaceZ(Xs[Corner],Ys[Corner]);
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,Z*25.0);
NewNormals.push_back(Normal);
const double PredictedZ=SurfaceZ(Xs[Corner],Ys[Corner]);
const std::vector<double> CornerCrossings=FindFarStratumCrossings(
Shape,Xs[Corner],Ys[Corner],ZMin,ZMax);
if(CornerCrossings.empty()){bValidPatch=false;break;}
CornerZ[Corner]=*std::min_element(CornerCrossings.begin(),CornerCrossings.end(),
[PredictedZ](double A,double B)
{return std::abs(A-PredictedZ)<std::abs(B-PredictedZ);});
const float CornerGX=Density(Xs[Corner]+2,Ys[Corner],CornerZ[Corner])
-Density(Xs[Corner]-2,Ys[Corner],CornerZ[Corner]);
const float CornerGY=Density(Xs[Corner],Ys[Corner]+2,CornerZ[Corner])
-Density(Xs[Corner],Ys[Corner]-2,CornerZ[Corner]);
const float CornerGZ=Density(Xs[Corner],Ys[Corner],CornerZ[Corner]+2)
-Density(Xs[Corner],Ys[Corner],CornerZ[Corner]-2);
CornerNormals[Corner]=FVector(CornerGX,CornerGY,CornerGZ);
if(CornerNormals[Corner].SizeSquared()<1.0e-8)
CornerNormals[Corner]=Normal;
else CornerNormals[Corner].Normalize();
}
// A center crossing alone cannot justify a large tangent quad: it can
// bridge air over an isolated far-LOD feature and draw as a horizon slab.
// Keep the fallback only when the source field has a matching crossing
// at every corner, and place each corner on that source crossing.
if(!bValidPatch)continue;
for(int32 Corner=0;Corner<4;++Corner)
{
NewVertices.emplace_back(Xs[Corner]*25.0,Ys[Corner]*25.0,CornerZ[Corner]*25.0);
NewNormals.push_back(CornerNormals[Corner]);
if(bHasUVs)NewUVs.emplace_back(static_cast<float>((Xs[Corner]-Origin.X)/256.0),
static_cast<float>((Ys[Corner]-Origin.Y)/256.0));
if(bHasColors)NewColors.push_back(Color);
+45 -3
View File
@@ -627,12 +627,42 @@ namespace VoxelForgeCore
mesher.bGenerateSkirts=world.bGenerateSkirts;mesher.SkirtCells=static_cast<float>(world.SkirtCells);
mesher.LODOctaveDrop=world.LODOctaveDrop;
const int32 level=key.lod,extent=CHUNK_SIZE<<level;
const int32 cells=level<world.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,world.CoarseTileCells);
const int32 step=FMath::Max(1,extent/cells);
int64 sampleCount=0;std::vector<float> densityValues;
const FVoxelTileKey nativeKey=ToNative(key);
const FIntVector origin=nativeKey.OriginVoxels();
int32 cells=level<world.FullResClipLevels?CHUNK_SIZE:FMath::Max(1,world.CoarseTileCells);
int32 step=FMath::Max(1,extent/cells);
bool bFeatureRefinedResolution=false;
if(impl_->worldAssembly&&level<=2)
{
constexpr std::int64_t Scale=VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
const std::array<std::int64_t,3> tileMin={
static_cast<std::int64_t>(origin.X)*Scale,
static_cast<std::int64_t>(origin.Y)*Scale,
static_cast<std::int64_t>(origin.Z)*Scale};
const std::array<std::int64_t,3> tileMax={
static_cast<std::int64_t>(origin.X+extent)*Scale,
static_cast<std::int64_t>(origin.Y+extent)*Scale,
static_cast<std::int64_t>(origin.Z+extent)*Scale};
for(const auto& stratum:impl_->worldAssembly->plan.strates)
for(const auto& feature:stratum.features)
{
if(feature.type!=4)continue;
bool overlaps=true;
for(std::size_t axis=0;axis<3;++axis)
overlaps=overlaps&&feature.minimumSubvoxels[axis]<=tileMax[axis]
&&feature.maximumSubvoxels[axis]>=tileMin[axis];
if(overlaps)
{
// A two-voxel shelf is only one sample wide at LOD1's normal step.
// Mesh feature-intersecting tiles at the one-voxel Core lattice so
// the full-resolution block cannot turn into a one-cell plate.
cells=extent;step=1;bFeatureRefinedResolution=true;break;
}
}
}
int64 sampleCount=0;std::vector<float> densityValues;
TileMesh out;out.key=key;
out.diagnostics.featureRefinedResolution=bFeatureRefinedResolution;
const bool bHasWorldAssembly=impl_->worldAssembly!=nullptr;
const bool bNeedsFarShell=bHasWorldAssembly&&key.lod>=7
&&(tileShape.HasFarWorld()||impl_->shape->HasFarWorld());
@@ -691,6 +721,18 @@ namespace VoxelForgeCore
AppendFarStratumShells(world,farShape,nativeKey,step,mesh);
AppendFarConnectorShells(farShape,nativeKey,step,world.CoarseTileCells,mesh);
}
if(bHasWorldAssembly&&key.lod>=8&&mesh.NumCeilingTriangles>0)
{
// The finite sky cap remains in the composed field, queries, and local meshes.
// At the coarsest clipmap level its large planar triangles read as a detached
// horizon slab, so omit only that far render group after far-shell construction.
const int32 KeptTriangleCount=std::max(0,mesh.Triangles.Num()/3-mesh.NumCeilingTriangles);
const int32 KeptIndexCount=KeptTriangleCount*3;
out.diagnostics.farCapTrianglesSuppressed=static_cast<std::uint64_t>(mesh.NumCeilingTriangles);
mesh.Triangles.SetNum(KeptIndexCount);
mesh.TriangleSkirtFaceMasks.SetNum(KeptTriangleCount);
mesh.NumCeilingTriangles=0;
}
out.densitySampleCount=static_cast<std::uint64_t>(std::max<int64>(0,sampleCount));
if(bHasWorldAssembly)
+351 -8
View File
@@ -16,6 +16,7 @@ namespace VoxelForgeCore
namespace world_assembly_detail
{
constexpr std::int64_t Q16 = 1ll << 16;
constexpr std::int64_t MinimumCliffShelfThicknessSubvoxels = 2 * WorldAssemblySubvoxelsPerVoxel;
constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll;
constexpr std::size_t MaxBiomeCount = 8;
constexpr std::size_t MaxStrateCount = 64;
@@ -228,6 +229,60 @@ namespace world_assembly_detail
|| !std::all_of(point.positionSubvoxels.begin(), point.positionSubvoxels.end(), ValidScalar))
{ error = "world assembly route point has an invalid feature ID, position or radius"; return false; }
}
std::vector<std::uint64_t> routeIds;
routeIds.reserve(stratum.route.size());
for (const auto& point : stratum.route)
{
if (std::find(routeIds.begin(), routeIds.end(), point.stableFeatureId) != routeIds.end())
{ error = "world assembly route point IDs must be unique"; return false; }
routeIds.push_back(point.stableFeatureId);
}
for (std::size_t i = 1; i < stratum.route.size(); ++i)
{
std::uint64_t squared = 0;
for (std::size_t axis = 0; axis < 3; ++axis)
{
const std::int64_t delta = stratum.route[i].positionSubvoxels[axis]
- stratum.route[i - 1].positionSubvoxels[axis];
if (std::abs(delta) > 4096 * WorldAssemblySubvoxelsPerVoxel)
{ error = "world assembly route segment exceeds its bounded sweep length"; return false; }
squared += static_cast<std::uint64_t>(delta * delta);
}
if (squared == 0)
{ error = "world assembly route contains a zero-length segment"; return false; }
}
const auto& constraints = stratum.routeConstraints;
if (constraints.version == 0)
{
if (constraints.capsuleRadiusSubvoxels != 0 || constraints.capsuleHeightSubvoxels != 0
|| constraints.maximumGradeQ16 != 0 || constraints.sweepSpacingSubvoxels != 0
|| constraints.supportProbeRadiusSubvoxels != 0
|| constraints.supportSearchDepthSubvoxels != 0
|| constraints.minimumClearanceSubvoxels != 0 || constraints.minimumSupportProbes != 0)
{ error = "world assembly route constraints need a supported version"; return false; }
}
else if (constraints.version != 1
|| !ValidScalar(constraints.capsuleRadiusSubvoxels)
|| !ValidScalar(constraints.capsuleHeightSubvoxels)
|| !ValidScalar(constraints.maximumGradeQ16)
|| !ValidScalar(constraints.sweepSpacingSubvoxels)
|| !ValidScalar(constraints.supportProbeRadiusSubvoxels)
|| !ValidScalar(constraints.supportSearchDepthSubvoxels)
|| !ValidScalar(constraints.minimumClearanceSubvoxels)
|| constraints.capsuleRadiusSubvoxels <= 0
|| constraints.capsuleHeightSubvoxels < 2 * constraints.capsuleRadiusSubvoxels
|| constraints.maximumGradeQ16 <= 0 || constraints.maximumGradeQ16 > Q16
|| constraints.sweepSpacingSubvoxels <= 0
|| constraints.sweepSpacingSubvoxels > WorldAssemblySubvoxelsPerVoxel / 2
|| constraints.supportProbeRadiusSubvoxels <= 0
|| constraints.supportSearchDepthSubvoxels <= 0
|| constraints.minimumClearanceSubvoxels < 0
|| constraints.minimumSupportProbes == 0 || constraints.minimumSupportProbes > 5)
{ error = "world assembly route support constraints are outside the supported range"; return false; }
if (constraints.version == 1)
for (const auto& point : stratum.route)
if (point.radiusSubvoxels < constraints.capsuleRadiusSubvoxels)
{ error = "world assembly route corridor is narrower than its player capsule"; return false; }
return true;
}
@@ -310,6 +365,20 @@ namespace world_assembly_detail
for (const auto value : feature.maximumSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
for (const auto value : feature.parametersSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
}
if (stratum.routeConstraints.version != 0)
{
const auto& constraints = stratum.routeConstraints;
HashWord(hash, 0x524f5554455631ull, 8);
HashWord(hash, constraints.version, 4);
HashWord(hash, static_cast<std::uint64_t>(constraints.capsuleRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(constraints.capsuleHeightSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(constraints.maximumGradeQ16), 8);
HashWord(hash, static_cast<std::uint64_t>(constraints.sweepSpacingSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(constraints.supportProbeRadiusSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(constraints.supportSearchDepthSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(constraints.minimumClearanceSubvoxels), 8);
HashWord(hash, constraints.minimumSupportProbes, 4);
}
HashWord(hash, stratum.route.size(), 4);
for (const auto& point : stratum.route)
{
@@ -440,6 +509,24 @@ namespace world_assembly_detail
{ error = "world assembly stratum has malformed optional evaluator-2 fields"; return false; }
if(hasWaterBoolean)stratum.waterEnabled=waterEnabled.Boolean(false);
if(hasWaterNumber)stratum.waterEnabled=waterEnabled.Number()!=0.0;
const VFJson::Value& routeConstraintsValue = item.Get("route_constraints");
if (!std::holds_alternative<std::nullptr_t>(routeConstraintsValue.Data))
{
auto& constraints = stratum.routeConstraints;
std::uint32_t minimumSupportProbes = 0;
if (!routeConstraintsValue.IsObject()
|| !ReadU32(routeConstraintsValue.Get("version"), constraints.version)
|| !ReadInteger(routeConstraintsValue.Get("capsule_radius_q"), constraints.capsuleRadiusSubvoxels)
|| !ReadInteger(routeConstraintsValue.Get("capsule_height_q"), constraints.capsuleHeightSubvoxels)
|| !ReadInteger(routeConstraintsValue.Get("maximum_grade_q16"), constraints.maximumGradeQ16)
|| !ReadInteger(routeConstraintsValue.Get("sweep_spacing_q"), constraints.sweepSpacingSubvoxels)
|| !ReadInteger(routeConstraintsValue.Get("support_probe_radius_q"), constraints.supportProbeRadiusSubvoxels)
|| !ReadInteger(routeConstraintsValue.Get("support_search_depth_q"), constraints.supportSearchDepthSubvoxels)
|| !ReadInteger(routeConstraintsValue.Get("minimum_clearance_q"), constraints.minimumClearanceSubvoxels)
|| !ReadU32(routeConstraintsValue.Get("minimum_support_probes"), minimumSupportProbes))
{ error = "world assembly route constraints are missing a fixed-scale field"; return false; }
constraints.minimumSupportProbes = minimumSupportProbes;
}
for (const auto& featureValue : item.Get("features").AsArray())
{
WorldAssemblyFeatureV1 feature;
@@ -1109,7 +1196,7 @@ namespace world_assembly_detail
inline std::int64_t CliffShelfDensity(const WorldAssemblyFeatureV1& feature,
const WorldAssemblyRecipeV1& recipe,const WorldAssemblyStratumPlanV1& plan,
std::int64_t x,std::int64_t y,std::int64_t z,
const HeightfieldPointSample& localColumn)
const HeightfieldPointSample& localColumn,bool enforceMinimumThickness)
{
const auto& lo=feature.minimumSubvoxels;const auto& hi=feature.maximumSubvoxels;
if(x<lo[0]||x>hi[0]||y<lo[1]||y>hi[1]||z<lo[2]||z>hi[2])
@@ -1134,17 +1221,36 @@ namespace world_assembly_detail
return std::numeric_limits<std::int64_t>::max()/4;
const std::uint32_t vertical=SmoothQ16(static_cast<std::uint32_t>(
std::clamp<std::int64_t>(aboveGround*Q16/height,0,Q16)));
const std::int64_t sourceOffset=MulQ16(reach,vertical);
// Evaluator v4 samples the source column on the shelf's root line. The offset
// therefore follows horizontal progress through the block and stays fixed for
// every z slice. Advancing it with z can make the shelf switch on at its top;
// sampling the far end everywhere instead can pull a distant cliff cap across
// the whole shelf. Earlier evaluators retain their height-dependent taper.
const std::int64_t sourceOffset=enforceMinimumThickness
?std::clamp<std::int64_t>(along,0,reach):MulQ16(reach,vertical);
const std::int64_t sourceX=x-dirX*sourceOffset;
const std::int64_t sourceY=y-dirY*sourceOffset;
const HeightfieldPointSample source=CachedColumn(recipe,plan,sourceX,sourceY);
if(source.groundHeightSubvoxels<=localColumn.groundHeightSubvoxels)
return std::numeric_limits<std::int64_t>::max()/4;
std::int64_t candidate=std::max({localColumn.groundHeightSubvoxels-z,
z-source.groundHeightSubvoxels,-along,along-reach,
std::abs(lateral)-halfWidth,-aboveGround,aboveGround-height});
const std::uint32_t edgeFade=FeatureFadeQ16(feature,x,y,Feather);
candidate+=static_cast<std::int64_t>(Q16-edgeFade)*Feather/Q16;
const std::int64_t edgeInset=static_cast<std::int64_t>(Q16-edgeFade)*Feather/Q16;
const std::int64_t sourceTop=enforceMinimumThickness
?std::max(source.groundHeightSubvoxels,
localColumn.groundHeightSubvoxels+MinimumCliffShelfThicknessSubvoxels)
:source.groundHeightSubvoxels;
std::int64_t candidate=std::max({localColumn.groundHeightSubvoxels-z,
z-sourceTop,-aboveGround,aboveGround-height});
if(enforceMinimumThickness)
{
// The feather narrows the shelf footprint. It must not offset the top and
// bottom surfaces, or their vertical separation collapses into MC sheets.
candidate=std::max({candidate,-along+edgeInset,along-reach+edgeInset,
std::abs(lateral)-halfWidth+edgeInset});
}
else
candidate=std::max({candidate,-along,along-reach,
std::abs(lateral)-halfWidth})+edgeInset;
return candidate;
}
@@ -1192,6 +1298,26 @@ bool WorldAssemblyManifest::finalize(std::string& error)
for (auto& stratum : plan.strates)
{
if (!world_assembly_detail::ValidateStratum(stratum, error)) return false;
if (recipe.evaluatorVersion >= 4)
{
if (stratum.routeConstraints.version != 1 || stratum.route.size() < 2)
{ error = "world assembly evaluator 4 requires a versioned entrance-to-exit route"; return false; }
const std::int64_t bottomZ = static_cast<std::int64_t>(stratum.bottomChunkZ)
* 32 * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t topZ = (static_cast<std::int64_t>(stratum.topChunkZ) + 1)
* 32 * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t halfHeight = stratum.routeConstraints.capsuleHeightSubvoxels / 2;
for (const auto& point : stratum.route)
{
if (point.positionSubvoxels[2] - halfHeight < bottomZ
|| point.positionSubvoxels[2] + halfHeight > topZ)
{ error = "world assembly route capsule escapes its finite stratum Z bounds"; return false; }
}
for (const auto& feature : stratum.features)
if (feature.type == 4 && feature.parametersSubvoxels[2]
< 2 * WorldAssemblySubvoxelsPerVoxel)
{ error = "world assembly evaluator 4 cliff shelf is thinner than its two-voxel minimum"; return false; }
}
if (std::find(strateIds.begin(), strateIds.end(), stratum.strateIndex) != strateIds.end())
{ error = "world assembly plan contains a duplicate strate index"; return false; }
strateIds.push_back(stratum.strateIndex);
@@ -1322,7 +1448,21 @@ std::string WorldAssemblyManifest::ToJson() const
<< feature.parametersSubvoxels[0] << ',' << feature.parametersSubvoxels[1] << ','
<< feature.parametersSubvoxels[2] << ',' << feature.parametersSubvoxels[3] << "]}";
}
out << "],\"route\":[";
out << ']';
if (stratum.routeConstraints.version != 0)
{
const auto& constraints = stratum.routeConstraints;
out << ",\"route_constraints\":{\"version\":" << constraints.version
<< ",\"capsule_radius_q\":" << constraints.capsuleRadiusSubvoxels
<< ",\"capsule_height_q\":" << constraints.capsuleHeightSubvoxels
<< ",\"maximum_grade_q16\":" << constraints.maximumGradeQ16
<< ",\"sweep_spacing_q\":" << constraints.sweepSpacingSubvoxels
<< ",\"support_probe_radius_q\":" << constraints.supportProbeRadiusSubvoxels
<< ",\"support_search_depth_q\":" << constraints.supportSearchDepthSubvoxels
<< ",\"minimum_clearance_q\":" << constraints.minimumClearanceSubvoxels
<< ",\"minimum_support_probes\":" << constraints.minimumSupportProbes << '}';
}
out << ",\"route\":[";
for (std::size_t p = 0; p < stratum.route.size(); ++p)
{
const auto& point = stratum.route[p];
@@ -1391,7 +1531,8 @@ HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
}
else if(feature.type==4&&recipe_->evaluatorVersion>=3)
{
const std::int64_t shelf=CliffShelfDensity(feature,*recipe_,*plan_,x,y,z,out);
const std::int64_t shelf=CliffShelfDensity(feature,*recipe_,*plan_,x,y,z,out,
recipe_->evaluatorVersion>=4);
out.densitySubvoxels=SmoothMinSubvoxels(out.densitySubvoxels,shelf,
4*WorldAssemblySubvoxelsPerVoxel);
}
@@ -1592,6 +1733,9 @@ FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
(feature.maximumSubvoxels[2]-feature.minimumSubvoxels[2])/2}));
if(feature.type==4)maximum=std::max(maximum,feature.parametersSubvoxels[2]
+WorldAssemblySubvoxelsPerVoxel);
if(feature.type==4&&recipe_->evaluatorVersion>=4)
minimumFeature=std::min(minimumFeature,
world_assembly_detail::MinimumCliffShelfThicknessSubvoxels);
if(feature.type==3)maximum=std::max(maximum,std::abs(feature.parametersSubvoxels[0]));
}
out.maximumAbsoluteDisplacementSubvoxels = maximum;
@@ -1746,4 +1890,203 @@ WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block
&& result.unknownBounds == 0 && result.negativeControlDetected;
return result;
}
WorldAssemblyShelfThicknessAuditResult AuditHeightfieldShelfThickness(const Heightfield2D& block)
{
WorldAssemblyShelfThicknessAuditResult result;
const auto* recipe=block.recipe();
const auto* plan=block.plan();
if(!block.isValid()||!recipe||!plan||recipe->evaluatorVersion<4)return result;
constexpr std::int64_t sampleStep=WorldAssemblySubvoxelsPerVoxel/4;
constexpr std::int64_t xyStep=WorldAssemblySubvoxelsPerVoxel/2;
std::int64_t minimumObserved=std::numeric_limits<std::int64_t>::max();
for(std::size_t featureIndex=0;featureIndex<plan->features.size();++featureIndex)
{
const auto& feature=plan->features[featureIndex];
if(feature.type!=4)continue;
++result.featuresTested;
auto baselinePlan=*plan;
baselinePlan.features.erase(baselinePlan.features.begin()+featureIndex);
const Heightfield2D baseline(*recipe,baselinePlan);
const std::int64_t firstX=feature.minimumSubvoxels[0]+WorldAssemblySubvoxelsPerVoxel;
const std::int64_t lastX=feature.maximumSubvoxels[0]-WorldAssemblySubvoxelsPerVoxel;
const std::int64_t firstY=feature.minimumSubvoxels[1]+WorldAssemblySubvoxelsPerVoxel;
const std::int64_t lastY=feature.maximumSubvoxels[1]-WorldAssemblySubvoxelsPerVoxel;
for(std::int64_t y=firstY;y<=lastY;y+=xyStep)
for(std::int64_t x=firstX;x<=lastX;x+=xyStep)
{
++result.columnsTested;
const std::int64_t ground=baseline.point(x,y,0).groundHeightSubvoxels;
const std::int64_t range=std::max<std::int64_t>(feature.parametersSubvoxels[2],
world_assembly_detail::MinimumCliffShelfThicknessSubvoxels)+sampleStep;
std::int64_t currentRun=0,legacyCurrentRun=0;
std::int64_t currentStart=0,legacyCurrentStart=0;
bool columnActive=false,columnSubVoxel=false,legacyColumnSubVoxel=false;
const auto recordRun=[&](std::int64_t run,std::int64_t start,bool legacy)
{
if(run<=0)return;
if(legacy)
{
if(run<WorldAssemblySubvoxelsPerVoxel)
{++result.legacySubVoxelRuns;legacyColumnSubVoxel=true;}
return;
}
columnActive=true;
++result.negativeRuns;
if(run<minimumObserved)
{
minimumObserved=run;
result.minimumThicknessColumnXSubvoxels=x;
result.minimumThicknessColumnYSubvoxels=y;
result.minimumThicknessGroundSubvoxels=ground;
result.minimumThicknessBottomSubvoxels=start;
result.minimumThicknessTopSubvoxels=start+run-sampleStep;
}
if(run<WorldAssemblySubvoxelsPerVoxel)
{++result.subVoxelRuns;columnSubVoxel=true;}
};
for(std::int64_t dz=sampleStep;dz<=range;dz+=sampleStep)
{
const std::int64_t z=ground+dz;
const auto baselineSample=baseline.point(x,y,z);
if(baselineSample.densitySubvoxels<=0)
{
recordRun(currentRun,currentStart,false);currentRun=0;
recordRun(legacyCurrentRun,legacyCurrentStart,true);legacyCurrentRun=0;
continue;
}
const auto composedSample=block.point(x,y,z);
if(composedSample.densitySubvoxels<0)
{
if(currentRun==0)currentStart=z;
currentRun+=sampleStep;
}
else {recordRun(currentRun,currentStart,false);currentRun=0;}
const std::int64_t oldShelf=world_assembly_detail::CliffShelfDensity(feature,*recipe,baselinePlan,
x,y,z,baselineSample,false);
const std::int64_t legacyDensity=world_assembly_detail::SmoothMinSubvoxels(
baselineSample.densitySubvoxels,oldShelf,4*WorldAssemblySubvoxelsPerVoxel);
if(legacyDensity<0)
{if(legacyCurrentRun==0)legacyCurrentStart=z;legacyCurrentRun+=sampleStep;}
else {recordRun(legacyCurrentRun,legacyCurrentStart,true);legacyCurrentRun=0;}
}
recordRun(currentRun,currentStart,false);
recordRun(legacyCurrentRun,legacyCurrentStart,true);
if(columnActive)++result.activeColumns;
if(columnSubVoxel)++result.subVoxelColumns;
if(legacyColumnSubVoxel)++result.legacySubVoxelColumns;
}
}
if(minimumObserved!=std::numeric_limits<std::int64_t>::max())
result.minimumObservedThicknessSubvoxels=minimumObserved;
result.negativeControlDetected=result.legacySubVoxelColumns>0;
result.passed=result.featuresTested>0&&result.activeColumns>0
&&result.minimumObservedThicknessSubvoxels>=world_assembly_detail::MinimumCliffShelfThicknessSubvoxels
&&result.subVoxelRuns==0&&result.negativeControlDetected;
return result;
}
WorldAssemblyRouteAuditResult AuditHeightfieldRoute(const Heightfield2D& block)
{
WorldAssemblyRouteAuditResult result;
const auto* plan=block.plan();
if(!block.isValid()||!plan||plan->routeConstraints.version!=1||plan->route.size()<2)
return result;
using namespace world_assembly_detail;
const auto& constraints=plan->routeConstraints;
const std::int64_t radius=constraints.capsuleRadiusSubvoxels;
const std::int64_t halfHeight=constraints.capsuleHeightSubvoxels/2;
const std::int64_t axisHalf=std::max<std::int64_t>(0,halfHeight-radius);
const std::int64_t spacing=std::min<std::int64_t>(constraints.sweepSpacingSubvoxels,
WorldAssemblySubvoxelsPerVoxel/2);
const std::int64_t supportSpacing=std::min<std::int64_t>(spacing,
WorldAssemblySubvoxelsPerVoxel/4);
std::int64_t minimumClearance=std::numeric_limits<std::int64_t>::max();
std::int64_t maximumSupportDepth=0;
const std::array<std::array<std::int64_t,2>,5> supportOffsets{{
{{0,0}},{{constraints.supportProbeRadiusSubvoxels,0}},
{{-constraints.supportProbeRadiusSubvoxels,0}},
{{0,constraints.supportProbeRadiusSubvoxels}},
{{0,-constraints.supportProbeRadiusSubvoxels}}}};
const auto sweepCapsule=[&](const std::array<std::int64_t,3>& center)
{
++result.centerSamples;
const std::int64_t footZ=center[2]-halfHeight;
const std::int64_t firstDz=-halfHeight+constraints.minimumClearanceSubvoxels;
for(std::int64_t dz=firstDz;dz<=halfHeight;dz+=spacing)
{
const std::int64_t capOffset=std::max<std::int64_t>(0,std::abs(dz)-axisHalf);
const std::int64_t radialSquared=radius*radius-capOffset*capOffset;
if(radialSquared<0)continue;
for(std::int64_t oy=-radius;oy<=radius;oy+=spacing)
for(std::int64_t ox=-radius;ox<=radius;ox+=spacing)
{
if(ox*ox+oy*oy>radialSquared)continue;
const auto sample=block.point(center[0]+ox,center[1]+oy,center[2]+dz);
++result.capsuleFieldSamples;
minimumClearance=std::min(minimumClearance,sample.densitySubvoxels);
if(sample.densitySubvoxels<constraints.minimumClearanceSubvoxels)
++result.blockedCapsuleSamples;
}
}
std::uint32_t supportedProbes=0;
for(const auto& offset:supportOffsets)
{
++result.supportProbeSamples;
bool supported=false;
for(std::int64_t depth=supportSpacing;
depth<=constraints.supportSearchDepthSubvoxels;depth+=supportSpacing)
{
const auto sample=block.point(center[0]+offset[0],center[1]+offset[1],footZ-depth);
if(sample.densitySubvoxels<0)
{
supported=true;
maximumSupportDepth=std::max(maximumSupportDepth,depth);
break;
}
}
if(supported)++supportedProbes;
}
if(supportedProbes<constraints.minimumSupportProbes)
++result.unsupportedCenterSamples;
};
for(std::size_t segment=1;segment<plan->route.size();++segment)
{
++result.segmentsTested;
const auto& from=plan->route[segment-1].positionSubvoxels;
const auto& to=plan->route[segment].positionSubvoxels;
const std::int64_t dx=to[0]-from[0],dy=to[1]-from[1],dz=to[2]-from[2];
const std::uint64_t horizontalSquared=static_cast<std::uint64_t>(dx*dx+dy*dy);
const std::uint64_t horizontalLength=IntegerSqrt(horizontalSquared);
const std::uint64_t rise=static_cast<std::uint64_t>(std::abs(dz));
if(horizontalLength==0||rise*static_cast<std::uint64_t>(Q16)
>horizontalLength*static_cast<std::uint64_t>(constraints.maximumGradeQ16))
++result.gradeFailures;
const std::uint64_t length=IntegerSqrt(horizontalSquared
+static_cast<std::uint64_t>(dz*dz));
const std::uint64_t sampleCount=std::max<std::uint64_t>(1,
(length+static_cast<std::uint64_t>(spacing)-1)/static_cast<std::uint64_t>(spacing));
const std::uint64_t first=segment==1?0:1;
for(std::uint64_t i=first;i<=sampleCount;++i)
{
const auto interpolate=[&](std::size_t axis)
{
const std::int64_t delta=to[axis]-from[axis];
return from[axis]+delta*static_cast<std::int64_t>(i)
/static_cast<std::int64_t>(sampleCount);
};
sweepCapsule({interpolate(0),interpolate(1),interpolate(2)});
}
}
result.minimumClearanceSubvoxels=minimumClearance==std::numeric_limits<std::int64_t>::max()
?0:minimumClearance;
result.maximumSupportDepthSubvoxels=maximumSupportDepth;
result.passed=result.segmentsTested>0&&result.centerSamples>0
&&result.blockedCapsuleSamples==0&&result.unsupportedCenterSamples==0
&&result.gradeFailures==0;
return result;
}
}