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:
1 parent
fc9227f9e0
commit
9136f5f0e7
7 files changed
+266
-47
No files matched your search
+2
-1
@@ -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,10 +1942,18 @@ struct FExploreWorld
|
||||
// loaded package remains untouched.
|
||||
Settings->TotalStrates = NumStrates;
|
||||
|
||||
if (Arguments.bOwnerFourStratePlan)
|
||||
{
|
||||
if (!ApplyOwnerFourStratePlan(Settings.Get(), Definitions, true, OutError))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bUseAuthoredStrate)
|
||||
{
|
||||
// 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.
|
||||
// Keep the historical single-asset interior probe isolated from loaded packages.
|
||||
Settings->StratePool.Reset();
|
||||
Settings->FixedStrates.Reset();
|
||||
}
|
||||
@@ -1923,6 +2009,7 @@ struct FExploreWorld
|
||||
}
|
||||
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 @@
|
||||
{"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
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,8 +2402,16 @@ try {
|
||||
$localAfter = Get-StateSnapshot $LocalAppDataRoot
|
||||
$LocalAppDataWrites = @(Get-StateChanges $localBefore $localAfter)
|
||||
if ($LocalAppDataWrites.Count -gt 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)
|
||||
|
||||
Reference in new issue
Block a user