name: Windows build and validation on: workflow_dispatch: push: paths: - "src/**" - "tests/**" - "mods/**" - "data/**" - "art/**" - "bin/**" - "Cargo.toml" - "Cargo.lock" - "run.cmd" - "runtime-manifest.json" - ".gitattributes" - "runtime-windows-manifest.json" - "tools/assemble_windows.py" - "tools/assemble_runtime.py" - "tools/test_runtime_archives.py" - ".github/workflows/windows.yml" permissions: contents: read jobs: windows: runs-on: windows-latest timeout-minutes: 60 steps: - name: Enable long paths for bundled artwork proof files shell: pwsh run: git config --global core.longpaths true - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.13" - name: Install the pinned Rust toolchain run: | rustup toolchain install 1.99.0 --profile minimal rustup default 1.99.0 - name: Download and verify the official SDL2 SDK shell: pwsh run: | $url = "https://github.com/libsdl-org/SDL/releases/download/release-2.32.10/SDL2-devel-2.32.10-VC.zip" Invoke-WebRequest $url -OutFile "$env:RUNNER_TEMP\SDL2.zip" $expected = "af347939395a58b365846aaea27391e69f9ec9d4dd650d6ac40802159b418a6e" if ((Get-FileHash "$env:RUNNER_TEMP\SDL2.zip" -Algorithm SHA256).Hash.ToLower() -ne $expected) { throw "SDL2 SDK checksum mismatch" } Expand-Archive "$env:RUNNER_TEMP\SDL2.zip" "$env:RUNNER_TEMP\sdl2" - name: Run portable simulation, mod and Windows save tests run: cargo test --locked --no-default-features - name: Complete Black Tide with carried pilot saves and paid repairs shell: pwsh run: | $env:RAPTOR_TEST_DATA = "$env:GITHUB_WORKSPACE\data" $env:RAPTOR_STAGE2_REPORT = "$env:RUNNER_TEMP\stage2-route.json" cargo test --release --locked --no-default-features --test iron_coast_stage2 starter_ship_completes_all_eight_missions_with_paid_repairs_and_finale_replay -- --ignored --nocapture if ($LASTEXITCODE -ne 0) { throw "Native Windows campaign continuation failed" } - name: Check the included Windows executable and launcher shell: pwsh run: | $package = $env:GITHUB_WORKSPACE $manifest = Get-Content "$package\runtime-windows-manifest.json" -Raw | ConvertFrom-Json foreach ($entry in $manifest.files.PSObject.Properties) { $actual = (Get-FileHash (Join-Path $package $entry.Name) -Algorithm SHA256).Hash.ToLower() if ($actual -ne $entry.Value) { throw "Bundled Windows hash mismatch: $($entry.Name)" } } Set-Location $env:RUNNER_TEMP & "$package\run.cmd" --verify-assets if ($LASTEXITCODE -ne 0) { throw "Included Windows launcher failed" } $env:SDL_VIDEODRIVER = "dummy" $env:SDL_AUDIODRIVER = "dummy" & "$package\run.cmd" --profile-root "$env:RUNNER_TEMP\bundled-profile" --smoke-frames 32 --preview-mission base:sector_1_wave_1 if ($LASTEXITCODE -ne 0) { throw "Included Windows SDL gameplay failed" } - name: Test runtime archive payload selection run: python -m unittest discover -s tools -p test_runtime_archives.py - name: Build and package Windows desktop run: python tools/assemble_windows.py --target x86_64-pc-windows-msvc --sdl2-dir "$env:RUNNER_TEMP\sdl2\SDL2-2.32.10" --zip dist/RustRaptor-windows-x86_64.zip - name: Check displayed boss phases and SD HD shop artwork shell: pwsh run: | $separator = [char]31 $sdkLibrary = "$env:RUNNER_TEMP\sdl2\SDL2-2.32.10\lib\x64" $env:CARGO_ENCODED_RUSTFLAGS = "-L${separator}native=$sdkLibrary${separator}-C${separator}target-feature=+crt-static" $env:PATH = "$env:GITHUB_WORKSPACE\bin;$env:PATH" cargo test --release --locked --target x86_64-pc-windows-msvc --target-dir target/windows-runtime --lib scripted_enemy_phase_frames_reach_sd_hd_sprites_and_both_shadow_layers -- --nocapture if ($LASTEXITCODE -ne 0) { throw "Windows phase sprite and shadow regression failed" } cargo test --release --locked --target x86_64-pc-windows-msvc --target-dir target/windows-runtime --lib iron_coast_stage2_art_and_shop_preview_captures_preserve_simulation -- --ignored --nocapture if ($LASTEXITCODE -ne 0) { throw "Windows Stage2 SD HD phase and shop preview failed" } Remove-Item Env:CARGO_ENCODED_RUSTFLAGS - name: Validate packaged data, HD artwork, mods and SDL gameplay shell: pwsh run: | $portable = "$env:RUNNER_TEMP\Portable Windows Ω folder" Expand-Archive "$env:GITHUB_WORKSPACE\dist\RustRaptor-windows-x86_64.zip" $portable $package = "$portable\RustRaptor" $exe = "$package\bin\raptor-rust.exe" Set-Location $env:RUNNER_TEMP & "$package\run.cmd" --verify-assets if ($LASTEXITCODE -ne 0) { throw "Catalog validation failed" } & $exe --validate-hd if ($LASTEXITCODE -ne 0) { throw "HD validation failed" } $mods = (Get-ChildItem "$package\mods" -Directory | Where-Object { Test-Path "$($_.FullName)\mod.toml" } | ForEach-Object { $_.Name }) -join "," & $exe --mods "$package\mods" --profile-root "$env:RUNNER_TEMP\raptor-profile" --validate-mods $mods if ($LASTEXITCODE -ne 0) { throw "Mod validation failed" } $env:SDL_VIDEODRIVER = "dummy" $env:SDL_AUDIODRIVER = "dummy" & $exe --profile-root "$env:RUNNER_TEMP\raptor-profile" --smoke-frames 32 --preview-mission base:sector_1_wave_1 --screenshot "$env:GITHUB_WORKSPACE\dist\windows-gameplay.png" if ($LASTEXITCODE -ne 0) { throw "Windows SDL gameplay failed" } $stageProfile = "$env:RUNNER_TEMP\stage2-profile" New-Item -ItemType Directory -Force "$stageProfile\config" | Out-Null $selection = (Get-ChildItem "$package\mods" -Directory | Where-Object { Test-Path "$($_.FullName)\mod.toml" } | ForEach-Object { '"' + $_.Name + '"' }) -join "," @" enabled_mods = [$selection] hd_mode = true scale = 4 width = 1920 height = 1080 "@ | Set-Content "$stageProfile\config\config.toml" -Encoding utf8 $env:RAPTOR_AUDIO_CAPTURE = "$env:GITHUB_WORKSPACE\dist\windows-stage2-audio.wav" $env:RAPTOR_AUDIO_CAPTURE_SECONDS = "3" & $exe --profile-root $stageProfile --smoke-frames 240 --preview-mission iron_coast_stage2_mission_1:mission_01 --screenshot "$env:GITHUB_WORKSPACE\dist\windows-stage2.png" if ($LASTEXITCODE -ne 0) { throw "Windows Stage2 SDL gameplay failed" } if (!(Test-Path $env:RAPTOR_AUDIO_CAPTURE)) { throw "Windows Stage2 audio capture missing" } python -c "import array, wave; w=wave.open(r'$env:RAPTOR_AUDIO_CAPTURE'); p=array.array('h',w.readframes(w.getnframes())); assert w.getframerate()==44100 and w.getnchannels()==2 and w.getnframes()>=44100 and sum(x!=0 for x in p)>1000" if ($LASTEXITCODE -ne 0) { throw "Windows Stage2 audio was empty or malformed" } Add-Content "$stageProfile\config\config.toml" "music_volume = 0" $env:RAPTOR_AUDIO_CAPTURE = "$env:GITHUB_WORKSPACE\dist\windows-stage2-sfx.wav" & $exe --profile-root $stageProfile --smoke-frames 240 --preview-mission iron_coast_stage2_mission_8:mission_08 if ($LASTEXITCODE -ne 0) { throw "Windows Stage2 sound-effect gameplay failed" } python -c "import array, wave; w=wave.open(r'$env:RAPTOR_AUDIO_CAPTURE'); p=array.array('h',w.readframes(w.getnframes())); assert w.getframerate()==44100 and w.getnchannels()==2 and w.getnframes()>=44100 and sum(x!=0 for x in p)>1000" if ($LASTEXITCODE -ne 0) { throw "Windows Stage2 sound effects were silent" } Remove-Item Env:RAPTOR_AUDIO_CAPTURE Remove-Item Env:RAPTOR_AUDIO_CAPTURE_SECONDS - uses: actions/upload-artifact@v4 with: name: RustRaptor-windows-x86_64 path: | dist/RustRaptor-windows-x86_64.zip dist/windows-gameplay.png dist/windows-stage2.png dist/windows-stage2-audio.wav dist/windows-stage2-sfx.wav .reference/iron-coast-stage2/visual/*.png if-no-files-found: error