param( [Parameter(Mandatory=$true)][string]$WorldFile, [string]$Root = 'D:\NDC_MISSIONCORE\runtime\simulation', [string]$Python = 'D:\NDC_MISSIONCORE\runtime\simulation\isaac-sim-6.1.0\kit\python\python.exe' ) $ErrorActionPreference = 'Stop' [Threading.Thread]::CurrentThread.CurrentCulture = [Globalization.CultureInfo]::InvariantCulture $world = Get-Content -Raw $WorldFile | ConvertFrom-Json if ($world.sha256 -notmatch '^[a-f0-9]{64}$') { throw 'Invalid source hash' } $source = Join-Path $Root ('state\worlds\' + $world.sha256 + '.ply') if ((Get-FileHash $source -Algorithm SHA256).Hash.ToLower() -ne $world.sha256) { throw 'World source changed' } $tool = Join-Path $Root 'tools\splat-transform-3.4.0\node_modules\@playcanvas\splat-transform' $package = Get-Content -Raw (Join-Path $tool 'package.json') | ConvertFrom-Json if ($package.version -ne '3.4.0') { throw 'SplatTransform version changed' } $settings = $world.settings $json = ($settings | ConvertTo-Json -Compress) + (Get-FileHash $PSCommandPath -Algorithm SHA256).Hash $sha = [Security.Cryptography.SHA256]::Create() $digest = [BitConverter]::ToString($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($json))).Replace('-','').ToLower() $out = Join-Path $Root ('assets\terrain-v1\' + $world.sha256 + '-' + $digest.Substring(0,16)) New-Item -ItemType Directory -Force $out | Out-Null $mesh = Join-Path $out 'terrain.collision.glb' $migrator = Join-Path $PSScriptRoot 'migrate_terrain_coordinates.py' if ((Get-FileHash $migrator -Algorithm SHA256).Hash.ToLower() -ne 'ff08de8b97b9f2b0040b379840c402f5c7c327e3bc1915548d99b6372ff5ca37') { throw 'Coordinate migrator identity changed' } if (-not (Test-Path $mesh)) { # Exact rigid correction of the admitted historical cache avoids rerunning # a faulty third-party BVH build. Never mutates or reclassifies that mesh. $ErrorActionPreference = 'Continue' & $Python $migrator --root $Root --world $WorldFile --output $mesh if ($LASTEXITCODE -notin @(0,2)) { throw 'Collision coordinate migration failed' } $ErrorActionPreference = 'Stop' } if (-not (Test-Path $mesh)) { # SplatTransform 3.4 assigns PLY an implicit Rz(180) before CLI actions. # Cancel it FIRST, then apply the same XYZ rotation as the USD visual. # World Z-up -> glTF Y-up is Rx(-90): (x,y,z) -> (x,z,-y). # Runtime's (x,y,z) -> (x,-z,y) is exactly its inverse. Do not use Rx(+90). $x = [double]$settings.spawn_xy[0]; $y = [double]$settings.spawn_xy[1]; $z = [double]$settings.ground_z $box = @(($x-15),($z-4),(-$y-15),($x+15),($z+8),(-$y+15)) -join ',' $args = @('--max-old-space-size=12288', (Join-Path $tool 'bin\cli.mjs'), $source, '--filter-nan', '--rotate', '0,0,180', '--rotate', ($settings.rotation_degrees -join ','), '--scale', [string]$settings.meters_per_unit, '--rotate', '-90,0,0', '--filter-box', $box, '--voxel-size', '0.06', '--voxel-opacity', '0.25', '--voxel-floor-fill', '0.3', '--collision-mesh', 'smooth', (Join-Path $out 'terrain.voxel.json')) $ErrorActionPreference = 'Continue' & node @args if ($LASTEXITCODE -ne 0) { throw 'SplatTransform collision generation failed' } $ErrorActionPreference = 'Stop' } if (-not (Test-Path $mesh)) { throw 'Collision mesh was not produced' } $correctionPath = Join-Path $out 'coordinate-correction.json' $correction = if (Test-Path $correctionPath) { Get-Content -Raw $correctionPath | ConvertFrom-Json } else { $null } if ($correction) { if ((Get-FileHash $mesh -Algorithm SHA256).Hash.ToLower() -ne $correction.collider_sha256) { throw 'Corrected collision changed' } $settings = $correction.settings } $manifest = @{ schema_version = 'missioncore.ai-polygon-terrain/v1' source_sha256 = $world.sha256 settings = $settings generator = '@playcanvas/splat-transform@3.4.0' generator_sha256 = (Get-FileHash $PSCommandPath -Algorithm SHA256).Hash.ToLower() voxel_size_m = 0.06 collider = $mesh collider_sha256 = (Get-FileHash $mesh -Algorithm SHA256).Hash.ToLower() coordinate_system = 'gltf-y-up-from-metric-world' coordinate_revision = 'ply-rz180-cancelled-usd-xyz-gltf-rx-minus90-v2' coordinate_correction = $correction qualification = 'reconstructed-proxy-requires-contact-validation' } [IO.File]::WriteAllText((Join-Path $out 'terrain.json'), ($manifest | ConvertTo-Json -Depth 6), [Text.UTF8Encoding]::new($false)) $manifest | ConvertTo-Json -Depth 6 -Compress