# Two-pod LAN mesh test: two -net pods on loopback, marshaled by a # minimal console feeder speaking the Munga protocol (vendored # TeslaSuite Munga Net.dll). Proves the deterministic TCP mesh works # through the NetTransport seam end to end: # pod A listens on game port 1502, pod B connects to it from 1602, # both ACK the egg only after "All connections completed!", race the # selected 60s, stop on the console's StopMission(0), and report # final scores. param( # a full game install: rpl4opt.exe + RPL4.RES + *.INI + AUDIO/GAUGE/VIDEO [string]$GameDir = "$PSScriptRoot\..\dist", # the vendored console-protocol library from TeslaSuite [string]$MungaNetDll = 'C:\VWE\TeslaSuite\Console\lib\Munga Net.dll', # ordinal plasma art is lifted from the reference egg [string]$ReferenceEgg = "$PSScriptRoot\..\assets\RP411\TEST.EGG", [string]$WorkDir = "$env:TEMP\rp412-twopod" ) $ErrorActionPreference = 'Stop' $GameDir = (Resolve-Path $GameDir).Path $ReferenceEgg = (Resolve-Path $ReferenceEgg).Path if (-not (Test-Path $WorkDir)) { New-Item -ItemType Directory $WorkDir | Out-Null } $scratch = $WorkDir $smoke = $GameDir Add-Type -Path $MungaNetDll Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName System.Windows.Forms $src = @' using System; using System.Runtime.InteropServices; public class PodWin { [DllImport("user32.dll")] public static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int cx, int cy, uint flags); [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr h, out RECT r); [StructLayout(LayoutKind.Sequential)] public struct RECT { public int L, T, R, B; } } '@ Add-Type -TypeDefinition $src #----------------------------------------------------------------- # Pod working dirs: junction the big asset dirs, copy the roots #----------------------------------------------------------------- foreach ($pod in 'podA', 'podB') { $dir = "$scratch\$pod" if (-not (Test-Path $dir)) { New-Item -ItemType Directory $dir | Out-Null } foreach ($assets in 'AUDIO', 'GAUGE', 'VIDEO') { if (-not (Test-Path "$dir\$assets")) { New-Item -ItemType Junction -Path "$dir\$assets" -Target "$smoke\$assets" | Out-Null } } foreach ($file in 'environ.ini', 'JOYSTICK.INI', 'RPDPL.INI', 'RPL4.RES', 'libsndfile-1.dll', 'OpenAL32.dll', 'rpl4opt.exe') { if (Test-Path "$smoke\$file") { Copy-Item "$smoke\$file" $dir -Force } } Remove-Item "$dir\rpl4.log", "$dir\last.egg" -Force -Confirm:$false -ErrorAction SilentlyContinue } #----------------------------------------------------------------- # The two-pilot egg (RPMission.ToEggString layout; blank plasma # name bitmaps - the glass is hidden in RP412 anyway; ordinals # lifted verbatim from TEST.EGG) #----------------------------------------------------------------- function Add-BlankBitmap([Text.StringBuilder]$sb, [int]$w, [int]$h) { $row = 'bitmap=' + ('0' * ($w / 4)) for ($i = 0; $i -lt $h; $i++) { [void]$sb.AppendLine($row) } [void]$sb.AppendLine("x=$w"); [void]$sb.AppendLine("y=$h") } $test = [IO.File]::ReadAllText($ReferenceEgg) $ordinals = $test.Substring($test.IndexOf('[ordinals]')) $pilots = @( @{ addr = '127.0.0.1:1502'; name = 'PODA'; vehicle = 'speck'; color = 'Red' }, @{ addr = '127.0.0.1:1602'; name = 'PODB'; vehicle = 'roach'; color = 'Blue' } ) $sb = New-Object Text.StringBuilder [void]$sb.AppendLine('[mission]') [void]$sb.AppendLine('adventure=Red Planet') [void]$sb.AppendLine('map=wise') [void]$sb.AppendLine('scenario=race') [void]$sb.AppendLine('time=day') [void]$sb.AppendLine('weather=clear') [void]$sb.AppendLine('temperature=0') [void]$sb.AppendLine('compression=0') [void]$sb.AppendLine('length=60') [void]$sb.AppendLine('[pilots]') foreach ($p in $pilots) { [void]$sb.AppendLine("pilot=$($p.addr)") } $index = 1 foreach ($p in $pilots) { [void]$sb.AppendLine("[$($p.addr)]") [void]$sb.AppendLine('hostType=0') [void]$sb.AppendLine('dropzone=one') [void]$sb.AppendLine("name=$($p.name)") [void]$sb.AppendLine("bitmapindex=$index") [void]$sb.AppendLine('loadzones=1') [void]$sb.AppendLine("vehicle=$($p.vehicle)") [void]$sb.AppendLine("color=$($p.color)") [void]$sb.AppendLine('badge=None') $index++ } [void]$sb.AppendLine('[largebitmap]') foreach ($p in $pilots) { [void]$sb.AppendLine("bitmap=BitMap::Large::$($p.name)") } [void]$sb.AppendLine('[smallbitmap]') foreach ($p in $pilots) { [void]$sb.AppendLine("bitmap=BitMap::Small::$($p.name)") } foreach ($p in $pilots) { [void]$sb.AppendLine("[BitMap::Large::$($p.name)]") Add-BlankBitmap $sb 128 32 [void]$sb.AppendLine('width=8') [void]$sb.AppendLine("[BitMap::Small::$($p.name)]") Add-BlankBitmap $sb 64 16 [void]$sb.AppendLine('width=4') } [void]$sb.Append($ordinals) $eggText = $sb.ToString() [IO.File]::WriteAllText("$scratch\twopod.egg", $eggText) Write-Output "egg built: $($eggText.Length) chars" # wire form: newlines -> NUL, chunked into EggFileMessages (RPMission.ToEggFileMessages) $wire = $eggText.Replace("`r`n", "`0").Replace("`n", "`0") $bytes = [Text.Encoding]::ASCII.GetBytes($wire) $chunks = @() for ($off = 0; $off -lt $bytes.Length; $off += 1000) { $n = [Math]::Min(1000, $bytes.Length - $off) $buf = New-Object byte[] 1000 [Buffer]::BlockCopy($bytes, $off, $buf, 0, $n) $chunks += New-Object Munga.Net.EggFileMessage([int]($off / 1000), $bytes.Length, $n, $buf) } Write-Output "egg chunks: $($chunks.Count)" #----------------------------------------------------------------- # Launch the pods #----------------------------------------------------------------- Get-Process rpl4opt -ErrorAction SilentlyContinue | Stop-Process -Force -Confirm:$false -ErrorAction SilentlyContinue Start-Sleep -Seconds 1 $procs = @{} $procs['A'] = Start-Process -FilePath "$scratch\podA\rpl4opt.exe" -ArgumentList '-windowed','-res','1920','1080','-net','1501' -WorkingDirectory "$scratch\podA" -PassThru Start-Sleep -Seconds 2 $procs['B'] = Start-Process -FilePath "$scratch\podB\rpl4opt.exe" -ArgumentList '-windowed','-res','1920','1080','-net','1601' -WorkingDirectory "$scratch\podB" -PassThru Start-Sleep -Seconds 8 # offset pod B's window so a desktop screenshot shows both if ($procs['B'].MainWindowHandle -ne 0) { [PodWin]::SetWindowPos($procs['B'].MainWindowHandle, [IntPtr]::Zero, 700, 250, 0, 0, 0x0015) | Out-Null } #----------------------------------------------------------------- # The feeder: connect, poll state, feed eggs, launch, time, stop #----------------------------------------------------------------- $pods = @{} foreach ($k in @('A','B')) { $port = if ($k -eq 'A') { 1501 } else { 1601 } $sock = New-Object Munga.Net.MungaSocket $sock.Connect([Net.IPAddress]::Loopback, [uint16]$port) $pods[$k] = @{ sock = $sock; state = $null; ack = $false eggSent = [DateTime]::MinValue; lastQuery = [DateTime]::MinValue score = $null; runSent = $false; stopSent = $false } Write-Output "console connected to pod $k (port $port)" } $stateQuery = New-Object Munga.Net.StateQueryMessage(1) $runStart = $null $deadline = (Get-Date).AddMinutes(6) $raceShotTaken = $false $log = New-Object System.Collections.Generic.List[string] while ((Get-Date) -lt $deadline) { foreach ($k in @('A','B')) { $pod = $pods[$k] $now = Get-Date if (($now - $pod.lastQuery).TotalSeconds -ge 1) { $pod.sock.Send(0, 1, $stateQuery) $pod.lastQuery = $now } for ($m = $pod.sock.Receive(); $m -ne $null; $m = $pod.sock.Receive()) { $msg = $m.Message if ($msg -eq $null) { continue } $type = $msg.GetType().Name switch ($type) { 'StateResponseMessage' { if ("$($pod.state)" -ne "$($msg.ApplicationState)") { $log.Add("pod ${k}: state -> $($msg.ApplicationState)") Write-Output "pod ${k}: state -> $($msg.ApplicationState)" } $pod.state = $msg.ApplicationState } 'AcknowledgeEggFileMessage' { if (-not $pod.ack) { $log.Add("pod ${k}: EGG ACK (mesh complete)"); Write-Output "pod ${k}: EGG ACK (mesh complete)" } $pod.ack = $true } 'EndMissionMessage' { $pod.score = $msg.FinalScore $log.Add("pod ${k}: FINAL SCORE host=$($msg.PlayerHostID) score=$($msg.FinalScore)") Write-Output "pod ${k}: FINAL SCORE host=$($msg.PlayerHostID) score=$($msg.FinalScore)" } default { $log.Add("pod ${k}: $type") } } } if ("$($pod.state)" -eq 'WaitingForEgg' -and -not $pod.ack -and ($now - $pod.eggSent).TotalSeconds -ge 5) { Write-Output "pod ${k}: sending egg ($($chunks.Count) chunks)" foreach ($chunk in $chunks) { $pod.sock.Send(0, 1, $chunk) } $pod.eggSent = $now } } $bothWaiting = ("$($pods['A'].state)" -eq 'WaitingForLaunch') -and ("$($pods['B'].state)" -eq 'WaitingForLaunch') if ($bothWaiting -and -not $pods['A'].runSent) { Write-Output 'both pods WaitingForLaunch: sending RunMission' foreach ($k in @('A','B')) { $pods[$k].sock.Send(0, 1, (New-Object Munga.Net.RunMissionMessage)) $pods[$k].runSent = $true } } $bothRunning = ("$($pods['A'].state)" -eq 'RunningMission') -and ("$($pods['B'].state)" -eq 'RunningMission') if ($bothRunning -and $runStart -eq $null) { $runStart = Get-Date Write-Output "both pods RUNNING at $runStart - 60s race underway" } if ($runStart -ne $null -and -not $raceShotTaken -and ((Get-Date) - $runStart).TotalSeconds -ge 20) { $raceShotTaken = $true $screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds $bmp = New-Object System.Drawing.Bitmap($screen.Width, $screen.Height) $g = [System.Drawing.Graphics]::FromImage($bmp) $g.CopyFromScreen(0, 0, 0, 0, $bmp.Size) $g.Dispose() $bmp.Save("$scratch\twopod_race.png", [System.Drawing.Imaging.ImageFormat]::Png) $bmp.Dispose() Write-Output 'race screenshot saved' } if ($runStart -ne $null -and -not $pods['A'].stopSent -and ((Get-Date) - $runStart).TotalSeconds -ge 60) { Write-Output 'race time expired: sending StopMission(0) to both pods' foreach ($k in @('A','B')) { $pods[$k].sock.Send(0, 1, (New-Object Munga.Net.StopMissionMessage(0))) $pods[$k].stopSent = $true } } if ($pods['A'].stopSent -and $pods['A'].score -ne $null -and $pods['B'].score -ne $null) { Write-Output 'SUCCESS: both pods raced the mesh, stopped on command, scores collected' break } Start-Sleep -Milliseconds 250 } Start-Sleep -Seconds 5 Write-Output '=== summary ===' Write-Output "pod A: ack=$($pods['A'].ack) finalState=$($pods['A'].state) score=$($pods['A'].score)" Write-Output "pod B: ack=$($pods['B'].ack) finalState=$($pods['B'].state) score=$($pods['B'].score)" foreach ($k in @('A','B')) { try { $pods[$k].sock.Shutdown() } catch {} } Get-Process rpl4opt -ErrorAction SilentlyContinue | Stop-Process -Force -Confirm:$false -ErrorAction SilentlyContinue Write-Output '=== pod A netlog ===' Get-Content "$scratch\podA\rpl4.log" -ErrorAction SilentlyContinue | Select-String 'isten|onnect|ccept|egg|Egg|host|Host' | Select-Object -First 14 | ForEach-Object Line Write-Output '=== pod B netlog ===' Get-Content "$scratch\podB\rpl4.log" -ErrorAction SilentlyContinue | Select-String 'isten|onnect|ccept|egg|Egg|host|Host' | Select-Object -First 14 | ForEach-Object Line