2026-07-15 03:21:28 +01:00
param (
[ string ] $UnityExe = " C:\Program Files\Unity\Hub\Editor\6000.4.2f1\Editor\Unity.exe " ,
[ string ] $AetheriaRoot = " E:\Projects\Aetheria " ,
2026-07-19 01:06:04 +01:00
[ string ] $CultLibRoot = " E:\Projects\CultLib-release " ,
2026-07-15 03:21:28 +01:00
[ string ] $EveUnityRoot = " E:\Projects\EveUnity " ,
[ string ] $YmirRoot = " E:\Projects\Ymir " ,
[ int ] $Port = 3076 ,
[ string ] $OutputDirectory = " artifacts\interactive " ,
[ switch ] $SkipBuild
)
$ErrorActionPreference = " Stop "
$root = Split-Path -Parent $PSScriptRoot
$project = Join-Path $root " ReleaseConsumerProject "
$outputRoot = if ( [ IO.Path ] :: IsPathRooted ( $OutputDirectory ) ) { $OutputDirectory } else { Join-Path $root $OutputDirectory }
$buildLog = Join-Path $outputRoot " build.log "
$bundleBuildLog = Join-Path $outputRoot " asset-bundle-build.log "
$daemonLog = Join-Path $outputRoot " daemon.log "
$importLog = Join-Path $outputRoot " import.log "
$importErrorLog = Join-Path $outputRoot " import.error.log "
$state = Join-Path $outputRoot " aetheria.cc "
2026-08-17 21:02:01 +02:00
$clientCache = Join-Path $outputRoot " eve-unity-cache "
2026-07-15 03:21:28 +01:00
$assetCache = Join-Path $outputRoot " asset-cache "
$exe = Join-Path $project " Build\Windows\EveUnity.exe "
$importProject = Join-Path $AetheriaRoot " Aetheria.State.Import\Aetheria.State.Import.csproj "
$daemonProject = Join-Path $AetheriaRoot " Aetheria.State.Daemon\Aetheria.State.Daemon.csproj "
foreach ( $required in @ ( $UnityExe , $project , $importProject , $daemonProject , ( Join-Path $YmirRoot " src\Ymir.Core\Ymir.Core.csproj " ) ) ) {
if ( -not ( Test-Path -LiteralPath $required ) ) { throw " Required interactive witness path not found: $required " }
}
if ( Get-NetUDPEndpoint -LocalPort $Port -ErrorAction SilentlyContinue ) {
throw " UDP port $Port is already occupied. "
}
New-Item -ItemType Directory -Force $outputRoot , $assetCache | Out-Null
2026-08-17 21:02:01 +02:00
foreach ( $ephemeralPath in @ ( $state , " $state .records " , " $state .cultmesh " , $clientCache ) ) {
2026-07-15 03:21:28 +01:00
$resolved = [ IO.Path ] :: GetFullPath ( $ephemeralPath )
if ( -not $resolved . StartsWith ( [ IO.Path ] :: GetFullPath ( $outputRoot ) , [ StringComparison ] :: OrdinalIgnoreCase ) ) {
throw " Interactive cleanup escaped its artifact directory: $resolved "
}
if ( Test-Path -LiteralPath $resolved ) { Remove-Item -LiteralPath $resolved -Recurse -Force }
}
if ( -not $SkipBuild -or -not ( Test-Path $exe ) ) {
$build = Start-Process $UnityExe -ArgumentList @ ( " -batchmode " , " -quit " , " -projectPath " , $project , " -executeMethod " , " GenericEveUnityBuild.BuildWindows " , " -logFile " , $buildLog ) -PassThru -WindowStyle Hidden
Write-Host " Build PID: $( $build . Id ) "
Write-Host " Build log: $buildLog "
if ( -not $build . WaitForExit ( 300000 ) ) { Stop-Process $build . Id -Force ; throw " EveUnity build timed out. " }
if ( $build . ExitCode -ne 0 -or -not ( Test-Path $exe ) -or ( Select-String $buildLog -Pattern " Scripts have compiler errors|Build completed with a result of 'Failed' " -Quiet ) ) { Get-Content $buildLog -Tail 120 ; throw " EveUnity build failed. " }
}
if ( -not $SkipBuild ) {
$bundleBuilder = Start-Process $UnityExe -ArgumentList @ (
" -batchmode " , " -quit " , " -projectPath " , " . " ,
" -executeMethod " , " Aetheria.Editor.EveAssetBundleBuilder.BuildWindows " ,
" -logFile " , $bundleBuildLog
) -WorkingDirectory $AetheriaRoot -PassThru -WindowStyle Hidden
Write-Host " AssetBundle build PID: $( $bundleBuilder . Id ) "
Write-Host " AssetBundle build log: $bundleBuildLog "
if ( -not $bundleBuilder . WaitForExit ( 240000 ) ) { Stop-Process $bundleBuilder . Id -Force ; throw " Aetheria AssetBundle build timed out. " }
if ( $bundleBuilder . ExitCode -ne 0 ) { Get-Content $bundleBuildLog -Tail 120 ; throw " Aetheria AssetBundle build failed. " }
}
$importArguments = @ (
" run " , " --project " , $importProject ,
" -p:CultLibRoot= $CultLibRoot " , " -p:EveUnityRoot= $EveUnityRoot " , " -p:YmirRoot= $YmirRoot " ,
" -- " , $AetheriaRoot , $state
)
$previousErrorActionPreference = $ErrorActionPreference
try {
$ErrorActionPreference = " Continue "
& dotnet @importArguments 1 > $importLog 2 > $importErrorLog
$importExitCode = $LASTEXITCODE
}
finally {
$ErrorActionPreference = $previousErrorActionPreference
}
if ( $importExitCode -ne 0 ) { throw " Aetheria state import failed. See $importErrorLog " }
$daemon = Start-Process dotnet -ArgumentList @ (
" run " , " --project " , $daemonProject ,
" -p:CultLibRoot= $CultLibRoot " , " -p:EveUnityRoot= $EveUnityRoot " , " -p:YmirRoot= $YmirRoot " ,
" -- " ,
" --root " , $AetheriaRoot ,
" --state " , $state ,
" --client-cultmesh-host " , " 127.0.0.1 " ,
" --client-cultmesh-advertise-host " , " 127.0.0.1 " ,
" --client-cultmesh-port " , $Port ,
2026-07-16 03:34:47 +01:00
" --tick-interval-ms " , 20 ,
2026-07-15 03:21:28 +01:00
" --fixed-delta-ms " , 20 ,
" --no-odin-announcements "
) -PassThru -WindowStyle Hidden -RedirectStandardOutput $daemonLog -RedirectStandardError " $daemonLog .error "
Write-Host " Daemon PID: $( $daemon . Id ) "
Write-Host " Daemon log: $daemonLog "
try {
$ready = $false
for ( $i = 0 ; $i -lt 240 ; $i + + ) {
if ( $daemon . HasExited ) { throw " Aetheria daemon exited. See $daemonLog " }
if ( ( Test-Path $daemonLog ) -and
( Select-String $daemonLog -Pattern " Aetheria client CultMesh endpoint: rudp://127.0.0.1: $Port " -Quiet ) ) {
$ready = $true
break
}
Start-Sleep -Milliseconds 500
}
if ( -not $ready ) { throw " Aetheria daemon did not open CultMesh port $Port within 120 seconds. See $daemonLog " }
$env:EVEUNITY_RENDEZVOUS_ENDPOINT = " rudp://127.0.0.1: $Port "
$env:EVEUNITY_PROVIDER_ID = " aetheria "
2026-08-17 21:02:01 +02:00
$env:EVEUNITY_CACHE_DIRECTORY = $clientCache
2026-07-15 03:21:28 +01:00
$env:EVEUNITY_ASSET_CACHE_PATH = $assetCache
Remove-Item Env : EVEUNITY_SURFACE_ID -ErrorAction SilentlyContinue
$client = Start-Process $exe -ArgumentList " -force-d3d11 " -PassThru
Write-Host " Client PID: $( $client . Id ) "
Write-Host " Asset cache: $assetCache "
Write-Host " Close the EveUnity window when finished. "
$client . WaitForExit ( )
}
finally {
if ( $null -ne $daemon -and -not $daemon . HasExited ) { Stop-Process $daemon . Id -Force }
2026-08-17 21:02:01 +02:00
Remove-Item Env : EVEUNITY_RENDEZVOUS_ENDPOINT , Env : EVEUNITY_PROVIDER_ENDPOINT , Env : EVEUNITY_PROVIDER_ID , Env : EVEUNITY_SURFACE_ID , Env : EVEUNITY_CACHE_DIRECTORY , Env : EVEUNITY_ASSET_CACHE_PATH -ErrorAction SilentlyContinue
2026-07-15 03:21:28 +01:00
}