docs: add validation guide, hardware and Windows scripts, CI

Documents what the rig proved and what it found, adds the hardware sign-off
script for the Hyprland machine and an (untested) Windows validation script,
and updates CI to build the cached OBS runtime, run tests and lint the Windows
code paths.
This commit is contained in:
2026-10-08 14:31:54 +02:00
parent 9178a303ca
commit 3588d5b212
5 changed files with 362 additions and 19 deletions
+42 -4
View File
@@ -5,20 +5,58 @@ on:
pull_request:
jobs:
checks:
linux:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
- uses: https://github.com/dtolnay/rust-toolchain@1.98.0
with:
components: rustfmt, clippy
- name: Install native UI build dependencies
- name: Install native build dependencies
run: |
sudo apt-get update
sudo apt-get install -y build-essential pkg-config libfontconfig1-dev libfreetype6-dev libwayland-dev libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev libssl-dev
# GPUI, plus clang for the libobs bindings.
sudo apt-get install -y build-essential pkg-config clang libfontconfig1-dev libfreetype6-dev \
libwayland-dev libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev libssl-dev
# OBS 32.0.4 libobs and the bundled plugins (scripts/obs/build-linux.sh).
sudo apt-get install -y cmake ninja-build extra-cmake-modules libavcodec-dev libavdevice-dev \
libavfilter-dev libavformat-dev libavutil-dev libswresample-dev libswscale-dev libx264-dev \
libcurl4-openssl-dev libmbedtls-dev libgl1-mesa-dev libglvnd-dev libgles2-mesa-dev libjansson-dev \
libx11-dev libx11-xcb-dev libxcb-randr0-dev libxcb-shm0-dev libxcb-xinerama0-dev \
libxcb-composite0-dev libxcb-xfixes0-dev libxcb-xinput-dev libxcomposite-dev libxinerama-dev \
libxss-dev libpci-dev libpipewire-0.3-dev libpulse-dev libudev-dev libva-dev libdrm-dev \
librist-dev libsrt-openssl-dev uthash-dev libsimde-dev nlohmann-json3-dev libffmpeg-nvenc-dev \
uuid-dev libglib2.0-dev
- name: Cache the OBS runtime
uses: https://github.com/actions/cache@v4
with:
path: target/obs-runtime/linux-x86_64
key: obs-runtime-linux-${{ hashFiles('scripts/obs/versions.env', 'scripts/obs/build-linux.sh') }}
- name: Build the OBS runtime
run: |
if [ ! -f target/obs-runtime/linux-x86_64/VERSION ]; then
scripts/obs/build-linux.sh
fi
- name: Check formatting
run: cargo fmt --all -- --check
- name: Check Rust
- name: Lint
run: cargo clippy --locked --workspace --all-targets -- -D warnings
- name: Test
run: cargo test --locked --workspace
- name: Build
run: cargo build --locked --workspace
windows-cross-check:
runs-on: ubuntu-latest
steps:
- uses: https://github.com/actions/checkout@v4
- uses: https://github.com/dtolnay/rust-toolchain@1.98.0
with:
components: clippy
targets: x86_64-pc-windows-msvc
# Type-checks the Windows code paths; nothing is linked or run. The GPUI
# crate needs the MSVC C toolchain and is checked on Windows instead.
- name: Lint Windows code paths
run: >
cargo clippy --locked --target x86_64-pc-windows-msvc
-p clipbin-core -p clipbin-obs -p clipbin-probe --all-targets -- -D warnings
+38 -14
View File
@@ -1,26 +1,34 @@
# Clipbin Desktop
An open-source game recorder for Windows and Linux, inspired by Plays.tv. Rust and GPUI provide the desktop interface; the recording engine will use libobs and official OBS capture components.
An open-source game recorder for Windows and Linux, inspired by Plays.tv. Rust and GPUI provide the desktop interface; the recording engine uses libobs and official OBS capture components.
[SPEC.md](SPEC.md) defines the agreed v1 product requirements.
## Current state
This is the initial project scaffold. It opens a native GPUI window, displays the agreed recording defaults, and always reports **Not recording**.
The recording foundation is being validated (see [docs/validation.md](docs/validation.md)). `clipbin-obs` records official OBS capture sources through one hardware encoder into keyframe-aligned segments and reports honest status; `clipbin-probe` exercises it. It passes the Linux headless rig; hardware sign-off on Hyprland and all Windows validation are still to do.
Capture, Steam detection, recording, playback, clipping, bookmarks, persistence, global hotkeys, autostart, and background recording are not implemented yet. Closing the window exits this initial shell. The full application will keep its recorder running independently of window closure, as specified in SPEC.md.
The desktop app is still the initial shell: it opens a GPUI window, displays the agreed recording defaults, and always reports **Not recording**. Steam detection, the recorder in the app, playback, clipping, bookmarks, persistence, global hotkeys, autostart, and background recording are not implemented yet.
Windows is a first-class target. Every change must keep the Windows code paths building (`cargo clippy --target x86_64-pc-windows-msvc`) and list what still needs validating on Windows.
## Layout
```text
crates/
clipbin-core/ Settings and recorder state; independent of GPUI and native OBS.
clipbin-obs/ Recording engine over the pinned libobs runtime; all unsafe code is in src/ffi.rs.
clipbin-probe/ Validation probe for capture, encoders, segments and status.
clipbin-desktop/ GPUI application and initial session-library view.
.gitea/workflows/ Rust formatting and compilation checks.
scripts/obs/ Assemble the pinned OBS runtime for Linux (built from source) and Windows (official release).
scripts/rig/ Headless Wayland/X11 desktop for capture tests on machines without a display.
scripts/validate/ Validation suites: Linux rig, Linux hardware, Windows.
docs/validation.md Validation status and findings.
.gitea/workflows/ Formatting, lint, test and Windows cross-checks.
SPEC.md Product requirements and acceptance checks.
```
The core currently has no external dependencies. Keep capture and session operations out of the UI thread when the recording engine is added.
The core has no external dependencies. Keep capture and session operations out of the UI thread.
## Development
@@ -31,9 +39,23 @@ Keep the lockfile checked in and use `--locked`. It currently pins `libc` to 0.2
On Arch/CachyOS, install the native build dependencies:
```sh
sudo pacman -S --needed base-devel pkgconf fontconfig freetype2 wayland libxcb libxkbcommon libxkbcommon-x11 openssl
sudo pacman -S --needed base-devel pkgconf fontconfig freetype2 wayland libxcb libxkbcommon libxkbcommon-x11 openssl clang
```
### OBS runtime
Clipbin bundles a pinned OBS **32.0.4** runtime (`scripts/obs/versions.env`) with only the official plugins it uses. The `libobs` crate's headers match that version; the engine refuses to start against any other.
- Linux: `scripts/obs/build-linux.sh` builds libobs and the plugins from source into `target/obs-runtime/linux-x86_64`. Arch/CachyOS build dependencies:
```sh
sudo pacman -S --needed cmake ninja extra-cmake-modules ffmpeg x264 pipewire libpulse libdrm libva jansson curl libxcomposite libxinerama libxrandr wayland-protocols simde uthash nlohmann-json mbedtls3 pciutils srt librist ffnvcodec-headers amf-headers
```
- Windows: `scripts/obs/assemble-windows.sh` (Git Bash) verifies and unpacks the official release zip into `target/obs-runtime/windows-x86_64`, copying files byte-for-byte so the Game Capture hook keeps its signature.
Development builds find the runtime through `.cargo/config.toml`; `clipbin-probe`'s build script places `obs-ffmpeg-mux` (and on Windows the OBS DLLs) next to the executable, where OBS looks for them.
On Debian/Ubuntu:
```sh
@@ -41,7 +63,7 @@ sudo apt-get update
sudo apt-get install build-essential pkg-config libfontconfig1-dev libfreetype6-dev libwayland-dev libxcb1-dev libxkbcommon-dev libxkbcommon-x11-dev libssl-dev
```
For Windows, use the MSVC Rust toolchain and Visual Studio Build Tools with the Desktop development with C++ workload. Windows support needs validation on a Windows machine.
For Windows, use the MSVC Rust toolchain and Visual Studio Build Tools with the Desktop development with C++ workload. Windows support needs validation on a Windows machine; see [docs/validation.md](docs/validation.md#windows).
Run the desktop shell from an active graphical session:
@@ -58,9 +80,13 @@ Checks:
```sh
cargo fmt --all -- --check
cargo clippy --locked --workspace --all-targets -- -D warnings
cargo build --locked --workspace
cargo test --locked --workspace
# Windows code paths, from any platform (rustup target add x86_64-pc-windows-msvc):
cargo clippy --locked --target x86_64-pc-windows-msvc -p clipbin-core -p clipbin-obs -p clipbin-probe --all-targets -- -D warnings
```
Recording validation on Linux, headless: `scripts/validate/linux-rig.sh`.
The core can also be checked without native UI dependencies:
```sh
@@ -69,14 +95,12 @@ cargo check --locked -p clipbin-core
## First recording milestone
1. Validate official OBS capture and recording-health signals on CachyOS, Hyprland and the RX 9070 XT at 1440p60.
2. Select and pin a tested OBS runtime, Rust bindings, and official plugin set together. Preserve the original signed Windows capture-hook binaries.
3. Prove one continuous hardware encoder can produce short keyframe-aligned segments and remux clips across their boundaries while recording continues.
1. Validate official OBS capture and recording-health signals on CachyOS, Hyprland and the RX 9070 XT at 1440p60. *Rig validated; hardware sign-off pending.*
2. Select and pin a tested OBS runtime, Rust bindings, and official plugin set together. Preserve the original signed Windows capture-hook binaries. *Pinned to OBS 32.0.4 and libobs crate 5.0.1; Windows runtime unvalidated.*
3. Prove one continuous hardware encoder can produce short keyframe-aligned segments and remux clips across their boundaries while recording continues. *Proven in the rig with VA-API H.264 and HEVC.*
4. Integrate the recorder without blocking GPUI, then add native playback and seeking.
OBS is not linked or bundled in this scaffold. Adding an unused native dependency would not validate the required runtime/plugin compatibility. The runtime bundle and adapter belong to the first recording milestone.
The capture integration must use official OBS components: Windows Game Capture/Window Capture, X11 Xcomposite, and Wayland PipeWire/desktop-portal capture. Third-party game hooks are excluded.
The capture integration uses official OBS components only: Windows Game Capture/Window Capture, X11 Xcomposite, and Wayland PipeWire/desktop-portal capture. Third-party game hooks are excluded.
## License
+117
View File
@@ -0,0 +1,117 @@
# Recording foundation validation
The first recording milestone proves the OBS capture, encoder and segment
pipeline before the UI is built (SPEC.md, "Implementation validation before
building out the UI"). `clipbin-probe` drives the real `clipbin-obs` engine and
prints `key=value` lines; the scripts in `scripts/validate/` turn them into
pass/fail results.
| Where | Script | Status |
| --- | --- | --- |
| Linux, headless rig (Wayland portal + Xwayland) | `scripts/validate/linux-rig.sh` | All 9 scenarios pass on devbox (Intel UHD 770, VA-API) |
| Linux, real session (CachyOS, Hyprland, RX 9070 XT, 1440p60) | `scripts/validate/linux-hardware.sh` | Not run yet |
| Windows (Game Capture, NVENC/AMF/QSV) | `scripts/validate/windows.ps1` | Not run yet; script untested |
## Probe commands
```sh
cargo run -p clipbin-probe -- encoders --verify # hardware encoders per codec, proven by encoding
cargo run -p clipbin-probe -- record --target monitor # records; prints status, health and segments
cargo run -p clipbin-probe -- check # keyframes, codec, durations, cross-boundary remux
```
`record` targets `monitor`, `window` or `game` (Game Capture on Windows; the
same as `window` elsewhere). On Windows and X11, `--window` takes OBS's window
identifier; on Wayland the portal picker chooses and `--restore-token` reuses
an earlier choice. See the top of `crates/clipbin-probe/src/main.rs` for all
options. `check` needs FFmpeg's `ffprobe` and `ffmpeg` on `PATH`.
## Linux headless rig
`scripts/rig/headless.sh [--x11] <command>` runs a command inside a private
D-Bus session with PipeWire, a virtual audio sink, headless Sway on the GPU,
xdg-desktop-portal-wlr (auto-selecting the only output) and a 60 FPS test
pattern with a tone. `--x11` runs the game and command on Sway's Xwayland.
Arch packages: `sway xdg-desktop-portal xdg-desktop-portal-wlr xorg-xwayland
xorg-xwininfo pipewire pipewire-pulse wireplumber libpulse ffmpeg` plus a VA-API
driver (`intel-media-driver` or `libva-mesa-driver`).
The suite covers:
1. Encoder verification: H.264 must be usable; HEVC and AV1 are reported.
2. Wayland monitor capture in H.264 and HEVC: segments start on keyframes,
last 2 s, remux to MP4 across boundaries, decode cleanly, carry the tone.
3. X11 window capture (Xcomposite).
4. Failures that must end in `status=failed`: PipeWire stream lost, captured
X11 window closed, segment directory unwritable, free space below the margin.
5. A portal that grants no usable source never reports `Recording`.
## Linux hardware sign-off
Run `scripts/validate/linux-hardware.sh` from the Hyprland session. It verifies
encoders at 1440p60, records the monitor with each usable codec (the first run
shows the portal picker; later runs pass the restore token and ask whether the
picker stayed away), records a window, and asks you to close a window
mid-recording. Send back `report.txt` from the output directory.
## Windows
Run `scripts/validate/windows.ps1 -GameWindow "<title>:<class>:<exe>"` with a
game running. It assembles the official OBS 32.0.4 runtime, checks the Game
Capture hook signatures, builds the probe, verifies NVENC/AMF/QSV encoders and
records Monitor, Game and Window Capture.
Windows status:
- `clipbin-core`, `clipbin-obs` and `clipbin-probe` pass `cargo clippy` for
`x86_64-pc-windows-msvc` (cross-checked from Linux). Nothing has been linked
or run on Windows.
- The GPUI desktop crate has not been checked for Windows; its `ring`
dependency needs the MSVC C toolchain.
- The assembled runtime keeps the six Game Capture hook binaries byte-identical
to the release, with their Authenticode signatures present; signature
validity has not been checked on Windows.
- Unvalidated: encoder ids and settings for NVENC, AMF and QSV; Game Capture
hooking and its loss detection (the engine expects the source size to drop
to 0); WASAPI audio; `obs-ffmpeg-mux.exe` beside the executable; linking
`dstr_vprintf` through the libobs crate's `obs.lib`.
## Findings for the next milestones
- **Plugin loading.** libobs always adds default plugin paths: the build-time
install prefix on Linux and `../../obs-plugins/64bit` relative to the
working directory on Windows. Searching would load other OBS installations'
plugins or load ours twice, so the engine opens exactly the bundled
allowlist (`runtime::PLUGINS`) by path.
- **Segment timestamps.** The FFmpeg muxer restarts each segment's video and
audio timestamps at zero independently. Concatenating segments naively leaves
22–43 ms video gaps at boundaries. Video is constant frame rate and AAC
packets are continuous, so a segment's exact session offset is the count of
earlier frames and audio packets; the A/V offset stays within one AAC packet
(≤21 ms) and does not drift. The timeline and export work must place
segments this way.
- **Availability.** With 1 s keyframes and 2 s segments, splits happen on
schedule every 2.0 s, so the newest complete footage is about 2 s behind
plus finalisation: inside the spec's 2–5 s window.
- **Honest status needs more than OBS's counters.** OBS kept counting bytes
into a dead muxer process, and once reported a clean stop after the disk
filled. Status therefore requires footage to grow on disk, and recording
stops while 1 GB remains so the last segment can be finalised.
- **PipeWire source loss.** OBS's PipeWire capture keeps its size and renders
its last frame after the stream stops, and only logs the stream state. The
engine reads that log line (stable for the pinned OBS version). A more robust
follow-up is watching the PipeWire node state directly.
- **Audio capture failures are not detected yet.** When the PulseAudio server
was unreachable, recording continued with a silent track.
- **Linux runtime dependencies.** The Linux OBS build links the system's
FFmpeg, PipeWire and other libraries, so it is tied to the distribution it
was built on. Packaging (for example Flatpak, or bundling FFmpeg) is an open
decision; OBS 32.0 needs mbedtls 3 (`mbedtls3` on Arch).
- **OBS objects.** Scenes and sources are created private (OBS 31+ otherwise
registers scenes with the main canvas, which keeps them alive), unknown
source types fail instead of becoming placeholders, and shutdown drains
libobs's destroy queue first.
- **Plugin configuration.** libobs is started without a module config path;
set one under the app's config directory before plugins that save settings
are added.
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# Hardware sign-off for Linux: run from a real desktop session (Hyprland,
# another Wayland compositor, or X11) on the machine being validated.
#
# scripts/validate/linux-hardware.sh [output-dir]
#
# Records at 1440p60 with every usable hardware codec, checks the segments,
# confirms the portal restores its selection without asking again, and
# records a window. Writes report.txt in the output directory.
set -uo pipefail
repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
out="${1:-${TMPDIR:-/tmp}/clipbin-hardware}"
probe="${repo}/target/debug/clipbin-probe"
report="${out}/report.txt"
rm -rf "${out}" && mkdir -p "${out}"
format=(--width 2560 --height 1440 --fps 60 --bitrate 40000)
cargo build --locked -p clipbin-probe --manifest-path "${repo}/Cargo.toml" || exit 1
log() { echo "$*" | tee -a "${report}"; }
ask() {
local answer
read -r -p "$1 [y/n] " answer
log "answer: $1 -> ${answer}"
}
log "Clipbin hardware validation $(date -Is)"
log "host: $(uname -r) session=${XDG_SESSION_TYPE:-?} desktop=${XDG_CURRENT_DESKTOP:-?}"
log "gpu: $(lspci 2>/dev/null | grep -iE 'vga|display' | cut -d: -f3- | xargs)"
"${probe}" encoders --verify "${format[@]}" --out "${out}/encoders" > "${out}/encoders.log" 2> "${out}/encoders.obs.log"
log "$(grep -E '^(obs_version|usable\.|verify\.)' "${out}/encoders.log")"
token=""
for codec in h264 hevc av1; do
if grep -q "^usable\.${codec}=none" "${out}/encoders.log"; then
log "SKIP ${codec}: no usable hardware encoder"
continue
fi
if [[ -z "${token}" ]]; then
echo
echo "Recording a monitor for 20 seconds with ${codec}. The portal will ask"
echo "you to pick a monitor: pick one and keep some motion on screen."
fi
dir="${out}/monitor-${codec}"
restore=()
[[ -n "${token}" ]] && restore=(--restore-token "${token}")
"${probe}" record --target monitor --codec "${codec}" "${restore[@]}" --seconds 20 "${format[@]}" \
--out "${dir}" > "${dir}.log" 2> "${dir}.obs.log"
record_exit=$?
"${probe}" check --codec "${codec}" --out "${dir}" >> "${dir}.log" 2>&1
check_exit=$?
log "monitor-${codec}: record_exit=${record_exit} check_exit=${check_exit} $(grep -E '^(encoder|recorded|check)=' "${dir}.log" | tr '\n' ' ')"
log "$(grep -E 'status=' "${dir}.log" | tr '\n' ' ')"
if [[ -z "${token}" ]]; then
token="$(sed -n 's/^restore_token=//p' "${dir}.log")"
log "restore token received: $([[ -n "${token}" ]] && echo yes || echo no)"
else
ask "Did ${codec} start WITHOUT showing the portal picker again?"
fi
done
echo
echo "Now recording a window for 20 seconds. Start a game (or any moving"
echo "window) first, then pick it in the portal."
read -r -p "Press Enter when ready. " _
dir="${out}/window-h264"
"${probe}" record --target window --seconds 20 "${format[@]}" --out "${dir}" > "${dir}.log" 2> "${dir}.obs.log"
record_exit=$?
"${probe}" check --out "${dir}" >> "${dir}.log" 2>&1
log "window-h264: record_exit=${record_exit} check_exit=$? $(grep -E '^(recorded|check)=' "${dir}.log" | tr '\n' ' ')"
echo
echo "Last: source loss. Record a window again, then close that window"
echo "about 5 seconds in. The probe should report a failure."
read -r -p "Press Enter when ready. " _
dir="${out}/window-closed"
"${probe}" record --target window --seconds 20 "${format[@]}" --out "${dir}" > "${dir}.log" 2> "${dir}.obs.log"
log "window-closed: exit=$? $(grep -E 'status=failed' "${dir}.log" | head -1)"
echo
echo "Report: ${report}"
echo "Recordings and logs: ${out}"
+80
View File
@@ -0,0 +1,80 @@
# Hardware validation for Windows. Not yet run on Windows: it is the first
# job for the Windows implementor, who should fix what it finds.
#
# powershell -ExecutionPolicy Bypass -File scripts\validate\windows.ps1 [-Out <dir>] [-GameWindow "<title>:<class>:<exe>"]
#
# Needs: Rust (rustup, MSVC toolchain), Visual Studio Build Tools (C++),
# Git for Windows (its bash runs scripts/obs/assemble-windows.sh) and FFmpeg
# on PATH for the segment checks (winget install Gyan.FFmpeg).
#
# -GameWindow is OBS's window identifier for a running game, as shown in OBS
# Studio's Game Capture "Window" list: "<title>:<window class>:<executable>".
param(
[string]$Out = "$env:TEMP\clipbin-windows",
[string]$GameWindow = ""
)
$ErrorActionPreference = "Stop"
$repo = Resolve-Path "$PSScriptRoot\..\.."
$report = Join-Path $Out "report.txt"
Remove-Item -Recurse -Force $Out -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Force $Out | Out-Null
function Log($text) { $text | Tee-Object -FilePath $report -Append }
Log "Clipbin Windows validation $(Get-Date -Format o)"
Log "os: $((Get-CimInstance Win32_OperatingSystem).Caption) $([Environment]::OSVersion.Version)"
Log "gpu: $((Get-CimInstance Win32_VideoController).Name -join ', ')"
# 1. The pinned official OBS runtime, with the Game Capture hook still signed.
& bash "$repo/scripts/obs/assemble-windows.sh"
if ($LASTEXITCODE -ne 0) { throw "assembling the OBS runtime failed" }
$runtime = Join-Path $repo "target\obs-runtime\windows-x86_64"
Get-ChildItem "$runtime\data\obs-plugins\win-capture\*" -Include *.exe, *.dll | ForEach-Object {
$signature = Get-AuthenticodeSignature $_.FullName
Log ("signature {0}: {1} ({2})" -f $_.Name, $signature.Status, $signature.SignerCertificate.Subject)
}
# 2. Build the probe (also checks the Windows code paths compile and link).
Push-Location $repo
cargo build --locked -p clipbin-probe
if ($LASTEXITCODE -ne 0) { throw "building clipbin-probe failed" }
Pop-Location
$probe = Join-Path $repo "target\debug\clipbin-probe.exe"
$format = @("--width", "2560", "--height", "1440", "--fps", "60", "--bitrate", "40000")
function Record($name, $codec, [string[]]$arguments) {
$dir = Join-Path $Out $name
& $probe record @arguments --codec $codec @format --seconds 20 --out $dir > "$dir.log" 2> "$dir.obs.log"
$recordExit = $LASTEXITCODE
& $probe check --codec $codec --out $dir >> "$dir.log" 2>&1
$checkExit = $LASTEXITCODE
Log "${name}: record_exit=$recordExit check_exit=$checkExit $((Select-String -Path "$dir.log" -Pattern '^(encoder|recorded|check)=').Line -join ' ')"
Log ((Select-String -Path "$dir.log" -Pattern 'status=').Line -join ' ')
}
# 3. Hardware encoders: NVENC, AMF or QSV for H.264, HEVC and AV1.
& $probe encoders --verify @format --out (Join-Path $Out "encoders") > "$Out\encoders.log" 2> "$Out\encoders.obs.log"
Log ((Select-String -Path "$Out\encoders.log" -Pattern '^(obs_version|usable\.|verify\.)').Line -join "`n")
# 4. Monitor capture with each usable codec.
foreach ($codec in "h264", "hevc", "av1") {
if (Select-String -Path "$Out\encoders.log" -Pattern "^usable\.$codec=none" -Quiet) {
Log "SKIP ${codec}: no usable hardware encoder"
continue
}
Record "monitor-$codec" $codec @("--target", "monitor")
}
# 5. Game Capture (signed hook) and Window Capture of a running game.
if ($GameWindow) {
Record "game-capture" "h264" @("--target", "game", "--window", $GameWindow)
Record "window-capture" "h264" @("--target", "window", "--window", $GameWindow)
Write-Host "Last: start a recording, then quit the game about 5 seconds in."
Read-Host "Press Enter when ready"
$dir = Join-Path $Out "game-closed"
& $probe record --target game --window $GameWindow @format --seconds 20 --out $dir > "$dir.log" 2> "$dir.obs.log"
Log "game-closed: exit=$LASTEXITCODE $((Select-String -Path "$dir.log" -Pattern 'status=failed').Line | Select-Object -First 1)"
} else {
Log "SKIP game capture: pass -GameWindow with a running game's window identifier"
}
Write-Host "Report: $report"