wip(assembler): M1 round 1 -- manifest format, block contract, bound audit, native Heightfield2D

Luna, M1 round 1 of 4 (plan in Standalone/ASSEMBLER_M1_REPORT.md):
- Versioned fixed-scale world manifest (the shipped plan format) and the Core block
  contract: point evaluation, interval-or-unknown, influence bound, detail envelope, channels.
- Interval audit: 256 boxes / 6,912 samples, 0 missed, 0 unknown; a deliberately false bound
  is detected (negative control).
- Native Heightfield2D with biome input: 4,096-point hash fae2721c052aec5c identical at
  W1/W8/W16; point queries 2.059 ms vs 62.115 ms legacy -- but NOT a match yet (mean density
  error 274 voxels, 2,261 sign mismatches vs the legacy surface). 45 mesh-free Core queries
  match direct evaluation exactly. Existing cave world unchanged (walkability, 64/64 tiles).
- Open: collision readiness, a valid full-view timing (viewer captures were occluded/empty;
  top-down height maps are the only usable pair).
Also carries the design doc's owner decisions (fixed water; player edits unrestricted, route
law for automatic changes only) and three plugin files + BuildHost scripts that earlier round
commits had missed (also fixed on unreal/core-integration as 2574b56).

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 00:04:30 +02:00
1 parent d5ce5d675d
commit 874c796c65
20 files changed
+2880 -12

No files matched your search

@@ -52,3 +52,4 @@
// VFJson is a small, stateless standalone input parser with no Unreal-shaped names. Keeping its
// value tree shared lets the viewer resolve its reference world and Core snapshot from one parse.
#include "Json.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
+86 -7
View File
@@ -281,11 +281,13 @@ public:
bool bInRoundTunnels=false,bool bInProfileDetailBand=false,
bool bValidatePassageRoutes=true,
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InGpuVisualDetailTiles={},
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InCpuVisualDetailTiles={})
const std::vector<VoxelForgeCore::DetailBandTileRegion>& InCpuVisualDetailTiles={},
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly=nullptr)
{
using Clock=std::chrono::steady_clock;
const auto TotalStart=Clock::now();
Owner=&World;WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant;
Owner=&World;WorldAssembly=InWorldAssembly;
WorkerCount=std::clamp(Workers,1,16);DetailVariant=Variant;
Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels;
bAnalyticOnly=false;
bProfileDetailBand=bInProfileDetailBand;
@@ -919,6 +921,9 @@ public:
// edge interpolation and normals to the existing mesher.
float DensityAt(float X,float Y,float Z) const
{
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
return ApplyEditBrushes(NativeDensity,X,Y,Z);
if(UsesLegacySurfaceField(X,Y,Z))
return LegacySurfaceDensityAt(X,Y,Z);
int32 IX=0,IY=0,IZ=0;
@@ -931,6 +936,10 @@ public:
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
{
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z),NativeDensity))
return ApplyEditBrushes(NativeDensity,X,Y,Z);
if(UsesLegacySurfaceField(static_cast<float>(X),static_cast<float>(Y),
static_cast<float>(Z)))
return LegacySurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
@@ -945,6 +954,9 @@ public:
float DensityAtUnclamped(float X,float Y,float Z) const
{
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
return ApplyEditBrushes(NativeDensity,X,Y,Z);
if(UsesLegacySurfaceField(X,Y,Z))
return LegacySurfaceDensityAt(X,Y,Z);
return AnalyticDensityAt(X,Y,Z,false);
@@ -1066,6 +1078,9 @@ private:
if(OutFloorInfluence)*OutFloorInfluence=0.0f;
if(UsesLegacySurfaceField(X,Y,Z))
{
float NativeDensity=0.0f;
if(NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
return bApplyEdits?ApplyEditBrushes(NativeDensity,X,Y,Z):NativeDensity;
const float Density=Owner->Generator->GetDensityAt(X,Y,Z);
return bApplyEdits?ApplyEditBrushes(Density,X,Y,Z):Density;
}
@@ -1119,10 +1134,8 @@ public:
const int32 Pad=std::max(Step,1);
int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad};
int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,Origin.Z+(Cells+1)*Step};
// A primitive-only proof cannot classify the biome heightfield. Keep overlapping
// SurfaceWorld boxes mixed so generation samples the exact copied evaluator.
// Une preuve fondee sur les primitives ne classe pas la hauteur par biomes; garder les
// boites SurfaceWorld en Mixed force l'evaluation exacte lors du maillage.
const VoxelForgeCore::WorldAssemblyStratumPlanV1* SurfacePlan=nullptr;
bool bSurfaceOverlap=false,bOtherStrateOverlap=false;
if(Owner&&Owner->Manager)
{
const int32 FirstChunk=FMath::FloorToInt(static_cast<float>(BMin[2])
@@ -1130,10 +1143,24 @@ public:
const int32 LastChunk=FMath::FloorToInt(static_cast<float>(BMax[2])
/static_cast<float>(CHUNK_SIZE));
for(int32 ChunkZ=FirstChunk;ChunkZ<=LastChunk;++ChunkZ)
{
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
==ECaveGeneratorType::SurfaceWorld)
return EVoxelTileClass::Mixed;
{
bSurfaceOverlap=true;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
const auto* Candidate=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
if(!Candidate||(SurfacePlan&&SurfacePlan!=Candidate))
return EVoxelTileClass::Mixed;
SurfacePlan=Candidate;
}
else bOtherStrateOverlap=true;
}
}
// A box crossing recipes cannot use one field interval; unknown composition stays Mixed.
// Une boite qui traverse plusieurs recettes reste Mixed, car l'intervalle d'un seul champ ne suffit pas.
if(bSurfaceOverlap&&bOtherStrateOverlap)return EVoxelTileClass::Mixed;
if(!EditBrushes.empty()&&EditBoundsMin.X<=BMax[0]&&EditBoundsMax.X>=BMin[0]
&&EditBoundsMin.Y<=BMax[1]&&EditBoundsMax.Y>=BMin[1]
&&EditBoundsMin.Z<=BMax[2]&&EditBoundsMax.Z>=BMin[2])
@@ -1155,6 +1182,22 @@ public:
if(R.Lo<=BMax[2]&&R.Hi>=BMin[2]
&&(R.Lo>=BMin[2]||R.Hi<=BMax[2]))
return EVoxelTileClass::Mixed;
if(bSurfaceOverlap)
{
if(!WorldAssembly||!SurfacePlan)return EVoxelTileClass::Mixed;
const VoxelForgeCore::Heightfield2D Block(WorldAssembly->recipe,*SurfacePlan);
const auto Bounds=Block.interval(
static_cast<int64>(BMin[0])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMin[1])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMin[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMax[0])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMax[1])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel,
static_cast<int64>(BMax[2])*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
if(!Bounds.known)return EVoxelTileClass::Mixed;
if(Bounds.minimumDensitySubvoxels>0)return EVoxelTileClass::AllAir;
if(Bounds.maximumDensitySubvoxels<0)return EVoxelTileClass::AllSolid;
return EVoxelTileClass::Mixed;
}
const bool bRock=EvaluateFast((BMin[0]+BMax[0])*0.5,
(BMin[1]+BMax[1])*0.5,(BMin[2]+BMax[2])*0.5);
return bRock?EVoxelTileClass::AllSolid:EVoxelTileClass::AllAir;
@@ -1619,6 +1662,41 @@ public:
}
private:
const VoxelForgeCore::WorldAssemblyStratumPlanV1* FindWorldAssemblyStratum(
int32 StrateIndex,int32 ChunkZ) const
{
if(!WorldAssembly||StrateIndex<0)return nullptr;
const auto It=std::find_if(WorldAssembly->plan.strates.begin(),WorldAssembly->plan.strates.end(),
[StrateIndex,ChunkZ](const auto& Entry)
{return Entry.strateIndex==StrateIndex&&ChunkZ<=Entry.topChunkZ&&ChunkZ>=Entry.bottomChunkZ;});
return It==WorldAssembly->plan.strates.end()?nullptr:&*It;
}
bool NativeSurfaceDensityAt(float X,float Y,float Z,float& OutDensity) const
{
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
||!std::isfinite(X)||!std::isfinite(Y)||!std::isfinite(Z))return false;
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE));
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
!=ECaveGeneratorType::SurfaceWorld)return false;
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
const auto* Plan=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
if(!Plan)return false;
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
const double MaxVoxel=static_cast<double>(std::numeric_limits<int64>::max())/Scale;
if(std::abs(static_cast<double>(X))>MaxVoxel||std::abs(static_cast<double>(Y))>MaxVoxel
||std::abs(static_cast<double>(Z))>MaxVoxel)return false;
const auto ToFixed=[](float Value)
{return static_cast<int64>(std::llround(static_cast<double>(Value)
*VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel));};
const VoxelForgeCore::Heightfield2D Block(WorldAssembly->recipe,*Plan);
if(!Block.isValid())return false;
OutDensity=static_cast<float>(Block.point(ToFixed(X),ToFixed(Y),ToFixed(Z)).densitySubvoxels
/Scale);
return true;
}
bool UsesLegacySurfaceField(float X,float Y,float Z) const
{
if(!Owner||!Owner->Manager||!Owner->Generator)return false;
@@ -4850,6 +4928,7 @@ private:
}
FStandaloneOwnerWorld* Owner=nullptr;
const VoxelForgeCore::WorldAssemblyManifest* WorldAssembly=nullptr;
int32 WorkerCount=6;
const VoxelForgeCore::JobScheduler* Scheduler=nullptr;
bool RoundTunnelsEnabled=false;
+30 -5
View File
@@ -1,6 +1,7 @@
#include "CoreNamespacePreamble.h"
#include "StandaloneSnapshotAdapter.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include "WorldAssemblerInternal.h"
#include <shared_mutex>
#include <mutex>
@@ -23,6 +24,7 @@ namespace VoxelForgeCore
struct ResolvedWorldSnapshot::Data
{
std::shared_ptr<FStandaloneOwnerWorld> owner;
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
WorldConfiguration configuration;
};
@@ -216,12 +218,14 @@ namespace VoxelForgeCore
struct detail_StandaloneSnapshotAdapter
{
static ResolvedWorldSnapshot FromOwner(std::shared_ptr<FStandaloneOwnerWorld> owner)
static ResolvedWorldSnapshot FromOwner(std::shared_ptr<FStandaloneOwnerWorld> owner,
std::shared_ptr<const WorldAssemblyManifest> worldAssembly = {})
{
if(!owner||!owner->Settings||!owner->Manager||!owner->Generator)return {};
auto data=std::make_shared<ResolvedWorldSnapshot::Data>();
data->configuration=MakeConfiguration(*owner);
data->owner=std::move(owner);
data->worldAssembly=std::move(worldAssembly);
return ResolvedWorldSnapshot(std::move(data));
}
};
@@ -232,6 +236,13 @@ namespace VoxelForgeCore
{
auto owner=std::make_shared<FStandaloneOwnerWorld>();
if(!StandaloneOwnerLoader::Resolve(jsonRoot,*owner,error))return {};
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
if(jsonRoot.Get("world_assembly").IsObject())
{
auto parsed=detail_world_assembly::ParseNestedManifest(jsonRoot,error);
if(!parsed)return {};
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
}
const bool seedChanged=owner->Seed!=seed;
owner->Settings->Seed=seed;
@@ -254,7 +265,7 @@ namespace VoxelForgeCore
owner->MaxClipLevel=maxLodOverride;
owner->Settings->MaxClipLevel=maxLodOverride;
}
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner));
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly));
}
std::optional<ResolvedWorldSnapshot> ResolvedWorldSnapshot::FromJson(
@@ -262,9 +273,21 @@ namespace VoxelForgeCore
{
auto owner=std::make_shared<FStandaloneOwnerWorld>();
if(!StandaloneOwnerLoader::LoadJson(json,*owner,error))return std::nullopt;
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner));
VFJson::Value root;
if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt;
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
if(root.Get("world_assembly").IsObject())
{
auto parsed=detail_world_assembly::ParseNestedManifest(root,error);
if(!parsed)return std::nullopt;
worldAssembly=std::make_shared<WorldAssemblyManifest>(std::move(*parsed));
}
return detail_StandaloneSnapshotAdapter::FromOwner(std::move(owner),std::move(worldAssembly));
}
const WorldAssemblyManifest* ResolvedWorldSnapshot::worldAssembly() const noexcept
{ return data_?data_->worldAssembly.get():nullptr; }
const WorldConfiguration& ResolvedWorldSnapshot::configuration() const
{
static const WorldConfiguration empty{};
@@ -274,6 +297,7 @@ namespace VoxelForgeCore
struct World::Impl
{
std::shared_ptr<FStandaloneOwnerWorld> owner;
std::shared_ptr<const WorldAssemblyManifest> worldAssembly;
WorldConfiguration configuration;
std::unique_ptr<FStandaloneShapeWorld> shape;
ShapeBuildRequest buildRequest;
@@ -291,6 +315,7 @@ namespace VoxelForgeCore
if(snapshot.data_)
{
impl_->owner=snapshot.data_->owner;
impl_->worldAssembly=snapshot.data_->worldAssembly;
impl_->configuration=snapshot.data_->configuration;
}
}
@@ -307,7 +332,7 @@ namespace VoxelForgeCore
static_cast<int32>(std::clamp<std::uint32_t>(request.workerLimit,1,16)),ToNative(request.detail),
request.includeFarWorld,error,&scheduler,request.roundTunnels,request.profileDetailBand,
request.validatePassageRoutes,request.gpuVisualDetailTiles,
request.cpuVisualDetailTiles);
request.cpuVisualDetailTiles,impl_->worldAssembly.get());
if(ok)
{
impl_->shape=std::move(shape);
@@ -443,7 +468,7 @@ namespace VoxelForgeCore
auto region=std::make_shared<FStandaloneShapeWorld>();
if(!region->Build(*impl_->owner,fineMin,fineMax,regionMin,regionMax,
static_cast<int32>(clampedWorkers),ToNative(base.detail),false,error,&scheduler,
base.roundTunnels,base.profileDetailBand,false))return false;
base.roundTunnels,base.profileDetailBand,false,{}, {},impl_->worldAssembly.get()))return false;
if(!impl_->editLog.empty())region->SetEditTransaction(impl_->editLog);
const auto& build=region->GetStats();
++stats.regionsBuilt;
@@ -0,0 +1,766 @@
#pragma once
#include "Json.h"
#include "VoxelForgeCore/VoxelForgeCore.h"
#include <algorithm>
#include <charconv>
#include <cmath>
#include <limits>
#include <sstream>
namespace VoxelForgeCore
{
namespace world_assembly_detail
{
constexpr std::int64_t Q16 = 1ll << 16;
constexpr std::int64_t MaxSerializedMagnitude = 1000000000000ll;
constexpr std::size_t MaxBiomeCount = 8;
constexpr std::size_t MaxStrateCount = 64;
constexpr std::size_t MaxFeatureCount = 4096;
constexpr std::size_t MaxRoutePointCount = 4096;
inline void HashWord(std::uint64_t& hash, std::uint64_t word, int bytes)
{
for (int i = 0; i < bytes; ++i)
{
hash ^= static_cast<std::uint8_t>(word >> (i * 8));
hash *= 1099511628211ull;
}
}
inline std::uint64_t StartHash() { return 14695981039346656037ull; }
inline bool ReadInteger(const VFJson::Value& value, std::int64_t& out)
{
if (!value.IsNumber()) return false;
const double number = value.Number();
constexpr double ExactJsonIntegerLimit = 9007199254740991.0;
if (!std::isfinite(number) || std::trunc(number) != number
|| std::abs(number) > ExactJsonIntegerLimit) return false;
out = static_cast<std::int64_t>(number);
return true;
}
inline bool ReadU32(const VFJson::Value& value, std::uint32_t& out)
{
std::int64_t parsed = 0;
if (!ReadInteger(value, parsed) || parsed < 0
|| parsed > std::numeric_limits<std::uint32_t>::max()) return false;
out = static_cast<std::uint32_t>(parsed);
return true;
}
inline bool ReadI32(const VFJson::Value& value, std::int32_t& out)
{
std::int64_t parsed = 0;
if (!ReadInteger(value, parsed) || parsed < std::numeric_limits<std::int32_t>::min()
|| parsed > std::numeric_limits<std::int32_t>::max()) return false;
out = static_cast<std::int32_t>(parsed);
return true;
}
inline bool ReadArray(const VFJson::Value& value, std::int64_t* out, std::size_t count)
{
if (!value.IsArray() || value.AsArray().size() != count) return false;
for (std::size_t i = 0; i < count; ++i)
if (!ReadInteger(value.AsArray()[i], out[i])) return false;
return true;
}
inline bool ReadHash(const VFJson::Value& value, std::uint64_t& out)
{
if (!value.IsString()) return false;
std::string_view text = value.String();
if (text.starts_with("0x") || text.starts_with("0X")) text.remove_prefix(2);
if (text.empty() || text.size() > 16) return false;
const auto result = std::from_chars(text.data(), text.data() + text.size(), out, 16);
return result.ec == std::errc{} && result.ptr == text.data() + text.size();
}
inline std::string HashText(std::uint64_t hash)
{
constexpr char Hex[] = "0123456789abcdef";
std::string out(16, '0');
for (int i = 15; i >= 0; --i)
{
out[static_cast<std::size_t>(i)] = Hex[hash & 0xfu];
hash >>= 4;
}
return out;
}
inline bool ValidScalar(std::int64_t value)
{ return value >= -MaxSerializedMagnitude && value <= MaxSerializedMagnitude; }
inline bool OutsideRange(std::int64_t value)
{ return value < -MaxSerializedMagnitude || value > MaxSerializedMagnitude; }
inline bool ValidateRecipe(const WorldAssemblyRecipeV1& recipe, std::string& error)
{
if (recipe.schemaVersion != WorldAssemblySchemaVersion
|| recipe.evaluatorVersion != WorldAssemblyEvaluatorVersion
|| recipe.unitsPerVoxel != WorldAssemblySubvoxelsPerVoxel)
{ error = "world assembly recipe version, evaluator or fixed scale is unsupported"; return false; }
if (recipe.biomes.empty() || recipe.biomes.size() > MaxBiomeCount)
{ error = "world assembly recipe must contain one to eight biome profiles"; return false; }
if (recipe.biomeCellSizeSubvoxels <= 0 || recipe.biomeCellSizeSubvoxels > 100000000ll
|| recipe.biomeWarpSubvoxels < 0 || recipe.biomeWarpSubvoxels > recipe.biomeCellSizeSubvoxels / 2
|| recipe.biomeWarpWavelengthSubvoxels <= 0
|| recipe.biomeWarpWavelengthSubvoxels > 100000000ll
|| recipe.biomeBorderBlendSubvoxels < 0
|| recipe.biomeBorderBlendSubvoxels > recipe.biomeCellSizeSubvoxels)
{ error = "world assembly biome-map parameters are outside the supported fixed-scale range"; return false; }
std::vector<std::uint32_t> ids;
ids.reserve(recipe.biomes.size());
for (const auto& biome : recipe.biomes)
{
if (biome.stableId == 0 || std::find(ids.begin(), ids.end(), biome.stableId) != ids.end())
{ error = "world assembly biome IDs must be nonzero and unique"; return false; }
ids.push_back(biome.stableId);
const std::array<std::int64_t, 7> values = {biome.baseHeightSubvoxels,
biome.continentAmplitudeSubvoxels, biome.continentWavelengthSubvoxels,
biome.mountainAmplitudeSubvoxels, biome.mountainWavelengthSubvoxels,
biome.detailAmplitudeSubvoxels, biome.detailWavelengthSubvoxels};
if (!std::all_of(values.begin(), values.end(), ValidScalar)
|| biome.continentAmplitudeSubvoxels < 0 || biome.mountainAmplitudeSubvoxels < 0
|| biome.detailAmplitudeSubvoxels < 0 || biome.continentWavelengthSubvoxels <= 0
|| biome.mountainWavelengthSubvoxels <= 0 || biome.detailWavelengthSubvoxels <= 0)
{ error = "world assembly biome profile has an invalid fixed-scale value"; return false; }
}
return true;
}
inline bool ValidateStratum(const WorldAssemblyStratumPlanV1& stratum, std::string& error)
{
if (stratum.strateIndex < 0 || stratum.topChunkZ < stratum.bottomChunkZ
|| stratum.capWavelengthSubvoxels <= 0 || stratum.capUndulationSubvoxels < 0
|| stratum.features.size() > MaxFeatureCount || stratum.route.size() > MaxRoutePointCount)
{ error = "world assembly stratum plan has an invalid extent or exceeds its bounded payload"; return false; }
if (!ValidScalar(stratum.capHeightSubvoxels) || !ValidScalar(stratum.capUndulationSubvoxels)
|| !ValidScalar(stratum.capWavelengthSubvoxels))
{ error = "world assembly cap contains a value outside the fixed-scale range"; return false; }
std::vector<std::uint64_t> ids;
ids.reserve(stratum.features.size());
for (const auto& feature : stratum.features)
{
if (feature.stableId == 0 || feature.type < 1 || feature.type > 3
|| std::find(ids.begin(), ids.end(), feature.stableId) != ids.end())
{ error = "world assembly feature IDs and types must be stable, unique and supported"; return false; }
ids.push_back(feature.stableId);
for (std::size_t axis = 0; axis < 3; ++axis)
if (!ValidScalar(feature.minimumSubvoxels[axis])
|| !ValidScalar(feature.maximumSubvoxels[axis])
|| feature.minimumSubvoxels[axis] > feature.maximumSubvoxels[axis])
{ error = "world assembly feature has an invalid influence bound"; return false; }
if (!std::all_of(feature.parametersSubvoxels.begin(), feature.parametersSubvoxels.end(), ValidScalar))
{ error = "world assembly feature parameter is outside the fixed-scale range"; return false; }
}
for (const auto& point : stratum.route)
{
if (point.stableFeatureId == 0 || point.radiusSubvoxels <= 0
|| !ValidScalar(point.radiusSubvoxels)
|| !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; }
}
return true;
}
inline std::uint64_t HashRecipe(const WorldAssemblyRecipeV1& recipe)
{
std::uint64_t hash = StartHash();
HashWord(hash, recipe.schemaVersion, 4); HashWord(hash, recipe.evaluatorVersion, 4);
HashWord(hash, recipe.unitsPerVoxel, 4); HashWord(hash, recipe.seed, 4);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeCellSizeSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeWarpSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeWarpWavelengthSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(recipe.biomeBorderBlendSubvoxels), 8);
HashWord(hash, recipe.biomes.size(), 4);
for (const auto& biome : recipe.biomes)
{
HashWord(hash, biome.stableId, 4);
HashWord(hash, static_cast<std::uint64_t>(biome.baseHeightSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(biome.continentAmplitudeSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(biome.continentWavelengthSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(biome.mountainAmplitudeSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(biome.mountainWavelengthSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(biome.detailAmplitudeSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(biome.detailWavelengthSubvoxels), 8);
}
return hash;
}
inline std::uint64_t HashStratum(const WorldAssemblyStratumPlanV1& stratum)
{
std::uint64_t hash = StartHash();
HashWord(hash, static_cast<std::uint32_t>(stratum.strateIndex), 4);
HashWord(hash, static_cast<std::uint32_t>(stratum.topChunkZ), 4);
HashWord(hash, static_cast<std::uint32_t>(stratum.bottomChunkZ), 4);
HashWord(hash, static_cast<std::uint64_t>(stratum.capHeightSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(stratum.capUndulationSubvoxels), 8);
HashWord(hash, static_cast<std::uint64_t>(stratum.capWavelengthSubvoxels), 8);
HashWord(hash, stratum.features.size(), 4);
for (const auto& feature : stratum.features)
{
HashWord(hash, feature.stableId, 8); HashWord(hash, feature.type, 4);
for (const auto value : feature.minimumSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
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);
}
HashWord(hash, stratum.route.size(), 4);
for (const auto& point : stratum.route)
{
HashWord(hash, point.stableFeatureId, 8);
for (const auto value : point.positionSubvoxels) HashWord(hash, static_cast<std::uint64_t>(value), 8);
HashWord(hash, static_cast<std::uint64_t>(point.radiusSubvoxels), 8);
}
return hash;
}
inline std::uint64_t HashPlan(const WorldAssemblyPlanV1& plan)
{
std::uint64_t hash = StartHash();
HashWord(hash, plan.schemaVersion, 4); HashWord(hash, plan.strates.size(), 4);
for (const auto& stratum : plan.strates) HashWord(hash, stratum.contentHash, 8);
return hash;
}
inline std::uint64_t HashManifest(const WorldAssemblyManifest& manifest)
{
std::uint64_t hash = StartHash();
HashWord(hash, manifest.schemaVersion, 4);
HashWord(hash, manifest.recipe.contentHash, 8);
HashWord(hash, manifest.plan.contentHash, 8);
return hash;
}
inline bool ParseRecipe(const VFJson::Value& value, WorldAssemblyRecipeV1& recipe,
std::string& error)
{
if (!value.IsObject() || !ReadU32(value.Get("schema_version"), recipe.schemaVersion)
|| !ReadU32(value.Get("evaluator_version"), recipe.evaluatorVersion)
|| !ReadU32(value.Get("units_per_voxel"), recipe.unitsPerVoxel)
|| !ReadU32(value.Get("seed"), recipe.seed)
|| !ReadInteger(value.Get("biome_cell_size_q"), recipe.biomeCellSizeSubvoxels)
|| !ReadInteger(value.Get("biome_warp_q"), recipe.biomeWarpSubvoxels)
|| !ReadInteger(value.Get("biome_warp_wavelength_q"), recipe.biomeWarpWavelengthSubvoxels)
|| !ReadInteger(value.Get("biome_border_blend_q"), recipe.biomeBorderBlendSubvoxels)
|| !ReadHash(value.Get("content_hash"), recipe.contentHash)
|| !value.Get("biomes").IsArray())
{ error = "world assembly recipe is missing a required versioned fixed-scale field"; return false; }
for (const auto& item : value.Get("biomes").AsArray())
{
WorldAssemblyBiomeProfileV1 biome;
if (!item.IsObject() || !ReadU32(item.Get("id"), biome.stableId)
|| !ReadInteger(item.Get("base_height_q"), biome.baseHeightSubvoxels)
|| !ReadInteger(item.Get("continent_amplitude_q"), biome.continentAmplitudeSubvoxels)
|| !ReadInteger(item.Get("continent_wavelength_q"), biome.continentWavelengthSubvoxels)
|| !ReadInteger(item.Get("mountain_amplitude_q"), biome.mountainAmplitudeSubvoxels)
|| !ReadInteger(item.Get("mountain_wavelength_q"), biome.mountainWavelengthSubvoxels)
|| !ReadInteger(item.Get("detail_amplitude_q"), biome.detailAmplitudeSubvoxels)
|| !ReadInteger(item.Get("detail_wavelength_q"), biome.detailWavelengthSubvoxels))
{ error = "world assembly biome profile is missing a required fixed-scale field"; return false; }
recipe.biomes.push_back(biome);
}
return true;
}
inline bool ParsePlan(const VFJson::Value& value, WorldAssemblyPlanV1& plan,
std::string& error)
{
if (!value.IsObject() || !ReadU32(value.Get("schema_version"), plan.schemaVersion)
|| !ReadHash(value.Get("content_hash"), plan.contentHash)
|| !value.Get("strates").IsArray())
{ error = "world assembly plan is missing a required versioned field"; return false; }
for (const auto& item : value.Get("strates").AsArray())
{
WorldAssemblyStratumPlanV1 stratum;
if (!item.IsObject() || !ReadI32(item.Get("strate_index"), stratum.strateIndex)
|| !ReadI32(item.Get("top_chunk_z"), stratum.topChunkZ)
|| !ReadI32(item.Get("bottom_chunk_z"), stratum.bottomChunkZ)
|| !ReadInteger(item.Get("cap_height_q"), stratum.capHeightSubvoxels)
|| !ReadInteger(item.Get("cap_undulation_q"), stratum.capUndulationSubvoxels)
|| !ReadInteger(item.Get("cap_wavelength_q"), stratum.capWavelengthSubvoxels)
|| !ReadHash(item.Get("content_hash"), stratum.contentHash)
|| !item.Get("features").IsArray() || !item.Get("route").IsArray())
{ error = "world assembly stratum is missing a required fixed-scale field"; return false; }
for (const auto& featureValue : item.Get("features").AsArray())
{
WorldAssemblyFeatureV1 feature;
std::uint32_t type = 0;
if (!featureValue.IsObject() || !ReadHash(featureValue.Get("stable_id"), feature.stableId)
|| !ReadU32(featureValue.Get("type"), type)
|| !ReadArray(featureValue.Get("min_q"), feature.minimumSubvoxels.data(), 3)
|| !ReadArray(featureValue.Get("max_q"), feature.maximumSubvoxels.data(), 3)
|| !ReadArray(featureValue.Get("parameters_q"), feature.parametersSubvoxels.data(), 4))
{ error = "world assembly feature is missing a fixed-scale field"; return false; }
feature.type = type;
stratum.features.push_back(feature);
}
for (const auto& pointValue : item.Get("route").AsArray())
{
WorldAssemblyRoutePointV1 point;
if (!pointValue.IsObject() || !ReadHash(pointValue.Get("feature_id"), point.stableFeatureId)
|| !ReadArray(pointValue.Get("position_q"), point.positionSubvoxels.data(), 3)
|| !ReadInteger(pointValue.Get("radius_q"), point.radiusSubvoxels))
{ error = "world assembly route point is missing a fixed-scale field"; return false; }
stratum.route.push_back(point);
}
plan.strates.push_back(std::move(stratum));
}
return true;
}
inline std::optional<WorldAssemblyManifest> ParseManifestValue(const VFJson::Value& value,
std::string& error)
{
WorldAssemblyManifest manifest;
if (!value.IsObject() || !ReadU32(value.Get("schema_version"), manifest.schemaVersion)
|| !ReadHash(value.Get("content_hash"), manifest.contentHash)
|| !ParseRecipe(value.Get("recipe"), manifest.recipe, error)
|| !ParsePlan(value.Get("plan"), manifest.plan, error))
{
if (error.empty()) error = "world assembly manifest is missing a required field";
return std::nullopt;
}
if (!manifest.validate(error)) return std::nullopt;
return manifest;
}
inline std::int64_t FloorDiv(std::int64_t value, std::int64_t divisor)
{
const std::int64_t quotient = value / divisor, remainder = value % divisor;
return quotient - ((remainder != 0 && remainder < 0) ? 1 : 0);
}
inline std::int64_t FloorMod(std::int64_t value, std::int64_t divisor)
{ return value - FloorDiv(value, divisor) * divisor; }
inline std::uint64_t Mix64(std::uint64_t value)
{
value += 0x9e3779b97f4a7c15ull;
value = (value ^ (value >> 30)) * 0xbf58476d1ce4e5b9ull;
value = (value ^ (value >> 27)) * 0x94d049bb133111ebull;
return value ^ (value >> 31);
}
inline std::uint64_t CoordinateHash(std::uint32_t seed, std::int64_t x,
std::int64_t y, std::uint64_t salt)
{
std::uint64_t hash = Mix64(static_cast<std::uint64_t>(seed) ^ salt);
hash ^= Mix64(static_cast<std::uint64_t>(x) + 0x517cc1b727220a95ull);
hash ^= Mix64(static_cast<std::uint64_t>(y) + 0x6eed0e9da4d94a4full);
return Mix64(hash);
}
inline std::int32_t LatticeValue(std::uint32_t seed, std::int64_t x,
std::int64_t y, std::uint64_t salt)
{
const std::uint32_t bits = static_cast<std::uint32_t>(CoordinateHash(seed, x, y, salt) >> 48) & 0x1ffffu;
return static_cast<std::int32_t>(bits) - 65536;
}
inline std::int32_t LerpQ16(std::int32_t a, std::int32_t b, std::uint32_t t)
{ return static_cast<std::int32_t>(a + (static_cast<std::int64_t>(b - a) * t) / Q16); }
inline std::uint32_t SmoothQ16(std::uint32_t t)
{
const std::int64_t t2 = (static_cast<std::int64_t>(t) * t) / Q16;
return static_cast<std::uint32_t>((t2 * (3 * Q16 - 2 * static_cast<std::int64_t>(t))) / Q16);
}
inline std::int32_t Noise2D(std::int64_t x, std::int64_t y, std::int64_t wavelength,
std::uint32_t seed, std::uint64_t salt)
{
const std::int64_t cellX = FloorDiv(x, wavelength), cellY = FloorDiv(y, wavelength);
const std::uint32_t fractionX = static_cast<std::uint32_t>((FloorMod(x, wavelength) * Q16) / wavelength);
const std::uint32_t fractionY = static_cast<std::uint32_t>((FloorMod(y, wavelength) * Q16) / wavelength);
const std::uint32_t smoothX = SmoothQ16(fractionX), smoothY = SmoothQ16(fractionY);
const std::int32_t low = LerpQ16(LatticeValue(seed, cellX, cellY, salt),
LatticeValue(seed, cellX + 1, cellY, salt), smoothX);
const std::int32_t high = LerpQ16(LatticeValue(seed, cellX, cellY + 1, salt),
LatticeValue(seed, cellX + 1, cellY + 1, salt), smoothX);
return LerpQ16(low, high, smoothY);
}
inline std::uint32_t RatioQ16(std::uint64_t numerator, std::uint64_t denominator)
{
if (denominator == 0 || numerator >= denominator) return 65536;
std::uint32_t result = 0;
std::uint64_t remainder = numerator;
for (int bit = 15; bit >= 0; --bit)
{
remainder *= 2;
if (remainder >= denominator)
{ remainder -= denominator; result |= (1u << bit); }
}
return result;
}
inline std::int64_t MulQ16(std::int64_t value, std::int64_t q16)
{ return (value * q16) / Q16; }
inline std::int64_t ProfileHeight(const WorldAssemblyBiomeProfileV1& biome,
std::int64_t x, std::int64_t y, std::uint32_t seed)
{
const std::int64_t continent = MulQ16(biome.continentAmplitudeSubvoxels,
Noise2D(x, y, biome.continentWavelengthSubvoxels, seed, 0x434f4e54494e454eull));
const std::int64_t mountain = MulQ16(biome.mountainAmplitudeSubvoxels,
Noise2D(x, y, biome.mountainWavelengthSubvoxels, seed, 0x4d4f554e5441494eull));
const std::int64_t detail = MulQ16(biome.detailAmplitudeSubvoxels,
Noise2D(x, y, biome.detailWavelengthSubvoxels, seed, 0x44455441494c3031ull));
return biome.baseHeightSubvoxels + continent + mountain + detail;
}
inline bool InInterval(const FieldInterval& interval, std::int64_t density)
{ return interval.known && density >= interval.minimumDensitySubvoxels
&& density <= interval.maximumDensitySubvoxels; }
}
namespace detail_world_assembly
{
inline std::optional<WorldAssemblyManifest> ParseNestedManifest(const VFJson::Value& root,
std::string& error)
{
const VFJson::Value& payload = root.Get("world_assembly");
if (!payload.IsObject()) return std::nullopt;
return world_assembly_detail::ParseManifestValue(payload, error);
}
}
std::optional<WorldAssemblyManifest> WorldAssemblyManifest::FromJson(
std::string_view json, std::string& error)
{
VFJson::Value root;
if (!VFJson::Parser(std::string(json)).Parse(root, error)) return std::nullopt;
return world_assembly_detail::ParseManifestValue(root, error);
}
bool WorldAssemblyManifest::finalize(std::string& error)
{
if (schemaVersion != WorldAssemblySchemaVersion || plan.schemaVersion != WorldAssemblySchemaVersion
|| !world_assembly_detail::ValidateRecipe(recipe, error))
{ if (error.empty()) error = "world assembly manifest or plan version is unsupported"; return false; }
if (plan.strates.empty() || plan.strates.size() > world_assembly_detail::MaxStrateCount)
{ error = "world assembly plan must contain one to sixty-four finite strates"; return false; }
std::vector<std::int32_t> strateIds;
for (auto& stratum : plan.strates)
{
if (!world_assembly_detail::ValidateStratum(stratum, error)) 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);
stratum.contentHash = world_assembly_detail::HashStratum(stratum);
}
recipe.contentHash = world_assembly_detail::HashRecipe(recipe);
plan.contentHash = world_assembly_detail::HashPlan(plan);
contentHash = world_assembly_detail::HashManifest(*this);
return true;
}
bool WorldAssemblyManifest::validate(std::string& error) const
{
WorldAssemblyManifest copy = *this;
const std::uint64_t expectedRecipe = recipe.contentHash;
const std::uint64_t expectedPlan = plan.contentHash;
const std::uint64_t expectedManifest = contentHash;
std::vector<std::uint64_t> expectedStrates;
for (const auto& stratum : plan.strates) expectedStrates.push_back(stratum.contentHash);
if (!copy.finalize(error)) return false;
if (expectedRecipe != copy.recipe.contentHash || expectedPlan != copy.plan.contentHash
|| expectedManifest != copy.contentHash || expectedStrates.size() != copy.plan.strates.size())
{ error = "world assembly recipe, plan or manifest content hash does not match its fields"; return false; }
for (std::size_t i = 0; i < expectedStrates.size(); ++i)
if (expectedStrates[i] != copy.plan.strates[i].contentHash)
{ error = "world assembly stratum content hash does not match its fields"; return false; }
return true;
}
std::string WorldAssemblyManifest::ToJson() const
{
std::string error;
WorldAssemblyManifest value = *this;
if (!value.finalize(error)) return {};
std::ostringstream out;
out << "{\"schema_version\":" << value.schemaVersion
<< ",\"content_hash\":\"" << world_assembly_detail::HashText(value.contentHash)
<< "\",\"recipe\":{\"schema_version\":" << value.recipe.schemaVersion
<< ",\"evaluator_version\":" << value.recipe.evaluatorVersion
<< ",\"units_per_voxel\":" << value.recipe.unitsPerVoxel
<< ",\"seed\":" << value.recipe.seed
<< ",\"biome_cell_size_q\":" << value.recipe.biomeCellSizeSubvoxels
<< ",\"biome_warp_q\":" << value.recipe.biomeWarpSubvoxels
<< ",\"biome_warp_wavelength_q\":" << value.recipe.biomeWarpWavelengthSubvoxels
<< ",\"biome_border_blend_q\":" << value.recipe.biomeBorderBlendSubvoxels
<< ",\"content_hash\":\"" << world_assembly_detail::HashText(value.recipe.contentHash)
<< "\",\"biomes\":[";
for (std::size_t i = 0; i < value.recipe.biomes.size(); ++i)
{
const auto& biome = value.recipe.biomes[i];
if (i) out << ',';
out << "{\"id\":" << biome.stableId << ",\"base_height_q\":" << biome.baseHeightSubvoxels
<< ",\"continent_amplitude_q\":" << biome.continentAmplitudeSubvoxels
<< ",\"continent_wavelength_q\":" << biome.continentWavelengthSubvoxels
<< ",\"mountain_amplitude_q\":" << biome.mountainAmplitudeSubvoxels
<< ",\"mountain_wavelength_q\":" << biome.mountainWavelengthSubvoxels
<< ",\"detail_amplitude_q\":" << biome.detailAmplitudeSubvoxels
<< ",\"detail_wavelength_q\":" << biome.detailWavelengthSubvoxels << '}';
}
out << "]},\"plan\":{\"schema_version\":" << value.plan.schemaVersion
<< ",\"content_hash\":\"" << world_assembly_detail::HashText(value.plan.contentHash)
<< "\",\"strates\":[";
for (std::size_t i = 0; i < value.plan.strates.size(); ++i)
{
const auto& stratum = value.plan.strates[i];
if (i) out << ',';
out << "{\"strate_index\":" << stratum.strateIndex << ",\"top_chunk_z\":" << stratum.topChunkZ
<< ",\"bottom_chunk_z\":" << stratum.bottomChunkZ
<< ",\"cap_height_q\":" << stratum.capHeightSubvoxels
<< ",\"cap_undulation_q\":" << stratum.capUndulationSubvoxels
<< ",\"cap_wavelength_q\":" << stratum.capWavelengthSubvoxels
<< ",\"content_hash\":\"" << world_assembly_detail::HashText(stratum.contentHash)
<< "\",\"features\":[";
for (std::size_t f = 0; f < stratum.features.size(); ++f)
{
const auto& feature = stratum.features[f];
if (f) out << ',';
out << "{\"stable_id\":\"" << world_assembly_detail::HashText(feature.stableId)
<< "\",\"type\":" << feature.type << ",\"min_q\":["
<< feature.minimumSubvoxels[0] << ',' << feature.minimumSubvoxels[1] << ',' << feature.minimumSubvoxels[2]
<< "],\"max_q\":[" << feature.maximumSubvoxels[0] << ',' << feature.maximumSubvoxels[1]
<< ',' << feature.maximumSubvoxels[2] << "],\"parameters_q\":["
<< feature.parametersSubvoxels[0] << ',' << feature.parametersSubvoxels[1] << ','
<< feature.parametersSubvoxels[2] << ',' << feature.parametersSubvoxels[3] << "]}";
}
out << "],\"route\":[";
for (std::size_t p = 0; p < stratum.route.size(); ++p)
{
const auto& point = stratum.route[p];
if (p) out << ',';
out << "{\"feature_id\":\"" << world_assembly_detail::HashText(point.stableFeatureId)
<< "\",\"position_q\":[" << point.positionSubvoxels[0] << ','
<< point.positionSubvoxels[1] << ',' << point.positionSubvoxels[2]
<< "],\"radius_q\":" << point.radiusSubvoxels << '}';
}
out << "]}";
}
out << "]}}";
return out.str();
}
Heightfield2D::Heightfield2D(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan) noexcept
: recipe_(&recipe), plan_(&plan)
{}
bool Heightfield2D::isValid() const noexcept
{
if (!recipe_ || !plan_ || recipe_->schemaVersion != WorldAssemblySchemaVersion
|| recipe_->evaluatorVersion != WorldAssemblyEvaluatorVersion
|| recipe_->unitsPerVoxel != WorldAssemblySubvoxelsPerVoxel
|| recipe_->biomes.empty() || recipe_->biomes.size() > world_assembly_detail::MaxBiomeCount
|| recipe_->biomeCellSizeSubvoxels <= 0 || recipe_->biomeWarpWavelengthSubvoxels <= 0
|| recipe_->biomeWarpSubvoxels < 0 || recipe_->biomeBorderBlendSubvoxels < 0
|| plan_->strateIndex < 0 || plan_->topChunkZ < plan_->bottomChunkZ
|| plan_->capWavelengthSubvoxels <= 0 || plan_->capUndulationSubvoxels < 0)
return false;
return std::all_of(recipe_->biomes.begin(), recipe_->biomes.end(), [](const auto& biome)
{
return biome.stableId != 0 && biome.continentAmplitudeSubvoxels >= 0
&& biome.mountainAmplitudeSubvoxels >= 0 && biome.detailAmplitudeSubvoxels >= 0
&& biome.continentWavelengthSubvoxels > 0 && biome.mountainWavelengthSubvoxels > 0
&& biome.detailWavelengthSubvoxels > 0;
});
}
HeightfieldPointSample Heightfield2D::point(std::int64_t x, std::int64_t y,
std::int64_t z) const noexcept
{
HeightfieldPointSample out;
if (!isValid() || world_assembly_detail::OutsideRange(x)
|| world_assembly_detail::OutsideRange(y)
|| world_assembly_detail::OutsideRange(z)) return out;
using namespace world_assembly_detail;
const std::int64_t warpX = MulQ16(recipe_->biomeWarpSubvoxels,
Noise2D(x, y, recipe_->biomeWarpWavelengthSubvoxels, recipe_->seed, 0x574152505f585f31ull));
const std::int64_t warpY = MulQ16(recipe_->biomeWarpSubvoxels,
Noise2D(x, y, recipe_->biomeWarpWavelengthSubvoxels, recipe_->seed, 0x574152505f595f31ull));
const std::int64_t warpedX = x + warpX, warpedY = y + warpY;
const std::int64_t cellX = FloorDiv(warpedX, recipe_->biomeCellSizeSubvoxels);
const std::int64_t cellY = FloorDiv(warpedY, recipe_->biomeCellSizeSubvoxels);
std::uint64_t nearestDistance = std::numeric_limits<std::uint64_t>::max();
std::uint64_t secondDistance = std::numeric_limits<std::uint64_t>::max();
std::uint32_t nearestId = 0, secondId = 0;
const std::uint32_t jitterSeed = recipe_->seed ^ 0xa341316cu;
const std::int64_t cellSize = recipe_->biomeCellSizeSubvoxels;
for (std::int64_t cy = cellY - 2; cy <= cellY + 2; ++cy)
for (std::int64_t cx = cellX - 2; cx <= cellX + 2; ++cx)
{
const std::uint64_t h = CoordinateHash(jitterSeed, cx, cy, 0x42494f4d455f5349ull);
const std::int64_t jitterX = static_cast<std::int64_t>((h & 0xffffu)) * (cellSize / 2) / 65535 - cellSize / 4;
const std::int64_t jitterY = static_cast<std::int64_t>(((h >> 16) & 0xffffu)) * (cellSize / 2) / 65535 - cellSize / 4;
const std::int64_t siteX = cx * cellSize + cellSize / 2 + jitterX;
const std::int64_t siteY = cy * cellSize + cellSize / 2 + jitterY;
const std::int64_t dx = warpedX - siteX, dy = warpedY - siteY;
const std::uint64_t distance = static_cast<std::uint64_t>(dx * dx + dy * dy);
const std::uint32_t biomeId = recipe_->biomes[CoordinateHash(jitterSeed, cx, cy,
0x42494f4d455f4944ull) % recipe_->biomes.size()].stableId;
if (distance < nearestDistance)
{
secondDistance = nearestDistance; secondId = nearestId;
nearestDistance = distance; nearestId = biomeId;
}
else if (distance < secondDistance && biomeId != nearestId)
{ secondDistance = distance; secondId = biomeId; }
}
const auto findBiome = [&](std::uint32_t id) -> const WorldAssemblyBiomeProfileV1*
{
const auto found = std::find_if(recipe_->biomes.begin(), recipe_->biomes.end(),
[id](const auto& biome) { return biome.stableId == id; });
return found == recipe_->biomes.end() ? nullptr : &*found;
};
const auto* primary = findBiome(nearestId);
const auto* secondary = findBiome(secondId ? secondId : nearestId);
if (!primary || !secondary) return out;
std::uint32_t primaryWeight = 65536;
if (recipe_->biomeBorderBlendSubvoxels > 0 && nearestDistance < secondDistance)
{
const std::uint64_t difference = secondDistance - nearestDistance;
const std::uint64_t denominator = static_cast<std::uint64_t>(2 * cellSize)
* static_cast<std::uint64_t>(recipe_->biomeBorderBlendSubvoxels);
primaryWeight = SmoothQ16(RatioQ16(difference, denominator));
}
const std::int64_t primaryHeight = ProfileHeight(*primary, warpedX, warpedY, recipe_->seed);
const std::int64_t secondaryHeight = ProfileHeight(*secondary, warpedX, warpedY, recipe_->seed);
out.groundHeightSubvoxels = (primaryHeight * primaryWeight
+ secondaryHeight * (65536 - primaryWeight)) / 65536;
out.biomeId = primary->stableId;
out.neighborBiomeId = secondary->stableId;
out.biomeWeightQ16 = primaryWeight;
out.capHeightSubvoxels = plan_->capHeightSubvoxels + MulQ16(plan_->capUndulationSubvoxels,
Noise2D(x, y, plan_->capWavelengthSubvoxels, recipe_->seed, 0x4341505f554e4431ull));
out.densitySubvoxels = std::min(z - out.groundHeightSubvoxels,
out.capHeightSubvoxels - z);
return out;
}
FieldInterval Heightfield2D::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
{
FieldInterval out;
if (!isValid() || minX > maxX || minY > maxY || minZ > maxZ
|| world_assembly_detail::OutsideRange(minX)
|| world_assembly_detail::OutsideRange(minY)
|| world_assembly_detail::OutsideRange(minZ)
|| world_assembly_detail::OutsideRange(maxX)
|| world_assembly_detail::OutsideRange(maxY)
|| world_assembly_detail::OutsideRange(maxZ)) return out;
std::int64_t groundMin = std::numeric_limits<std::int64_t>::max();
std::int64_t groundMax = std::numeric_limits<std::int64_t>::min();
for (const auto& biome : recipe_->biomes)
{
const std::int64_t amplitude = biome.continentAmplitudeSubvoxels
+ biome.mountainAmplitudeSubvoxels + biome.detailAmplitudeSubvoxels;
groundMin = std::min(groundMin, biome.baseHeightSubvoxels - amplitude);
groundMax = std::max(groundMax, biome.baseHeightSubvoxels + amplitude);
}
const std::int64_t capMin = plan_->capHeightSubvoxels - plan_->capUndulationSubvoxels;
const std::int64_t capMax = plan_->capHeightSubvoxels + plan_->capUndulationSubvoxels;
const std::int64_t groundLower = minZ - groundMax;
const std::int64_t groundUpper = maxZ - groundMin;
const std::int64_t capLower = capMin - maxZ;
const std::int64_t capUpper = capMax - minZ;
out.known = true;
out.minimumDensitySubvoxels = std::min(groundLower, capLower);
out.maximumDensitySubvoxels = std::min(groundUpper, capUpper);
return out;
}
FieldInfluenceBounds Heightfield2D::influenceBounds() const noexcept
{
FieldInfluenceBounds out;
if (!isValid()) return out;
out.minimumZSubvoxels = static_cast<std::int64_t>(plan_->bottomChunkZ) * 32 * WorldAssemblySubvoxelsPerVoxel;
out.maximumZSubvoxels = (static_cast<std::int64_t>(plan_->topChunkZ) + 1) * 32 * WorldAssemblySubvoxelsPerVoxel;
return out;
}
FieldDetailEnvelope Heightfield2D::detailEnvelope() const noexcept
{
FieldDetailEnvelope out;
if (!isValid()) return out;
std::int64_t maximum = plan_->capUndulationSubvoxels;
std::int64_t minimumFeature = std::numeric_limits<std::int64_t>::max();
for (const auto& biome : recipe_->biomes)
{
maximum = std::max(maximum, biome.continentAmplitudeSubvoxels
+ biome.mountainAmplitudeSubvoxels + biome.detailAmplitudeSubvoxels);
minimumFeature = std::min({minimumFeature, biome.continentWavelengthSubvoxels,
biome.mountainWavelengthSubvoxels, biome.detailWavelengthSubvoxels});
}
out.maximumAbsoluteDisplacementSubvoxels = maximum;
out.minimumFeatureSizeSubvoxels = std::max<std::int64_t>(1,
std::min(minimumFeature, recipe_->biomeBorderBlendSubvoxels > 0
? recipe_->biomeBorderBlendSubvoxels : minimumFeature));
return out;
}
FieldBlockContract Heightfield2D::contract() const noexcept
{
return {ChannelBit(FieldChannel::biome),
ChannelBit(FieldChannel::height) | ChannelBit(FieldChannel::rock),
WorldAssemblyEvaluatorVersion};
}
WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(const Heightfield2D& block,
std::uint32_t boxCount, std::uint64_t seed)
{
WorldAssemblyBoundsAuditResult result;
if (!block.isValid()) return result;
boxCount = std::clamp<std::uint32_t>(boxCount, 1, 100000);
const auto random = [&seed]()
{
seed = world_assembly_detail::Mix64(seed);
return seed;
};
const auto signedVoxel = [&random]()
{ return static_cast<std::int64_t>(random() % 12001) - 6000; };
for (std::uint32_t i = 0; i < boxCount; ++i)
{
const std::int64_t x = signedVoxel() * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t y = signedVoxel() * WorldAssemblySubvoxelsPerVoxel;
const auto column = block.point(x, y, 0);
const std::int64_t z = column.groundHeightSubvoxels
+ (static_cast<std::int64_t>(random() % 257) - 128) * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t halfX = static_cast<std::int64_t>(1 + random() % 64) * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t halfY = static_cast<std::int64_t>(1 + random() % 64) * WorldAssemblySubvoxelsPerVoxel;
const std::int64_t halfZ = static_cast<std::int64_t>(1 + random() % 64) * WorldAssemblySubvoxelsPerVoxel;
const FieldInterval bounds = block.interval(x - halfX, y - halfY, z - halfZ,
x + halfX, y + halfY, z + halfZ);
++result.boxesTested;
if (!bounds.known) { ++result.unknownBounds; continue; }
for (int iz = 0; iz <= 2; ++iz)
for (int iy = 0; iy <= 2; ++iy)
for (int ix = 0; ix <= 2; ++ix)
{
const std::int64_t px = x - halfX + (2 * halfX * ix) / 2;
const std::int64_t py = y - halfY + (2 * halfY * iy) / 2;
const std::int64_t pz = z - halfZ + (2 * halfZ * iz) / 2;
const auto sample = block.point(px, py, pz);
++result.pointSamples;
if (!world_assembly_detail::InInterval(bounds, sample.densitySubvoxels))
++result.missedBounds;
}
}
const auto ground = block.point(0, 0, 0).groundHeightSubvoxels;
const auto controlPoint = block.point(0, 0, ground + 17).densitySubvoxels;
// Negative control: this fabricated one-sided range excludes its known sample. The same
// containment check used by the brute-force audit must reject the deliberate lie.
// Controle negatif : cette plage unilaterale exclut un echantillon connu; l'audit doit rejeter cette borne mensongere.
const FieldInterval deliberatelyWrong{true, controlPoint + 1, controlPoint + 2};
result.negativeControlDetected = !world_assembly_detail::InInterval(deliberatelyWrong, controlPoint);
result.passed = result.boxesTested == boxCount && result.missedBounds == 0
&& result.unknownBounds == 0 && result.negativeControlDetected;
return result;
}
}