chore(unreal): U2 -- owner's four-generator plan staged; owner_world.json re-exported

Owner re-saved strates 2026-10-26: DA_Strate1 SurfaceWorld, 2 FloatingIslands,
3 TunnelNetwork, 4 VerticalShafts (default params). BuildHost copies and the
enforced SHA-256 tables updated (old U1 set kept in the report); the export
commandlet handles the four-strate plan; Standalone/data/owner_world.json
regenerated (SHA-256 F42F96AD...). One normal BuildHost gate passed (64/64
tiles) but its top-strate window produced 0 triangles, and a missing
NewDataAsset reference warns -- both to check. No measurements this round.
Report: Standalone/Research/WorldAssemblerM3/Unreal2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-26 14:18:19 +01:00
1 parent fc9227f9e0
commit 9136f5f0e7
7 files changed
+314 -95

No files matched your search

+2 -1
View File
@@ -177,7 +177,8 @@ remains `0`, and lateral regions remain gated off.
| `ResolvedLayoutStratePassageCapabilitiesV1::{hasNativeCaveField,hasNativeLatticeField}`, `IsNativeLatticeFieldStratum`, `HasNativeGraphField` | `Standalone/Core/include/VoxelForgeCore/VoxelForgeCore.h` / `Standalone/Core/src/ResolvedLayoutInternal.h` | Explicit per-strate native graph capability; `Core.Lattice.PrimitiveField` is restricted to Maze/VerticalShafts recipe version 1. Lattice seeds include world seed, strate, recipe version and stable feature ID; explicit polyline records keep their endpoints and receive conservative bounds. §8.54. |
| `FCavernRuntimeFeature::FSegmentNode`, `ApplyResolvedNativeCavesField`, `ClassifyNativeCaveFieldBox` | `Standalone/Core/src/ShapeWorldInternal.h` | A deterministic nested segment BVH accelerates native graph distance and conservative interval evaluation; the outer graph BVH still supplies tile candidates and protects `isTileShapeFree`. The 0.4 m feature-union width is separate from the floor fillet width. §8.54. |
| `../../Standalone/Core/src/FloatingIslandsInternal.h` | Canonical standalone/Core floating-island planner, geometry-versioned field, manifest serializer, influence bounds, capsule-route checks, and thickness audit. |
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, serializes one Unreal launch at a time, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| `ApplyOwnerFourStratePlan` | `Source/VoxelForgeEditor/Private/VoxelForgeExploreCommandlet.cpp` | BuildHost-only transient fixed-slot plan from the four staged owner strate assets; leaves the authored settings and strate packages untouched. |
| `Tools/VoxelForgeTest.ps1` | The single guarded test entry point. `-Scenario canonical|owner|probe|perf|tests|surface-fall|crossing|gate-stress|visual-edit|visual-lod|visual-gap-walk|visual-gap-inject|dig-motion|parity|horizon|horizon-render|determinism|region-eval|standalone-export|compile-only|step2d|step2e|step2f|step2f-capture|step3-scale|step3-game`, optional `-Build`, `-Assets owner|default`, `-Cvars`, absolute `-Out`, `-Label`, `-HorizonLevel`, `-HorizonRenderDistanceChunks`, `-Step2fProfile=0..7`, `-Step2fCaptureView=inside|wall-closeup`, `-ParityExportSize=32|64|128`, `-ParityExportTile=x,y,z`, `-SharedDDC`, `-SampleDensityStacks`, `-StackSampleIntervalUs`, and `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH`; stages the host/assets, isolates user/DDC/config state, allows the owner's interactive editor while refusing overlapping `UnrealEditor-Cmd` launches, runs owned processes at Normal priority with a 12-logical-CPU cap, enforces the 8 GiB/30-minute guards, waits for 6 GiB free RAM before launches, and emits compact `result.json`/`summary.txt` plus full logs. Parity can render its game leg with `-EnableRender` and compares the selected export region against the exact streamed tile hashes. `visual-gap-walk` records camera-frustum surface-point coverage at 6000 cm/s; `visual-gap-inject` proves an early component hide is detected; `dig-motion` logs per-frame vertical velocity, gate, support readiness, and movement mode. `-Cvars @{ 'voxel.TestGapAuditLegacyHandoff'=1 }` replays the old visibility handoff for A/B audit baselines. Horizon traces report desired/loaded tiles, resident stream geometry, worker seconds, steady wall time, and peak private memory; `horizon-render` captures the owner view; `step2f-capture` selects one of eight parameter sets, finds a positive-density camera point and aims at a sampled rock wall for an inside or wall-closeup view, then saves it under `Saved/Research/step2f/captures` (pass the persistent render DDC with `-SharedDDC`); `determinism` compares per-tile hashes across the default CRT path and `-voxel.CrtFma3=0`; `region-eval` compares the live path with the default-off editor chunk program on the 259³ lattice and five-seed probe set; `step2d` retains the earlier independent field; `step2e` retains its earlier benchmark; `step2f` compares frozen step 2e and the coherent field for all eight profiles on the same 259³ ring, applies geometry gates, and checks sound bounds, seams, and 1-vs-6-worker hashes. `-RegionHashProbe -RegionWorkers N -RegionReferenceHash HASH` checks a full-ring hash at another partition count. `-SampleDensityStacks` captures profiles, and `-StackSampleIntervalUs` sets the sampling interval.
| Step-3 scale and game validation | `step3-scale` measures the same 519³ and 259³ six-worker lattices at 12.5 cm. The cached medians are 7.347496 s and 1.169567 s respectively; determinism and brick sound-proof audits pass. `step3-game` routes in-game generation through the brick evaluator and records tile timings, spawn fill, moving frames, and profile-switch rebuilds. The visible D3D12 run measured tile generation p50/p95/max 3.326/31.045/169.867 ms, full-detail L0 fill 12.369 s for 2,765 tiles, and moving-frame p50/p95/p99/max 4.856/8.922/13.090/80.575 ms. Switching profiles rebuilt 5,815 loaded tiles. Both high-resolution screenshot requests were logged, but the harness found only one fresh PNG and it is a blank pale viewport; no lit comparison capture is accepted. Raw output: `Saved/TestHarness/step3_game_visible_final`; scale output: `Saved/Research/step3/scale/step3_scale_cacheperf_v1`. |
| `Public/VoxelForgeModule.h` / `Private/VoxelForgeModule.cpp` | `FVoxelForgeModule` boilerplate (Startup/Shutdown just log). |
| `Source/VoxelForgeEditor/VoxelForgeEditor.Build.cs` / `Private/VoxelForgeExploreCommandlet.*` | Editor/commandlet-only `-run=VoxelForgeExplore`; schema-v1 owner-asset export check and BuildHost Core actor tests. `-coreworldassembly=PATH` supplies a reviewed manifest transiently to `-coreworldtest=1`; it does not save a data asset. `FStandaloneCoreAdapterScheduler` runs Core work at 1/8/16 workers; `VerifyStandaloneCoreAdapter` checks hashes and walkability. Existing bounded render/`VF_` walk/OBJ orchestration and research options remain editor/commandlet-only and are not linked by Game/Shipping. |
@@ -878,6 +878,8 @@ struct FExploreArguments
bool bPassageFloorWritesOverride = false;
int32 PassageFloorWrites = 1;
bool bStandaloneWorldExport = false;
bool bOwnerFourStratePlan = false;
bool bSkipStandaloneCoreAdapterParity = false;
bool bCoreWorldTest = false;
bool bCoreWorldStreamTraceOnly = false;
bool bRenderCameraOverride = false;
@@ -922,6 +924,12 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
int32 StandaloneWorldExport = 0;
FParse::Value(*Params, TEXT("standaloneworldexport="), StandaloneWorldExport);
OutArguments.bStandaloneWorldExport = StandaloneWorldExport != 0;
int32 OwnerFourStratePlan = 0;
FParse::Value(*Params, TEXT("ownerfourstrateplan="), OwnerFourStratePlan);
OutArguments.bOwnerFourStratePlan = OwnerFourStratePlan != 0;
int32 SkipStandaloneCoreAdapterParity = 0;
FParse::Value(*Params, TEXT("skipstandalonecoreadapterparity="), SkipStandaloneCoreAdapterParity);
OutArguments.bSkipStandaloneCoreAdapterParity = SkipStandaloneCoreAdapterParity != 0;
int32 CoreWorldTest = 0;
FParse::Value(*Params, TEXT("coreworldtest="), CoreWorldTest);
OutArguments.bCoreWorldTest = CoreWorldTest != 0;
@@ -1266,12 +1274,11 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
return false;
}
// The historical explorer contract needs two adjacent mouths around an interior slot. A
// parity export can instead target the game's authored single-strate layout, which has no
// such mouths and is valid for export-only mode. Build() still validates the slot against
// the loaded settings layout.
const int32 MinimumSlot = OutArguments.bGameRuntimeConfig ? 0 : 1;
const int32 MaximumSlot = OutArguments.bGameRuntimeConfig
// Passage walks need both mouths; a render/export-only generation can inspect an end slot.
// Parity exports also accept the game's authored single-strate layout.
const bool bRequiresInteriorSlot = OutArguments.bWalk;
const int32 MinimumSlot = OutArguments.bGameRuntimeConfig || !bRequiresInteriorSlot ? 0 : 1;
const int32 MaximumSlot = OutArguments.bGameRuntimeConfig || !bRequiresInteriorSlot
? MaxSyntheticStrates - 1 : MaxSyntheticStrates - 2;
if (OutArguments.Slot < MinimumSlot || OutArguments.Slot > MaximumSlot)
{
@@ -1284,8 +1291,8 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
else
{
OutError = FString::Printf(
TEXT("slot must be an interior slot in [1,%d] so arrival and departure mouths exist."),
MaxSyntheticStrates - 2);
TEXT("slot must be in [0,%d]; walk mode requires an interior slot in [1,%d] for both passage mouths."),
MaxSyntheticStrates - 1, MaxSyntheticStrates - 2);
}
return false;
}
@@ -1498,6 +1505,17 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
OutError = TEXT("standaloneworldexport requires modes=export and settingsref, and must omit strateref.");
return false;
}
if (OutArguments.bOwnerFourStratePlan
&& (OutArguments.SettingsReference.IsEmpty() || !OutArguments.StrateReference.IsEmpty()))
{
OutError = TEXT("ownerfourstrateplan requires settingsref and must omit strateref.");
return false;
}
if (OutArguments.bSkipStandaloneCoreAdapterParity && !OutArguments.bStandaloneWorldExport)
{
OutError = TEXT("skipstandalonecoreadapterparity requires standaloneworldexport.");
return false;
}
if (OutArguments.bCoreWorldTest
&& (OutArguments.SettingsReference.IsEmpty() || !OutArguments.StrateReference.IsEmpty()))
{
@@ -1702,6 +1720,66 @@ struct FExploreMeshBvhNode
}
};
bool ApplyOwnerFourStratePlan(UVoxelSettings* Settings,
TArray<TStrongObjectPtr<UVoxelStrateDefinition>>& OutDefinitions,
bool bDuplicateDefinitions, FString& OutError)
{
if (Settings == nullptr)
{
OutError = TEXT("The transient owner plan has no settings object.");
return false;
}
if (!Settings->Season.IsNull())
{
OutError = TEXT("The transient four-strate owner plan cannot replace an assigned season manifest.");
return false;
}
static const TCHAR* AssetPaths[] = {
TEXT("/Game/VoxelForge/DA_Strate1.DA_Strate1"),
TEXT("/Game/VoxelForge/DA_Strate2.DA_Strate2"),
TEXT("/Game/VoxelForge/DA_Strate3.DA_Strate3"),
TEXT("/Game/VoxelForge/DA_Strate4.DA_Strate4")
};
Settings->TotalStrates = UE_ARRAY_COUNT(AssetPaths);
Settings->StratePool.Reset();
Settings->FixedStrates.Reset();
OutDefinitions.Reset();
OutDefinitions.Reserve(UE_ARRAY_COUNT(AssetPaths));
for (int32 Index = 0; Index < UE_ARRAY_COUNT(AssetPaths); ++Index)
{
UVoxelStrateDefinition* AuthoredDefinition = LoadObject<UVoxelStrateDefinition>(
nullptr, AssetPaths[Index]);
if (AuthoredDefinition == nullptr)
{
OutError = FString::Printf(
TEXT("Could not load staged owner strate asset '%s'."), AssetPaths[Index]);
return false;
}
UVoxelStrateDefinition* Definition = AuthoredDefinition;
if (bDuplicateDefinitions)
{
Definition = DuplicateObject<UVoxelStrateDefinition>(
AuthoredDefinition, GetTransientPackage(),
FName(*FString::Printf(TEXT("VoxelForgeOwnerPlan_Strate_%d"), Index)));
if (Definition == nullptr)
{
OutError = FString::Printf(
TEXT("Could not make a transient copy of owner strate asset '%s'."), AssetPaths[Index]);
return false;
}
}
Settings->FixedStrates.Add(Index,
TSoftObjectPtr<UVoxelStrateDefinition>(Definition));
OutDefinitions.Add(TStrongObjectPtr<UVoxelStrateDefinition>(Definition));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeOwnerPlan] transient fixed slots=4 assets=DA_Strate1,DA_Strate2,DA_Strate3,DA_Strate4 settings_asset_untouched=1"));
return true;
}
struct FExploreWorld
{
TStrongObjectPtr<UVoxelSettings> Settings;
@@ -1834,7 +1912,7 @@ struct FExploreWorld
Settings->InterStrateGapChunks, Settings->SkirtCells);
}
const int32 NumStrates = bUseAuthoredSettings
const int32 NumStrates = Arguments.bOwnerFourStratePlan ? 4 : bUseAuthoredSettings
? (Arguments.bGameRuntimeConfig
? FMath::Max(Settings->TotalStrates, 1)
: FMath::Max(Settings->TotalStrates, Arguments.Slot + 2))
@@ -1864,64 +1942,73 @@ struct FExploreWorld
// loaded package remains untouched.
Settings->TotalStrates = NumStrates;
if (bUseAuthoredStrate)
if (Arguments.bOwnerFourStratePlan)
{
// The owner’s snapshot states that every slot uses DA_Strate3. Clone it per slot so
// commandlet overrides never mutate a loaded package or leak between slots.
Settings->StratePool.Reset();
Settings->FixedStrates.Reset();
}
Definitions.Reserve(NumStrates);
for (int32 Index = 0; Index < NumStrates; ++Index)
{
UVoxelStrateDefinition* Definition = bUseAuthoredStrate
? DuplicateObject<UVoxelStrateDefinition>(
AuthoredDefinition, GetTransientPackage())
: NewObject<UVoxelStrateDefinition>(
GetTransientPackage(), NAME_None, RF_Transient);
if (Definition == nullptr)
if (!ApplyOwnerFourStratePlan(Settings.Get(), Definitions, true, OutError))
{
OutError = FString::Printf(
TEXT("Could not create transient strate definition for slot %d."), Index);
return false;
}
if (!bUseAuthoredStrate)
}
else
{
if (bUseAuthoredStrate)
{
Definition->StrateName = FText::FromString(FString::Printf(
TEXT("VoxelForgeExplore_%s_slot%d"),
ArchetypeName(Index == Arguments.Slot
// Keep the historical single-asset interior probe isolated from loaded packages.
Settings->StratePool.Reset();
Settings->FixedStrates.Reset();
}
Definitions.Reserve(NumStrates);
for (int32 Index = 0; Index < NumStrates; ++Index)
{
UVoxelStrateDefinition* Definition = bUseAuthoredStrate
? DuplicateObject<UVoxelStrateDefinition>(
AuthoredDefinition, GetTransientPackage())
: NewObject<UVoxelStrateDefinition>(
GetTransientPackage(), NAME_None, RF_Transient);
if (Definition == nullptr)
{
OutError = FString::Printf(
TEXT("Could not create transient strate definition for slot %d."), Index);
return false;
}
if (!bUseAuthoredStrate)
{
Definition->StrateName = FText::FromString(FString::Printf(
TEXT("VoxelForgeExplore_%s_slot%d"),
ArchetypeName(Index == Arguments.Slot
? Arguments.Archetype
: ECaveGeneratorType::TunnelNetwork),
Index));
Definition->StrateDescription = FText::FromString(
TEXT("Transient, deterministic commandlet definition."));
// The commandlet's acceptance sweep uses the owner-facing base spacing:
// 8 chunks * 32 voxels * 0.125 m = 32 m per strate. Production assets may be taller;
// this keeps the measured tunnel arithmetic tied to the stated ~32 m descent.
Definition->StrateHeightInChunks = 8;
Definition->TransitionType = EVoxelStrateTransition::Hard;
Definition->GeneratorType = Index == Arguments.Slot
? Arguments.Archetype
: ECaveGeneratorType::TunnelNetwork),
Index));
Definition->StrateDescription = FText::FromString(
TEXT("Transient, deterministic commandlet definition."));
// The commandlet's acceptance sweep uses the owner-facing base spacing:
// 8 chunks * 32 voxels * 0.125 m = 32 m per strate. Production assets may be taller;
// this keeps the measured tunnel arithmetic tied to the stated ~32 m descent.
Definition->StrateHeightInChunks = 8;
Definition->TransitionType = EVoxelStrateTransition::Hard;
Definition->GeneratorType = Index == Arguments.Slot
? Arguments.Archetype
: ECaveGeneratorType::TunnelNetwork;
if (Arguments.bWormsEnabledOverride)
{
Definition->bEnableWorms = Arguments.bWormsEnabled;
: ECaveGeneratorType::TunnelNetwork;
if (Arguments.bWormsEnabledOverride)
{
Definition->bEnableWorms = Arguments.bWormsEnabled;
}
if (Arguments.bWormStrengthOverride
&& (Definition->GeneratorType == ECaveGeneratorType::TunnelNetwork
|| Definition->GeneratorType == ECaveGeneratorType::Underwater))
{
Definition->GenerationParams.WormStrength = Arguments.WormStrength;
}
}
if (Arguments.bWormStrengthOverride
&& (Definition->GeneratorType == ECaveGeneratorType::TunnelNetwork
|| Definition->GeneratorType == ECaveGeneratorType::Underwater))
{
Definition->GenerationParams.WormStrength = Arguments.WormStrength;
}
}
const TSoftObjectPtr<UVoxelStrateDefinition> SoftDefinition(Definition);
Settings->FixedStrates.Add(Index, SoftDefinition);
if (!bUseAuthoredStrate)
{
Settings->StratePool.Add(SoftDefinition);
const TSoftObjectPtr<UVoxelStrateDefinition> SoftDefinition(Definition);
Settings->FixedStrates.Add(Index, SoftDefinition);
if (!bUseAuthoredStrate)
{
Settings->StratePool.Add(SoftDefinition);
}
Definitions.Add(TStrongObjectPtr<UVoxelStrateDefinition>(Definition));
}
Definitions.Add(TStrongObjectPtr<UVoxelStrateDefinition>(Definition));
}
auto ApplyTargetOverrides = [&Arguments](UVoxelStrateDefinition* Definition) -> bool
@@ -13515,6 +13602,24 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
*Arguments.SettingsReference);
return false;
}
TStrongObjectPtr<UVoxelSettings> OwnerPlanSettings;
TArray<TStrongObjectPtr<UVoxelStrateDefinition>> OwnerPlanDefinitions;
UVoxelSettings* ExportSettings = AuthoredSettings;
if (Arguments.bOwnerFourStratePlan)
{
OwnerPlanSettings = TStrongObjectPtr<UVoxelSettings>(
DuplicateObject<UVoxelSettings>(AuthoredSettings, GetTransientPackage()));
if (!OwnerPlanSettings.IsValid())
{
OutError = TEXT("Could not make a transient copy of owner settings for the four-strate plan.");
return false;
}
ExportSettings = OwnerPlanSettings.Get();
if (!ApplyOwnerFourStratePlan(ExportSettings, OwnerPlanDefinitions, false, OutError))
{
return false;
}
}
UVoxelStrateManager* Manager = NewObject<UVoxelStrateManager>(
GetTransientPackage(), NAME_None, RF_Transient);
int32 Seed = AuthoredSettings->Seed;
@@ -13526,7 +13631,7 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
OriginSpineRadius = Season->GetOriginSpineRadius();
WorldRadiusVoxels = Season->GetWorldRadiusVoxels();
}
if (!Manager->Initialize(AuthoredSettings, Seed))
if (!Manager->Initialize(ExportSettings, Seed))
{
OutError = TEXT("Owner settings did not produce a valid resolved strate layout.");
return false;
@@ -13534,7 +13639,7 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
Seed = Manager->GetWorldSeed();
UVoxelGenerator* Generator = NewObject<UVoxelGenerator>(
GetTransientPackage(), NAME_None, RF_Transient);
Generator->InitializeSettings(AuthoredSettings);
Generator->InitializeSettings(ExportSettings);
Generator->SetStrateManager(Manager);
TArray<FStandaloneExportSample> Samples;
@@ -13668,25 +13773,25 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
Writer->WriteValue(TEXT("source"), TEXT("VoxelForgeExplore standalone base-world export"));
Writer->WriteValue(TEXT("voxel_size_cm"), StandaloneCoreVoxelSizeCm);
Writer->WriteValue(TEXT("seed"), Seed);
Writer->WriteValue(TEXT("open_surface_entry"), AuthoredSettings->bOpenSurfaceEntry);
Writer->WriteValue(TEXT("open_surface_entry"), ExportSettings->bOpenSurfaceEntry);
Writer->WriteValue(TEXT("origin_spine_radius"), OriginSpineRadius);
Writer->WriteValue(TEXT("world_radius_voxels"), WorldRadiusVoxels);
Writer->WriteValue(TEXT("edge_seal_thickness"), AuthoredSettings->EdgeSealThickness);
Writer->WriteValue(TEXT("edge_seal_thickness"), ExportSettings->EdgeSealThickness);
Writer->WriteValue(TEXT("total_strates"), Manager->GetNumStrates());
Writer->WriteValue(TEXT("inter_strate_gap_chunks"), AuthoredSettings->InterStrateGapChunks);
Writer->WriteValue(TEXT("clip_radius"), AuthoredSettings->ClipRadius);
Writer->WriteValue(TEXT("max_clip_level"), AuthoredSettings->MaxClipLevel);
Writer->WriteValue(TEXT("full_res_clip_levels"), AuthoredSettings->FullResClipLevels);
Writer->WriteValue(TEXT("coarse_tile_cells"), AuthoredSettings->CoarseTileCells);
Writer->WriteValue(TEXT("render_distance_chunks"), AuthoredSettings->RenderDistanceChunks);
Writer->WriteValue(TEXT("generate_skirts"), AuthoredSettings->bGenerateSkirts);
Writer->WriteValue(TEXT("skirt_cells"), AuthoredSettings->SkirtCells);
Writer->WriteValue(TEXT("lod_octave_drop"), AuthoredSettings->LODOctaveDrop);
Writer->WriteValue(TEXT("strate_content_cut_min_level"), AuthoredSettings->GetEffectiveStrateContentCutMinLevel());
Writer->WriteValue(TEXT("inter_strate_gap_chunks"), ExportSettings->InterStrateGapChunks);
Writer->WriteValue(TEXT("clip_radius"), ExportSettings->ClipRadius);
Writer->WriteValue(TEXT("max_clip_level"), ExportSettings->MaxClipLevel);
Writer->WriteValue(TEXT("full_res_clip_levels"), ExportSettings->FullResClipLevels);
Writer->WriteValue(TEXT("coarse_tile_cells"), ExportSettings->CoarseTileCells);
Writer->WriteValue(TEXT("render_distance_chunks"), ExportSettings->RenderDistanceChunks);
Writer->WriteValue(TEXT("generate_skirts"), ExportSettings->bGenerateSkirts);
Writer->WriteValue(TEXT("skirt_cells"), ExportSettings->SkirtCells);
Writer->WriteValue(TEXT("lod_octave_drop"), ExportSettings->LODOctaveDrop);
Writer->WriteValue(TEXT("strate_content_cut_min_level"), ExportSettings->GetEffectiveStrateContentCutMinLevel());
TSet<const UObject*> ActiveObjects;
StandaloneWriteJsonValue(*Writer, TEXT("settings_asset"),
StandaloneSerializeStruct(AuthoredSettings->GetClass(), AuthoredSettings,
ActiveObjects, AuthoredSettings));
StandaloneSerializeStruct(ExportSettings->GetClass(), ExportSettings,
ActiveObjects, ExportSettings));
Writer->WriteArrayStart(TEXT("strates"));
for (const FStrateSlot& Slot : Manager->GetLayout())
@@ -13764,11 +13869,16 @@ bool RunStandaloneWorldExport(const FExploreArguments& Arguments, FString& OutEr
return false;
}
if (OutJson) *OutJson = Json;
if (!VerifyStandaloneCoreAdapter(Json, OutError))
if (!Arguments.bSkipStandaloneCoreAdapterParity && !VerifyStandaloneCoreAdapter(Json, OutError))
{
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreAdapter] %s"), *OutError);
return false;
}
if (Arguments.bSkipStandaloneCoreAdapterParity)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreAdapter] skipped legacy W1/W8/W16 fixture parity for this export-only update."));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStandaloneExport] seed=%d voxel_size_cm=%.3f strates=%d passages=%d samples=%d camera_found=%d file=%s"),
Seed, StandaloneCoreVoxelSizeCm, Manager->GetNumStrates(), Manager->GetPassages().Num(),
@@ -0,0 +1,44 @@
# World Assembler M3 — Unreal update
**Reduced U2 scope complete.** The four owner strates and unchanged settings asset were copied to BuildHost; the staged/source hashes match. The owner editor and Live Coding Console were absent before the build and after the runs. BuildHost compiled, the four-strate export completed, and the normal `owner` gate completed successfully. No `.uasset` was edited and the owner project was not built; UBT used `Saved/BuildHost/HostProject/HostProject.uproject`.
## Asset hashes
Old U1 hashes are retained for reproducibility. New staged BuildHost hashes match the read-only source assets.
| Asset | Old U1 SHA-256 | Current SHA-256 |
|---|---|---|
| `DA_Strate1` | `76063D691D5CE26D2014B587E1972A6724787BE1204E4652CA863BB8D5001BD0` | `A9CA6CDDD102250A893E379350B1D0F4AAF04C4F7FCA70E0DB46052A067D2CFB` |
| `DA_Strate2` | `02F56A18CED991BAF93DFB2F69438152275E8353B5E279C1CB7DD6E079E352E2` | `8F9A12A3181635149E6F79F192E0DDAF6B6577442EE324071A7749DCC78B9A0C` |
| `DA_Strate3` | `F47437F8D156C608AA06E40C5B76716ED4A19FA0F057688E7FECC04DC976428C` | `5470D538C3C5E88237463E71BCA9E2EB170DFE3A0543C0F33298D9FB010FD2F9` |
| `DA_Strate4` | `7C0DE7D06876035AC7BC4F117B9166AFD77402021843BA4900B470F4B0D0185B` | `A98E33E1CD42F374D6CB70F5071E8EBBA74D91ED59D2FD3201782DAB84D28F5C` |
| `DA_Settings` | `8ABE8243104980260FECA0A6F4CF4927768E8E568221A6F6A2C2694F6D15D454` | `8ABE8243104980260FECA0A6F4CF4927768E8E568221A6F6A2C2694F6D15D454` |
Enforced hash tables are updated in `Tools/BuildVoxelForgeCoreHost.ps1` and `Tools/TestVoxelForgeCoreWorldHost.ps1`.
## Manifest
Regenerated by the existing `standalone-export` commandlet path from the BuildHost owner assets and copied to `Standalone/data/owner_world.json` for the Linux machine. It contains four fixed strates in order: Surface World (`0..-63`), Floating Islands (`-64..-93`), Tunnel Network (`-94..-109`), and Vertical Shafts (`-110..-125`). Export reported 4 strates, 4 passages, 6,917 samples, and a camera sample.
- Size: 685,722 bytes
- SHA-256: `F42F96ADCA58A4EBD680B8553B2969D6CD9CA8A73F875F042BFBC7B8C649FDCA`
- Export result: commandlet exit 0; wrapper pass; peak private memory 3.63 GiB
- This replaces the failed one-strate Vertical Shafts manifest (`A328E04556BE6CCCDDF736C389C7122F76F01192EB99B5FC3CB1E444D0888964`, 162,800 bytes).
## BuildHost build and gate
`U2Owner4BuildGateClean` passed: BuildHost UBT exit 0, commandlet exit 0, completion observed, 64/64 mesh tiles processed, no commandlet failures, and no new crash. Build peak private memory was 1.74 GiB; commandlet peak was 2.04 GiB. The log confirms the transient four-slot plan loaded and slot 0 is `Surface World`.
The gate's sampled mesh window was empty: 0 vertices and 0 triangles across those 64 tiles (`geometry_hash=A3C1CA20`). The gate therefore proves the staged plan loaded and the normal commandlet completed; it does not establish visible surface geometry. The log also has a non-fatal missing-package warning for `/Game/VoxelForge/Blocks/NewDataAsset.NewDataAsset`; the commandlet reports 0 errors and 3 warnings.
The only staging fix was in `Tools/VoxelForgeTest.ps1`: directory contents now copy recursively into existing destinations without nesting the source directory, and linked entries are refused. No runtime or async-streaming code changed.
## Failed and retried runs
- `U2SurfaceBuild`: build passed; earlier `surface-fall` checks failed with `layout_is_not_one_surface_strate` and `pawn_start_not_observed` at `(0,0,0)`.
- `U2TopSurface` and `U2TopSurfaceExport`: the previous commandlet rejected slot 0 as non-interior.
- `U2ManifestExport`: one-strate export returned result 1 because its embedded W1/W8/W16 parity expected stale 2,226 tiles and 5,642/5,642 walk samples; the run had 1,817 tiles and 25/25 samples. That invalid one-strate manifest was replaced above.
- `U2Owner4BuildGate` and `U2Owner4BuildGateRetry`: UBT refused to build while the editor held the Live Coding mutex. `-NoLiveCoding` did not clear it. The owner later closed the editor; I did not interact with it.
- `U2Owner4BuildGateFinal`: UBT failed after 431.68 seconds on duplicate staged `CpuCycleCounter.h` headers. `U2Owner4BuildGateFinalRetry` reproduced it because the staging helper re-created the nested directory. After correcting staging and clearing only that stale BuildHost directory, `U2Owner4BuildGateClean` passed.
No `0xC0000142` occurred. No new `UECC-*` folder appeared; the sole BuildHost crash folder predates this work (2026-09-17). Core-off legacy, M2 fixtures, per-strate measures, determinism matrices, and captures were skipped under the reduced scope.
+1 -1
View File
@@ -1 +1 @@
{"schema_version":1,"source":"VoxelForgeExplore standalone base-world export","voxel_size_cm":25,"seed":0,"open_surface_entry":true,"origin_spine_radius":28,"world_radius_voxels":0,"edge_seal_thickness":128,"total_strates":2,"inter_strate_gap_chunks":0,"clip_radius":6,"max_clip_level":8,"full_res_clip_levels":2,"coarse_tile_cells":16,"render_distance_chunks":1536,"generate_skirts":true,"skirt_cells":4,"lod_octave_drop":1,"strate_content_cut_min_level":9,"settings_asset":{"ViewDistanceXY":32,"ViewDistanceUp":10,"ViewDistanceDown":10,"MaxConcurrentTasks":16,"MaxGenerationWorkerCores":0,"NearTaskReserve":4,"MaxMeshAppliesPerFrame":4,"MaxMeshApplyMilliseconds":2,"MaxUnloadsPerFrame":16,"PawnCollisionRadiusChunks":2,"bClampViewToStrate":true,"StrateViewMarginChunks":6,"StrateContentCutMinLevel":9,"ClipRadius":6,"MaxClipLevel":8,"FullResClipLevels":2,"CoarseTileCells":16,"RenderDistanceChunks":1536,"bGenerateSkirts":true,"SkirtCells":4,"LODOctaveDrop":1,"WorldRadiusVoxels":0,"EdgeSealThickness":128,"DecorationRadiusChunks":12,"DecorationNearRadiusChunks":6,"DecorationRegionSizeCells":8,"DecorationSpacingVoxels":8,"DecorationFarSpacingVoxels":8,"DecorationMarchStepVoxels":4,"DecorationMaxCrossingsPerColumn":4,"DecorationColumnDepthVoxels":320,"MaxDecorationCellsPerFrame":2,"MaxConcurrentDecorationTasks":4,"bEnableDensityVolume":false,"DensityVolumeResolution":256,"DensityVolumeLevels":3,"DensityVolumeFillSlabCells":8,"DensityVolumeMarchSteps":64,"bDensityVolumeGPUUpload":false,"bDebugDrawDensityVolume":false,"DensityVolumeDebugRadiusCells":12,"VoxelMaterial":"/Game/VoxelForge/M_Test1_Inst.M_Test1_Inst","Seed":0,"Season":"","CurrentSeason":1,"StratePool":["/Game/VoxelForge/DA_Strate1.DA_Strate1","/Game/VoxelForge/DA_Strate3.DA_Strate3"],"FixedStrates":[],"TotalStrates":2,"InterStrateGapChunks":0,"OriginSpineRadius":28,"bOpenSurfaceEntry":true,"MaxModifications":0,"MaxBrushRadius":30,"MaxTotalVolume":0,"AssetBundleData":{"Bundles":[]},"NativeClass":"/Script/VoxelForge.VoxelSettings"},"strates":[{"index":0,"top_chunk_z":0,"bottom_chunk_z":-15,"height_chunks":16,"asset":{"StrateName":"","StrateDescription":"","StrateHeightInChunks":16,"TransitionType":0,"TransitionBlendChunks":2,"GeneratorType":0,"bEnableWorms":false,"GenerationParams":{"BaseDensity":8,"VerticalScale":0.800000011920929,"WormFrequency":0.014999999664723873,"WormHorizontalBias":3,"WormThreshold":0.05999999865889549,"WormStrength":10,"WormNetworkRange":64,"RoomSpacing":128,"RoomDensity":0.3499999940395355,"MinRoomRadius":65,"MaxRoomRadius":120,"RoomHeightRatio":0.5,"RoomFloorCutMin":0,"RoomFloorCutMax":0,"FloorReliefStrength":0,"FloorReliefFrequency":1,"RoomMouthRiseStrength":0,"RoomMouthRiseBlendVoxels":12,"RoomShapeVariety":0.5,"OriginRoomRadius":65,"OriginRoomMaxConnections":4,"TunnelMinRadius":8,"TunnelMaxRadius":12,"TunnelDensity":0.4000000059604645,"MaxTunnelLength":360,"TunnelWarpStrength":24,"TunnelHorizontalBias":0.5,"bTunnelsFlowTowardOrigin":true,"TunnelEndpointZOffset":0.25,"bTunnelFloorEnabled":true,"bTunnelFloorTerracingEnabled":true,"TunnelFloorTerraceStepHeight":5,"TunnelFloorMaxLedgeHeight":24,"TunnelFloorGentleSlopeThreshold":88.09085083007812,"TunnelFloorLedgeCountPreference":0,"TunnelFloorMaxLedges":4096,"SDFBlendRadius":25,"CaveWarpStrength":16,"CaveWarpFrequency":0.014999999664723873,"SurfaceRoughness":1,"RoughnessFrequency":0.05000000074505806,"RoughnessNoiseType":2,"DomainWarpStrength":0,"DomainWarpFrequency":0.029999999329447746,"FloorBias":4,"BoundarySealThickness":8,"WaterLevelRelative":0},"SlabParams":{"FloorRelativeHeight":0.25,"CeilingRelativeHeight":0.6000000238418579,"FloorRoughness":4,"FloorRoughnessFrequency":0.03999999910593033,"CeilingRoughness":6,"CeilingRoughnessFrequency":0.03999999910593033,"ColumnDensity":0.07999999821186066,"ColumnMinRadius":8,"ColumnMaxRadius":16,"ColumnSpacing":96,"BoundarySealThickness":4,"BaseDensity":8},"MazeParams":{"CellSize":160,"CorridorRadius":24,"BranchProbability":1,"Verticality":0.20000000298023224,"SurfaceRoughness":2,"BoundarySealThickness":4,"BaseDensity":8},"SurfaceParams":{"BaseGroundRelative":0.25,"ElevationRange":80,"ContinentFrequency":0.006000000052154064,"MountainStrength":0.5,"MountainFrequency":0.012000000104308128,"DetailFrequency":0.03999999910593033,"SurfaceRoughness":2,"HeightWarpStrength":48,"HeightWarpFrequency":0.00800000037997961,"ReliefFrequency":0.001500000013038516,"ReliefStrength":0.699999988079071,"ReliefContrast":1.600000023841858,"TerraceStrength":0,"TerraceHeight":8,"TerraceHardness":0.5,"LayerLineDepth":0,"LayerLineSpacing":8,"CliffStrength":0,"CliffSlopeThreshold":0.5,"CliffSharpness":2,"CliffSampleDist":2,"OverhangStrength":0,"OverhangReach":16,"OverhangHeight":24,"OverhangFrequency":0.019999999552965164,"OverhangZScale":0.5,"OverhangSlopeThreshold":0.30000001192092896,"WaterLevelRelative":0.27000001072883606,"BeachWidth":12,"CeilingRelative":0.949999988079071,"CeilingRoughness":6,"CeilingRoughnessFrequency":0.03999999910593033,"CeilingUndulation":0,"CLine truncated
{"schema_version":1,"source":"VoxelForgeExplore standalone base-world export","voxel_size_cm":25,"seed":0,"open_surface_entry":true,"origin_spine_radius":28,"world_radius_voxels":0,"edge_seal_thickness":128,"total_strates":4,"inter_strate_gap_chunks":0,"clip_radius":6,"max_clip_level":6,"full_res_clip_levels":2,"coarse_tile_cells":16,"render_distance_chunks":1536,"generate_skirts":true,"skirt_cells":4,"lod_octave_drop":1,"strate_content_cut_min_level":9,"settings_asset":{"AssetBundleData":{"Bundles":[]},"bClampViewToStrate":true,"bDebugDrawDensityVolume":false,"bDensityVolumeGPUUpload":false,"bEnableDensityVolume":false,"bGenerateSkirts":true,"bOpenSurfaceEntry":true,"bUseWorldAssemblyManifestOverride":false,"ClipRadius":6,"CoarseTileCells":16,"CurrentSeason":1,"DecorationColumnDepthVoxels":320,"DecorationFarSpacingVoxels":8,"DecorationMarchStepVoxels":4,"DecorationMaxCrossingsPerColumn":4,"DecorationNearRadiusChunks":6,"DecorationRadiusChunks":12,"DecorationRegionSizeCells":8,"DecorationSpacingVoxels":8,"DensityVolumeDebugRadiusCells":12,"DensityVolumeFillSlabCells":8,"DensityVolumeLevels":3,"DensityVolumeMarchSteps":64,"DensityVolumeResolution":256,"EdgeSealThickness":128,"FixedStrates":[{"key":0,"value":"/Game/VoxelForge/DA_Strate1.DA_Strate1"},{"key":1,"value":"/Game/VoxelForge/DA_Strate2.DA_Strate2"},{"key":2,"value":"/Game/VoxelForge/DA_Strate3.DA_Strate3"},{"key":3,"value":"/Game/VoxelForge/DA_Strate4.DA_Strate4"}],"FullResClipLevels":2,"InterStrateGapChunks":0,"InterStrateGapMeters":0,"LODOctaveDrop":1,"MaxBrushRadius":30,"MaxClipLevel":6,"MaxConcurrentDecorationTasks":4,"MaxConcurrentTasks":16,"MaxDecorationCellsPerFrame":2,"MaxGenerationWorkerCores":0,"MaxMeshAppliesPerFrame":4,"MaxMeshApplyMilliseconds":2,"MaxModifications":0,"MaxTotalVolume":0,"MaxUnloadsPerFrame":16,"NativeClass":"/Script/VoxelForge.VoxelSettings","NearTaskReserve":4,"OriginSpineRadius":28,"PawnCollisionRadiusChunks":2,"RenderDistanceChunks":1536,"Season":"","Seed":0,"SkirtCells":4,"SourceWorldAssemblyManifestJson":{"FilePath":"../../../../../../Projet Unreal/VoxelM/Plugins/VoxelForge/Standalone/Research/WorldAssemblerM3/Round3/results/world_assembly_floating_islands.json"},"StrateContentCutMinLevel":9,"StrateLayoutChunkSizeMeters":8,"StratePool":[],"StrateViewMarginChunks":6,"TotalStrates":4,"ViewDistanceDown":10,"ViewDistanceUp":10,"ViewDistanceXY":32,"VoxelMaterial":"/Game/VoxelForge/M_Test1_Inst.M_Test1_Inst","WorldAssemblyManifestHash":"94b563f1ce622def","WorldAssemblyManifestJson":"{\"schema_version\":1,\"content_hash\":\"94b563f1ce622def\",\"recipe\":{\"schema_version\":1,\"evaluator_version\":4,\"units_per_voxel\":256,\"seed\":0,\"biome_cell_size_q\":204800,\"biome_warp_q\":64000,\"biome_warp_wavelength_q\":142222,\"biome_border_blend_q\":30720,\"climate_relief_wavelength_q\":853333,\"climate_moisture_wavelength_q\":640000,\"climate_relief_contrast_q16\":131072,\"content_hash\":\"c37de78b759c0a4c\",\"biomes\":[{\"id\":4097,\"base_height_q\":-516096,\"continent_amplitude_q\":128000,\"continent_wavelength_q\":256000,\"mountain_amplitude_q\":0,\"mountain_wavelength_q\":128000,\"detail_amplitude_q\":512,\"detail_wavelength_q\":6400,\"relief_min_q16\":0,\"relief_max_q16\":19661,\"moisture_min_q16\":0,\"moisture_max_q16\":19661,\"relief_strength_q16\":6554,\"relief_contrast_q16\":104858,\"relief_wavelength_q\":2560000,\"height_warp_strength_q\":12288,\"height_warp_wavelength_q\":32000,\"beach_width_q\":3072,\"cliff_strength_q16\":0,\"cliff_slope_threshold_q16\":32768,\"cliff_sharpness_q16\":131072,\"cliff_sample_distance_q\":512,\"cliff_max_displacement_q\":0,\"cap_enabled\":true,\"cap_height_q\":-149094,\"cap_warp_strength_q\":0,\"cap_warp_wavelength_q\":25600,\"cap_undulation_q\":256000,\"cap_wavelength_q\":1280000,\"cap_detail_amplitude_q\":2560,\"cap_detail_wavelength_q\":25600,\"cap_ridge_amplitude_q\":0,\"cap_ridge_wavelength_q\":12800},{\"id\":4098,\"base_height_q\":-484639,\"continent_amplitude_q\":128000,\"continent_wavelength_q\":256000,\"mountain_amplitude_q\":128000,\"mountain_wavelength_q\":512000,\"detail_amplitude_q\":512,\"detail_wavelength_q\":25600,\"relief_min_q16\":52429,\"relief_max_q16\":65536,\"moisture_min_q16\":52429,\"moisture_max_q16\":65536,\"relief_strength_q16\":45875,\"relief_contrast_q16\":104858,\"relief_wavelength_q\":170667,\"height_warp_strength_q\":12288,\"height_warp_wavelength_q\":25600,\"beach_width_q\":3072,\"cliff_strength_q16\":65536,\"cliff_slope_threshold_q16\":19661,\"cliff_sharpness_q16\":327680,\"cliff_sample_distance_q\":5120,\"cliff_max_displacement_q\":2570240,\"cap_enabled\":true,\"cap_height_q\":-149094,\"cap_warp_strength_q\":0,\"cap_warp_wavelength_q\":25600,\"cap_undulation_q\":230400,\"cap_wavelength_q\":1280000,\"cap_detail_amplitude_q\":2560,\"cap_detail_wavelength_q\":25600,\"cap_ridge_amplitude_q\":0,\"cap_ridge_wavelength_q\":12800},{\"id\":4099,\"base_height_q\":-516096,\"continent_amplitude_q\":20480,\"continent_wavelength_q\":85333,\"mountain_amplitude_q\":0,\"Line truncated
+5 -5
View File
@@ -129,10 +129,10 @@ Copy-Item -LiteralPath (Join-Path $PluginRoot 'VoxelForge.uplugin') `
$hostAssetRoot = Join-Path $HostRoot 'Content\VoxelForge'
$expectedAssetHashes = @{
'DA_Settings' = '8ABE8243104980260FECA0A6F4CF4927768E8E568221A6F6A2C2694F6D15D454'
'DA_Strate1' = '76063D691D5CE26D2014B587E1972A6724787BE1204E4652CA863BB8D5001BD0'
'DA_Strate2' = '02F56A18CED991BAF93DFB2F69438152275E8353B5E279C1CB7DD6E079E352E2'
'DA_Strate3' = 'F47437F8D156C608AA06E40C5B76716ED4A19FA0F057688E7FECC04DC976428C'
'DA_Strate4' = '7C0DE7D06876035AC7BC4F117B9166AFD77402021843BA4900B470F4B0D0185B'
'DA_Strate1' = 'A9CA6CDDD102250A893E379350B1D0F4AAF04C4F7FCA70E0DB46052A067D2CFB'
'DA_Strate2' = '8F9A12A3181635149E6F79F192E0DDAF6B6577442EE324071A7749DCC78B9A0C'
'DA_Strate3' = '5470D538C3C5E88237463E71BCA9E2EB170DFE3A0543C0F33298D9FB010FD2F9'
'DA_Strate4' = 'A98E33E1CD42F374D6CB70F5071E8EBBA74D91ED59D2FD3201782DAB84D28F5C'
}
foreach ($assetName in $expectedAssetHashes.Keys) {
$stagedAsset = Join-Path $hostAssetRoot "$assetName.uasset"
@@ -141,7 +141,7 @@ foreach ($assetName in $expectedAssetHashes.Keys) {
}
$stagedHash = (Get-FileHash -LiteralPath $stagedAsset -Algorithm SHA256).Hash
if ($stagedHash -ne $expectedAssetHashes[$assetName]) {
throw "BuildHost owner asset copy differs from the recorded round-4 hash: $assetName"
throw "BuildHost owner asset copy differs from the recorded owner-asset hash: $assetName"
}
}
+5 -5
View File
@@ -220,10 +220,10 @@ if ($foreignEditors.Count -gt 0) {
$ExpectedAssetHashes = @{
'DA_Settings' = '8ABE8243104980260FECA0A6F4CF4927768E8E568221A6F6A2C2694F6D15D454'
'DA_Strate1' = '76063D691D5CE26D2014B587E1972A6724787BE1204E4652CA863BB8D5001BD0'
'DA_Strate2' = '02F56A18CED991BAF93DFB2F69438152275E8353B5E279C1CB7DD6E079E352E2'
'DA_Strate3' = 'F47437F8D156C608AA06E40C5B76716ED4A19FA0F057688E7FECC04DC976428C'
'DA_Strate4' = '7C0DE7D06876035AC7BC4F117B9166AFD77402021843BA4900B470F4B0D0185B'
'DA_Strate1' = 'A9CA6CDDD102250A893E379350B1D0F4AAF04C4F7FCA70E0DB46052A067D2CFB'
'DA_Strate2' = '8F9A12A3181635149E6F79F192E0DDAF6B6577442EE324071A7749DCC78B9A0C'
'DA_Strate3' = '5470D538C3C5E88237463E71BCA9E2EB170DFE3A0543C0F33298D9FB010FD2F9'
'DA_Strate4' = 'A98E33E1CD42F374D6CB70F5071E8EBBA74D91ED59D2FD3201782DAB84D28F5C'
}
foreach ($assetName in $ExpectedAssetHashes.Keys) {
$stagedAsset = Join-Path $HostRoot "Content\VoxelForge\$assetName.uasset"
@@ -231,7 +231,7 @@ foreach ($assetName in $ExpectedAssetHashes.Keys) {
throw "Verified BuildHost asset copy is missing: $assetName"
}
if ((Get-FileHash -LiteralPath $stagedAsset -Algorithm SHA256).Hash -ne $ExpectedAssetHashes[$assetName]) {
throw "BuildHost owner asset copy differs from the recorded round-4 hash: $assetName"
throw "BuildHost owner asset copy differs from the recorded owner-asset hash: $assetName"
}
}
+72 -8
View File
@@ -50,6 +50,8 @@ $EditorExe = Join-Path $EngineRoot 'Engine\Binaries\Win64\UnrealEditor-Cmd.exe'
$UbtDll = Join-Path $EngineRoot 'Engine\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.dll'
$LaunchTimeoutSeconds = 1800
$UnrealPrivateMemoryLimitBytes = 8GB
$HostLogicalProcessorLimit = [Math]::Min(12, [Environment]::ProcessorCount)
$HostProcessorAffinityMask = ([int64]1 -shl $HostLogicalProcessorLimit) - 1
# The VoxelForge category includes two late, several-minute op-stack sweeps. They remain in
# the suite; when the serialized aggregate launch reaches this 30-minute guard, validate those
# named tests with -TestFilter so every test still runs under the standing launch limit.
@@ -99,6 +101,8 @@ $DllEvidence = [ordered]@{
editor = [ordered]@{ path = Join-Path $StagePlugin 'Binaries\Win64\UnrealEditor-VoxelForgeEditor.dll'; sha256 = $null }
}
$LocalAppDataWrites = @()
$OwnerEditorPidsAtStart = @()
$LocalAppDataChangesDuringOwnerEditor = $false
$LocalAppDataRoot = Join-Path ([Environment]::GetFolderPath('LocalApplicationData')) 'UnrealEngine'
$RunRoot = $null
$ResultPath = $null
@@ -203,11 +207,10 @@ function ConvertTo-CompactJson($Object) {
}
function Get-UnrealProcesses {
# Get-Process is deliberately the primary guard. Some managed shells deny Win32_Process
# command-line inspection, but they still expose the PID/name needed to protect an owner
# editor before a launch.
# Only commandlet processes conflict with this serialized BuildHost harness. The owner's
# interactive UnrealEditor stays open and is never inspected for command-line or touched.
$rows = @(Get-Process -ErrorAction SilentlyContinue |
Where-Object { $_.ProcessName -like 'UnrealEditor*' } |
Where-Object { $_.ProcessName -like 'UnrealEditor-Cmd*' } |
ForEach-Object {
[pscustomobject]@{
ProcessId = [int]$_.Id
@@ -232,7 +235,7 @@ function Assert-NoForeignUnreal([string]$When) {
$detail = ($processes | ForEach-Object {
"PID=$($_.pid) Name=$($_.name) Exe=$($_.executable)"
}) -join '; '
throw "UnrealEditor process already exists $When; refusing to touch it: $detail"
throw "UnrealEditor-Cmd process already exists $When; refusing to overlap BuildHost launches: $detail"
}
}
@@ -493,6 +496,11 @@ function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPa
try { Stop-Process -Id ([int]$process.Id) -Force -ErrorAction Stop } catch { }
throw "Could not set owned Unreal PID $($process.Id) to Normal priority: $($_.Exception.Message)"
}
try { $process.ProcessorAffinity = [IntPtr]$HostProcessorAffinityMask }
catch {
try { Stop-Process -Id ([int]$process.Id) -Force -ErrorAction Stop } catch { }
throw "Could not cap owned Unreal PID $($process.Id) to $HostLogicalProcessorLimit logical processors: $($_.Exception.Message)"
}
$ledger.pid = [int]$process.Id
$wait = Wait-OwnedProcess $process $LaunchTimeoutSeconds -EnforceMemoryLimit
$ledger.timed_out = [bool]$wait.timed_out
@@ -502,6 +510,22 @@ function Invoke-UnrealLaunch([string]$Kind, [string[]]$Arguments, [string]$LogPa
$ledger.status = if ($ledger.timed_out) { 'timeout' }
elseif ($ledger.memory_limit_hit) { 'memory_limit' }
elseif ($ledger.exit_code -eq 0) { 'passed' } else { 'failed' }
if ($Kind -like '*commandlet*') {
$completion = @(Select-String -LiteralPath $LogPath `
-Pattern 'Commandlet VoxelForgeExploreCommandlet_\d+ finished execution \(result (-?\d+)\)' `
-ErrorAction SilentlyContinue | Select-Object -Last 1)
if ($completion.Count -eq 0) {
$ledger.commandlet_result_code = $null
$ledger.commandlet_completion = 'missing'
$ledger.status = 'failed'
} else {
$match = [regex]::Match([string]$completion[0].Line,
'Commandlet VoxelForgeExploreCommandlet_\d+ finished execution \(result (-?\d+)\)')
$ledger.commandlet_result_code = [int]$match.Groups[1].Value
$ledger.commandlet_completion = 'observed'
if ($ledger.commandlet_result_code -ne 0) { $ledger.status = 'failed' }
}
}
} catch {
$ledger.status = 'launch_error'
$ledger.error = $_.Exception.Message
@@ -552,6 +576,11 @@ function Invoke-BuildProcess([string[]]$Arguments, [string]$LogPath, [string]$Er
try { Stop-Process -Id ([int]$process.Id) -Force -ErrorAction Stop } catch { }
throw "Could not set owned build PID $($process.Id) to Normal priority: $($_.Exception.Message)"
}
try { $process.ProcessorAffinity = [IntPtr]$HostProcessorAffinityMask }
catch {
try { Stop-Process -Id ([int]$process.Id) -Force -ErrorAction Stop } catch { }
throw "Could not cap owned build PID $($process.Id) to $HostLogicalProcessorLimit logical processors: $($_.Exception.Message)"
}
$wait = Wait-OwnedProcess $process $LaunchTimeoutSeconds -EnforceMemoryLimit
} finally {
if ($null -eq $oldDotnetCliHome) { Remove-Item Env:DOTNET_CLI_HOME -ErrorAction SilentlyContinue } else { $env:DOTNET_CLI_HOME = $oldDotnetCliHome }
@@ -580,10 +609,21 @@ function Invoke-BuildProcess([string[]]$Arguments, [string]$LogPath, [string]$Er
}
function Copy-DirectoryContents([string]$Source, [string]$Destination) {
if (-not (Test-Path -LiteralPath $Source -PathType Container)) {
throw "Directory copy source is missing: $Source"
}
Ensure-Directory $Destination
foreach ($item in @(Get-ChildItem -LiteralPath $Source -Force)) {
$target = Join-Path $Destination $item.Name
$null = Copy-Item -LiteralPath $item.FullName -Destination $target -Recurse -Force -PassThru
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
throw "Refusing to stage a linked directory entry: $($item.FullName)"
}
if ($item.PSIsContainer) {
Ensure-Directory $target
Copy-DirectoryContents $item.FullName $target
} else {
$null = Copy-Item -LiteralPath $item.FullName -Destination $target -Force -PassThru
}
}
}
@@ -792,6 +832,7 @@ function New-CommonArguments([string]$Kind, [string]$LogPath, $CvarMap,
[void]$args.Add('-forcelogflush')
[void]$args.Add('-NoZenAutoLaunch')
[void]$args.Add('-notraceserver')
[void]$args.Add("-corelimit=$HostLogicalProcessorLimit")
[void]$args.Add('-nocrashreports')
# The editor's default usage-data setting creates a process summary under the
# machine-wide Epic analytics directory, outside this run's isolated output.
@@ -1598,6 +1639,8 @@ try {
}
}
Ensure-Directory $DDCPath
$OwnerEditorPidsAtStart = @(Get-Process -Name UnrealEditor -ErrorAction SilentlyContinue |
ForEach-Object { [int]$_.Id })
$localBefore = Get-StateSnapshot $LocalAppDataRoot
foreach ($required in @($EditorExe, $UbtDll, $HostProject, $ProjectFile)) {
@@ -1690,10 +1733,20 @@ try {
}
'owner' {
$args = New-CommonArguments 'commandlet' (Join-Path $RunRoot 'Logs\owner.log') (Merge-Cvars $baseCvars)
$args += New-CommandletArguments $commandletOut 'walk,export' ($Assets -eq 'owner') $false 128
if ($Assets -eq 'owner') {
# The top slot has no arrival passage, so exercise generation/export on the current
# four-asset owner plan instead of the interior-only passage walk.
$args += New-CommandletArguments $commandletOut 'export' $false $false 128 0
$args += @('-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings', '-ownerfourstrateplan=1')
} else {
$args += New-CommandletArguments $commandletOut 'walk,export' $false $false 128
}
$commandletLedger = Invoke-UnrealLaunch 'owner-commandlet' $args `
(Join-Path $RunRoot 'Logs\owner.log') (Join-Path $RunRoot 'Logs\owner.stdout.log') (Join-Path $RunRoot 'Logs\owner.stderr.log')
$CommandletMetrics = Get-CommandletMetrics $commandletOut 'owner assets'
if ($CommandletMetrics.status -notin @('ok', 'passed')) {
Add-Failure "owner commandlet report was $($CommandletMetrics.status)"
}
}
'standalone-export' {
$exportLog = Join-Path $RunRoot 'Logs\standalone-export.log'
@@ -1702,6 +1755,7 @@ try {
'-seed=0', '-archetype=TunnelNetwork', '-slot=0', '-modes=export',
'-blockearlyout=0', '-gameconfig=1', '-standaloneworldexport=1',
'-settingsref=/Game/VoxelForge/DA_Settings.DA_Settings',
'-ownerfourstrateplan=1', '-skipstandalonecoreadapterparity=1',
('-out="{0}"' -f $commandletOut)
)
$commandletLedger = Invoke-UnrealLaunch 'standalone-export-commandlet' $args `
@@ -2348,7 +2402,15 @@ try {
$localAfter = Get-StateSnapshot $LocalAppDataRoot
$LocalAppDataWrites = @(Get-StateChanges $localBefore $localAfter)
if ($LocalAppDataWrites.Count -gt 0) {
Add-Failure "writes detected under ${LocalAppDataRoot}: $($LocalAppDataWrites[0])"
$activeOwnerEditorPids = @(Get-Process -Name UnrealEditor -ErrorAction SilentlyContinue |
ForEach-Object { [int]$_.Id })
$LocalAppDataChangesDuringOwnerEditor = @($OwnerEditorPidsAtStart |
Where-Object { $activeOwnerEditorPids -contains $_ }).Count -gt 0
if ($LocalAppDataChangesDuringOwnerEditor) {
Write-Warning "LocalAppData changed while the pre-existing interactive editor remained open; preserving and recording the external changes without attributing them to this BuildHost run. First change: $($LocalAppDataWrites[0])"
} else {
Add-Failure "writes detected under ${LocalAppDataRoot}: $($LocalAppDataWrites[0])"
}
}
} catch {
Add-Failure $_.Exception.Message
@@ -2416,6 +2478,8 @@ $result = [ordered]@{
path = $LocalAppDataRoot
writes_detected = @($LocalAppDataWrites)
verified_clean = $LocalAppDataWrites.Count -eq 0
owner_editor_pids_at_start = @($OwnerEditorPidsAtStart)
changes_during_preexisting_owner_editor = $LocalAppDataChangesDuringOwnerEditor
}
failures = @($Failures)
launch_ledger = @($LaunchLedger)