wip(assembler): round L21 -- resolved strate layout snapshot + validator (no world change)
Core ResolvedLayoutSnapshotV1 (canonical JSON, stable identity): per strate stable ID, generator, recipe, interior bounds in metres, ceiling/bedrock bands as Core really applies them, transition mode/width, dispatch provenance. Validator: compat mode reports today's deviations; strict mode enforces ceiling -> content -> bedrock -> next ceiling (owner rule), two separate bands, nominal contiguity, interpenetration only within the declared EVoxelStrateTransition width; 7 negative controls rejected. Findings: SW built at -120..-632 m although the params say -60 m; a 60 m unowned gap filled by analytic FI fallback; no active bedrock bands; transitions ignored by Core. All L19 hashes unchanged (28 + 14 crossing runs). The Unreal adapter exports resolved_layout (uncompiled here). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
5d5447dc32
commit
8753f6dbe4
12 files changed
+1347
-3
No files matched your search
@@ -563,6 +563,96 @@ namespace VoxelForgeCore
|
||||
std::string ToJson() const;
|
||||
};
|
||||
|
||||
// Read-only, versioned account of the physical layout resolved by the director and the
|
||||
// Core adapter. Coordinates are half-open micrometre intervals; recipe anchors retain their
|
||||
// source fixed-point units so this record never rounds the recipe itself.
|
||||
inline constexpr std::uint32_t ResolvedLayoutSchemaVersionV1 = 1;
|
||||
enum class ResolvedLayoutValidationModeV1 : std::uint8_t { compatibility, strict };
|
||||
|
||||
struct ResolvedLayoutAnchorV1
|
||||
{
|
||||
std::string kind;
|
||||
std::string stableId;
|
||||
std::string units;
|
||||
std::vector<std::int64_t> values;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutBandV1
|
||||
{
|
||||
std::string kind; // ceiling, content, bedrock
|
||||
std::int64_t bottomMicrometres = 0;
|
||||
std::int64_t topMicrometres = 0;
|
||||
bool presentInCurrentField = false;
|
||||
bool diggable = true;
|
||||
bool protectedBand = false;
|
||||
std::string behaviour;
|
||||
std::string densitySide;
|
||||
std::string capPath;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutStratumV1
|
||||
{
|
||||
std::string stableId;
|
||||
std::int32_t order = 0;
|
||||
std::int32_t strateIndex = -1;
|
||||
std::string generatorKind;
|
||||
std::string recipeId;
|
||||
std::uint32_t recipeVersion = 0;
|
||||
std::int64_t interiorBottomMicrometres = 0;
|
||||
std::int64_t interiorTopMicrometres = 0;
|
||||
std::uint32_t voxelSizeMicrometres = 0;
|
||||
std::array<std::int64_t, 3> latticeOriginMicrometres{};
|
||||
ResolvedLayoutBandV1 ceiling;
|
||||
ResolvedLayoutBandV1 content;
|
||||
ResolvedLayoutBandV1 bedrock;
|
||||
std::string transitionMode; // mode at this strate's lower interface
|
||||
std::int32_t transitionBlendChunks = 0;
|
||||
std::int64_t transitionWidthMicrometres = 0;
|
||||
std::int64_t transitionWarpAmplitudeMicrometres = 0;
|
||||
std::int64_t transitionInterpenetrationMicrometres = 0;
|
||||
bool transitionHonouredInCore = false;
|
||||
std::string transitionDispatchPath;
|
||||
std::string boundDispatchPath;
|
||||
std::string queryDispatchPath;
|
||||
std::string managerLookup;
|
||||
std::int64_t managerChunkWidthMicrometres = 0;
|
||||
std::int64_t dispatchChunkWidthMicrometres = 0;
|
||||
std::vector<ResolvedLayoutAnchorV1> anchors;
|
||||
};
|
||||
|
||||
struct ResolvedLayoutIssueV1
|
||||
{
|
||||
std::string code;
|
||||
std::string detail;
|
||||
};
|
||||
|
||||
struct VFCORE_API ResolvedLayoutSnapshotV1
|
||||
{
|
||||
std::uint32_t schemaVersion = ResolvedLayoutSchemaVersionV1;
|
||||
std::string worldIdentity;
|
||||
std::uint32_t ownerVoxelSizeMicrometres = 0;
|
||||
std::uint32_t coreVoxelSizeMicrometres = 0;
|
||||
std::vector<ResolvedLayoutStratumV1> strates;
|
||||
std::string stableIdentity;
|
||||
|
||||
bool finalize(std::string& error);
|
||||
bool validate(ResolvedLayoutValidationModeV1 mode,
|
||||
std::vector<ResolvedLayoutIssueV1>& issues,
|
||||
std::string& error) const;
|
||||
std::string ToJson() const;
|
||||
static std::optional<ResolvedLayoutSnapshotV1> FromJson(
|
||||
std::string_view json, std::string& error);
|
||||
};
|
||||
|
||||
struct ResolvedLayoutValidatorAuditV1
|
||||
{
|
||||
std::uint32_t controlsRun = 0;
|
||||
std::uint32_t controlsRejectedAsExpected = 0;
|
||||
bool passed = false;
|
||||
std::vector<ResolvedLayoutIssueV1> failures;
|
||||
};
|
||||
VFCORE_API ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1();
|
||||
|
||||
struct FloatingIslandBoundsAuditResult
|
||||
{
|
||||
std::uint64_t boxesTested = 0;
|
||||
@@ -1154,6 +1244,8 @@ namespace VoxelForgeCore
|
||||
ShapeBuildStats buildStats() const;
|
||||
|
||||
float densityAt(Vec3d voxelPosition) const;
|
||||
// Audit-only selector provenance for the same point evaluator; never used by meshing.
|
||||
std::string densityDispatchPathAt(Vec3d voxelPosition) const;
|
||||
// Analytic source distance without the occupancy-preserving near-zero clamp or packed proxy.
|
||||
float unclampedDensityAt(Vec3d voxelPosition) const;
|
||||
DetailFieldSample detailSampleAt(Vec3d voxelPosition) const;
|
||||
|
||||
@@ -0,0 +1,330 @@
|
||||
#pragma once
|
||||
|
||||
#include "Json.h"
|
||||
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
namespace VoxelForgeCore
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string LayoutEscape(std::string_view value)
|
||||
{
|
||||
std::string out="\"";
|
||||
for(unsigned char c:value)
|
||||
{
|
||||
switch(c)
|
||||
{
|
||||
case '"':out+="\\\"";break;
|
||||
case '\\':out+="\\\\";break;
|
||||
case '\b':out+="\\b";break;
|
||||
case '\f':out+="\\f";break;
|
||||
case '\n':out+="\\n";break;
|
||||
case '\r':out+="\\r";break;
|
||||
case '\t':out+="\\t";break;
|
||||
default:if(c<0x20){char b[7];std::snprintf(b,sizeof(b),"\\u%04x",c);out+=b;}
|
||||
else out+=static_cast<char>(c);
|
||||
}
|
||||
}
|
||||
out+='"';return out;
|
||||
}
|
||||
std::uint64_t LayoutHash(std::string_view value)
|
||||
{
|
||||
std::uint64_t hash=14695981039346656037ull;
|
||||
for(unsigned char c:value){hash^=c;hash*=1099511628211ull;}
|
||||
return hash;
|
||||
}
|
||||
std::string LayoutHex(std::uint64_t value)
|
||||
{std::ostringstream out;out<<std::hex<<std::setfill('0')<<std::setw(16)<<value;return out.str();}
|
||||
std::int64_t LayoutTransitionLimit(const ResolvedLayoutStratumV1& stratum)
|
||||
{
|
||||
if(stratum.transitionMode=="Hard")return 0;
|
||||
return stratum.transitionWidthMicrometres
|
||||
+(stratum.transitionMode=="Interleaved"?stratum.transitionWarpAmplitudeMicrometres:0);
|
||||
}
|
||||
std::string LayoutBandJson(const ResolvedLayoutBandV1& band)
|
||||
{
|
||||
std::ostringstream out;out<<"{\"kind\":"<<LayoutEscape(band.kind)
|
||||
<<",\"bottom_um\":"<<band.bottomMicrometres<<",\"top_um\":"<<band.topMicrometres
|
||||
<<",\"present\":"<<(band.presentInCurrentField?"true":"false")
|
||||
<<",\"diggable\":"<<(band.diggable?"true":"false")
|
||||
<<",\"protected\":"<<(band.protectedBand?"true":"false")
|
||||
<<",\"behaviour\":"<<LayoutEscape(band.behaviour)
|
||||
<<",\"density_side\":"<<LayoutEscape(band.densitySide)
|
||||
<<",\"cap_path\":"<<LayoutEscape(band.capPath)<<'}';return out.str();
|
||||
}
|
||||
ResolvedLayoutBandV1 ParseLayoutBand(const VFJson::Value& value)
|
||||
{
|
||||
ResolvedLayoutBandV1 b;b.kind=value.Get("kind").String();
|
||||
b.bottomMicrometres=value.Get("bottom_um").Integer();b.topMicrometres=value.Get("top_um").Integer();
|
||||
b.presentInCurrentField=value.Get("present").Boolean();b.diggable=value.Get("diggable").Boolean(true);
|
||||
b.protectedBand=value.Get("protected").Boolean();b.behaviour=value.Get("behaviour").String();
|
||||
b.densitySide=value.Get("density_side").String();b.capPath=value.Get("cap_path").String();return b;
|
||||
}
|
||||
std::string LayoutBodyJson(const ResolvedLayoutSnapshotV1& s)
|
||||
{
|
||||
std::ostringstream out;out<<"{\"schema_version\":"<<s.schemaVersion
|
||||
<<",\"world_identity\":"<<LayoutEscape(s.worldIdentity)
|
||||
<<",\"owner_voxel_size_um\":"<<s.ownerVoxelSizeMicrometres
|
||||
<<",\"core_voxel_size_um\":"<<s.coreVoxelSizeMicrometres<<",\"strates\":[";
|
||||
for(std::size_t i=0;i<s.strates.size();++i)
|
||||
{
|
||||
if(i)out<<',';const auto& x=s.strates[i];
|
||||
out<<"{\"stable_id\":"<<LayoutEscape(x.stableId)<<",\"order\":"<<x.order
|
||||
<<",\"strate_index\":"<<x.strateIndex<<",\"generator_kind\":"<<LayoutEscape(x.generatorKind)
|
||||
<<",\"recipe_id\":"<<LayoutEscape(x.recipeId)<<",\"recipe_version\":"<<x.recipeVersion
|
||||
<<",\"interior_bottom_um\":"<<x.interiorBottomMicrometres
|
||||
<<",\"interior_top_um\":"<<x.interiorTopMicrometres
|
||||
<<",\"voxel_size_um\":"<<x.voxelSizeMicrometres
|
||||
<<",\"lattice_origin_um\":["<<x.latticeOriginMicrometres[0]<<','
|
||||
<<x.latticeOriginMicrometres[1]<<','<<x.latticeOriginMicrometres[2]<<']'
|
||||
<<",\"ceiling\":"<<LayoutBandJson(x.ceiling)
|
||||
<<",\"content\":"<<LayoutBandJson(x.content)
|
||||
<<",\"bedrock\":"<<LayoutBandJson(x.bedrock)
|
||||
<<",\"transition_mode\":"<<LayoutEscape(x.transitionMode)
|
||||
<<",\"transition_blend_chunks\":"<<x.transitionBlendChunks
|
||||
<<",\"transition_width_um\":"<<x.transitionWidthMicrometres
|
||||
<<",\"transition_warp_amplitude_um\":"<<x.transitionWarpAmplitudeMicrometres
|
||||
<<",\"transition_interpenetration_um\":"<<x.transitionInterpenetrationMicrometres
|
||||
<<",\"transition_honoured_in_core\":"<<(x.transitionHonouredInCore?"true":"false")
|
||||
<<",\"transition_dispatch_path\":"<<LayoutEscape(x.transitionDispatchPath)
|
||||
<<",\"bound_dispatch_path\":"<<LayoutEscape(x.boundDispatchPath)
|
||||
<<",\"query_dispatch_path\":"<<LayoutEscape(x.queryDispatchPath)
|
||||
<<",\"manager_lookup\":"<<LayoutEscape(x.managerLookup)
|
||||
<<",\"manager_chunk_width_um\":"<<x.managerChunkWidthMicrometres
|
||||
<<",\"dispatch_chunk_width_um\":"<<x.dispatchChunkWidthMicrometres
|
||||
<<",\"anchors\":[";
|
||||
for(std::size_t a=0;a<x.anchors.size();++a)
|
||||
{
|
||||
if(a)out<<',';const auto& anchor=x.anchors[a];
|
||||
out<<"{\"kind\":"<<LayoutEscape(anchor.kind)<<",\"stable_id\":"
|
||||
<<LayoutEscape(anchor.stableId)<<",\"units\":"<<LayoutEscape(anchor.units)<<",\"values\":[";
|
||||
for(std::size_t v=0;v<anchor.values.size();++v){if(v)out<<',';out<<anchor.values[v];}
|
||||
out<<"]}";
|
||||
}
|
||||
out<<"]}";
|
||||
}
|
||||
out<<"]}";return out.str();
|
||||
}
|
||||
std::int64_t LayoutInt(const VFJson::Value& obj,std::string_view key)
|
||||
{return obj.Get(key).Integer();}
|
||||
std::string LayoutString(const VFJson::Value& obj,std::string_view key)
|
||||
{return obj.Get(key).String();}
|
||||
bool LayoutBool(const VFJson::Value& obj,std::string_view key,bool fallback=false)
|
||||
{return obj.Get(key).Boolean(fallback);}
|
||||
}
|
||||
|
||||
std::string ResolvedLayoutSnapshotV1::ToJson() const
|
||||
{
|
||||
const std::string body=LayoutBodyJson(*this);
|
||||
return body.substr(0,body.size()-1)+",\"stable_identity\":"
|
||||
+LayoutEscape(stableIdentity.empty()?"resolved-layout-v1-fnv64:"+LayoutHex(LayoutHash(body)):stableIdentity)+"}";
|
||||
}
|
||||
|
||||
bool ResolvedLayoutSnapshotV1::finalize(std::string& error)
|
||||
{
|
||||
error.clear();
|
||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV1||worldIdentity.empty()||strates.empty())
|
||||
{error="resolved layout has an unsupported version, empty world identity, or no strates";return false;}
|
||||
std::stable_sort(strates.begin(),strates.end(),[](const auto& a,const auto& b)
|
||||
{return a.interiorTopMicrometres!=b.interiorTopMicrometres
|
||||
?a.interiorTopMicrometres>b.interiorTopMicrometres:a.order<b.order;});
|
||||
for(std::size_t i=0;i<strates.size();++i)
|
||||
{
|
||||
auto& x=strates[i];x.order=static_cast<std::int32_t>(i);
|
||||
if(x.stableId.empty())x.stableId=worldIdentity+"/strate/"+std::to_string(x.strateIndex);
|
||||
if(x.content.kind.empty())x.content.kind="content";
|
||||
if(x.ceiling.kind.empty())x.ceiling.kind="ceiling";
|
||||
if(x.bedrock.kind.empty())x.bedrock.kind="bedrock";
|
||||
if(x.interiorTopMicrometres<=x.interiorBottomMicrometres||x.voxelSizeMicrometres==0)
|
||||
{error="resolved layout contains an invalid interior interval or voxel size";return false;}
|
||||
}
|
||||
stableIdentity.clear();const std::string body=LayoutBodyJson(*this);
|
||||
stableIdentity="resolved-layout-v1-fnv64:"+LayoutHex(LayoutHash(body));
|
||||
std::vector<ResolvedLayoutIssueV1> issues;
|
||||
return validate(ResolvedLayoutValidationModeV1::compatibility,issues,error);
|
||||
}
|
||||
|
||||
bool ResolvedLayoutSnapshotV1::validate(ResolvedLayoutValidationModeV1 mode,
|
||||
std::vector<ResolvedLayoutIssueV1>& issues,std::string& error) const
|
||||
{
|
||||
issues.clear();error.clear();
|
||||
if(schemaVersion!=ResolvedLayoutSchemaVersionV1||worldIdentity.empty()||strates.empty())
|
||||
{error="resolved layout schema/world/strata header is invalid";return false;}
|
||||
const auto add=[&](std::string code,std::string detail)
|
||||
{issues.push_back({std::move(code),std::move(detail)});};
|
||||
const bool strict=mode==ResolvedLayoutValidationModeV1::strict;
|
||||
for(std::size_t i=0;i<strates.size();++i)
|
||||
{
|
||||
const auto& s=strates[i];
|
||||
const std::int64_t effectiveTransitionWidth=LayoutTransitionLimit(s);
|
||||
if(s.interiorTopMicrometres<=s.interiorBottomMicrometres)
|
||||
add("invalid_interior",s.stableId+" has non-positive content height");
|
||||
if(s.transitionInterpenetrationMicrometres>effectiveTransitionWidth
|
||||
||s.transitionInterpenetrationMicrometres<0)
|
||||
add("transition_overlap_exceeds_width",s.stableId+" transition interpenetration exceeds its declared width");
|
||||
if(s.managerChunkWidthMicrometres>0&&s.dispatchChunkWidthMicrometres>0
|
||||
&&s.managerChunkWidthMicrometres!=s.dispatchChunkWidthMicrometres)
|
||||
add("ambiguous_dispatch_units",s.stableId+" manager chunk width differs from Core dispatch chunk width");
|
||||
if(i+1<strates.size()&&s.transitionMode!="Hard"&&!s.transitionHonouredInCore)
|
||||
add("transition_not_honoured",s.stableId+" declares a blend/interleave that Core does not apply on this interface");
|
||||
if(!s.ceiling.presentInCurrentField)add("missing_ceiling",s.stableId+" has no explicit ceiling band in today's field");
|
||||
if(!s.bedrock.presentInCurrentField)add("missing_bedrock",s.stableId+" has no explicit bedrock floor in today's field");
|
||||
if(strict)
|
||||
{
|
||||
if(s.order!=static_cast<std::int32_t>(i))add("wrong_order",s.stableId+" order is not top-to-bottom");
|
||||
if(!s.ceiling.presentInCurrentField||!s.bedrock.presentInCurrentField)
|
||||
add(!s.ceiling.presentInCurrentField?"missing_ceiling":"missing_bedrock",s.stableId+" lacks a required boundary band");
|
||||
if(!s.content.presentInCurrentField)
|
||||
add("missing_content",s.stableId+" has no content generator band");
|
||||
const auto bandShape=[&](const ResolvedLayoutBandV1& b,std::string_view expected)
|
||||
{
|
||||
if(b.kind!=expected||b.topMicrometres<=b.bottomMicrometres)
|
||||
add("wrong_order",s.stableId+" has an invalid "+std::string(expected)+" band");
|
||||
if(b.protectedBand&&b.diggable)
|
||||
add("invalid_protection",s.stableId+" band is both protected and diggable");
|
||||
};
|
||||
bandShape(s.ceiling,"ceiling");bandShape(s.content,"content");bandShape(s.bedrock,"bedrock");
|
||||
if(s.ceiling.bottomMicrometres!=s.content.topMicrometres
|
||||
||s.content.bottomMicrometres!=s.bedrock.topMicrometres)
|
||||
add("wrong_order",s.stableId+" bands do not follow ceiling -> content -> bedrock");
|
||||
if(i+1<strates.size()
|
||||
&&s.bedrock.bottomMicrometres!=strates[i+1].ceiling.topMicrometres)
|
||||
add(s.bedrock.bottomMicrometres>strates[i+1].ceiling.topMicrometres?"gap":"overlap",
|
||||
s.stableId+" bedrock and next strate ceiling are not nominally contiguous");
|
||||
if(s.transitionInterpenetrationMicrometres>effectiveTransitionWidth)
|
||||
add("transition_overlap_exceeds_width",s.stableId+" interpenetration is wider than the declared transition zone");
|
||||
}
|
||||
}
|
||||
if(!strict)
|
||||
{
|
||||
for(const auto& s:strates)
|
||||
{
|
||||
if(s.content.presentInCurrentField&&s.ceiling.presentInCurrentField)
|
||||
{
|
||||
if(s.ceiling.bottomMicrometres<s.content.topMicrometres)
|
||||
add("band_overlap",s.stableId+" ceiling overlaps content by "+std::to_string(
|
||||
s.content.topMicrometres-s.ceiling.bottomMicrometres)+" um");
|
||||
else if(s.ceiling.bottomMicrometres>s.content.topMicrometres)
|
||||
add("uncovered_interval",s.stableId+" has a "+std::to_string(
|
||||
s.ceiling.bottomMicrometres-s.content.topMicrometres)+" um gap below its ceiling");
|
||||
}
|
||||
if(s.content.presentInCurrentField&&s.bedrock.presentInCurrentField)
|
||||
{
|
||||
if(s.bedrock.topMicrometres>s.content.bottomMicrometres)
|
||||
add("band_overlap",s.stableId+" bedrock overlaps content by "+std::to_string(
|
||||
s.bedrock.topMicrometres-s.content.bottomMicrometres)+" um");
|
||||
else if(s.bedrock.topMicrometres<s.content.bottomMicrometres)
|
||||
add("uncovered_interval",s.stableId+" has a "+std::to_string(
|
||||
s.content.bottomMicrometres-s.bedrock.topMicrometres)+" um gap above its bedrock");
|
||||
}
|
||||
}
|
||||
for(std::size_t i=0;i+1<strates.size();++i)
|
||||
{
|
||||
const auto& a=strates[i];const auto& b=strates[i+1];
|
||||
const std::int64_t effectiveTransitionWidth=LayoutTransitionLimit(a);
|
||||
if(a.interiorBottomMicrometres>b.interiorTopMicrometres)
|
||||
add("uncovered_interval",a.stableId+" to "+b.stableId+" leaves "+std::to_string(
|
||||
a.interiorBottomMicrometres-b.interiorTopMicrometres)+" um uncovered");
|
||||
else if(a.interiorBottomMicrometres<b.interiorTopMicrometres)
|
||||
{
|
||||
const std::int64_t overlap=b.interiorTopMicrometres-a.interiorBottomMicrometres;
|
||||
if(overlap>effectiveTransitionWidth)
|
||||
add("overlap",a.stableId+" and "+b.stableId+" interiors overlap by "+std::to_string(overlap)+" um");
|
||||
else add("transition_interpenetration",a.stableId+" and "+b.stableId+" overlap inside the declared transition zone");
|
||||
}
|
||||
if(a.bedrock.presentInCurrentField&&b.ceiling.presentInCurrentField)
|
||||
{
|
||||
if(a.bedrock.bottomMicrometres>b.ceiling.topMicrometres)
|
||||
add("uncovered_interval",a.stableId+" bedrock and "+b.stableId+" ceiling leave "+std::to_string(
|
||||
a.bedrock.bottomMicrometres-b.ceiling.topMicrometres)+" um uncovered");
|
||||
else if(a.bedrock.bottomMicrometres<b.ceiling.topMicrometres)
|
||||
{
|
||||
const std::int64_t overlap=b.ceiling.topMicrometres-a.bedrock.bottomMicrometres;
|
||||
if(overlap>effectiveTransitionWidth)
|
||||
add("overlap",a.stableId+" bedrock and "+b.stableId+" ceiling overlap by "+std::to_string(overlap)+" um");
|
||||
else add("transition_interpenetration",a.stableId+" bedrock and "+b.stableId+" ceiling interpenetrate by "+std::to_string(overlap)+" um inside the declared transition zone");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(strict&&!issues.empty()){error="strict resolved-layout validation rejected "+issues.front().code;return false;}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<ResolvedLayoutSnapshotV1> ResolvedLayoutSnapshotV1::FromJson(
|
||||
std::string_view json,std::string& error)
|
||||
{
|
||||
VFJson::Value root;if(!VFJson::Parser(std::string(json)).Parse(root,error))return std::nullopt;
|
||||
if(!root.IsObject()){error="resolved layout JSON root must be an object";return std::nullopt;}
|
||||
ResolvedLayoutSnapshotV1 s;s.schemaVersion=static_cast<std::uint32_t>(LayoutInt(root,"schema_version"));
|
||||
s.worldIdentity=LayoutString(root,"world_identity");
|
||||
s.ownerVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"owner_voxel_size_um"));
|
||||
s.coreVoxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(root,"core_voxel_size_um"));
|
||||
for(const auto& v:root.Get("strates").AsArray())
|
||||
{
|
||||
ResolvedLayoutStratumV1 x;x.stableId=LayoutString(v,"stable_id");
|
||||
x.order=static_cast<std::int32_t>(LayoutInt(v,"order"));x.strateIndex=static_cast<std::int32_t>(LayoutInt(v,"strate_index"));
|
||||
x.generatorKind=LayoutString(v,"generator_kind");x.recipeId=LayoutString(v,"recipe_id");
|
||||
x.recipeVersion=static_cast<std::uint32_t>(LayoutInt(v,"recipe_version"));
|
||||
x.interiorBottomMicrometres=LayoutInt(v,"interior_bottom_um");x.interiorTopMicrometres=LayoutInt(v,"interior_top_um");
|
||||
x.voxelSizeMicrometres=static_cast<std::uint32_t>(LayoutInt(v,"voxel_size_um"));
|
||||
const auto& origin=v.Get("lattice_origin_um").AsArray();for(std::size_t i=0;i<3&&i<origin.size();++i)x.latticeOriginMicrometres[i]=origin[i].Integer();
|
||||
x.ceiling=ParseLayoutBand(v.Get("ceiling"));x.content=ParseLayoutBand(v.Get("content"));x.bedrock=ParseLayoutBand(v.Get("bedrock"));
|
||||
x.transitionMode=LayoutString(v,"transition_mode");x.transitionBlendChunks=static_cast<std::int32_t>(LayoutInt(v,"transition_blend_chunks"));
|
||||
x.transitionWidthMicrometres=LayoutInt(v,"transition_width_um");
|
||||
x.transitionWarpAmplitudeMicrometres=LayoutInt(v,"transition_warp_amplitude_um");
|
||||
x.transitionInterpenetrationMicrometres=LayoutInt(v,"transition_interpenetration_um");
|
||||
x.transitionHonouredInCore=LayoutBool(v,"transition_honoured_in_core");x.transitionDispatchPath=LayoutString(v,"transition_dispatch_path");
|
||||
x.boundDispatchPath=LayoutString(v,"bound_dispatch_path");x.queryDispatchPath=LayoutString(v,"query_dispatch_path");
|
||||
x.managerLookup=LayoutString(v,"manager_lookup");x.managerChunkWidthMicrometres=LayoutInt(v,"manager_chunk_width_um");
|
||||
x.dispatchChunkWidthMicrometres=LayoutInt(v,"dispatch_chunk_width_um");
|
||||
for(const auto& a:v.Get("anchors").AsArray())
|
||||
{ResolvedLayoutAnchorV1 p;p.kind=LayoutString(a,"kind");p.stableId=LayoutString(a,"stable_id");p.units=LayoutString(a,"units");
|
||||
for(const auto& value:a.Get("values").AsArray())p.values.push_back(value.Integer());x.anchors.push_back(std::move(p));}
|
||||
s.strates.push_back(std::move(x));
|
||||
}
|
||||
const std::string serializedIdentity=LayoutString(root,"stable_identity");
|
||||
if(!s.finalize(error))return std::nullopt;
|
||||
if(s.stableIdentity!=serializedIdentity){error="resolved layout stable identity does not match its canonical payload";return std::nullopt;}
|
||||
return s;
|
||||
}
|
||||
|
||||
ResolvedLayoutValidatorAuditV1 AuditResolvedLayoutValidatorV1()
|
||||
{
|
||||
ResolvedLayoutValidatorAuditV1 result;
|
||||
auto make=[](std::string id,std::int32_t order,std::int64_t top,std::int64_t bottom)
|
||||
{
|
||||
ResolvedLayoutStratumV1 s;s.stableId=std::move(id);s.order=order;s.strateIndex=order;s.generatorKind="synthetic";
|
||||
s.interiorTopMicrometres=top;s.interiorBottomMicrometres=bottom;s.voxelSizeMicrometres=250000;
|
||||
s.transitionMode="Gradient";s.transitionWidthMicrometres=4000000;
|
||||
s.transitionHonouredInCore=true;
|
||||
s.ceiling={"ceiling",top,top+1000000,true,true,false,"closed ceiling band","upper face","synthetic"};
|
||||
s.content={"content",bottom,top,true,true,false,"generator content","none","none"};
|
||||
s.bedrock={"bedrock",bottom-1000000,bottom,true,true,false,"breakable bedrock","lower face","none"};
|
||||
return s;
|
||||
};
|
||||
const auto expectReject=[&](std::string name,ResolvedLayoutSnapshotV1 candidate)
|
||||
{
|
||||
++result.controlsRun;std::vector<ResolvedLayoutIssueV1> issues;std::string error;
|
||||
if(!candidate.validate(ResolvedLayoutValidationModeV1::strict,issues,error))++result.controlsRejectedAsExpected;
|
||||
else result.failures.push_back({"negative_control_accepted",std::move(name)+" was accepted by strict validator"});
|
||||
};
|
||||
auto base=ResolvedLayoutSnapshotV1{};base.worldIdentity="synthetic";base.strates={make("a",0,10000000,0),make("b",1,-2000000,-12000000)};
|
||||
{std::vector<ResolvedLayoutIssueV1> issues;std::string error;
|
||||
if(!base.validate(ResolvedLayoutValidationModeV1::strict,issues,error))
|
||||
result.failures.push_back({"positive_control_rejected","valid ceiling/content/bedrock layout was rejected"});}
|
||||
auto boundaryGap=base;boundaryGap.strates[1].ceiling.topMicrometres-=1;
|
||||
expectReject("bedrock/next-ceiling gap",boundaryGap);
|
||||
auto missingBedrock=base;missingBedrock.strates[0].bedrock.presentInCurrentField=false;expectReject("missing bedrock",missingBedrock);
|
||||
auto missingCeiling=base;missingCeiling.strates[0].ceiling.presentInCurrentField=false;expectReject("missing ceiling",missingCeiling);
|
||||
auto wrongOrder=base;wrongOrder.strates[0].content.kind="bedrock";expectReject("wrong band order",wrongOrder);
|
||||
auto largeOverlap=base;largeOverlap.strates[0].transitionInterpenetrationMicrometres=4000001;expectReject("interpenetration exceeds transition width",largeOverlap);
|
||||
auto genericOverlap=base;genericOverlap.strates[0].bedrock.bottomMicrometres-=1;expectReject("nominal band overlap",genericOverlap);
|
||||
auto genericGap=base;genericGap.strates[0].bedrock.bottomMicrometres+=1;expectReject("nominal band gap",genericGap);
|
||||
result.passed=result.controlsRun==result.controlsRejectedAsExpected&&result.failures.empty();return result;
|
||||
}
|
||||
}
|
||||
@@ -1095,6 +1095,32 @@ public:
|
||||
return AnalyticDensityAt(X,Y,Z);
|
||||
}
|
||||
|
||||
std::string DensityDispatchPathAt(float X,float Y,float Z) const
|
||||
{
|
||||
if(SharedDensitySource)return SharedDensitySource->DensityDispatchPathAt(X,Y,Z);
|
||||
float Density=0.0f;
|
||||
if(NativeFloatingIslandDensityAt(X,Y,Z,Density))return "FloatingIslands-native";
|
||||
if(NativeSurfaceDensityAt(X,Y,Z,Density))return "SurfaceWorld-native";
|
||||
if(UsesLegacySurfaceField(X,Y,Z))return "SurfaceWorld-legacy";
|
||||
if(Owner&&Owner->Manager)
|
||||
{
|
||||
const int32 ChunkZ=FMath::FloorToInt(Z/static_cast<float>(CHUNK_SIZE));
|
||||
const ECaveGeneratorType Type=Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ));
|
||||
switch(Type)
|
||||
{
|
||||
case ECaveGeneratorType::TunnelNetwork:return "compiled-analytic/TunnelNetwork";
|
||||
case ECaveGeneratorType::FlatPlain:return "compiled-analytic/FlatPlain";
|
||||
case ECaveGeneratorType::CrystalChamber:return "compiled-analytic/CrystalChamber";
|
||||
case ECaveGeneratorType::Maze:return "compiled-analytic/Maze";
|
||||
case ECaveGeneratorType::SurfaceWorld:return "compiled-analytic/SurfaceWorld";
|
||||
case ECaveGeneratorType::VerticalShafts:return "compiled-analytic/VerticalShafts";
|
||||
case ECaveGeneratorType::FloatingIslands:return "compiled-analytic/FloatingIslands";
|
||||
case ECaveGeneratorType::Underwater:return "compiled-analytic/Underwater";
|
||||
}
|
||||
}
|
||||
return "compiled-analytic/fallback";
|
||||
}
|
||||
|
||||
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
|
||||
{
|
||||
if(SharedDensitySource)return SharedDensitySource->DensityAtLattice(X,Y,Z);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "StandaloneSnapshotAdapter.h"
|
||||
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||
#include "WorldAssemblerInternal.h"
|
||||
#include "ResolvedLayoutInternal.h"
|
||||
#include "FloatingIslandsInternal.h"
|
||||
|
||||
#include <shared_mutex>
|
||||
@@ -1457,6 +1458,10 @@ namespace VoxelForgeCore
|
||||
|
||||
float World::densityAt(Vec3d position) const
|
||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
|
||||
std::string World::densityDispatchPathAt(Vec3d position) const
|
||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape
|
||||
?impl_->shape->DensityDispatchPathAt(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z))
|
||||
:"uncompiled"; }
|
||||
float World::unclampedDensityAt(Vec3d position) const
|
||||
{ return impl_&&impl_->shapeReady.load(std::memory_order_acquire)&&impl_->shape?impl_->shape->DensityAtUnclamped(static_cast<float>(position.x),static_cast<float>(position.y),static_cast<float>(position.z)):0.0f; }
|
||||
DetailFieldSample World::detailSampleAt(Vec3d position) const
|
||||
|
||||
Reference in new issue
Block a user