# Phase 5: Native Addon and DWM Capture - Research

**Researched:** 2026-04-12
**Domain:** C++ NAPI native addon using Windows.Graphics.Capture (WGC) for per-window DWM capture
**Confidence:** MEDIUM-HIGH

## Summary

Phase 5 delivers a standalone C++ NAPI addon that captures any window by HWND using the Windows.Graphics.Capture API, returning a PNG buffer. The addon loads into Node.js, exposes `captureWindow(hwnd)` and `isAvailable()`, and is tested against GDI apps, occluded windows, and for absence of flicker. Integration into existing targets is Phase 6.

The primary technical risks are: (1) C++/WinRT header compilation with the right coroutine mode, (2) COM initialization on libuv worker threads, and (3) resource leak prevention via RAII. The WGC API itself is well-documented with excellent reference implementations (robmikh/Win32CaptureSample). The build toolchain (cmake-js + node-addon-api) is mature.

**Primary recommendation:** Build in three waves -- scaffold/build system first, then core WGC capture, then TypeScript wrapper with validation tests. Use C++20 mode (not C++17 with `/await`) to avoid C++/WinRT coroutine header compatibility issues.

<user_constraints>
## User Constraints (from CONTEXT.md)

### Locked Decisions
- **D-01:** Use Windows.Graphics.Capture (WGC) via `IGraphicsCaptureItemInterop::CreateForWindow(hwnd)`
- **D-02:** Do NOT pursue DwmGetDxSharedSurface
- **D-03:** Do NOT pursue DwmRegisterThumbnail
- **D-04:** Satisfy success criteria #5 via empirical WGC testing against GDI app, occluded window, no flicker
- **D-05:** cmake-js for native addon build
- **D-06:** node-addon-api (C++ wrapper over Node-API)
- **D-07:** Target NAPI version 8 (Node.js 18.17+)
- **D-08:** Add cmake-js and node-addon-api as devDependencies; node-gyp-build as dependency
- **D-09:** In-process native addon (no child-process IPC)
- **D-10:** Mandatory RAII wrappers (`ComPtr<T>`) for all COM/D3D11/GDI resources
- **D-11:** `__try/__except` structured exception handling around capture hot path
- **D-12:** Validate HWND with `IsWindow()` before every capture attempt
- **D-13:** PNG encoding in C++ via stb_image_write
- **D-14:** BGRA-to-RGBA channel swap before PNG encoding
- **D-15:** Use `Napi::AsyncWorker` for capture
- **D-16:** Call `RoInitialize(RO_INIT_MULTITHREADED)` at start of each AsyncWorker::Execute()
- **D-17:** D3D11 device created once at addon load, cached as module-level `ComPtr<ID3D11Device>`
- **D-18:** Use `Direct3D11CaptureFramePool::CreateFreeThreaded()` to avoid DispatcherQueue requirement
- **D-19:** Per-capture session lifecycle (create, capture one frame, tear down)
- **D-20:** 2-second timeout on `WaitForSingleObject` for frame arrival
- **D-21:** Accept yellow WGC capture border in Phase 5
- **D-22:** Borderless capture investigation deferred to Phase 6 or later
- **D-23:** Ban printf/std::cout in C++ code; use fprintf(stderr, ...) only; define DWM_LOG macro

### Claude's Discretion
- Native addon directory structure details (file organization within `native/`)
- stb_image_write integration specifics (header inclusion pattern)
- CMakeLists.txt exact configuration beyond the skeleton in ARCHITECTURE.md
- Error message wording for DWM capture failures
- Whether to add `isAvailable()` as synchronous or async export

### Deferred Ideas (OUT OF SCOPE)
None -- discussion stayed within phase scope
</user_constraints>

<phase_requirements>
## Phase Requirements

| ID | Description | Research Support |
|----|-------------|------------------|
| DWM-01 | Native C++ NAPI addon compiles on Windows with cmake-js and produces a loadable .node binary | Standard Stack (cmake-js, node-addon-api), CMakeLists.txt skeleton, C++20 mode finding |
| DWM-02 | Addon can capture a specific window by HWND using DWM composition APIs (no PrintWindow/WM_PRINT) | Architecture Patterns (WGC data flow), Code Examples (CaptureWorker pattern) |
| DWM-03 | Captured frames are returned as PNG buffers to JavaScript with correct RGBA pixel data | BGRA-to-RGBA swap pattern, stb_image_write integration, AsyncWorker return pattern |
| DWM-04 | Capture does not cause visible flicker or rendering disruption in the target window | WGC reads from DWM composition surface -- inherently flicker-free (no WM_PRINT sent) |
| DWM-05 | Capture ignores overlapping windows (reads from DWM composition, not screen buffer) | WGC captures from composition surface by design -- overlapping windows excluded |
</phase_requirements>

## Standard Stack

### Core (New for Phase 5)

| Library | Version | Purpose | Why Standard |
|---------|---------|---------|--------------|
| node-addon-api | ^8.7.0 | C++ NAPI wrapper | ABI-stable across Node.js versions; C++ convenience over raw C napi [VERIFIED: npm registry -- 8.7.0 current] |
| cmake-js | ^8.0.0 | Native addon build | Handles C++20, WinRT headers, D3D11/DXGI linking naturally; node-gyp struggles with C++17+ flags [VERIFIED: npm registry -- 8.0.0 current] |
| node-gyp-build | ^4.8.4 | Runtime binary loader | Loads correct prebuilt .node binary at runtime [VERIFIED: npm registry -- 4.8.4 current] |
| stb_image_write | v1.16 | PNG encoding in C++ | Single-header, public domain; avoids 8MB raw buffer NAPI transfer [VERIFIED: github.com/nothings/stb] |

### Windows SDK / System Dependencies

| Dependency | Purpose | Required Headers |
|-----------|---------|------------------|
| Windows SDK 10.0.19041+ | WinRT, D3D11, DXGI, DWM APIs | winrt/Windows.Graphics.Capture.h, windows.graphics.capture.interop.h, d3d11.h, dxgi1_2.h, dwmapi.h |
| Microsoft.Windows.CppWinRT | C++/WinRT projection headers | Auto-generated C++ projections for WinRT types |
| d3d11.lib, dxgi.lib, dwmapi.lib, windowsapp.lib | Link libraries | System SDK libs |

### Alternatives Considered

| Instead of | Could Use | Tradeoff |
|------------|-----------|----------|
| cmake-js | node-gyp | node-gyp struggles with C++17+/C++20 and /await flags; GYP format is awkward for Windows SDK linking |
| stb_image_write | libpng | libpng is heavier dependency; stb is single-header drop-in |
| node-addon-api | nan | nan requires recompilation per Node.js major version; node-addon-api is ABI-stable |

**Installation:**
```bash
npm install node-gyp-build
npm install -D cmake-js node-addon-api
```

## Architecture Patterns

### Recommended Project Structure
```
native/
  CMakeLists.txt           # cmake-js build configuration
  src/
    addon.cpp              # NAPI module init, exports captureWindow() and isAvailable()
    capture.cpp            # Windows.Graphics.Capture CaptureWorker implementation
    capture.h              # CaptureWorker class declaration
    d3d_device.cpp         # D3D11 device creation and caching (module-level singleton)
    d3d_device.h
    png_encoder.cpp        # BGRA -> RGBA swap + stb_image_write PNG encoding
    png_encoder.h
  vendor/
    stb_image_write.h      # Vendored single-header (public domain, v1.16)

src/capture/targets/
    dwm-capture.ts         # TypeScript wrapper: loads .node binary, exports captureWindowDwm() and isDwmCaptureAvailable()
```

### Pattern 1: AsyncWorker for DWM Capture

**What:** Each `captureWindow(hwnd)` call creates a `Napi::AsyncWorker` that runs the full WGC capture pipeline on a libuv worker thread, returning a Promise that resolves with a PNG Buffer.

**When to use:** Every capture call. D3D11 operations and WaitForSingleObject must never block the event loop.

**Example:**
```cpp
// Source: ARCHITECTURE.md + robmikh/Win32CaptureSample CaptureSnapshot pattern
class CaptureWorker : public Napi::AsyncWorker {
    HWND hwnd_;
    ComPtr<ID3D11Device> device_;
    std::vector<uint8_t> pngData_;

    void Execute() override {
        // 1. Initialize COM/WinRT on this worker thread
        RoInitialize(RO_INIT_MULTITHREADED);

        // 2. Validate HWND
        if (!IsWindow(hwnd_)) {
            SetError("Invalid or destroyed HWND");
            RoUninitialize();
            return;
        }

        __try {
            // 3. Create capture item from HWND
            auto interop = /* get IGraphicsCaptureItemInterop */;
            winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{nullptr};
            interop->CreateForWindow(hwnd_, winrt::guid_of<decltype(item)>(), winrt::put_abi(item));

            // 4. Create free-threaded frame pool (no DispatcherQueue needed)
            auto framePool = winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool::CreateFreeThreaded(
                d3dDevice_, winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized,
                1, item.Size());

            // 5. Set up event signaling for frame arrival
            HANDLE frameEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
            framePool.FrameArrived([&](auto&&, auto&&) { SetEvent(frameEvent); });

            // 6. Start capture session
            auto session = framePool.CreateCaptureSession(item);
            session.StartCapture();

            // 7. Wait for frame with 2s timeout
            DWORD result = WaitForSingleObject(frameEvent, 2000);
            if (result == WAIT_TIMEOUT) {
                SetError("DWM capture timeout: no frame received within 2 seconds");
                // cleanup...
                return;
            }

            // 8. Get frame, copy to staging texture, map, encode PNG
            auto frame = framePool.TryGetNextFrame();
            // ... copy texture, map, BGRA->RGBA swap, stb_write_png_to_mem ...

            session.Close();
            framePool.Close();
            CloseHandle(frameEvent);
        } __except(EXCEPTION_EXECUTE_HANDLER) {
            SetError("DWM capture crashed (access violation caught)");
        }

        RoUninitialize();
    }

    void OnOK() override {
        // Safe to use NAPI here (main thread)
        auto buffer = Napi::Buffer<uint8_t>::Copy(Env(), pngData_.data(), pngData_.size());
        deferred_.Resolve(buffer);
    }
};
```

### Pattern 2: D3D11 Device Singleton

**What:** Create D3D11 device once at addon initialization, cache as module-level `ComPtr<ID3D11Device>`. Reuse across all captures.

**When to use:** Always. Device creation is ~50ms and should not happen per-capture.

```cpp
// d3d_device.cpp
static ComPtr<ID3D11Device> g_device;
static ComPtr<ID3D11DeviceContext> g_context;
static bool g_initialized = false;

bool InitializeD3D() {
    if (g_initialized) return g_device != nullptr;
    g_initialized = true;

    D3D_FEATURE_LEVEL featureLevel;
    HRESULT hr = D3D11CreateDevice(
        nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr,
        D3D11_CREATE_DEVICE_BGRA_SUPPORT,
        nullptr, 0, D3D11_SDK_VERSION,
        &g_device, &featureLevel, &g_context);

    return SUCCEEDED(hr);
}
```

### Pattern 3: TypeScript Wrapper with Lazy Loading

**What:** TypeScript module that lazily loads the .node binary and exports async capture function. Returns null on failure for fallback support.

```typescript
// src/capture/targets/dwm-capture.ts
// Source: ARCHITECTURE.md NAPI Export Surface
let nativeAddon: NativeAddon | null = null;
let loadAttempted = false;

interface NativeAddon {
    captureWindow(hwnd: number): Promise<Buffer>;
    isAvailable(): boolean;
}

function loadAddon(): NativeAddon | null {
    if (loadAttempted) return nativeAddon;
    loadAttempted = true;
    try {
        // node-gyp-build finds the correct .node binary
        nativeAddon = require('node-gyp-build')(__dirname);
        return nativeAddon;
    } catch {
        return null;
    }
}

export function isDwmCaptureAvailable(): boolean {
    const addon = loadAddon();
    return addon?.isAvailable() ?? false;
}

export async function captureWindowDwm(hwnd: number): Promise<Buffer | null> {
    const addon = loadAddon();
    if (!addon?.isAvailable()) return null;
    try {
        return await addon.captureWindow(hwnd);
    } catch {
        return null;
    }
}
```

### Anti-Patterns to Avoid
- **Raw BGRA across NAPI boundary:** 8MB+ for 1080p. Encode PNG in C++ instead.
- **Blocking the event loop:** Never call D3D11 or WaitForSingleObject synchronously. Always use AsyncWorker.
- **Creating D3D11 device per capture:** ~50ms overhead each time. Create once, cache.
- **Using Napi:: types inside Execute():** V8 is single-threaded. Store results in plain C++ members, convert in OnOK().
- **Persistent WGC sessions:** Complex lifecycle management for minimal gain at 500ms+ intervals.

## Don't Hand-Roll

| Problem | Don't Build | Use Instead | Why |
|---------|-------------|-------------|-----|
| PNG encoding | Custom PNG encoder | stb_image_write.h | PNG format has CRC, deflate, filtering -- deceptively complex |
| COM pointer management | Manual Release() calls | ComPtr<T> from wrl/client.h | Error paths leak without RAII; ComPtr handles all code paths |
| NAPI C bindings | Raw napi_* C functions | node-addon-api C++ wrappers | Verbose, error-prone; C++ wrappers add type safety |
| Build system | Manual MSVC compilation | cmake-js | Handles node-addon-api include paths, Windows SDK linking, .node output |
| Binary distribution | Manual binary hosting | prebuild + prebuild-install | Standard ecosystem tool for prebuilt native addon distribution |

**Key insight:** The C++ code in this addon touches three complex domains (COM/WinRT, D3D11, NAPI) -- use established wrappers for all three to minimize the surface area for bugs.

## Common Pitfalls

### Pitfall 1: C++/WinRT Coroutine Header Compatibility
**What goes wrong:** C++/WinRT headers internally include `<experimental/coroutine>` or `<coroutine>` depending on compiler mode. With MSVC Build Tools 14.51+, `<experimental/coroutine>` is deprecated and triggers hard errors. Using `/std:c++17` with `/await` flag relies on the deprecated experimental path.
**Why it happens:** C++/WinRT was designed around coroutines. Even if YOUR code does not use `co_await`, the headers themselves reference coroutine types.
**How to avoid:** Use `/std:c++20` instead of `/std:c++17`. C++20 provides standard `<coroutine>` header that C++/WinRT detects and uses. This eliminates the need for `/await` entirely. The event-based synchronization pattern (FrameArrived + WaitForSingleObject) means our code never calls co_await, but the headers need the coroutine types to exist.
**Warning signs:** Compilation errors mentioning `experimental/coroutine`, `_RESUMABLE_FUNCTIONS_SUPPORTED`, or `implement pre-C++20 coroutine support`.
[VERIFIED: github.com/microsoft/cppwinrt/issues/1520 -- experimental/coroutine deprecated in MSVC 14.51]

### Pitfall 2: COM Initialization on Worker Threads
**What goes wrong:** WGC APIs require COM initialized on the calling thread. Libuv worker threads have no COM apartment.
**Why it happens:** AsyncWorker::Execute() runs on libuv thread pool, not the main thread.
**How to avoid:** Call `RoInitialize(RO_INIT_MULTITHREADED)` at start of Execute(), `RoUninitialize()` at end. Check HRESULT.
**Warning signs:** HRESULT 0x800401F0 (CO_E_NOTINITIALIZED).
[CITED: learn.microsoft.com WinRT threading docs]

### Pitfall 3: GDI/DirectX Resource Leaks
**What goes wrong:** Leaked D3D11 textures, frame pools, or GDI objects exhaust per-process limits. At 2 captures/second, even 1 leak per cycle hits limits within minutes.
**Why it happens:** Error paths and early returns skip cleanup.
**How to avoid:** ComPtr<T> for all COM objects. RAII wrappers for HANDLE (frame event). Monitor with `GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS)` during testing.
**Warning signs:** GDI Objects column in Task Manager climbs monotonically during capture sessions.
[CITED: PITFALLS.md Pitfall 3]

### Pitfall 4: BGRA vs RGBA Color Swap
**What goes wrong:** Windows DWM outputs BGRA. PNG expects RGBA. Without swap, red and blue channels are reversed.
**Why it happens:** Platform format mismatch. BGRA and RGBA look similar in code.
**How to avoid:** Swap bytes[0] and bytes[2] for every pixel before PNG encoding. Validate with a red-element test.
**Warning signs:** Red elements appear blue in captured images.
[CITED: PITFALLS.md Pitfall 4]

### Pitfall 5: stdout Pollution from C++ Code
**What goes wrong:** printf/std::cout in C++ writes to stdout, corrupting MCP JSON-RPC stream.
**Why it happens:** C++ debug output habits. JavaScript stdout guard cannot intercept native writes.
**How to avoid:** Ban printf/cout. Define `DWM_LOG` macro writing to stderr. Grep for printf/cout in code review.
**Warning signs:** MCP client receives malformed JSON.
[CITED: PITFALLS.md Pitfall 12]

### Pitfall 6: NAPI Thread Safety Violations
**What goes wrong:** Accessing Napi::Env or creating Napi::Value inside AsyncWorker::Execute() causes segfaults.
**Why it happens:** V8 is single-threaded. Execute() runs on a worker thread.
**How to avoid:** Only use plain C++ types in Execute(). Convert to NAPI types in OnOK()/OnError().
**Warning signs:** Intermittent segfaults during capture.
[CITED: PITFALLS.md Pitfall 7]

## Code Examples

### stb_image_write Integration Pattern
```cpp
// native/src/png_encoder.cpp
// Source: stb_image_write.h documentation (github.com/nothings/stb)

// In exactly ONE .cpp file:
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

// Custom write callback to write to std::vector instead of file
static void stbi_write_to_vector(void* context, void* data, int size) {
    auto* vec = static_cast<std::vector<uint8_t>*>(context);
    auto* bytes = static_cast<uint8_t*>(data);
    vec->insert(vec->end(), bytes, bytes + size);
}

// Encode BGRA pixel data to PNG (performs BGRA->RGBA swap)
std::vector<uint8_t> EncodePng(const uint8_t* bgra, int width, int height, int stride) {
    // Copy and swap BGRA -> RGBA
    std::vector<uint8_t> rgba(width * height * 4);
    for (int y = 0; y < height; y++) {
        const uint8_t* src = bgra + y * stride;
        uint8_t* dst = rgba.data() + y * width * 4;
        for (int x = 0; x < width; x++) {
            dst[x*4 + 0] = src[x*4 + 2]; // R <- B
            dst[x*4 + 1] = src[x*4 + 1]; // G <- G
            dst[x*4 + 2] = src[x*4 + 0]; // B <- R
            dst[x*4 + 3] = src[x*4 + 3]; // A <- A
        }
    }

    std::vector<uint8_t> png;
    stbi_write_png_to_func(stbi_write_to_vector, &png, width, height, 4, rgba.data(), width * 4);
    return png;
}
```

### D3D11 Texture Readback Pattern
```cpp
// Source: robmikh/Win32CaptureSample + D3D11 documentation
// Copy GPU texture to CPU-readable staging texture

ComPtr<ID3D11Texture2D> CopyToStaging(ID3D11Device* device, ID3D11DeviceContext* context, ID3D11Texture2D* source) {
    D3D11_TEXTURE2D_DESC desc;
    source->GetDesc(&desc);
    desc.Usage = D3D11_USAGE_STAGING;
    desc.BindFlags = 0;
    desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    desc.MiscFlags = 0;

    ComPtr<ID3D11Texture2D> staging;
    HRESULT hr = device->CreateTexture2D(&desc, nullptr, &staging);
    if (FAILED(hr)) return nullptr;

    context->CopyResource(staging.Get(), source);
    return staging;
}

// Map the staging texture to read pixel data
D3D11_MAPPED_SUBRESOURCE mapped;
hr = context->Map(staging.Get(), 0, D3D11_MAP_READ, 0, &mapped);
if (SUCCEEDED(hr)) {
    // mapped.pData = BGRA pixel data
    // mapped.RowPitch = bytes per row (may include padding)
    auto png = EncodePng(static_cast<uint8_t*>(mapped.pData), desc.Width, desc.Height, mapped.RowPitch);
    context->Unmap(staging.Get(), 0);
}
```

### WinRT Interop for HWND-based Capture
```cpp
// Source: learn.microsoft.com IGraphicsCaptureItemInterop::CreateForWindow
// Getting a GraphicsCaptureItem from an HWND (no picker UI needed)

#include <windows.graphics.capture.interop.h>
#include <winrt/Windows.Graphics.Capture.h>

winrt::Windows::Graphics::Capture::GraphicsCaptureItem CreateCaptureItemForWindow(HWND hwnd) {
    auto interop = winrt::get_activation_factory<
        winrt::Windows::Graphics::Capture::GraphicsCaptureItem,
        IGraphicsCaptureItemInterop>();

    winrt::Windows::Graphics::Capture::GraphicsCaptureItem item{nullptr};
    winrt::check_hresult(interop->CreateForWindow(
        hwnd,
        winrt::guid_of<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>(),
        winrt::put_abi(item)));
    return item;
}
```

### DWM_LOG Macro
```cpp
// native/src/common.h
#define DWM_LOG(fmt, ...) fprintf(stderr, "[dwm-capture] " fmt "\n", ##__VA_ARGS__)

// Usage:
DWM_LOG("Capturing window HWND=%p (%dx%d)", hwnd, width, height);
DWM_LOG("D3D11 device created successfully");
DWM_LOG("ERROR: WGC frame timeout after 2000ms");
```

## State of the Art

| Old Approach | Current Approach | When Changed | Impact |
|--------------|------------------|--------------|--------|
| `/await` + `<experimental/coroutine>` | `/std:c++20` + standard `<coroutine>` | MSVC 14.51 (2025) | `/await` deprecated; use C++20 or `/await:strict` |
| prebuildify for prebuilt binaries | prebuild + prebuild-install | Ongoing | prebuildify does not support cmake-js backend |
| node-gyp for all native addons | cmake-js for C++17+/WinRT projects | cmake-js v7+ | cmake-js handles modern MSVC flags properly |
| nan for NAPI bindings | node-addon-api | Node-API v6+ | ABI stability across Node.js versions |

**Deprecated/outdated:**
- `/await` MSVC flag: Being replaced by standard C++20 coroutines. Use `/std:c++20` instead. [VERIFIED: github.com/microsoft/cppwinrt/issues/1520]
- `<experimental/coroutine>`: Hard-deprecated in MSVC Build Tools 14.51+. [VERIFIED: github.com/microsoft/cppwinrt/issues/1520]

## Critical Research Finding: Use C++20, Not C++17

The CONTEXT.md decisions reference C++17 (D-05 mentions "C++17"). However, research reveals that **C++20 is the safer choice**:

1. C++/WinRT headers internally reference coroutine types. With `/std:c++17`, the compiler uses `<experimental/coroutine>` which is being deprecated.
2. C++20 provides standard `<coroutine>` header that C++/WinRT auto-detects.
3. Our code does NOT use `co_await` -- we use WaitForSingleObject. But the headers need the types.
4. C++20 is a superset of C++17 -- all existing C++17 code compiles under C++20.
5. MSVC in VS 2022 (available on this machine) fully supports C++20.

**Recommendation:** Set `CMAKE_CXX_STANDARD 20` in CMakeLists.txt. Remove `/await` flag. This is forward-compatible and avoids deprecated header paths. [VERIFIED: MSVC Build Tools 14.51 deprecation of experimental/coroutine]

## Assumptions Log

| # | Claim | Section | Risk if Wrong |
|---|-------|---------|---------------|
| A1 | WGC CreateFreeThreaded + WaitForSingleObject works without co_await on libuv worker threads | Architecture Patterns | HIGH -- if WGC requires coroutine co_await, the entire async architecture changes. Mitigation: the robmikh sample shows event-based waiting is possible. |
| A2 | stb_write_png_to_func callback approach produces valid PNG from BGRA-swapped data | Code Examples | LOW -- stb_image_write is extremely well-tested; callback API is documented. Fallback: use stbi_write_png to temp file then read. |
| A3 | D3D11CreateDevice with BGRA support works on all Windows 10 1903+ / Windows 11 machines | Architecture Patterns | LOW -- D3D11 with BGRA is universal on modern Windows. |
| A4 | cmake-js correctly finds the VS 2022 CMake bundled on this machine | Environment | MEDIUM -- cmake-js may need CMAKE_PREFIX_PATH. Testing will validate. |

## Open Questions

1. **WinRT IDirect3DDevice interop**
   - What we know: WGC CreateFreeThreaded needs a `IDirect3DDevice` (WinRT type), but we create `ID3D11Device` (native D3D11). The robmikh sample uses `CreateDirect3DDevice()` helper to wrap it.
   - What's unclear: Exact interop function to convert ID3D11Device to WinRT IDirect3DDevice. Need `CreateDirect3D11DeviceFromDXGIDevice` from `windows.graphics.directx.direct3d11.interop.h`.
   - Recommendation: Follow robmikh sample's `d3dHelpers.h` pattern for device wrapping.

2. **isAvailable() scope**
   - What we know: Should check if WGC is available (Windows 10 1903+ with DWM enabled).
   - What's unclear: Best detection method -- try-and-check vs version check vs feature query.
   - Recommendation: Use `winrt::Windows::Graphics::Capture::GraphicsCaptureSession::IsSupported()` (returns bool). Make it synchronous (no async needed for a version check).

3. **Texture size vs window size**
   - What we know: WGC capture frame dimensions may differ from GetWindowRect due to DPI scaling.
   - What's unclear: Whether the PNG output should be at capture resolution or resized to logical window size.
   - Recommendation: Return at capture resolution (actual pixels). Phase 6 integration can handle DPI normalization if needed.

## Environment Availability

| Dependency | Required By | Available | Version | Fallback |
|------------|------------|-----------|---------|----------|
| Node.js | Runtime | Yes | v25.8.2 | -- |
| npm | Package management | Yes | 11.12.1 | -- |
| Visual Studio 2022 | MSVC compiler | Yes | Community | -- |
| Windows SDK | WinRT/D3D11 headers | Yes | 10.0.26100.0 | -- |
| CMake | cmake-js backend | Yes (via VS) | Bundled with VS 2022 | cmake-js may bundle its own |
| cmake-js (npm) | Build tool | Yes (npx) | 8.0.0 | -- |
| cl.exe | MSVC C++ compiler | Yes (not on PATH; VS Dev Prompt needed) | VS 2022 | cmake-js auto-detects VS installation |

**Missing dependencies with no fallback:** None -- all required build tools are present.

**Missing dependencies with fallback:** None.

**Note:** `cl.exe` is not on the default shell PATH but cmake-js handles VS detection automatically via the `cmake-js` npm package which uses `msvc-dev-cmd` or the VS installation registry. [ASSUMED]

## Project Constraints (from CLAUDE.md)

- Platform: Windows 11 primary target
- Protocol: MCP server spec (tools + resources over stdio) -- stdout must stay clean
- Performance: Screenshot capture should not noticeably slow the target application
- Image Size: Grid images must be reasonable size for LLM consumption
- All logging goes to stderr only
- Existing stack: Node.js 20 LTS, TypeScript 5.5+, sharp ^0.34.5, node-screenshots ^0.2.8

## Sources

### Primary (HIGH confidence)
- [npm registry: cmake-js 8.0.0](https://www.npmjs.com/package/cmake-js) -- version verified
- [npm registry: node-addon-api 8.7.0](https://www.npmjs.com/package/node-addon-api) -- version verified
- [npm registry: node-gyp-build 4.8.4](https://www.npmjs.com/package/node-gyp-build) -- version verified
- [npm registry: prebuild 13.0.1](https://www.npmjs.com/package/prebuild) -- version verified
- [npm registry: prebuild-install 7.1.3](https://www.npmjs.com/package/prebuild-install) -- version verified
- [Windows.Graphics.Capture API docs](https://learn.microsoft.com/en-us/uwp/api/windows.graphics.capture) -- API reference
- [IGraphicsCaptureItemInterop::CreateForWindow](https://learn.microsoft.com/en-us/windows/win32/api/windows.graphics.capture.interop/nf-windows-graphics-capture-interop-igraphicscaptureiteminterop-createforwindow) -- HWND capture
- [Direct3D11CaptureFramePool.CreateFreeThreaded](https://learn.microsoft.com/en-us/uwp/api/windows.graphics.capture.direct3d11captureframepool.createfreethreaded) -- Free-threaded frame pool
- [robmikh/Win32CaptureSample](https://github.com/robmikh/Win32CaptureSample) -- Reference C++ WGC implementation
- [stb_image_write.h](https://github.com/nothings/stb/blob/master/stb_image_write.h) -- PNG encoder source

### Secondary (MEDIUM confidence)
- [C++/WinRT experimental/coroutine deprecation](https://github.com/microsoft/cppwinrt/issues/1520) -- MSVC 14.51 deprecation confirmed
- [C++/WinRT concurrency docs](https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/concurrency) -- Threading patterns
- [robmikh/Win32CaptureSample CaptureSnapshot.cpp](https://github.com/robmikh/Win32CaptureSample/blob/main/Win32CaptureSample/CaptureSnapshot.cpp) -- Single-frame capture pattern

### Tertiary (LOW confidence)
- cmake-js auto-detecting VS 2022 on this specific machine -- needs empirical validation in first build attempt

## Metadata

**Confidence breakdown:**
- Standard stack: HIGH -- all npm versions verified, Windows SDK present on machine
- Architecture: MEDIUM-HIGH -- based on official MS docs + reference sample, but event-based WGC without coroutines needs empirical validation
- Pitfalls: HIGH -- documented from multiple sources with concrete prevention strategies

**Research date:** 2026-04-12
**Valid until:** 2026-05-12 (stable APIs, 30-day window)
