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

+39
View File
@@ -79,6 +79,45 @@ 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)
`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 reviewed sample payload is
[`round1-manifest.json`](../Research/WorldAssemblerM1/round1-manifest.json). Round 4 still needs to
connect this payload to the season asset/hash path.
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. Schema and evaluator versions are
both 1. JSON integer fields are canonicalized in declared field order and hashed as 4- or 8-byte
little-endian words with FNV-1a 64. Hashes are emitted as 16 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 and route points. Bounds,
feature count and route count are validated before a manifest is accepted.
```cpp
std::string error;
auto manifest = VoxelForgeCore::WorldAssemblyManifest::FromJson(payload, error);
if (!manifest || !manifest->validate(error)) { /* reject the payload */ }
const auto* loaded = snapshot.worldAssembly(); // null when the owner snapshot has no plan
```
`Heightfield2D` is the first field block. `point(xQ, yQ, zQ)` evaluates the biome map and blended
height profiles at fixed-scale coordinates, returning the ground and cap heights, primary and
neighbor biome IDs, Q16 blend weight, and signed density. Negative density is solid. Its block
contract reads `biome` and writes `height` plus `rock`; `water`, `waterSurface`, `route` and
`surfaceTags` are declared channels for later blocks. The detail envelope reports maximum absolute
vertical displacement and minimum feature size. Influence is global in XY and limited to the
finite stratum's Z extent.
`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
uses the maximum recipe-profile amplitude and cap undulation over the whole biome set. The public
`AuditHeightfieldBounds` samples points inside randomized boxes and includes a deliberately false
one-sided range as a 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.
## Queries and tiles
`densityAt(position)` returns signed density; negative is solid and nonnegative is air.
@@ -98,6 +98,182 @@ namespace VoxelForgeCore
Vec3d viewerStartVoxels{};
};
// World assembler v1 serializes every coordinate and scalar in 1/256 voxel units.
// This is independent of tile size and is the fixed input scale for Core field blocks.
// 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 = 1;
enum class FieldChannel : std::uint32_t
{
rock = 1u << 0, height = 1u << 1, biome = 1u << 2, water = 1u << 3,
waterSurface = 1u << 4, route = 1u << 5, surfaceTags = 1u << 6
};
constexpr std::uint32_t ChannelBit(FieldChannel channel) noexcept
{ return static_cast<std::uint32_t>(channel); }
struct WorldAssemblyBiomeProfileV1
{
std::uint32_t stableId = 0;
std::int64_t baseHeightSubvoxels = 0;
std::int64_t continentAmplitudeSubvoxels = 0;
std::int64_t continentWavelengthSubvoxels = 256 * 256;
std::int64_t mountainAmplitudeSubvoxels = 0;
std::int64_t mountainWavelengthSubvoxels = 256 * 128;
std::int64_t detailAmplitudeSubvoxels = 0;
std::int64_t detailWavelengthSubvoxels = 256 * 16;
};
struct WorldAssemblyRecipeV1
{
std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
std::uint32_t evaluatorVersion = WorldAssemblyEvaluatorVersion;
std::uint32_t unitsPerVoxel = static_cast<std::uint32_t>(WorldAssemblySubvoxelsPerVoxel);
std::uint32_t seed = 0;
std::int64_t biomeCellSizeSubvoxels = 256 * 800;
std::int64_t biomeWarpSubvoxels = 256 * 250;
std::int64_t biomeWarpWavelengthSubvoxels = 256 * 556;
std::int64_t biomeBorderBlendSubvoxels = 256 * 120;
std::vector<WorldAssemblyBiomeProfileV1> biomes;
std::uint64_t contentHash = 0;
};
struct WorldAssemblyRoutePointV1
{
std::uint64_t stableFeatureId = 0;
std::array<std::int64_t, 3> positionSubvoxels{};
std::int64_t radiusSubvoxels = 0;
};
struct WorldAssemblyFeatureV1
{
// Stable numeric type IDs are part of the plan contract; v1 reserves 1 for cliff planes,
// 2 for bounded overhang shelves and 3 for authored cap features.
// Les types numeriques stables font partie du plan; v1 reserve 1 aux falaises, 2 aux surplombs bornes et 3 aux caps.
std::uint64_t stableId = 0;
std::uint32_t type = 0;
std::array<std::int64_t, 3> minimumSubvoxels{};
std::array<std::int64_t, 3> maximumSubvoxels{};
std::array<std::int64_t, 4> parametersSubvoxels{};
};
struct WorldAssemblyStratumPlanV1
{
std::int32_t strateIndex = -1;
std::int32_t topChunkZ = 0;
std::int32_t bottomChunkZ = 0;
std::int64_t capHeightSubvoxels = 0;
std::int64_t capUndulationSubvoxels = 0;
std::int64_t capWavelengthSubvoxels = 256 * 512;
std::vector<WorldAssemblyFeatureV1> features;
std::vector<WorldAssemblyRoutePointV1> route;
std::uint64_t contentHash = 0;
};
struct WorldAssemblyPlanV1
{
std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
std::vector<WorldAssemblyStratumPlanV1> strates;
std::uint64_t contentHash = 0;
};
struct VFCORE_API WorldAssemblyManifest
{
std::uint32_t schemaVersion = WorldAssemblySchemaVersion;
WorldAssemblyRecipeV1 recipe;
WorldAssemblyPlanV1 plan;
std::uint64_t contentHash = 0;
// JSON is the reviewed season-manifest payload. Hashes use canonical ordered fields,
// little-endian integers and FNV-1a 64; textual key order and whitespace do not matter.
// Le JSON est le payload de saison valide; les entiers little-endian sont haches en FNV-1a 64, sans dependance a la mise en forme.
static std::optional<WorldAssemblyManifest> FromJson(
std::string_view json, std::string& error);
bool finalize(std::string& error);
bool validate(std::string& error) const;
std::string ToJson() const;
};
struct HeightfieldPointSample
{
std::int64_t groundHeightSubvoxels = 0;
std::int64_t capHeightSubvoxels = 0;
std::uint32_t biomeId = 0;
std::uint32_t neighborBiomeId = 0;
std::uint32_t biomeWeightQ16 = 0;
std::int64_t densitySubvoxels = 0;
};
struct FieldInterval
{
bool known = false;
std::int64_t minimumDensitySubvoxels = 0;
std::int64_t maximumDensitySubvoxels = 0;
};
struct FieldInfluenceBounds
{
bool unboundedXY = true;
std::int64_t minimumXSubvoxels = 0, maximumXSubvoxels = 0;
std::int64_t minimumYSubvoxels = 0, maximumYSubvoxels = 0;
std::int64_t minimumZSubvoxels = 0, maximumZSubvoxels = 0;
};
struct FieldDetailEnvelope
{
std::int64_t maximumAbsoluteDisplacementSubvoxels = 0;
std::int64_t minimumFeatureSizeSubvoxels = 1;
};
struct FieldBlockContract
{
std::uint32_t readsChannels = 0;
std::uint32_t writesChannels = 0;
std::uint32_t evaluatorVersion = WorldAssemblyEvaluatorVersion;
};
// Point evaluation is canonical at 1/256 voxel. Bounds fail closed when the block or input
// extent is invalid; callers must evaluate when interval() returns unknown.
// L'evaluation est canonique au 1/256 de voxel. Toute borne invalide echoue prudemment; une borne inconnue impose l'evaluation.
class VFCORE_API Heightfield2D
{
public:
Heightfield2D(const WorldAssemblyRecipeV1& recipe,
const WorldAssemblyStratumPlanV1& plan) noexcept;
bool isValid() const noexcept;
HeightfieldPointSample point(std::int64_t xSubvoxels, std::int64_t ySubvoxels,
std::int64_t zSubvoxels) const noexcept;
FieldInterval interval(std::int64_t minimumXSubvoxels,
std::int64_t minimumYSubvoxels,
std::int64_t minimumZSubvoxels,
std::int64_t maximumXSubvoxels,
std::int64_t maximumYSubvoxels,
std::int64_t maximumZSubvoxels) const noexcept;
FieldInfluenceBounds influenceBounds() const noexcept;
FieldDetailEnvelope detailEnvelope() const noexcept;
FieldBlockContract contract() const noexcept;
private:
const WorldAssemblyRecipeV1* recipe_ = nullptr;
const WorldAssemblyStratumPlanV1* plan_ = nullptr;
};
struct WorldAssemblyBoundsAuditResult
{
std::uint64_t boxesTested = 0;
std::uint64_t pointSamples = 0;
std::uint64_t missedBounds = 0;
std::uint64_t unknownBounds = 0;
bool negativeControlDetected = false;
bool passed = false;
};
VFCORE_API WorldAssemblyBoundsAuditResult AuditHeightfieldBounds(
const Heightfield2D& block, std::uint32_t boxCount = 256,
std::uint64_t seed = 0x574f524c44415331ull);
class VFCORE_API ResolvedWorldSnapshot
{
public:
@@ -110,6 +286,7 @@ namespace VoxelForgeCore
explicit operator bool() const noexcept { return static_cast<bool>(data_); }
const WorldConfiguration& configuration() const;
const WorldAssemblyManifest* worldAssembly() const noexcept;
private:
struct Data;
@@ -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;
}
}
@@ -0,0 +1,112 @@
[CmdletBinding()]
param(
[string]$ExecutableName = 'VoxelForgeUnrealSurfaceNative.exe',
[ValidateRange(1, 16)][int]$Workers = 8,
[ValidateRange(30, 900)][int]$TimeoutSeconds = 120
)
# Rejoue les probes de strates sauvegardées et fait échouer l'audit si un passage redevient solide.
# Replays the saved strate probes and fails the audit if any passage becomes solid again.
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$PluginRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..\..'))
$StandaloneRoot = Join-Path $PluginRoot 'Standalone'
$ExecutablePath = Join-Path $StandaloneRoot ('bin\' + $ExecutableName)
$ProbeRoot = Join-Path $StandaloneRoot 'Research\OwnerShapeWorld\core_strate_coverage\probes'
$LogRoot = Join-Path $ProbeRoot ('round5_walkability_w' + $Workers)
if ([IO.Path]::GetFileName($ExecutableName) -ne $ExecutableName -or
[IO.Path]::GetExtension($ExecutableName) -ne '.exe') {
throw 'ExecutableName must be an executable filename.'
}
if (-not (Test-Path -LiteralPath $ExecutablePath -PathType Leaf)) {
throw "Viewer not found: $ExecutablePath"
}
if (-not (Test-Path -LiteralPath $ProbeRoot -PathType Container)) {
throw "Passage probe directory not found: $ProbeRoot"
}
[IO.Directory]::CreateDirectory($LogRoot) | Out-Null
$HostProcess = Get-Process -Id $PID
$HostProcess.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
$HostProcess.ProcessorAffinity = [IntPtr]0xFFFF
$Probes = @(Get-ChildItem -LiteralPath $ProbeRoot -Filter '*.json' -File | Sort-Object Name)
if ($Probes.Count -ne 15) {
throw "Expected all 15 saved strate probes, found $($Probes.Count)."
}
$Passed = 0
foreach ($Probe in $Probes) {
$Stem = [IO.Path]::GetFileNameWithoutExtension($Probe.Name)
$StdoutPath = Join-Path $LogRoot ($Stem + '.stdout.log')
$StderrPath = Join-Path $LogRoot ($Stem + '.stderr.log')
# Start-Process flattens this array into one command-line string on Windows.
# Quote the JSON path because the workspace's parent directory contains a space.
$Arguments = @(('"' + $Probe.FullName + '"'), '--walkability-audit', "--workers=$Workers")
$Succeeded = $false
for ($Attempt = 1; $Attempt -le 2 -and -not $Succeeded; ++$Attempt) {
if ($Attempt -eq 2) {
Write-Warning "$Stem exited with 0xC0000142; retrying once after 60 seconds."
Start-Sleep -Seconds 60
}
Remove-Item -LiteralPath $StdoutPath, $StderrPath -Force -ErrorAction SilentlyContinue
$Process = Start-Process -FilePath $ExecutablePath -ArgumentList $Arguments `
-WorkingDirectory $StandaloneRoot -WindowStyle Hidden -PassThru `
-RedirectStandardOutput $StdoutPath -RedirectStandardError $StderrPath
$StartedUtc = [datetime]::UtcNow
$PeakPrivateBytes = [int64]0
try {
while (-not $Process.HasExited) {
Start-Sleep -Milliseconds 100
$Process.Refresh()
if ($Process.HasExited) { break }
$Process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
$Process.ProcessorAffinity = [IntPtr]0xFFFF
$PeakPrivateBytes = [Math]::Max($PeakPrivateBytes, [int64]$Process.PrivateMemorySize64)
if (([datetime]::UtcNow - $StartedUtc).TotalSeconds -gt $TimeoutSeconds) {
$Process.Kill()
$Process.WaitForExit()
throw "$Stem exceeded the $TimeoutSeconds-second per-probe time limit."
}
if ($PeakPrivateBytes -ge 8GB) {
$Process.Kill()
$Process.WaitForExit()
throw "$Stem exceeded the 8 GiB private-memory limit."
}
}
$Process.WaitForExit()
$Process.Refresh()
$ExitCode = [int]$Process.ExitCode
} finally {
if (-not $Process.HasExited) {
$Process.Kill()
$Process.WaitForExit()
}
}
if ($ExitCode -eq -1073741502 -and $Attempt -eq 1) { continue }
if ($ExitCode -eq -1073741502) {
throw "$Stem repeated 0xC0000142 after its single retry."
}
$LogText = Get-Content -LiteralPath $StdoutPath -Raw
$Walkability = [regex]::Match($LogText, '(?m)^\[Walkability\] PASS (\d+)/(\d+)')
if ($LogText -match '(?m)^\[Walkability\] FAIL ' -or -not $Walkability.Success) {
throw "$Stem failed the walkability regression gate; see $StdoutPath (exit $ExitCode)."
}
if ([int64]$Walkability.Groups[1].Value -ne [int64]$Walkability.Groups[2].Value -or
[int64]$Walkability.Groups[2].Value -le 0) {
throw "$Stem reported an incomplete or empty walkability sample set: $($Walkability.Value)."
}
if ($ExitCode -ne 0) {
throw "$Stem passed route samples but the shape audit exited $ExitCode; see $StdoutPath."
}
$Succeeded = $true
++$Passed
Write-Output "PASS probe=$Stem workers=$Workers samples=$($Walkability.Groups[1].Value)/$($Walkability.Groups[2].Value) peak_private_gb=$([math]::Round($PeakPrivateBytes / 1GB, 2))"
}
}
Write-Output "PASS saved_strate_probes=$Passed/$($Probes.Count) workers=$Workers"