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>
257 lines
13 KiB
PowerShell
257 lines
13 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[ValidateRange(1, 16)][int]$MaxParallelActions = 16,
|
|
[ValidateRange(60, 900)][int]$TimeoutSeconds = 900
|
|
)
|
|
|
|
# Stages only inside Saved/BuildHost, builds the isolated host, then audits the exact Core object
|
|
# files used by the UE module link. Le projet propriétaire et son éditeur ne sont jamais lancés.
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
$PluginRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
|
|
$HostRoot = Join-Path $PluginRoot 'Saved\BuildHost\HostProject'
|
|
$HostProject = Join-Path $HostRoot 'HostProject.uproject'
|
|
$StagePlugin = Join-Path $HostRoot 'Plugins\VoxelForge'
|
|
$StageStandalone = Join-Path $StagePlugin 'Standalone'
|
|
$LogRoot = Join-Path $PluginRoot 'Saved\BuildHost'
|
|
$EngineRoot = 'E:\Program Files\Epic Games\UE_5.7'
|
|
$UbtDll = Join-Path $EngineRoot 'Engine\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.dll'
|
|
$CoreManifest = Join-Path $PluginRoot 'Standalone\Core\core_sources.txt'
|
|
$BuildLog = Join-Path $LogRoot 'UBT-CoreIntegration.log'
|
|
$BuildStdout = Join-Path $LogRoot 'UBT-CoreIntegration.stdout.log'
|
|
$BuildStderr = Join-Path $LogRoot 'UBT-CoreIntegration.stderr.log'
|
|
$AuditLibrary = Join-Path $LogRoot 'VoxelForgeCore_ue_link_inputs.lib'
|
|
$AuditResult = Join-Path $LogRoot 'VoxelForgeCore_ue_link_safety.log'
|
|
|
|
function Assert-UnderPath([string]$Candidate, [string]$Root) {
|
|
$candidateFull = [IO.Path]::GetFullPath($Candidate).TrimEnd('\', '/')
|
|
$rootFull = [IO.Path]::GetFullPath($Root).TrimEnd('\', '/')
|
|
if (-not ($candidateFull.Equals($rootFull, [StringComparison]::OrdinalIgnoreCase) -or
|
|
$candidateFull.StartsWith($rootFull + '\', [StringComparison]::OrdinalIgnoreCase))) {
|
|
throw "Refusing a BuildHost path outside its root: $candidateFull"
|
|
}
|
|
}
|
|
|
|
if (-not ('VoxelForgeRound4BuildMemoryProbe' -as [type])) {
|
|
Add-Type -TypeDefinition @'
|
|
using System;
|
|
using System.Runtime.InteropServices;
|
|
public static class VoxelForgeRound4BuildMemoryProbe {
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
|
private struct MEMORYSTATUSEX {
|
|
public uint dwLength;
|
|
public uint dwMemoryLoad;
|
|
public ulong ullTotalPhys;
|
|
public ulong ullAvailPhys;
|
|
public ulong ullTotalPageFile;
|
|
public ulong ullAvailPageFile;
|
|
public ulong ullTotalVirtual;
|
|
public ulong ullAvailVirtual;
|
|
public ulong ullAvailExtendedVirtual;
|
|
}
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
|
|
public static ulong AvailablePhysicalBytes() {
|
|
var status = new MEMORYSTATUSEX();
|
|
status.dwLength = (uint)Marshal.SizeOf(typeof(MEMORYSTATUSEX));
|
|
if (!GlobalMemoryStatusEx(ref status)) throw new InvalidOperationException("GlobalMemoryStatusEx failed.");
|
|
return status.ullAvailPhys;
|
|
}
|
|
}
|
|
'@
|
|
}
|
|
|
|
function Wait-ForLaunchMemory {
|
|
while ([VoxelForgeRound4BuildMemoryProbe]::AvailablePhysicalBytes() -lt 4GB) {
|
|
Write-Warning 'BuildHost UBT launch is waiting for at least 4 GiB of free physical memory.'
|
|
Start-Sleep -Seconds 20
|
|
}
|
|
}
|
|
|
|
function Copy-DirectoryContents([string]$Source, [string]$Destination) {
|
|
if (-not (Test-Path -LiteralPath $Source -PathType Container)) {
|
|
throw "Canonical Core source directory is missing: $Source"
|
|
}
|
|
[IO.Directory]::CreateDirectory($Destination) | Out-Null
|
|
foreach ($item in @(Get-ChildItem -LiteralPath $Source -Force)) {
|
|
$target = Join-Path $Destination $item.Name
|
|
if ($item.PSIsContainer) {
|
|
Copy-DirectoryContents $item.FullName $target
|
|
} else {
|
|
Copy-Item -LiteralPath $item.FullName -Destination $target -Force
|
|
}
|
|
}
|
|
}
|
|
|
|
function Find-VsTool([string]$ToolName) {
|
|
$toolRoot = 'E:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC'
|
|
foreach ($version in @(Get-ChildItem -LiteralPath $toolRoot -Directory | Sort-Object Name -Descending)) {
|
|
$candidate = Join-Path $version.FullName "bin\Hostx64\x64\$ToolName"
|
|
if (Test-Path -LiteralPath $candidate -PathType Leaf) { return $candidate }
|
|
}
|
|
throw "$ToolName was not found in the configured VS 2022 toolchain."
|
|
}
|
|
|
|
foreach ($required in @($HostProject, $UbtDll, $CoreManifest)) {
|
|
if (-not (Test-Path -LiteralPath $required -PathType Leaf)) { throw "Required file is missing: $required" }
|
|
}
|
|
Assert-UnderPath $StagePlugin $HostRoot
|
|
Assert-UnderPath $StageStandalone $StagePlugin
|
|
Assert-UnderPath $LogRoot $PluginRoot
|
|
|
|
Write-Host 'Staging plugin source and canonical Core inputs into BuildHost.'
|
|
$stageSource = Join-Path $StagePlugin 'Source'
|
|
Assert-UnderPath $stageSource $StagePlugin
|
|
if (Test-Path -LiteralPath $stageSource -PathType Container) {
|
|
$sourceItem = Get-Item -LiteralPath $stageSource -Force
|
|
if (($sourceItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
|
|
throw "Refusing to replace a linked BuildHost source path: $stageSource"
|
|
}
|
|
$nestedLinks = @(Get-ChildItem -LiteralPath $stageSource -Force -Recurse |
|
|
Where-Object { ($_.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 })
|
|
if ($nestedLinks.Count -gt 0) {
|
|
throw "Refusing to replace BuildHost source containing a link: $($nestedLinks[0].FullName)"
|
|
}
|
|
Remove-Item -LiteralPath $stageSource -Recurse -Force
|
|
}
|
|
Copy-DirectoryContents (Join-Path $PluginRoot 'Source') $stageSource
|
|
Copy-DirectoryContents (Join-Path $PluginRoot 'Standalone\Core\include') (Join-Path $StageStandalone 'Core\include')
|
|
Copy-DirectoryContents (Join-Path $PluginRoot 'Standalone\Core\src') (Join-Path $StageStandalone 'Core\src')
|
|
Copy-DirectoryContents (Join-Path $PluginRoot 'Standalone\Compat') (Join-Path $StageStandalone 'Compat')
|
|
Copy-DirectoryContents (Join-Path $PluginRoot 'Standalone\Source') (Join-Path $StageStandalone 'Source')
|
|
Copy-Item -LiteralPath $CoreManifest -Destination (Join-Path $StageStandalone 'Core\core_sources.txt') -Force
|
|
Copy-Item -LiteralPath (Join-Path $PluginRoot 'VoxelForge.uplugin') `
|
|
-Destination (Join-Path $StagePlugin 'VoxelForge.uplugin') -Force
|
|
|
|
# The verified owner-asset copies are already staged under BuildHost. Read only those copies here;
|
|
# never resolve, open, copy, or compare against the owner's project Content directory.
|
|
$hostAssetRoot = Join-Path $HostRoot 'Content\VoxelForge'
|
|
$expectedAssetHashes = @{
|
|
'DA_Settings' = '8ABE8243104980260FECA0A6F4CF4927768E8E568221A6F6A2C2694F6D15D454'
|
|
'DA_Strate1' = 'A9CA6CDDD102250A893E379350B1D0F4AAF04C4F7FCA70E0DB46052A067D2CFB'
|
|
'DA_Strate2' = '8F9A12A3181635149E6F79F192E0DDAF6B6577442EE324071A7749DCC78B9A0C'
|
|
'DA_Strate3' = '5470D538C3C5E88237463E71BCA9E2EB170DFE3A0543C0F33298D9FB010FD2F9'
|
|
'DA_Strate4' = 'A98E33E1CD42F374D6CB70F5071E8EBBA74D91ED59D2FD3201782DAB84D28F5C'
|
|
}
|
|
foreach ($assetName in $expectedAssetHashes.Keys) {
|
|
$stagedAsset = Join-Path $hostAssetRoot "$assetName.uasset"
|
|
if (-not (Test-Path -LiteralPath $stagedAsset -PathType Leaf)) {
|
|
throw "Verified BuildHost asset copy is missing: $stagedAsset"
|
|
}
|
|
$stagedHash = (Get-FileHash -LiteralPath $stagedAsset -Algorithm SHA256).Hash
|
|
if ($stagedHash -ne $expectedAssetHashes[$assetName]) {
|
|
throw "BuildHost owner asset copy differs from the recorded owner-asset hash: $assetName"
|
|
}
|
|
}
|
|
|
|
$dotnet = (Get-Command dotnet.exe -ErrorAction Stop).Source
|
|
$argumentList = @(
|
|
('"{0}"' -f $UbtDll), 'UnrealEditor', 'Win64', 'Development',
|
|
('-Project="{0}"' -f $HostProject), '-WaitMutex', '-NoHotReloadFromIDE',
|
|
'-NoUBA', '-NoXGE', '-NoFASTBuild', '-NoSNDBS',
|
|
"-MaxParallelActions=$MaxParallelActions", ('-Log="{0}"' -f $BuildLog)
|
|
)
|
|
$crashRoots = @((Join-Path $HostRoot 'Saved\Crashes'), (Join-Path $StagePlugin 'Saved\Crashes'))
|
|
$crashBefore = @{}
|
|
foreach ($crashRoot in $crashRoots) {
|
|
if (Test-Path -LiteralPath $crashRoot -PathType Container) {
|
|
foreach ($crash in @(Get-ChildItem -LiteralPath $crashRoot -Directory -Filter 'UECC-*' -Recurse -Force)) {
|
|
$crashBefore[$crash.FullName.ToLowerInvariant()] = $crash.LastWriteTimeUtc.Ticks
|
|
}
|
|
}
|
|
}
|
|
|
|
$buildSucceeded = $false
|
|
for ($attempt = 1; $attempt -le 2 -and -not $buildSucceeded; ++$attempt) {
|
|
$attemptStdout = if ($attempt -eq 1) { $BuildStdout } else { Join-Path $LogRoot 'UBT-CoreIntegration.retry.stdout.log' }
|
|
$attemptStderr = if ($attempt -eq 1) { $BuildStderr } else { Join-Path $LogRoot 'UBT-CoreIntegration.retry.stderr.log' }
|
|
Write-Host "BuildHost UBT attempt $attempt of 2; max parallel actions=$MaxParallelActions; Normal priority."
|
|
Wait-ForLaunchMemory
|
|
$foreignEditors = @(Get-Process -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.ProcessName -like 'UnrealEditor*' })
|
|
if ($foreignEditors.Count -gt 0) {
|
|
$ids = ($foreignEditors | ForEach-Object { "$($_.ProcessName):$($_.Id)" }) -join ', '
|
|
throw "UnrealEditor is already running; leaving it untouched: $ids"
|
|
}
|
|
$process = Start-Process -FilePath $dotnet -ArgumentList $argumentList -WorkingDirectory $PluginRoot `
|
|
-RedirectStandardOutput $attemptStdout -RedirectStandardError $attemptStderr `
|
|
-WindowStyle Hidden -PassThru
|
|
$process.PriorityClass = [Diagnostics.ProcessPriorityClass]::Normal
|
|
$started = [datetime]::UtcNow
|
|
while (-not $process.HasExited) {
|
|
Start-Sleep -Seconds 5
|
|
$process.Refresh()
|
|
if (([datetime]::UtcNow - $started).TotalSeconds -gt $TimeoutSeconds) {
|
|
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
|
throw "BuildHost UBT exceeded the $TimeoutSeconds-second time box; stopped PID $($process.Id)."
|
|
}
|
|
if (-not $process.HasExited -and $process.PrivateMemorySize64 -gt 8GB) {
|
|
Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
|
|
throw "BuildHost UBT exceeded the 8 GiB private-memory limit; stopped PID $($process.Id)."
|
|
}
|
|
}
|
|
$process.WaitForExit()
|
|
$process.Refresh()
|
|
$buildExitCode = $process.ExitCode
|
|
if ([string]::IsNullOrEmpty([string]$buildExitCode) -and (Test-Path -LiteralPath $BuildLog -PathType Leaf) -and
|
|
((Get-Content -LiteralPath $BuildLog -Raw) -match '(?m)^Result: Succeeded\s*$')) {
|
|
# Some Windows Start-Process handles lose the exit-code field after UBT's child exits;
|
|
# accept only UBT's explicit successful target result in that case.
|
|
$buildExitCode = 0
|
|
}
|
|
if ($buildExitCode -eq 0) {
|
|
$buildSucceeded = $true
|
|
Write-Host "BuildHost UBT attempt $attempt passed."
|
|
} elseif ($buildExitCode -eq -1073741502 -and $attempt -eq 1) {
|
|
Write-Warning 'BuildHost UBT exited with 0xC0000142; retrying once as requested.'
|
|
} else {
|
|
throw "BuildHost UBT attempt $attempt failed with exit code '$buildExitCode'. See $BuildLog."
|
|
}
|
|
}
|
|
|
|
if (-not $buildSucceeded) { throw 'BuildHost UBT did not complete successfully.' }
|
|
|
|
$manifestRows = @(Get-Content -LiteralPath $CoreManifest | ForEach-Object { $_.Trim().Replace('\', '/') } |
|
|
Where-Object { $_ -and -not $_.StartsWith('#') })
|
|
$expectedObjects = @($manifestRows | ForEach-Object {
|
|
'VFCore_' + ($_ -replace '[^A-Za-z0-9]+', '_') + '.cpp.obj'
|
|
})
|
|
$objectRoot = Join-Path $StagePlugin 'Intermediate\Build'
|
|
$coreObjects = @(Get-ChildItem -LiteralPath $objectRoot -Recurse -File -Filter 'VFCore_*.cpp.obj' |
|
|
Where-Object { $_.FullName -match '\\VoxelForgeCore\\' })
|
|
$actualObjects = @($coreObjects | ForEach-Object { $_.Name } | Sort-Object -Unique)
|
|
$missingObjects = @($expectedObjects | Where-Object { $actualObjects -notcontains $_ })
|
|
$unexpectedObjects = @($actualObjects | Where-Object { $expectedObjects -notcontains $_ })
|
|
if ($missingObjects.Count -gt 0 -or $unexpectedObjects.Count -gt 0 -or $coreObjects.Count -ne $manifestRows.Count) {
|
|
throw "Core link input inventory mismatch: expected $($manifestRows.Count) shims, found $($coreObjects.Count); missing=[$($missingObjects -join ',')], extra=[$($unexpectedObjects -join ',')]."
|
|
}
|
|
|
|
$libTool = Find-VsTool 'lib.exe'
|
|
if (Test-Path -LiteralPath $AuditLibrary -PathType Leaf) { Remove-Item -LiteralPath $AuditLibrary -Force }
|
|
& $libTool '/nologo' ("/OUT:{0}" -f $AuditLibrary) @($coreObjects.FullName)
|
|
if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $AuditLibrary -PathType Leaf)) {
|
|
throw "Could not archive the exact UBT Core link inputs for symbol inspection (exit $LASTEXITCODE)."
|
|
}
|
|
& (Join-Path $PluginRoot 'Standalone\Core\CheckCoreSymbols.ps1') `
|
|
-LibraryPath $AuditLibrary -ResultPath $AuditResult
|
|
|
|
$newCrashes = [Collections.Generic.List[string]]::new()
|
|
foreach ($crashRoot in $crashRoots) {
|
|
if (Test-Path -LiteralPath $crashRoot -PathType Container) {
|
|
foreach ($crash in @(Get-ChildItem -LiteralPath $crashRoot -Directory -Filter 'UECC-*' -Recurse -Force)) {
|
|
if (-not $crashBefore.ContainsKey($crash.FullName.ToLowerInvariant())) {
|
|
[void]$newCrashes.Add($crash.FullName)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($newCrashes.Count -gt 0) {
|
|
throw "New UECC crash folders appeared during the BuildHost run: $($newCrashes -join '; ')"
|
|
}
|
|
|
|
Write-Host "Verified the five recorded asset hashes in the existing BuildHost Content copies."
|
|
Write-Host "Core manifest shims linked and audited: $($coreObjects.Count)"
|
|
Write-Host "Link-safety result: $AuditResult"
|
|
Write-Host "Build log: $BuildLog"
|