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

@@ -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;
}
}