# 同屏专家 C++ Local Smoke Runbook

This runbook covers the current C++ MVP on one Windows machine. It uses localhost, one relay server, one host client, and one viewer client.

## Prerequisites

- Windows with Visual Studio 2022 C++ desktop tools installed.
- PowerShell running from the repository root.
- No other process listening on TCP `127.0.0.1:38580`.

## One-Command Verification

Run the full native C++ MVP verification chain with:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\verify.ps1
```

This builds Debug and Release x64, runs `build\x64\Debug\SyncVisionTests.exe`, runs the server setup script tests, runs relay smoke, disabled-input smoke, file-transfer smoke, encrypted-client smoke, local explicit-relay smoke, reconnect smoke, LAN discovery smoke, host auto-server LAN smoke, encrypted start-host auto-server smoke, host-frame smoke, viewer-window smoke, creates a release-package smoke under `build\package-smoke`, verifies the generated package artifact, and runs a package-local installer dry-run.

By default, host-frame smoke requires real screen capture and fails when the desktop session is locked, non-interactive, or denies capture. For a degraded infrastructure check where this is expected, pass `-AllowCaptureSkip` explicitly:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\verify.ps1 -AllowCaptureSkip
```

For a shorter local-only smoke focused on localhost relay behavior, run:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-local-smoke.ps1
```

This runs relay, disabled-input, file-transfer, local explicit-relay, reconnect, LAN discovery, host auto-server LAN, host-frame, and viewer-window smokes on one machine.

Use `scripts\run-local-smoke.ps1 -AllowCaptureSkip` only for non-capturable sessions where skipping the visual-frame assertion is acceptable.

The default local verification does not install a Windows Service or create real Windows Firewall rules. Those checks require an elevated PowerShell session and are covered by the optional administrator smoke:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-elevated-service-install-smoke.ps1 -Configuration Release -ServiceAccount NetworkService
```

The default local verification also avoids mutating the user's OS clipboard. To explicitly prove real Windows Clipboard read/write behavior, run the optional clipboard edge smoke on an interactive desktop. It backs up the current clipboard data object, starts real host/viewer clients with fake peers, verifies OS clipboard writes and relayed clipboard payloads, stops the clients, and restores the previous clipboard data object in `finally`:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-os-clipboard-edge-smoke.ps1 -Configuration Debug
```

## Build

```powershell
powershell -ExecutionPolicy Bypass -File scripts\build.ps1 -Configuration Debug
```

The Debug executables are written to:

```text
build\x64\Debug\SyncVisionServer.exe
build\x64\Debug\SyncVisionClient.exe
```

## Automated Relay Smoke

This existing smoke script builds Debug, starts a temporary localhost relay server, verifies host-to-viewer frame relay, verifies viewer input is rejected before a host grant, verifies viewer-to-host input/file/clipboard relay after the grant, verifies live server status counters, and then stops the temporary server.

This automated smoke covers the relay authorization path for remote input. The next smoke covers `--disable-remote-input` host-side view-only behavior after a relay grant.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-relay-smoke.ps1
```

Expected success line:

```text
Relay smoke passed.
```

The live status check uses `scripts\status-server.ps1 -Probe` and expects `status=ok`, one host, one viewer, non-zero relay message counters, delivered-byte telemetry for frame, input, clipboard, and file relays, plus the server status fields for latest frame resolution/latency, dropped frames, and reconnect replacements.

## Automated Disabled Input Smoke

This smoke starts a real relay and a real host client with both `--grant-input-to viewer-disabled-input` and `--disable-remote-input`, registers a raw viewer client, sends an intentionally invalid input payload, verifies the relay observed the authorized input while the host remained registered and skipped local injection, then verifies viewer-to-host file transfer still works. Because the viewer is not connected when the host starts, the relay treats `--grant-input-to` as a one-shot pending grant for the next matching viewer connection. Once that viewer connects, the relay binds the grant to the raw viewer's current connection session, so reconnecting the same viewer device would require a fresh host grant before input is forwarded again.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-disabled-input-smoke.ps1
```

Expected success line:

```text
Disabled input smoke passed.
```

## Automated File Transfer Smoke

This smoke builds Debug, starts a temporary localhost relay server, sends a file from a viewer client to a host client, verifies the received bytes, and then stops the temporary processes.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-file-transfer-smoke.ps1
```

Expected success line:

```text
File transfer smoke passed.
```

## Automated Local Explicit-Relay Smoke

This smoke starts a temporary relay with LAN discovery disabled and a non-default token, then starts real host and viewer clients that both connect through an explicit `--server` address. The server listens on `0.0.0.0:<port>`, but both clients connect to `127.0.0.1:<port>`. It sends a viewer file to the host and verifies the received bytes, proving the explicit relay workflow without relying on LAN discovery or the localhost auto-server path.

This is not an external public reachability proof. It does not test DNS, NAT, cloud security groups, remote firewalls, or another client network.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-public-relay-smoke.ps1
```

Expected success line:

```text
Local explicit relay smoke passed.
```

## Automated Reconnect Smoke

This smoke starts a temporary relay and a real host client with short reconnect delays, stops the relay to force transport failure, restarts the relay on the same endpoint, and verifies through the status protocol that the host re-registers.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-reconnect-smoke.ps1
```

Expected success line:

```text
Reconnect smoke passed.
```

## Automated LAN Discovery Smoke

This smoke starts a temporary server with LAN discovery enabled, sends a UDP discovery probe, verifies the server response, and then stops the server.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\run-lan-discovery-smoke.ps1
```

Expected success line:

```text
LAN discovery smoke passed.
```

## Manual Localhost Smoke

Open two PowerShell terminals at the repository root.

### Terminal 1: Host And Local Relay

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-host.ps1 -Configuration Debug -Device host-001 -Token dev-token
```

To exercise host-side visual controls during manual smoke, add `-CaptureFps 24 -JpegQuality 82 -CaptureMonitor all -CaptureBackend auto`. The wrapper forwards these to `SyncVisionClient.exe --capture-fps 24 --jpeg-quality 82 --capture-monitor all --capture-backend auto`. `-CaptureMonitor` accepts `primary`, `all`, or a zero-based monitor index such as `1`; the default is `primary`. `-CaptureBackend` accepts `auto`, `gdi`, `desktop-duplication`, or `dxgi`; `auto` tries Desktop Duplication for single-monitor and all-monitor captures and falls back to GDI/WIC, while explicit `gdi` keeps the legacy WIC path.

The host wrapper starts `SyncVisionServer.exe` on `127.0.0.1:38580` when no local relay is already listening, waits for a SyncVision status response with the supplied token, and then starts the host client with `--server 127.0.0.1:38580`.

The C++ host binary has the same local default when used directly. If `--server` is omitted, `SyncVisionClient.exe --mode host` starts a sibling `SyncVisionServer.exe` with `--no-discovery`, then connects to it. Pass `--no-auto-server` to disable this behavior, or `--auto-server-listen 127.0.0.1:<port>` to choose the local listener.

The binary-level auto-start passes the server token through a per-user temporary config file instead of putting the token on the child process command line. That config is removed when the local relay process is stopped by the host client.

Equivalent direct commands:

```powershell
.\build\x64\Debug\SyncVisionServer.exe --listen 127.0.0.1:38580 --token dev-token
.\build\x64\Debug\SyncVisionClient.exe --mode host --server 127.0.0.1:38580 --device host-001 --token dev-token
```

To exercise the server token-file path during localhost smoke, put the shared token in a temporary file and pass `--token-file` instead:

```powershell
Set-Content -Encoding UTF8 .\build\local-smoke.token "dev-token"
.\build\x64\Debug\SyncVisionServer.exe --listen 127.0.0.1:38580 --token-file .\build\local-smoke.token
.\build\x64\Debug\SyncVisionClient.exe --mode host --server 127.0.0.1:38580 --device host-001 --token-file .\build\local-smoke.token
```

Remote keyboard/mouse control is denied by the relay until the host grants a viewer. To grant control in a manual smoke, add wrapper `-GrantInputTo viewer-001`, or append `--grant-input-to viewer-001` to the host binary command. If the viewer is not yet connected, this creates a one-shot pending grant for the next matching viewer connection. After it is consumed, the grant is bound to the viewer's active relay connection session; if the viewer reconnects, is replaced by another connection using the same device id, or falls out of the relay TTL window, input is denied until the host grants that new session:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-host.ps1 -Configuration Debug -Device host-001 -Token dev-token -GrantInputTo viewer-001
.\build\x64\Debug\SyncVisionClient.exe --mode host --server 127.0.0.1:38580 --device host-001 --token dev-token --grant-input-to viewer-001
```

For a view-only host smoke even when a relay grant exists, add the wrapper `-DisableRemoteInput` switch or append `--disable-remote-input` to the host binary command:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-host.ps1 -Configuration Debug -Device host-001 -Token dev-token -DisableRemoteInput
.\build\x64\Debug\SyncVisionClient.exe --mode host --server 127.0.0.1:38580 --device host-001 --token dev-token --disable-remote-input
```

To restrict viewer attachment during a smoke, add one or more wrapper `-AllowViewer viewer-001` values, or append `--allow-viewer viewer-001` to the host binary command. When a host has at least one allowed viewer, the relay rejects other viewer device IDs for that host with `viewer_not_authorized`; clients show a friendly next action and retain the raw reason as a diagnostic code. `-AllowViewer` controls who can connect; `-GrantInputTo` controls who can send keyboard/mouse input for the current viewer session.

To test interactive consent instead of a startup grant, start the viewer with `--request-input-on-start` and keep the host's default `--input-request-policy prompt`. The host shows a 同屏专家远程控制请求 dialog with the viewer id, host id, request id, and current relay session. Choosing Yes sends a session-bound grant; choosing No or closing the dialog sends a deny response.

Equivalent direct command using binary-level auto-start:

```powershell
.\build\x64\Debug\SyncVisionClient.exe --mode host --device host-001 --token dev-token
```

Expected signals:

```text
[SyncVisionServer] listening on 127.0.0.1:38580
[SyncVisionServer] LAN discovery listening on 0.0.0.0:38581 advertise=127.0.0.1:38580
[SyncVisionClient] host streaming device=host-001 server=127.0.0.1:38580
```

`dev-token` is accepted only on loopback listeners. Use a non-default token when binding the server to `0.0.0.0` or a LAN/public address.

For LAN hosting from the wrapper, keep the non-default token in a local token file and use `-Lan`. The wrapper listens on all interfaces and advertises the first usable non-loopback IPv4 address. Pass `-Advertise <reachable-host:port>` only when you need to override the automatic address choice.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-host.ps1 -Configuration Debug -Lan -Device host-001 -TokenFile "<path-to-file-containing-shared-token>" -AllowUnsafePublicRelay
```

### Terminal 2: Viewer

Start the host before the viewer. The current server rejects a viewer registration when the target host is offline.

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-viewer.ps1 -Configuration Debug -Device viewer-001 -Target host-001 -Token dev-token
```

Equivalent direct command:

```powershell
.\build\x64\Debug\SyncVisionClient.exe --mode viewer --device viewer-001 --target host-001 --token dev-token
```

Expected viewer signal:

```text
[SyncVisionClient] viewer connected device=viewer-001 target=host-001 server=127.0.0.1:38580
```

Expected viewer window signal: after the first valid frame, the viewer telemetry overlay remains visible and lists `frames`, `fps`, `bitrate`, latest frame size, last-frame `age`, and metadata-backed `resolution`, `latency`, and `dropped` frame counts when the sender provides them.

After both clients are connected, the server should log host and viewer registrations and frame relay messages. The viewer opens a window for received frames. Viewer input is relayed to the host process only when the host has a current `-GrantInputTo` / `--grant-input-to` grant for that viewer session; without the grant, the relay rejects input with `input_not_authorized` while video, clipboard, and file transfer remain available, and clients keep that raw reason in the diagnostic code. When input is granted, the current MVP applies basic native mouse, vertical/horizontal wheel, and keyboard input and logs applied input byte counts. For `-CaptureMonitor primary`, `all`, or an indexed monitor, the host translates frame-local mouse and wheel coordinates by the selected capture origin before injecting input regardless of whether the frame came from Desktop Duplication or GDI/WIC. Run granted-input smoke only on a trusted test desktop because viewer input can move the host cursor and send key events.

With `--disable-remote-input` enabled on the host, the viewer should still receive frames, but the host should log skipped disabled input and should not apply viewer keyboard or mouse input.

### Manual File Transfer

To verify file transfer manually, use the host wrapper with a receive directory and a viewer send file path. The quoted paths below intentionally include spaces.

```powershell
New-Item -ItemType Directory -Force -Path ".\build\manual send", ".\build\manual receive" | Out-Null
Set-Content -LiteralPath ".\build\manual send\syncvision manual smoke.txt" -Value "hello from manual file smoke"
```

Host:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-host.ps1 -Configuration Debug -Device host-001 -Token dev-token -ReceiveDir ".\build\manual receive"
```

Viewer:

```powershell
powershell -ExecutionPolicy Bypass -File scripts\start-viewer.ps1 -Configuration Debug -Device viewer-001 -Target host-001 -Token dev-token -SendFile ".\build\manual send\syncvision manual smoke.txt"
```

Equivalent direct commands:

```powershell
.\build\x64\Debug\SyncVisionClient.exe --mode host --device host-001 --token dev-token --receive-dir ".\build\manual receive"
.\build\x64\Debug\SyncVisionClient.exe --mode viewer --device viewer-001 --target host-001 --token dev-token --send-file ".\build\manual send\syncvision manual smoke.txt"
```

The host should write the sent file to `.\build\manual receive\syncvision manual smoke.txt`.

## Stop

- Close the viewer window to stop the viewer.
- Press `Ctrl+C` in the host terminal. The host wrapper stops the local relay process that it started.

The current MVP stores relay state in memory. Restarting the server disconnects existing clients.
