# Phase 9: Installer for Tester Distribution - Research

**Researched:** 2026-03-23
**Domain:** Windows installer packaging (Inno Setup), CMake build integration, git versioning
**Confidence:** HIGH

## Summary

Phase 9 packages the BeyondProximity driver into a single-click .exe installer using Inno Setup. The installer replicates the logic in `scripts/deploy_driver.ps1`: copies the DLL and manifest to the nested `bin/BeyondProximity/` directory inside the Beyond driver package, cleans up old flat-deploy artifacts, restores the root manifest to `resourceOnly=true`, and registers the nested driver with `vrpathreg`. The user has locked all major decisions -- Inno Setup technology, silent install UX, SteamVR blocking, directory validation, and CMake integration via a "package" custom target.

The three implementation pieces are: (1) a CMake git-version mechanism that extracts tag+hash at configure time and passes it to ISCC.exe, (2) an Inno Setup `.iss` script with Pascal Script for process detection, directory validation, old-file cleanup, and vrpathreg registration, and (3) a CMake custom target that chains DLL build to ISCC compilation.

**Primary recommendation:** Use Inno Setup 6.x stable (6.6.1 or 6.7.1), ISCC.exe `/D` defines for version injection from CMake, `PrepareToInstall` event for SteamVR process blocking, and `DisableWelcomePage`/`DisableDirPage`/`DisableReadyPage` directives to achieve the "silent with progress bar" UX.

<user_constraints>

## User Constraints (from CONTEXT.md)

### Locked Decisions
- Inno Setup (.exe installer) -- .iss script lives in the repo
- CMake custom target ("package") invokes ISCC.exe after building the DLL -- one command builds everything
- Git-derived version string (tag + commit hash) -- set release tags where appropriate
- Versioned output filename: e.g., `BeyondProximity-Setup-v0.1.0-abc1234.exe`
- Driver DLL (`driver_BeyondProximity.dll`) and manifest (`driver.vrdrivermanifest`) only -- no beyond_prox_ctl.exe
- Installs as nested sub-driver at `bin/BeyondProximity/` inside the Beyond driver directory
- Silent install with progress bar -- no wizard pages, minimal friction
- No uninstaller in Add/Remove Programs -- temporary distribution mechanism
- Block installation if SteamVR is running -- require it to be closed first
- Offer to launch SteamVR after installation completes (checkbox on finish page)
- UAC elevation handled automatically by Inno Setup
- Verify Bigscreen Beyond Driver directory exists (default Steam path)
- If not found at default path, allow tester to browse for it -- enforce directory structure validation
- Clean up old flat-deploy DLLs from `bin/win64/` root if present
- Restore root manifest to `resourceOnly=true` if a previous manual deploy changed it
- Register nested driver with vrpathreg after file copy
- Distributed via Discord / direct send to testers

### Claude's Discretion
- Inno Setup script structure and Pascal Script implementation details
- Git version extraction mechanism (CMake configure_file, script, etc.)
- Exact Inno Setup directives for silent mode, progress bar, and SteamVR launch
- Whether to use Inno Setup's `CloseApplications` directive or custom Pascal Script for SteamVR blocking

### Deferred Ideas (OUT OF SCOPE)
None

</user_constraints>

## Standard Stack

### Core
| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| Inno Setup | 6.7.x (stable) | Windows installer compiler | Free, open-source, mature, handles UAC elevation, Pascal scripting for custom logic |
| ISCC.exe | (bundled with Inno Setup) | Command-line compiler for .iss scripts | Enables CI/CMake integration without GUI |
| CMake | 3.20+ (already in project) | Build system, custom target for packaging | Already used by project; `execute_process` and `add_custom_target` for ISCC invocation |

### Supporting
| Tool | Purpose | When to Use |
|------|---------|-------------|
| git describe | Version string extraction | At CMake configure time to produce `vX.Y.Z-N-gHASH` strings |
| vrpathreg.exe | SteamVR driver registration | Called from Inno Setup [Run] section post-install |

### Alternatives Considered
| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| Inno Setup | NSIS | NSIS is more scriptable but less readable; Inno Setup has better defaults for simple installers |
| Inno Setup | WiX/MSI | MSI is overkill for temporary distribution; user explicitly chose Inno Setup |
| CMake CPack Inno Setup generator | Custom target | CPack generates its own .iss; we want full control over the script -- custom target is correct |

**Installation (dev machine):**
Inno Setup must be installed on the build machine. Download from https://jrsoftware.org/isdl.php. ISCC.exe will be at `C:\Program Files (x86)\Inno Setup 6\ISCC.exe` (default).

**Note:** Inno Setup is NOT currently installed on this machine. The CMake script should detect ISCC.exe location and emit a clear error if not found.

## Architecture Patterns

### Recommended Project Structure
```
installer/
    BeyondProximity.iss        # Inno Setup script (main)
CMakeLists.txt                 # Add "package" custom target
driver/BeyondProximity/
    driver.vrdrivermanifest    # Already exists
```

### Pattern 1: CMake Git Version Extraction
**What:** Use `git describe --tags --always --dirty` at configure time to produce a version string, pass to ISCC.exe via `/D` define.
**When to use:** Every time CMake configures or the package target runs.
**Example:**
```cmake
# Find git
find_package(Git QUIET)

# Get version from git tags
if(GIT_FOUND)
    execute_process(
        COMMAND ${GIT_EXECUTABLE} describe --tags --always --dirty
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        OUTPUT_VARIABLE GIT_VERSION
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_QUIET
    )
    if(NOT GIT_VERSION)
        set(GIT_VERSION "v0.0.0-unknown")
    endif()
else()
    set(GIT_VERSION "v0.0.0-nogit")
endif()

message(STATUS "Version: ${GIT_VERSION}")
```
Source: Standard CMake pattern, verified across multiple references.

### Pattern 2: CMake Custom Target for ISCC
**What:** `add_custom_target(package ...)` that depends on the DLL target and invokes ISCC.exe.
**When to use:** User runs `cmake --build build --config Release --target package`.
**Example:**
```cmake
# Find ISCC.exe
find_program(ISCC_EXECUTABLE NAMES ISCC
    PATHS "C:/Program Files (x86)/Inno Setup 6"
          "C:/Program Files/Inno Setup 6"
)

if(ISCC_EXECUTABLE)
    add_custom_target(package
        COMMAND ${ISCC_EXECUTABLE}
            "/DMyAppVersion=${GIT_VERSION}"
            "/DDriverBuildDir=${CMAKE_BINARY_DIR}/driver/${TARGET_NAME}"
            "/DDriverSourceDir=${CMAKE_SOURCE_DIR}/driver/${TARGET_NAME}"
            "/O${CMAKE_BINARY_DIR}/installer"
            "/F$<$<BOOL:TRUE>:BeyondProximity-Setup-${GIT_VERSION}>"
            "${CMAKE_SOURCE_DIR}/installer/BeyondProximity.iss"
        DEPENDS ${DRIVER_NAME}
        COMMENT "Building installer: BeyondProximity-Setup-${GIT_VERSION}.exe"
    )
else()
    message(WARNING "ISCC.exe not found -- 'package' target unavailable. Install Inno Setup 6 from https://jrsoftware.org/isdl.php")
endif()
```
Source: ISCC command-line docs at jrsoftware.org.

### Pattern 3: Inno Setup Script Structure (Minimal Wizard)
**What:** Disable all optional wizard pages for a streamlined experience. Keep only the dir page (conditionally shown when default path not found) and the finished page (for SteamVR launch checkbox).
**Example key directives:**
```iss
[Setup]
AppName=BeyondProximity
AppVersion={#MyAppVersion}
DefaultDirName={autopf}\Steam\steamapps\common\Bigscreen Beyond Driver
DisableWelcomePage=yes
DisableDirPage=auto
DisableReadyPage=yes
DisableProgramGroupPage=yes
DisableFinishedPage=no
Uninstallable=no
PrivilegesRequired=admin
OutputDir=.
OutputBaseFilename=BeyondProximity-Setup-{#MyAppVersion}
```
Source: Inno Setup documentation -- wizard page directives.

### Pattern 4: SteamVR Process Blocking via PrepareToInstall
**What:** Use `PrepareToInstall` event function to check for vrserver.exe and abort with a message if running.
**Why not CloseApplications:** CloseApplications only detects processes using files being installed. SteamVR locks driver DLLs in a different way (loaded into vrserver.exe). A custom check is more reliable and gives a clearer error message.
**Example:**
```pascal
[Code]
function IsProcessRunning(const ProcessName: String): Boolean;
var
  WbemLocator, WbemServices, WbemObjectSet: Variant;
begin
  Result := False;
  try
    WbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
    WbemServices := WbemLocator.ConnectServer('', 'root\cimv2');
    WbemObjectSet := WbemServices.ExecQuery(
      'SELECT Name FROM Win32_Process WHERE Name="' + ProcessName + '"');
    Result := (WbemObjectSet.Count > 0);
  except
    // WMI unavailable -- fall through, allow install
  end;
end;

function PrepareToInstall(var NeedsRestart: Boolean): String;
begin
  Result := '';
  if IsProcessRunning('vrserver.exe') then
    Result := 'SteamVR is currently running. Please close SteamVR before installing BeyondProximity.';
end;
```
Source: Inno Setup Pascal Scripting docs (event functions + OLE automation).

**Alternative (simpler, no WMI):** Use `FindWindowByClassName` or shell out to `tasklist`:
```pascal
function IsProcessRunning(const ProcessName: String): Boolean;
var
  ResultCode: Integer;
begin
  Exec('cmd.exe', '/C tasklist /FI "IMAGENAME eq ' + ProcessName + '" | find /I "' + ProcessName + '"',
       '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
  Result := (ResultCode = 0);
end;
```

**Recommendation:** Use the WMI approach -- it is cleaner, does not spawn visible processes, and handles edge cases well.

### Pattern 5: Directory Validation
**What:** If the default Beyond driver path does not exist, show the directory selection page. Validate the selected directory contains expected files.
**Example:**
```pascal
function NextButtonClick(CurPageID: Integer): Boolean;
var
  Dir: String;
begin
  Result := True;
  if CurPageID = wpSelectDir then
  begin
    Dir := WizardDirValue;
    if not FileExists(Dir + '\driver.vrdrivermanifest') then
    begin
      MsgBox('The selected folder does not appear to be a Bigscreen Beyond Driver installation.' + #13#10 +
             'Expected to find driver.vrdrivermanifest in the selected folder.', mbError, MB_OK);
      Result := False;
    end
    else if not DirExists(Dir + '\resources') then
    begin
      MsgBox('The selected folder does not appear to be a Bigscreen Beyond Driver installation.' + #13#10 +
             'Expected to find a resources\ directory.', mbError, MB_OK);
      Result := False;
    end;
  end;
end;
```
Source: Inno Setup Pascal Scripting docs (NextButtonClick event).

### Pattern 6: Post-Install Actions (vrpathreg + SteamVR Launch)
**What:** Register the nested driver and optionally launch SteamVR.
**Example:**
```iss
[Run]
; Register nested driver with SteamVR (always, even in silent mode)
Filename: "{autopf}\Steam\steamapps\common\SteamVR\bin\win64\vrpathreg.exe"; \
  Parameters: "adddriver ""{app}\bin\BeyondProximity"""; \
  Flags: runhidden waituntilterminated; \
  StatusMsg: "Registering driver with SteamVR..."

; Offer to launch SteamVR (checkbox on finished page)
Filename: "steam://run/250820"; \
  Description: "Launch SteamVR"; \
  Flags: postinstall shellexec nowait unchecked
```
Source: Inno Setup [Run] section docs.

### Anti-Patterns to Avoid
- **Using CloseApplications for SteamVR detection:** CloseApplications checks file locks on files being installed. SteamVR loads the DLL into vrserver.exe memory -- it may not show up in the file lock check if the DLL is being newly installed. Use explicit process detection instead.
- **Putting version in a header file via configure_file:** Overkill for installer versioning. The version only needs to reach ISCC.exe, not the DLL. Pass it via `/D` define on the ISCC command line.
- **Using CPack Inno Setup generator:** CPack generates its own .iss file, giving you less control. A hand-written .iss with a CMake custom target is simpler and more maintainable for this use case.
- **Installing beyond_prox_ctl.exe:** User explicitly excluded it from tester distribution.

## Don't Hand-Roll

| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| UAC elevation | Custom elevation logic | Inno Setup `PrivilegesRequired=admin` | Inno Setup handles the UAC prompt automatically |
| Wizard pages | Custom UI for dir selection | Inno Setup built-in `wpSelectDir` page | Proven UI, handles Browse button, path validation |
| Process detection | Custom EXE to check processes | Pascal Script WMI query in PrepareToInstall | Built into the installer, no external dependencies |
| File operations | Custom copy/delete logic | Inno Setup `[Files]` and `[InstallDelete]` sections | Declarative, handles errors, rollback-capable |
| Version string | Manual version file editing | `git describe` + CMake + ISCC `/D` | Fully automated from git tags |

**Key insight:** Inno Setup's declarative sections ([Files], [InstallDelete], [Run], [Dirs]) handle 90% of the deploy_driver.ps1 logic. Only the root manifest restoration and directory validation need Pascal Script.

## Common Pitfalls

### Pitfall 1: ISCC.exe Not Found
**What goes wrong:** CMake configure succeeds but `package` target fails because ISCC.exe is not in PATH.
**Why it happens:** Inno Setup is not installed or installed in a non-default location.
**How to avoid:** Use `find_program` with fallback paths. Emit a clear WARNING at configure time if not found. Make the `package` target conditional.
**Warning signs:** CMake configure completes without mentioning Inno Setup.

### Pitfall 2: No Git Tags Exist
**What goes wrong:** `git describe --tags` fails because no tags have been created yet.
**Why it happens:** This project currently has zero git tags.
**How to avoid:** Use `--always` flag so git describe falls back to abbreviated commit hash. Create an initial tag (e.g., `v0.1.0`) as part of this phase.
**Warning signs:** Version string is just a commit hash with no semantic version.

### Pitfall 3: SteamVR Locks DLL Files
**What goes wrong:** Installer cannot overwrite `driver_BeyondProximity.dll` because vrserver.exe has it loaded.
**Why it happens:** Tester ran installer while SteamVR was running.
**How to avoid:** `PrepareToInstall` blocks installation if vrserver.exe is running. Clear error message tells tester what to do.
**Warning signs:** Installer appears to succeed but DLL was not replaced (Windows silently skips locked files in some cases).

### Pitfall 4: Default Path Mismatch
**What goes wrong:** Installer targets wrong directory because Steam is installed on a different drive.
**Why it happens:** Not all testers have Steam at `C:\Program Files (x86)\Steam`.
**How to avoid:** Use `DisableDirPage=auto` -- Inno Setup shows the directory page only when the default path does not exist. Validate the chosen directory structure.
**Warning signs:** DLL deployed to wrong location, driver does not load.

### Pitfall 5: Old Flat-Deploy Artifacts Not Cleaned
**What goes wrong:** Old `driver_BeyondProximity.dll` or `driver_bigscreenbeyond.dll` in `bin\win64\` root causes conflicts.
**Why it happens:** Tester previously used the manual deploy script before it switched to nested deployment.
**How to avoid:** `[InstallDelete]` section removes old files from root `bin\win64\` directory.
**Warning signs:** Two copies of the DLL loaded, unexpected behavior.

### Pitfall 6: Root Manifest Not Restored
**What goes wrong:** Root `driver.vrdrivermanifest` has `resourceOnly=false` from a previous manual deploy, causing the root driver to activate alongside the nested driver.
**Why it happens:** Earlier versions of deploy_driver.ps1 modified the root manifest.
**How to avoid:** Pascal Script `CurStepChanged(ssInstall)` or `AfterInstall` checks root manifest content and rewrites if needed.
**Warning signs:** Two instances of the driver loading.

## Code Examples

### Complete .iss Script Skeleton
```iss
; BeyondProximity.iss - Inno Setup script for BeyondProximity driver
; Compiled by ISCC.exe from CMake "package" target

#ifndef MyAppVersion
  #define MyAppVersion "v0.0.0-dev"
#endif
#ifndef DriverBuildDir
  #error "DriverBuildDir must be defined via /D on ISCC command line"
#endif

[Setup]
AppName=BeyondProximity
AppVersion={#MyAppVersion}
AppPublisher=Bigscreen
DefaultDirName={autopf}\Steam\steamapps\common\Bigscreen Beyond Driver
DisableWelcomePage=yes
DisableDirPage=auto
DisableReadyPage=yes
DisableProgramGroupPage=yes
DisableFinishedPage=no
Uninstallable=no
PrivilegesRequired=admin
OutputDir=.
OutputBaseFilename=BeyondProximity-Setup-{#MyAppVersion}
SetupIconFile=compiler:SetupClassicIcon.ico

[Files]
; Nested driver DLL
Source: "{#DriverBuildDir}\bin\win64\driver_BeyondProximity.dll"; \
  DestDir: "{app}\bin\BeyondProximity\bin\win64"; Flags: ignoreversion

; Nested driver manifest
Source: "{#DriverBuildDir}\driver.vrdrivermanifest"; \
  DestDir: "{app}\bin\BeyondProximity"; Flags: ignoreversion

[InstallDelete]
; Clean up old flat-deploy artifacts
Type: files; Name: "{app}\bin\win64\driver_BeyondProximity.dll"
Type: files; Name: "{app}\bin\win64\driver_bigscreenbeyond.dll"

[Dirs]
Name: "{app}\bin\BeyondProximity\bin\win64"

[Run]
; Register nested driver with SteamVR
Filename: "{autopf}\Steam\steamapps\common\SteamVR\bin\win64\vrpathreg.exe"; \
  Parameters: "adddriver ""{app}\bin\BeyondProximity"""; \
  Flags: runhidden waituntilterminated; \
  StatusMsg: "Registering driver with SteamVR..."

; Launch SteamVR (optional, checkbox on finish page)
Filename: "steam://run/250820"; \
  Description: "Launch SteamVR"; \
  Flags: postinstall shellexec nowait unchecked skipifsilent

[Code]
// ... (Pascal Script for process checking, directory validation, manifest restoration)
```

### CMake Git Version + Package Target
```cmake
# --- Git version ---
find_package(Git QUIET)
if(GIT_FOUND)
    execute_process(
        COMMAND ${GIT_EXECUTABLE} describe --tags --always --dirty
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        OUTPUT_VARIABLE GIT_VERSION
        OUTPUT_STRIP_TRAILING_WHITESPACE
        ERROR_QUIET
    )
endif()
if(NOT GIT_VERSION)
    set(GIT_VERSION "v0.0.0-dev")
endif()
message(STATUS "BeyondProximity version: ${GIT_VERSION}")

# --- Installer target ---
find_program(ISCC_EXECUTABLE NAMES ISCC
    PATHS
        "C:/Program Files (x86)/Inno Setup 6"
        "C:/Program Files/Inno Setup 6"
    DOC "Inno Setup command-line compiler"
)

if(ISCC_EXECUTABLE)
    set(INSTALLER_OUTPUT_DIR "${CMAKE_BINARY_DIR}/installer")
    file(MAKE_DIRECTORY ${INSTALLER_OUTPUT_DIR})

    add_custom_target(package
        COMMAND ${ISCC_EXECUTABLE}
            "/DMyAppVersion=${GIT_VERSION}"
            "/DDriverBuildDir=${CMAKE_BINARY_DIR}/driver/${TARGET_NAME}"
            "/O${INSTALLER_OUTPUT_DIR}"
            "/FBeyondProximity-Setup-${GIT_VERSION}"
            "${CMAKE_SOURCE_DIR}/installer/BeyondProximity.iss"
        DEPENDS ${DRIVER_NAME}
        COMMENT "Building installer: BeyondProximity-Setup-${GIT_VERSION}.exe"
        VERBATIM
    )
else()
    message(WARNING "ISCC.exe not found. Install Inno Setup 6 from https://jrsoftware.org/isdl.php to enable the 'package' target.")
endif()
```

### Root Manifest Restoration (Pascal Script)
```pascal
procedure RestoreRootManifest;
var
  ManifestPath, Content, OriginalManifest: String;
begin
  ManifestPath := ExpandConstant('{app}\driver.vrdrivermanifest');
  if FileExists(ManifestPath) then
  begin
    if LoadStringFromFile(ManifestPath, Content) then
    begin
      if Pos('"resourceOnly" : false', Content) > 0 then
      begin
        OriginalManifest :=
          '{' + #13#10 +
          #9 + '"alwaysActivate": false,' + #13#10 +
          #9 + '"name" : "bigscreenbeyond",' + #13#10 +
          #9 + '"directory" : "",' + #13#10 +
          #9 + '"resourceOnly" : true,' + #13#10 +
          #9 + '"hmd_presence" : []' + #13#10 +
          '}';
        SaveStringToFile(ManifestPath, OriginalManifest, False);
        Log('Restored root manifest to resourceOnly=true');
      end;
    end;
  end;
end;

procedure CurStepChanged(CurStep: TSetupStep);
begin
  if CurStep = ssPostInstall then
    RestoreRootManifest;
end;
```

## State of the Art

| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| Inno Setup 5.x | Inno Setup 6.x (6.7.1 stable) | 2019+ | Unicode-only, modern Pascal Script, better HiDPI support |
| Manual CMake version | `git describe` + `find_package(Git)` | Long-standing | Zero-maintenance version strings tied to git tags |
| CPack Inno Setup | Custom target + hand-written .iss | N/A (preference) | Full control over installer behavior |
| NSIS for simple installers | Inno Setup | Ecosystem preference | Inno Setup is simpler for standard install/uninstall patterns |

**Deprecated/outdated:**
- Inno Setup 5.x: End of life, use 6.x
- `psvince.dll` for process detection: Old approach, WMI via OLE automation or tasklist is preferred

## Open Questions

1. **ISCC.exe path on build machines**
   - What we know: Default install is `C:\Program Files (x86)\Inno Setup 6\`; not currently installed on this machine
   - What's unclear: Whether testers/CI will build installers or just the developer
   - Recommendation: Use `find_program` with common paths, emit clear error if not found. Document one-time Inno Setup install requirement.

2. **Initial git tag**
   - What we know: No git tags exist in this repo. `git describe --tags --always` will fall back to abbreviated commit hash.
   - What's unclear: What version to start with
   - Recommendation: Create `v0.1.0` tag as part of this phase to establish a baseline version.

3. **vrpathreg.exe location variance**
   - What we know: Default path is `C:\Program Files (x86)\Steam\steamapps\common\SteamVR\bin\win64\vrpathreg.exe`
   - What's unclear: Whether all testers have SteamVR at this exact path
   - Recommendation: Use `{autopf}\Steam\...` Inno Setup constant. If vrpathreg is not found, log a warning but do not fail -- the driver may still load if SteamVR discovers it.

## Validation Architecture

### Test Framework
| Property | Value |
|----------|-------|
| Framework | PowerShell verification scripts (project pattern) |
| Config file | N/A -- scripts are standalone |
| Quick run command | `powershell -ExecutionPolicy Bypass -File scripts/verify_installer.ps1` |
| Full suite command | Same as quick run |

### Phase Requirements to Test Map
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|--------|----------|-----------|-------------------|-------------|
| DIST-01 | Installer .exe builds from CMake package target | smoke | Build with `cmake --build build --config Release --target package` and check output exists | No -- Wave 0 |
| DIST-01 | Installer contains correct files (DLL + manifest, no ctl.exe) | smoke | Run installer in silent mode to temp dir, verify files | No -- Wave 0 |
| DIST-02 | vrpathreg registration occurs during install | manual-only | Run installer, check `vrpathreg show` output | N/A -- requires SteamVR |
| N/A | SteamVR blocking works | manual-only | Run installer while SteamVR is running, verify it blocks | N/A |
| N/A | Old flat-deploy DLLs cleaned up | manual-only | Place dummy DLLs in bin/win64, run installer, verify removal | N/A |
| N/A | Root manifest restored | manual-only | Set resourceOnly=false, run installer, verify restored | N/A |
| N/A | Version string appears in filename | smoke | Check installer output filename matches pattern | No -- Wave 0 |

### Sampling Rate
- **Per task commit:** Build package target, verify .exe exists with correct filename
- **Per wave merge:** Full manual install test on real Beyond driver directory
- **Phase gate:** Successful install on clean system with SteamVR verification

### Wave 0 Gaps
- [ ] `installer/BeyondProximity.iss` -- the Inno Setup script (core deliverable)
- [ ] CMake `package` target in `CMakeLists.txt` -- build integration
- [ ] `scripts/verify_installer.ps1` -- verification script for build output
- [ ] Inno Setup 6 must be installed on build machine (`choco install innosetup` or manual download)

## Sources

### Primary (HIGH confidence)
- [Inno Setup official docs](https://jrsoftware.org/ishelp/) -- CloseApplications, wizard pages, Pascal Script events, Run section, ISCC command line
- [Inno Setup ISPP docs](https://jrsoftware.org/ishelp/topic_isppcc.htm) -- `/D` define syntax for command-line compilation
- [CMake documentation](https://cmake.org/cmake/help/latest/) -- find_package(Git), execute_process, add_custom_target
- Project source: `scripts/deploy_driver.ps1` -- reference implementation for all deployment logic
- Project source: `CMakeLists.txt` -- existing build structure, DRIVER_NAME/TARGET_NAME variables

### Secondary (MEDIUM confidence)
- [Matt Keeter blog on CMake git versioning](https://www.mattkeeter.com/blog/2018-01-06-versioning/) -- git describe + configure_file pattern
- [Marcus Folkesson blog](https://www.marcusfolkesson.se/blog/git-version-in-cmake/) -- CMake git integration pattern
- Web search results on Inno Setup process detection via WMI OLE automation

### Tertiary (LOW confidence)
- Inno Setup 7.0.0-preview-2 exists (March 2026) but is pre-release -- stick with 6.x stable

## Metadata

**Confidence breakdown:**
- Standard stack: HIGH -- Inno Setup is well-documented, user decision is locked
- Architecture: HIGH -- deploy_driver.ps1 provides exact reference implementation to replicate
- Pitfalls: HIGH -- all pitfalls derive from known project history (flat-deploy cleanup, root manifest, no git tags)

**Research date:** 2026-03-23
**Valid until:** 2026-04-23 (stable technology, no fast-moving dependencies)
