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,402 @@
#include "VoxelForgeCoreAdapter.h"
#include "Dom/JsonObject.h"
#include "Policies/CondensedJsonPrintPolicy.h"
#include "Serialization/JsonWriter.h"
#include "UObject/UnrealType.h"
#include "UObject/TextProperty.h"
#include "VoxelSeasonAsset.h"
#include "VoxelSettings.h"
#include "VoxelStrateDefinition.h"
#include "VoxelStrateManager.h"
#include "VoxelTerrainOpDefinition.h"
namespace
{
using FCoreJsonWriter = TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>;
constexpr double CoreVoxelSizeCm = 25.0;
TSharedPtr<FJsonValue> SerializeCoreStruct(
const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects,
const UObject* Object = nullptr);
TSharedPtr<FJsonValue> SerializeCoreProperty(
FProperty* Property, const void* Data, TSet<const UObject*>& ActiveObjects)
{
if (Property == nullptr || Data == nullptr) return MakeShared<FJsonValueNull>();
if (const FBoolProperty* Bool = CastField<FBoolProperty>(Property))
{
return MakeShared<FJsonValueBoolean>(Bool->GetPropertyValue(Data));
}
if (const FEnumProperty* Enum = CastField<FEnumProperty>(Property))
{
return MakeShared<FJsonValueNumber>(static_cast<double>(
Enum->GetUnderlyingProperty()->GetSignedIntPropertyValue(Data)));
}
if (const FNumericProperty* Numeric = CastField<FNumericProperty>(Property))
{
const double Value = Numeric->IsFloatingPoint()
? Numeric->GetFloatingPointPropertyValue(Data)
: static_cast<double>(Numeric->GetSignedIntPropertyValue(Data));
return MakeShared<FJsonValueNumber>(Value);
}
if (const FStrProperty* String = CastField<FStrProperty>(Property))
{
return MakeShared<FJsonValueString>(String->GetPropertyValue(Data));
}
if (const FNameProperty* Name = CastField<FNameProperty>(Property))
{
return MakeShared<FJsonValueString>(Name->GetPropertyValue(Data).ToString());
}
if (const FTextProperty* Text = CastField<FTextProperty>(Property))
{
return MakeShared<FJsonValueString>(Text->GetPropertyValue(Data).ToString());
}
if (const FSoftObjectProperty* Soft = CastField<FSoftObjectProperty>(Property))
{
return MakeShared<FJsonValueString>(
Soft->GetPropertyValue(Data).ToSoftObjectPath().ToString());
}
if (const FArrayProperty* Array = CastField<FArrayProperty>(Property))
{
TArray<TSharedPtr<FJsonValue>> Values;
FScriptArrayHelper Helper(Array, Data);
Values.Reserve(Helper.Num());
for (int32 Index = 0; Index < Helper.Num(); ++Index)
{
Values.Add(SerializeCoreProperty(Array->Inner, Helper.GetRawPtr(Index), ActiveObjects));
}
return MakeShared<FJsonValueArray>(MoveTemp(Values));
}
if (const FSetProperty* Set = CastField<FSetProperty>(Property))
{
TArray<TPair<FString, TSharedPtr<FJsonValue>>> OrderedValues;
FScriptSetHelper Helper(Set, Data);
for (int32 Index = 0; Index < Helper.GetMaxIndex(); ++Index)
{
if (!Helper.IsValidIndex(Index)) continue;
FString StableKey;
Set->ElementProp->ExportTextItem_Direct(
StableKey, Helper.GetElementPtr(Index), nullptr, nullptr, PPF_None);
OrderedValues.Emplace(StableKey, SerializeCoreProperty(
Set->ElementProp, Helper.GetElementPtr(Index), ActiveObjects));
}
OrderedValues.Sort([](const auto& A, const auto& B) { return A.Key < B.Key; });
TArray<TSharedPtr<FJsonValue>> Values;
Values.Reserve(OrderedValues.Num());
for (const auto& Entry : OrderedValues) Values.Add(Entry.Value);
return MakeShared<FJsonValueArray>(MoveTemp(Values));
}
if (const FMapProperty* Map = CastField<FMapProperty>(Property))
{
TArray<TPair<FString, TSharedPtr<FJsonValue>>> OrderedValues;
FScriptMapHelper Helper(Map, Data);
for (int32 Index = 0; Index < Helper.GetMaxIndex(); ++Index)
{
if (!Helper.IsValidIndex(Index)) continue;
TSharedRef<FJsonObject> Pair = MakeShared<FJsonObject>();
Pair->SetField(TEXT("key"), SerializeCoreProperty(
Map->KeyProp, Helper.GetKeyPtr(Index), ActiveObjects));
Pair->SetField(TEXT("value"), SerializeCoreProperty(
Map->ValueProp, Helper.GetValuePtr(Index), ActiveObjects));
FString StableKey;
Map->KeyProp->ExportTextItem_Direct(
StableKey, Helper.GetKeyPtr(Index), nullptr, nullptr, PPF_None);
OrderedValues.Emplace(StableKey, MakeShared<FJsonValueObject>(Pair));
}
OrderedValues.Sort([](const auto& A, const auto& B) { return A.Key < B.Key; });
TArray<TSharedPtr<FJsonValue>> Values;
Values.Reserve(OrderedValues.Num());
for (const auto& Entry : OrderedValues) Values.Add(Entry.Value);
return MakeShared<FJsonValueArray>(MoveTemp(Values));
}
if (const FStructProperty* Struct = CastField<FStructProperty>(Property))
{
return SerializeCoreStruct(Struct->Struct, Data, ActiveObjects);
}
if (const FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Property))
{
const UObject* Object = ObjectProperty->GetObjectPropertyValue(Data);
if (Object == nullptr) return MakeShared<FJsonValueNull>();
if (Object->GetClass()->GetName().StartsWith(TEXT("Voxel"), ESearchCase::CaseSensitive))
{
return SerializeCoreStruct(Object->GetClass(), Object, ActiveObjects, Object);
}
return MakeShared<FJsonValueString>(Object->GetPathName());
}
return MakeShared<FJsonValueNull>();
}
TSharedPtr<FJsonValue> SerializeCoreStruct(
const UStruct* Type, const void* Data, TSet<const UObject*>& ActiveObjects,
const UObject* Object)
{
if (Type == nullptr || Data == nullptr) return MakeShared<FJsonValueNull>();
if (Object != nullptr && ActiveObjects.Contains(Object))
{
return MakeShared<FJsonValueString>(Object->GetPathName());
}
if (Object != nullptr) ActiveObjects.Add(Object);
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
for (TFieldIterator<FProperty> It(Type); It; ++It)
{
FProperty* Property = *It;
Result->SetField(Property->GetName(), SerializeCoreProperty(
Property, Property->ContainerPtrToValuePtr<void>(const_cast<void*>(Data)), ActiveObjects));
}
if (Object != nullptr) ActiveObjects.Remove(Object);
return MakeShared<FJsonValueObject>(Result);
}
TSharedRef<FJsonObject> CoreVectorJson(const FVector& Value)
{
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetNumberField(TEXT("x"), Value.X);
Result->SetNumberField(TEXT("y"), Value.Y);
Result->SetNumberField(TEXT("z"), Value.Z);
return Result;
}
TSharedRef<FJsonObject> CoreLandingJson(const FVoxelPassageLanding& Landing)
{
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetObjectField(TEXT("standing_point"), CoreVectorJson(Landing.StandingPoint));
Result->SetObjectField(TEXT("door_point"), CoreVectorJson(Landing.DoorPoint));
Result->SetObjectField(TEXT("door_direction"), CoreVectorJson(Landing.DoorDirection));
Result->SetNumberField(TEXT("floor_z"), Landing.FloorZ);
Result->SetNumberField(TEXT("ceiling_z"), Landing.CeilingZ);
Result->SetNumberField(TEXT("half_width"), Landing.HalfWidth);
Result->SetNumberField(TEXT("floor_thickness"), Landing.FloorThickness);
Result->SetBoolField(TEXT("source_player_fit"), Landing.bSourcePlayerFit);
return Result;
}
TSharedRef<FJsonObject> CorePassageJson(const FVoxelPassage& Passage)
{
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
Result->SetNumberField(TEXT("upper_strate_index"), Passage.UpperStrateIndex);
Result->SetNumberField(TEXT("lower_strate_index"), Passage.LowerStrateIndex);
Result->SetObjectField(TEXT("upper_point"), CoreVectorJson(Passage.UpperPoint));
Result->SetObjectField(TEXT("lower_point"), CoreVectorJson(Passage.LowerPoint));
Result->SetObjectField(TEXT("requested_upper_point"), CoreVectorJson(Passage.RequestedUpperPoint));
Result->SetObjectField(TEXT("requested_lower_point"), CoreVectorJson(Passage.RequestedLowerPoint));
Result->SetNumberField(TEXT("radius"), Passage.Radius);
Result->SetNumberField(TEXT("passage_type"), static_cast<int32>(Passage.PassageType));
auto WriteVectors = [](const TCHAR* Key, const TArray<FVector>& Source,
TSharedRef<FJsonObject> Out)
{
TArray<TSharedPtr<FJsonValue>> Values;
Values.Reserve(Source.Num());
for (const FVector& Value : Source)
{
Values.Add(MakeShared<FJsonValueObject>(CoreVectorJson(Value)));
}
Out->SetArrayField(Key, MoveTemp(Values));
};
auto WriteFloats = [](const TCHAR* Key, const TArray<float>& Source,
TSharedRef<FJsonObject> Out)
{
TArray<TSharedPtr<FJsonValue>> Values;
Values.Reserve(Source.Num());
for (float Value : Source) Values.Add(MakeShared<FJsonValueNumber>(Value));
Out->SetArrayField(Key, MoveTemp(Values));
};
WriteVectors(TEXT("control_points"), Passage.ControlPoints, Result);
WriteFloats(TEXT("control_radii"), Passage.ControlRadii, Result);
Result->SetObjectField(TEXT("upper_landing"), CoreLandingJson(Passage.UpperLanding));
Result->SetObjectField(TEXT("lower_landing"), CoreLandingJson(Passage.LowerLanding));
Result->SetBoolField(TEXT("walkable_tunnel_contract"), Passage.bWalkableTunnelContract);
Result->SetBoolField(TEXT("native_floor_enabled"), Passage.bNativeFloorEnabled);
WriteFloats(TEXT("native_floor_profile_z"), Passage.NativeFloorProfileZ, Result);
WriteFloats(TEXT("native_floor_relief_scales"), Passage.NativeFloorReliefScales, Result);
Result->SetNumberField(TEXT("native_floor_relief_strength"), Passage.NativeFloorReliefStrength);
Result->SetNumberField(TEXT("native_floor_relief_frequency"), Passage.NativeFloorReliefFrequency);
Result->SetNumberField(TEXT("native_floor_seed"), static_cast<int64>(Passage.NativeFloorSeed));
Result->SetNumberField(TEXT("tunnel_max_gradient_degrees"), Passage.TunnelMaxGradientDegrees);
Result->SetNumberField(TEXT("tunnel_vertical_drop_voxels"), Passage.TunnelVerticalDropVoxels);
Result->SetNumberField(TEXT("tunnel_required_horizontal_run_voxels"), Passage.TunnelRequiredHorizontalRunVoxels);
Result->SetNumberField(TEXT("tunnel_horizontal_path_length_voxels"), Passage.TunnelHorizontalPathLengthVoxels);
Result->SetNumberField(TEXT("tunnel_minimum_clear_width_voxels"), Passage.TunnelMinimumClearWidthVoxels);
Result->SetNumberField(TEXT("tunnel_clear_height_voxels"), Passage.TunnelClearHeightVoxels);
Result->SetObjectField(TEXT("bound_center"), CoreVectorJson(Passage.BoundCenter));
Result->SetNumberField(TEXT("bound_radius"), Passage.BoundRadius);
Result->SetNumberField(TEXT("bound_radius_sq"), Passage.BoundRadiusSq);
return Result;
}
void WriteCoreJsonValue(FCoreJsonWriter& Writer, const TCHAR* Identifier,
const TSharedPtr<FJsonValue>& Value)
{
if (Identifier != nullptr) Writer.WriteIdentifierPrefix(Identifier);
if (!Value.IsValid() || Value->Type == EJson::Null)
{
Writer.WriteNull();
}
else if (Value->Type == EJson::Boolean)
{
Writer.WriteValue(Value->AsBool());
}
else if (Value->Type == EJson::Number)
{
Writer.WriteValue(Value->AsNumber());
}
else if (Value->Type == EJson::String)
{
Writer.WriteValue(Value->AsString());
}
else if (Value->Type == EJson::Array)
{
Writer.WriteArrayStart();
for (const TSharedPtr<FJsonValue>& Element : Value->AsArray())
{
WriteCoreJsonValue(Writer, nullptr, Element);
}
Writer.WriteArrayEnd();
}
else if (Value->Type == EJson::Object)
{
Writer.WriteObjectStart();
TArray<FString> Keys;
Value->AsObject()->Values.GetKeys(Keys);
Keys.Sort();
for (const FString& Key : Keys)
{
WriteCoreJsonValue(Writer, *Key, Value->AsObject()->Values.FindRef(Key));
}
Writer.WriteObjectEnd();
}
else
{
Writer.WriteNull();
}
}
}
bool VoxelForgeCoreAdapter::SerializeResolvedWorld(
const UVoxelSettings* Settings,
const UVoxelStrateManager* StrateManager,
int32 ClipRadius,
int32 MaxClipLevel,
FString& OutJson,
FString& OutError)
{
OutJson.Reset();
OutError.Reset();
if (Settings == nullptr || StrateManager == nullptr || StrateManager->GetLayout().IsEmpty())
{
OutError = TEXT("Core snapshot export requires initialized settings and a resolved strate layout.");
return false;
}
const int32 Seed = StrateManager->GetWorldSeed();
const float OriginSpineRadius = Settings->GetEffectiveOriginSpineRadius();
const float WorldRadiusVoxels = Settings->GetEffectiveWorldRadiusVoxels();
const int32 CoreClipRadius = FMath::Clamp(ClipRadius, 1, 64);
const int32 CoreMaxClipLevel = FMath::Clamp(MaxClipLevel, 0, 8);
TSet<const UObject*> ActiveObjects;
TSharedPtr<FJsonValue> SettingsValue = SerializeCoreStruct(
Settings->GetClass(), Settings, ActiveObjects, Settings);
if (!SettingsValue.IsValid() || SettingsValue->Type != EJson::Object)
{
OutError = TEXT("Core snapshot export could not serialize the settings asset.");
return false;
}
// Override only the temporary schema copy. Clipmap tuning on the owner's asset is preserved.
SettingsValue->AsObject()->SetNumberField(TEXT("ClipRadius"), CoreClipRadius);
SettingsValue->AsObject()->SetNumberField(TEXT("MaxClipLevel"), CoreMaxClipLevel);
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
Root->SetNumberField(TEXT("schema_version"), 1);
Root->SetStringField(TEXT("source"), TEXT("VoxelForge Runtime Core snapshot"));
Root->SetNumberField(TEXT("voxel_size_cm"), CoreVoxelSizeCm);
Root->SetNumberField(TEXT("seed"), Seed);
Root->SetBoolField(TEXT("open_surface_entry"), Settings->bOpenSurfaceEntry);
Root->SetNumberField(TEXT("origin_spine_radius"), OriginSpineRadius);
Root->SetNumberField(TEXT("world_radius_voxels"), WorldRadiusVoxels);
Root->SetNumberField(TEXT("edge_seal_thickness"), Settings->EdgeSealThickness);
Root->SetNumberField(TEXT("total_strates"), StrateManager->GetNumStrates());
Root->SetNumberField(TEXT("inter_strate_gap_chunks"), Settings->InterStrateGapChunks);
Root->SetNumberField(TEXT("clip_radius"), CoreClipRadius);
Root->SetNumberField(TEXT("max_clip_level"), CoreMaxClipLevel);
Root->SetNumberField(TEXT("full_res_clip_levels"), Settings->FullResClipLevels);
Root->SetNumberField(TEXT("coarse_tile_cells"), Settings->CoarseTileCells);
Root->SetNumberField(TEXT("render_distance_chunks"), Settings->RenderDistanceChunks);
Root->SetBoolField(TEXT("generate_skirts"), Settings->bGenerateSkirts);
Root->SetNumberField(TEXT("skirt_cells"), Settings->SkirtCells);
Root->SetNumberField(TEXT("lod_octave_drop"), Settings->LODOctaveDrop);
Root->SetNumberField(TEXT("strate_content_cut_min_level"),
Settings->GetEffectiveStrateContentCutMinLevel());
Root->SetField(TEXT("settings_asset"), SettingsValue);
TArray<TSharedPtr<FJsonValue>> Strates;
Strates.Reserve(StrateManager->GetLayout().Num());
for (const FStrateSlot& Slot : StrateManager->GetLayout())
{
if (Slot.Definition == nullptr)
{
OutError = FString::Printf(TEXT("Resolved strate %d has no definition asset."), Slot.StrateIndex);
return false;
}
TSharedRef<FJsonObject> Strate = MakeShared<FJsonObject>();
Strate->SetNumberField(TEXT("index"), Slot.StrateIndex);
Strate->SetNumberField(TEXT("top_chunk_z"), Slot.TopChunkZ);
Strate->SetNumberField(TEXT("bottom_chunk_z"), Slot.BottomChunkZ);
Strate->SetNumberField(TEXT("height_chunks"), Slot.HeightInChunks);
Strate->SetField(TEXT("asset"), SerializeCoreStruct(
Slot.Definition->GetClass(), Slot.Definition, ActiveObjects, Slot.Definition));
Strate->SetField(TEXT("generation_params"), SerializeCoreStruct(
FStrateGenerationParams::StaticStruct(), &Slot.Definition->GenerationParams, ActiveObjects));
Strate->SetField(TEXT("disturbances"), SerializeCoreStruct(
FStrateDisturbanceParams::StaticStruct(), &Slot.Definition->Disturbances, ActiveObjects));
Strate->SetField(TEXT("passage_config"), SerializeCoreStruct(
FStratePassageConfig::StaticStruct(), &Slot.Definition->PassageConfig, ActiveObjects));
TArray<TSharedPtr<FJsonValue>> Operations;
Operations.Reserve(Slot.Definition->TerrainOperations.Num());
for (const FStrateTerrainOpEntry& Entry : Slot.Definition->TerrainOperations)
{
TSharedRef<FJsonObject> Operation = MakeShared<FJsonObject>();
Operation->SetNumberField(TEXT("weight"), Entry.Weight);
Operation->SetNumberField(TEXT("probability"), Entry.Probability);
Operation->SetStringField(TEXT("asset_path"), Entry.Operation.ToString());
if (const UVoxelTerrainOpDefinition* Definition = Entry.Operation.LoadSynchronous())
{
Operation->SetField(TEXT("asset"), SerializeCoreStruct(
Definition->GetClass(), Definition, ActiveObjects, Definition));
}
Operations.Add(MakeShared<FJsonValueObject>(Operation));
}
Strate->SetArrayField(TEXT("terrain_operation_assets"), MoveTemp(Operations));
Strates.Add(MakeShared<FJsonValueObject>(Strate));
}
Root->SetArrayField(TEXT("strates"), MoveTemp(Strates));
TArray<TSharedPtr<FJsonValue>> Passages;
Passages.Reserve(StrateManager->GetPassages().Num());
for (const FVoxelPassage& Passage : StrateManager->GetPassages())
{
Passages.Add(MakeShared<FJsonValueObject>(CorePassageJson(Passage)));
}
Root->SetArrayField(TEXT("passages"), MoveTemp(Passages));
Root->SetArrayField(TEXT("density_samples"), {});
TSharedRef<FJsonObject> ViewerStart = MakeShared<FJsonObject>();
ViewerStart->SetBoolField(TEXT("found_open_space"), false);
ViewerStart->SetNumberField(TEXT("x"), 0.0);
ViewerStart->SetNumberField(TEXT("y"), 0.0);
ViewerStart->SetNumberField(TEXT("z"), 0.0);
Root->SetObjectField(TEXT("viewer_start"), ViewerStart);
FString WriterBuffer;
TSharedRef<FCoreJsonWriter> Writer = TJsonWriterFactory<TCHAR,
TCondensedJsonPrintPolicy<TCHAR>>::Create(&WriterBuffer);
WriteCoreJsonValue(*Writer, nullptr, MakeShared<FJsonValueObject>(Root));
if (!Writer->Close() || WriterBuffer.IsEmpty())
{
OutError = TEXT("Core snapshot export failed while writing schema-v1 JSON.");
return false;
}
OutJson = MoveTemp(WriterBuffer);
return true;
}
@@ -0,0 +1,3 @@
#pragma once
#include "VoxelForgeCoreAssetAdapter.h"
@@ -0,0 +1,21 @@
#pragma once
#include "CoreMinimal.h"
class UVoxelSettings;
class UVoxelStrateManager;
namespace VoxelForgeCoreAdapter
{
/**
* Export the already-resolved Runtime assets through the shared schema-v1 boundary.
* The override values are written into the JSON copy only; authored UObject properties stay
* untouched. This runs on the game thread because it walks UObjects and soft references.
*/
VOXELFORGE_API bool SerializeResolvedWorld(const UVoxelSettings* Settings,
const UVoxelStrateManager* StrateManager,
int32 ClipRadius,
int32 MaxClipLevel,
FString& OutJson,
FString& OutError);
}